Skip to content

Commit 34bed35

Browse files
authored
feat: add crewai uv wrapper for uv commands (#3581)
1 parent feeed50 commit 34bed35

File tree

5 files changed

+108
-29
lines changed

5 files changed

+108
-29
lines changed

docs/en/concepts/cli.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,10 @@ crewai config reset
404404
After resetting configuration, re-run `crewai login` to authenticate again.
405405
</Tip>
406406

407+
<Tip>
408+
CrewAI CLI handles authentication to the Tool Repository automatically when adding packages to your project. Just append `crewai` before any `uv` command to use it. E.g. `crewai uv add requests`. For more information, see [Tool Repository](https://docs.crewai.com/enterprise/features/tool-repository) docs.
409+
</Tip>
410+
407411
<Note>
408412
Configuration settings are stored in `~/.config/crewai/settings.json`. Some settings like organization name and UUID are read-only and managed through authentication and organization commands. Tool repository related settings are hidden and cannot be set directly by users.
409413
</Note>

docs/en/enterprise/features/tool-repository.mdx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,36 @@ researcher = Agent(
5252
)
5353
```
5454

55+
## Adding other packages after installing a tool
56+
57+
After installing a tool from the CrewAI Enterprise Tool Repository, you need to use the `crewai uv` command to add other packages to your project.
58+
Using pure `uv` commands will fail due to authentication to tool repository being handled by the CLI. By using the `crewai uv` command, you can add other packages to your project without having to worry about authentication.
59+
Any `uv` command can be used with the `crewai uv` command, making it a powerful tool for managing your project's dependencies without the hassle of managing authentication through environment variables or other methods.
60+
61+
Say that you have installed a custom tool from the CrewAI Enterprise Tool Repository called "my-tool":
62+
63+
```bash
64+
crewai tool install my-tool
65+
```
66+
67+
And now you want to add another package to your project, you can use the following command:
68+
69+
```bash
70+
crewai uv add requests
71+
```
72+
73+
Other commands like `uv sync` or `uv remove` can also be used with the `crewai uv` command:
74+
75+
```bash
76+
crewai uv sync
77+
```
78+
79+
```bash
80+
crewai uv remove requests
81+
```
82+
83+
This will add the package to your project and update `pyproject.toml` accordingly.
84+
5585
## Creating and Publishing Tools
5686

5787
To create a new tool project:

src/crewai/cli/cli.py

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import os
2+
import subprocess
13
from importlib.metadata import version as get_version
24

35
import click
@@ -8,6 +10,7 @@
810
from crewai.cli.create_flow import create_flow
911
from crewai.cli.crew_chat import run_chat
1012
from crewai.cli.settings.main import SettingsCommand
13+
from crewai.cli.utils import build_env_with_tool_repository_credentials, read_toml
1114
from crewai.memory.storage.kickoff_task_outputs_storage import (
1215
KickoffTaskOutputsSQLiteStorage,
1316
)
@@ -34,6 +37,46 @@ def crewai():
3437
"""Top-level command group for crewai."""
3538

3639

40+
@crewai.command(
41+
name="uv",
42+
context_settings=dict(
43+
ignore_unknown_options=True,
44+
),
45+
)
46+
@click.argument("uv_args", nargs=-1, type=click.UNPROCESSED)
47+
def uv(uv_args):
48+
"""A wrapper around uv commands that adds custom tool authentication through env vars."""
49+
env = os.environ.copy()
50+
try:
51+
pyproject_data = read_toml()
52+
sources = pyproject_data.get("tool", {}).get("uv", {}).get("sources", {})
53+
54+
for source_config in sources.values():
55+
if isinstance(source_config, dict):
56+
index = source_config.get("index")
57+
if index:
58+
index_env = build_env_with_tool_repository_credentials(index)
59+
env.update(index_env)
60+
except (FileNotFoundError, KeyError) as e:
61+
raise SystemExit(
62+
"Error. A valid pyproject.toml file is required. Check that a valid pyproject.toml file exists in the current directory."
63+
) from e
64+
except Exception as e:
65+
raise SystemExit(f"Error: {e}") from e
66+
67+
try:
68+
subprocess.run( # noqa: S603
69+
["uv", *uv_args], # noqa: S607
70+
capture_output=False,
71+
env=env,
72+
text=True,
73+
check=True,
74+
)
75+
except subprocess.CalledProcessError as e:
76+
click.secho(f"uv command failed with exit code {e.returncode}", fg="red")
77+
raise SystemExit(e.returncode) from e
78+
79+
3780
@crewai.command()
3881
@click.argument("type", type=click.Choice(["crew", "flow"]))
3982
@click.argument("name")
@@ -239,11 +282,6 @@ def deploy():
239282
"""Deploy the Crew CLI group."""
240283

241284

242-
@crewai.group()
243-
def tool():
244-
"""Tool Repository related commands."""
245-
246-
247285
@deploy.command(name="create")
248286
@click.option("-y", "--yes", is_flag=True, help="Skip the confirmation prompt")
249287
def deploy_create(yes: bool):
@@ -291,6 +329,11 @@ def deploy_remove(uuid: str | None):
291329
deploy_cmd.remove_crew(uuid=uuid)
292330

293331

332+
@crewai.group()
333+
def tool():
334+
"""Tool Repository related commands."""
335+
336+
294337
@tool.command(name="create")
295338
@click.argument("handle")
296339
def tool_create(handle: str):

src/crewai/cli/tools/main.py

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from crewai.cli.command import BaseCommand, PlusAPIMixin
1313
from crewai.cli.config import Settings
1414
from crewai.cli.utils import (
15+
build_env_with_tool_repository_credentials,
1516
extract_available_exports,
1617
get_project_description,
1718
get_project_name,
@@ -42,8 +43,7 @@ def create(self, handle: str):
4243
if project_root.exists():
4344
click.secho(f"Folder {folder_name} already exists.", fg="red")
4445
raise SystemExit
45-
else:
46-
os.makedirs(project_root)
46+
os.makedirs(project_root)
4747

4848
click.secho(f"Creating custom tool {folder_name}...", fg="green", bold=True)
4949

@@ -56,7 +56,7 @@ def create(self, handle: str):
5656
os.chdir(project_root)
5757
try:
5858
self.login()
59-
subprocess.run(["git", "init"], check=True)
59+
subprocess.run(["git", "init"], check=True) # noqa: S607
6060
console.print(
6161
f"[green]Created custom tool [bold]{folder_name}[/bold]. Run [bold]cd {project_root}[/bold] to start working.[/green]"
6262
)
@@ -76,10 +76,10 @@ def publish(self, is_public: bool, force: bool = False):
7676
raise SystemExit()
7777

7878
project_name = get_project_name(require=True)
79-
assert isinstance(project_name, str)
79+
assert isinstance(project_name, str) # noqa: S101
8080

8181
project_version = get_project_version(require=True)
82-
assert isinstance(project_version, str)
82+
assert isinstance(project_version, str) # noqa: S101
8383

8484
project_description = get_project_description(require=False)
8585
encoded_tarball = None
@@ -94,8 +94,8 @@ def publish(self, is_public: bool, force: bool = False):
9494
self._print_current_organization()
9595

9696
with tempfile.TemporaryDirectory() as temp_build_dir:
97-
subprocess.run(
98-
["uv", "build", "--sdist", "--out-dir", temp_build_dir],
97+
subprocess.run( # noqa: S603
98+
["uv", "build", "--sdist", "--out-dir", temp_build_dir], # noqa: S607
9999
check=True,
100100
capture_output=False,
101101
)
@@ -146,7 +146,7 @@ def install(self, handle: str):
146146
style="bold red",
147147
)
148148
raise SystemExit
149-
elif get_response.status_code != 200:
149+
if get_response.status_code != 200:
150150
console.print(
151151
"Failed to get tool details. Please try again later.", style="bold red"
152152
)
@@ -196,10 +196,10 @@ def _add_package(self, tool_details: dict[str, Any]):
196196
else:
197197
add_package_command.extend(["--index", index, tool_handle])
198198

199-
add_package_result = subprocess.run(
199+
add_package_result = subprocess.run( # noqa: S603
200200
add_package_command,
201201
capture_output=False,
202-
env=self._build_env_with_credentials(repository_handle),
202+
env=build_env_with_tool_repository_credentials(repository_handle),
203203
text=True,
204204
check=True,
205205
)
@@ -221,20 +221,6 @@ def _ensure_not_in_project(self):
221221
)
222222
raise SystemExit
223223

224-
def _build_env_with_credentials(self, repository_handle: str):
225-
repository_handle = repository_handle.upper().replace("-", "_")
226-
settings = Settings()
227-
228-
env = os.environ.copy()
229-
env[f"UV_INDEX_{repository_handle}_USERNAME"] = str(
230-
settings.tool_repository_username or ""
231-
)
232-
env[f"UV_INDEX_{repository_handle}_PASSWORD"] = str(
233-
settings.tool_repository_password or ""
234-
)
235-
236-
return env
237-
238224
def _print_current_organization(self) -> None:
239225
settings = Settings()
240226
if settings.org_uuid:

src/crewai/cli/utils.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import tomli
1212
from rich.console import Console
1313

14+
from crewai.cli.config import Settings
1415
from crewai.cli.constants import ENV_VARS
1516
from crewai.crew import Crew
1617
from crewai.flow import Flow
@@ -417,6 +418,21 @@ def extract_available_exports(dir_path: str = "src"):
417418
raise SystemExit(1) from e
418419

419420

421+
def build_env_with_tool_repository_credentials(repository_handle: str):
422+
repository_handle = repository_handle.upper().replace("-", "_")
423+
settings = Settings()
424+
425+
env = os.environ.copy()
426+
env[f"UV_INDEX_{repository_handle}_USERNAME"] = str(
427+
settings.tool_repository_username or ""
428+
)
429+
env[f"UV_INDEX_{repository_handle}_PASSWORD"] = str(
430+
settings.tool_repository_password or ""
431+
)
432+
433+
return env
434+
435+
420436
def _load_tools_from_init(init_file: Path) -> list[dict[str, Any]]:
421437
"""
422438
Load and validate tools from a given __init__.py file.

0 commit comments

Comments
 (0)