diff --git a/lib/cli/src/crewai_cli/run_crew.py b/lib/cli/src/crewai_cli/run_crew.py index 55d357dae..0f903a513 100644 --- a/lib/cli/src/crewai_cli/run_crew.py +++ b/lib/cli/src/crewai_cli/run_crew.py @@ -20,7 +20,7 @@ from crewai_cli.input_prompt import ( ) from crewai_cli.utils import ( build_env_with_all_tool_credentials, - ensure_project_id, + get_or_create_project_id, is_dmn_mode_enabled, ) from crewai_cli.version import get_crewai_tools_dependency, get_crewai_version @@ -620,7 +620,7 @@ def run_crew( """ # Backfills projects created before project_id existed. Only here, in a # command the user explicitly invoked - never from the SDK during kickoff. - ensure_project_id() + get_or_create_project_id() # --definition is a pure override: run that flow directly. if definition is not None: diff --git a/lib/cli/src/crewai_cli/tools/main.py b/lib/cli/src/crewai_cli/tools/main.py index 4d3d54e57..3f3caf21d 100644 --- a/lib/cli/src/crewai_cli/tools/main.py +++ b/lib/cli/src/crewai_cli/tools/main.py @@ -16,7 +16,7 @@ from crewai_cli.config import Settings from crewai_cli.constants import DEFAULT_CREWAI_ENTERPRISE_URL from crewai_cli.utils import ( build_env_with_tool_repository_credentials, - ensure_project_id, + get_or_create_project_id, get_project_description, get_project_name, get_project_version, @@ -231,7 +231,7 @@ class ToolCommand(BaseCommand, PlusAPIMixin): get_user_id = _require_get_user_id() login_response = self.plus_api_client.login_to_tool_repository( user_identifier=get_user_id(), - project_id=ensure_project_id(), + project_id=get_or_create_project_id(), ) if login_response.status_code != 200: diff --git a/lib/cli/src/crewai_cli/utils.py b/lib/cli/src/crewai_cli/utils.py index 7fb26f80a..71206c8a4 100644 --- a/lib/cli/src/crewai_cli/utils.py +++ b/lib/cli/src/crewai_cli/utils.py @@ -31,7 +31,6 @@ __all__ = [ "build_env_with_tool_repository_credentials", "copy_template", "enable_prompt_line_editing", - "ensure_project_id", "fetch_and_json_env_file", "get_or_create_project_id", "get_project_description", @@ -53,32 +52,6 @@ console = Console() _TEMPLATE_TOKEN_RE = re.compile(r"{{([a-zA-Z_][a-zA-Z0-9_]*)}}") -def ensure_project_id(pyproject_path: str | Path = "pyproject.toml") -> str | None: - """Return the project's id, minting one and telling the user if it was added. - - Safe to call from any CLI command: returns None rather than raising when - there is no project, or when ``pyproject.toml`` is not writable. - - Args: - pyproject_path: Path to the project's ``pyproject.toml``. - - Returns: - The project id, or None if one could not be read or created. - """ - project_id, created = get_or_create_project_id(pyproject_path) - - if created: - console.print( - f"Added [bold]project_id[/bold] to {pyproject_path} under " - # Escaped: Rich would otherwise parse [tool.crewai] as a style tag. - r"[bold]\[tool.crewai][/bold] so this project's runs and traces stay " - "linked. Commit it to share that link with your team.", - style="dim", - ) - - return project_id - - def is_dmn_mode_enabled() -> bool: """Return True when the enterprise non-interactive mode is enabled.""" value = os.environ.get("CREWAI_DMN") diff --git a/lib/crewai-core/src/crewai_core/project.py b/lib/crewai-core/src/crewai_core/project.py index 2ce1406bf..f3059ebc2 100644 --- a/lib/crewai-core/src/crewai_core/project.py +++ b/lib/crewai-core/src/crewai_core/project.py @@ -252,7 +252,7 @@ def get_project_id(pyproject_path: str | Path = "pyproject.toml") -> str | None: def get_or_create_project_id( pyproject_path: str | Path = "pyproject.toml", -) -> tuple[str | None, bool]: +) -> str | None: """Return the project's id, minting and persisting one if absent. Writes ``project_id`` into the ``[tool.crewai]`` table so it is committed @@ -267,36 +267,34 @@ def get_or_create_project_id( pyproject_path: Path to the project's ``pyproject.toml``. Returns: - A ``(project_id, created)`` tuple. ``created`` is True only when an id - was minted and written on this call, so callers can tell the user. Both - values are ``(None, False)`` when the file is missing or not writable - - this is best-effort and never raises. + The project id, or None when ``pyproject.toml`` is missing, malformed, + or not writable. Best-effort - never raises. """ existing = get_project_id(pyproject_path) if existing: - return existing, False + return existing path = Path(pyproject_path) if not path.is_file(): - return None, False + return None try: content = path.read_text(encoding="utf-8") except OSError: - return None, False + return None project_id = str(uuid.uuid4()) updated = _insert_project_id(content, project_id) if updated is None: - return None, False + return None try: path.write_text(updated, encoding="utf-8") except OSError: # Read-only checkout, permissions, container FS - not worth failing over. - return None, False + return None - return project_id, True + return project_id def _insert_project_id(content: str, project_id: str) -> str | None: diff --git a/lib/crewai/tests/telemetry/test_project_id.py b/lib/crewai/tests/telemetry/test_project_id.py index 9b991796d..1eb1cb4b6 100644 --- a/lib/crewai/tests/telemetry/test_project_id.py +++ b/lib/crewai/tests/telemetry/test_project_id.py @@ -38,24 +38,22 @@ def test_returns_none_when_no_id_configured(pyproject): def test_mints_and_persists_an_id(pyproject): - project_id, created = get_or_create_project_id(pyproject) + project_id = get_or_create_project_id(pyproject) - assert created is True assert uuid.UUID(project_id) assert get_project_id(pyproject) == project_id def test_id_is_stable_across_calls(pyproject): - first, created_first = get_or_create_project_id(pyproject) - second, created_second = get_or_create_project_id(pyproject) + first = get_or_create_project_id(pyproject) + second = get_or_create_project_id(pyproject) - assert created_first is True - assert created_second is False, "must not mint a second id" - assert first == second + assert first == second, "must not mint a second id" + assert uuid.UUID(first) def test_id_lands_in_the_tool_crewai_table(pyproject): - project_id, _ = get_or_create_project_id(pyproject) + project_id = get_or_create_project_id(pyproject) data = parse_toml(pyproject.read_text()) assert data["tool"]["crewai"]["project_id"] == project_id @@ -102,9 +100,9 @@ def test_produces_valid_toml_for_varied_layouts(tmp_path, source, label): path = tmp_path / "pyproject.toml" path.write_text(source) - project_id, created = get_or_create_project_id(path) + project_id = get_or_create_project_id(path) - assert created is True, label + assert project_id is not None, label data = parse_toml(path.read_text()) assert data["tool"]["crewai"]["project_id"] == project_id, label @@ -124,10 +122,7 @@ def test_id_does_not_leak_into_a_neighbouring_table(tmp_path): def test_missing_file_is_not_an_error(tmp_path): - project_id, created = get_or_create_project_id(tmp_path / "nope.toml") - - assert project_id is None - assert created is False + assert get_or_create_project_id(tmp_path / "nope.toml") is None def test_malformed_toml_is_not_an_error(tmp_path): @@ -141,12 +136,11 @@ def test_read_only_file_is_not_an_error(pyproject): """A read-only checkout must not break the command that called this.""" pyproject.chmod(0o444) try: - project_id, created = get_or_create_project_id(pyproject) + project_id = get_or_create_project_id(pyproject) finally: pyproject.chmod(0o644) assert project_id is None - assert created is False def test_get_project_id_never_creates_anything(pyproject): @@ -171,7 +165,7 @@ def test_ids_are_unique_across_projects(tmp_path): path = tmp_path / name / "pyproject.toml" path.parent.mkdir() path.write_text(CREW_PYPROJECT) - project_id, _ = get_or_create_project_id(path) + project_id = get_or_create_project_id(path) ids.add(project_id) assert len(ids) == 3