diff --git a/src/crewai/cli/cli.py b/src/crewai/cli/cli.py index c45db44e7..0ec06e7b9 100644 --- a/src/crewai/cli/cli.py +++ b/src/crewai/cli/cli.py @@ -1,3 +1,5 @@ +from typing import Optional + import click import pkg_resources @@ -183,9 +185,10 @@ def deploy(): @deploy.command(name="up") -def deploy_up(): +@click.option("-u", "--uuid", type=Optional[str], help="Crew UUID parameter") +def deploy_up(uuid: Optional[str]): """Deploy the crew.""" - deploy_cmd.deploy() + deploy_cmd.deploy(uuid=uuid) @deploy.command(name="create") diff --git a/src/crewai/cli/deploy/api.py b/src/crewai/cli/deploy/api.py new file mode 100644 index 000000000..2f80c7e18 --- /dev/null +++ b/src/crewai/cli/deploy/api.py @@ -0,0 +1,62 @@ +from os import getenv + +import requests + + +class CrewAPI: + """ + CrewAPI class to interact with the crewAI+ API. + """ + + CREW_BASE_URL = getenv("BASE_URL", "http://localhost:3000/crewai_plus/api/v1/crews") + MAIN_BASE_URL = getenv("MAIN_BASE_URL", "http://localhost:3000/crewai_plus/api/v1") + + def __init__(self, api_key: str) -> None: + self.api_key = api_key + self.headers = { + "Authorization": f"Bearer {api_key}", + "Content-Type": "application/json", + } + + def _make_request( + self, method: str, endpoint: str, base_url: str = CREW_BASE_URL, **kwargs + ) -> requests.Response: + url = f"{base_url}/{endpoint}" + return requests.request(method, url, headers=self.headers, **kwargs) + + def deploy_by_name(self, project_name: str) -> requests.Response: + return self._make_request("POST", f"by-name/{project_name}/deploy") + + def deploy_by_uuid(self, uuid: str) -> requests.Response: + return self._make_request("POST", f"{uuid}/deploy") + + def status_by_name(self, project_name: str) -> requests.Response: + return self._make_request("GET", f"by-name/{project_name}/status") + + def status_by_uuid(self, uuid: str) -> requests.Response: + return self._make_request("GET", f"{uuid}/status") + + def logs_by_name( + self, project_name: str, log_type: str = "deployment" + ) -> requests.Response: + return self._make_request("GET", f"by-name/{project_name}/logs/{log_type}") + + def logs_by_uuid( + self, uuid: str, log_type: str = "deployment" + ) -> requests.Response: + return self._make_request("GET", f"{uuid}/logs/{log_type}") + + def delete_by_name(self, project_name: str) -> requests.Response: + return self._make_request("DELETE", f"by-name/{project_name}") + + def delete_by_uuid(self, uuid: str) -> requests.Response: + return self._make_request("DELETE", f"{uuid}") + + def list_crews(self) -> requests.Response: + return self._make_request("GET", "") + + def create_crew(self, payload) -> requests.Response: + return self._make_request("POST", "", json=payload) + + def signup(self) -> requests.Response: + return self._make_request("GET", "signup_link", base_url=self.MAIN_BASE_URL) diff --git a/src/crewai/cli/deploy/main.py b/src/crewai/cli/deploy/main.py index 1c0c993a0..8e751be98 100644 --- a/src/crewai/cli/deploy/main.py +++ b/src/crewai/cli/deploy/main.py @@ -1,8 +1,9 @@ from os import getenv +from typing import Optional -import requests from rich.console import Console +from .api import CrewAPI from .utils import ( fetch_and_json_env_file, get_auth_token, @@ -18,76 +19,130 @@ class DeployCommand: def __init__(self): self.project_name = get_project_name() - self.remote_repo_url = get_git_remote_url() + self.client = CrewAPI(api_key=get_auth_token()) - def _make_request(self, method: str, endpoint: str, **kwargs) -> requests.Response: - url = f"{self.BASE_URL}/{endpoint}" - headers = { - "Authorization": f"Bearer {get_auth_token()}", - "Content-Type": "application/json", - } - return requests.request(method, url, headers=headers, **kwargs) - - def deploy(self) -> None: - console.print("Deploying the crew...", style="bold blue") - response = self._make_request( - "POST", f"crews/by-name/{self.project_name}/deploy" + def _handle_error(self, json_response: dict) -> None: + error = json_response.get("error") + message = json_response.get("message") + console.print( + f"Error: {error}", + style="bold red", ) - console.print(response.json()) + console.print( + f"Message: {message}", + style="bold red", + ) + + def _standard_no_param_error_message(self) -> None: + console.print( + "No uuid provided, project pyproject.toml not found or with error.", + style="bold red", + ) + + def deploy(self, uuid: Optional[str] = None) -> None: + console.print("Starting deployment...", style="bold blue") + if uuid: + response = self.client.deploy_by_uuid(uuid) + elif self.project_name: + response = self.client.deploy_by_name(self.project_name) + else: + self._standard_no_param_error_message() + return + + json_response = response.json() + if response.status_code == 200: + console.print("Deploying the crew...\n", style="bold blue") + + for key, value in json_response.items(): + console.print(f"{key.title()}: [green]{value}[/green]") + + console.print("\nTo check the status of the deployment, run:") + console.print("crewai deploy status") + console.print(" or") + console.print(f"crewai deploy status --uuid \"{json_response['uuid']}\"") + + else: + self._handle_error(json_response) def create_crew(self) -> None: console.print("Creating deployment...", style="bold blue") env_vars = fetch_and_json_env_file() + remote_repo_url = get_git_remote_url() + + input(f"Press Enter to continue with the following Env vars: {env_vars}") + input( + f"Press Enter to continue with the following remote repository: {remote_repo_url}\n" + ) payload = { "deploy": { "name": self.project_name, - "repo_clone_url": self.remote_repo_url, + "repo_clone_url": remote_repo_url, "env": env_vars, } } - response = self._make_request("POST", "crews", json=payload) - console.print(response.json()) + + response = self.client.create_crew(payload) + if response.status_code == 201: + json_response = response.json() + console.print("Deployment created successfully!\n", style="bold green") + console.print( + f"Name: {self.project_name} ({json_response['uuid']})", + style="bold green", + ) + console.print(f"Status: {json_response['status']}", style="bold green") + console.print("\nTo (re)deploy the crew, run:") + console.print("crewai deploy up") + console.print(" or") + console.print(f"crewai deploy --uuid {json_response['uuid']}") + else: + self._handle_error(response.json()) def list_crews(self) -> None: - console.print("Listing all Crews", style="bold blue") - response = self._make_request("GET", "crews") - crews_data = response.json() + console.print("Listing all Crews\n", style="bold blue") + response = self.client.list_crews() + json_response = response.json() if response.status_code == 200: - if crews_data: - for crew_data in crews_data: - console.print( - f"- {crew_data['name']} ({crew_data['uuid']}) [blue]{crew_data['status']}[/blue]" - ) - else: + for crew_data in json_response: console.print( - "You don't have any crews yet. Let's create one!", style="yellow" + f"- {crew_data['name']} ({crew_data['uuid']}) [blue]{crew_data['status']}[/blue]" ) - console.print("\t[green]crewai create --name [name][/green]") - - def get_crew_status(self) -> None: - console.print("Getting deployment status...", style="bold blue") - response = self._make_request( - "GET", f"crews/by-name/{self.project_name}/status" - ) - - if response.status_code == 200: - status_data = response.json() - console.print(f"Name:\t {status_data['name']}") - console.print(f"Status:\t {status_data['status']}") - console.print("\nUsage:") - console.print(f"\tcrewai inputs --name \"{status_data['name']}\"") - console.print( - f"\tcrewai kickoff --name \"{status_data['name']}\" --inputs [INPUTS]" - ) else: - console.print(response.json(), style="bold red") + console.print( + "You don't have any crews yet. Let's create one!", style="yellow" + ) + console.print(" [green]crewai create --name [name][/green]") - def get_crew_logs(self, log_type: str = "deployment") -> None: - console.print("Getting deployment logs...", style="bold blue") - response = self._make_request( - "GET", f"crews/by-name/{self.project_name}/logs/{log_type}" - ) + def get_crew_status(self, uuid: Optional[str] = None) -> None: + console.print("Fetching deployment status...", style="bold blue") + if uuid: + response = self.client.status_by_uuid(uuid) + elif self.project_name: + response = self.client.status_by_name(self.project_name) + else: + self._standard_no_param_error_message() + return + + json_response = response.json() + if response.status_code == 200: + console.print(f"Name:\t {json_response['name']}") + console.print(f"Status:\t {json_response['status']}") + + else: + self._handle_error(json_response) + + def get_crew_logs( + self, uuid: Optional[str], log_type: str = "dExacployment" + ) -> None: + console.print(f"Getting {log_type} logs...", style="bold blue") + + if uuid: + response = self.client.logs_by_uuid(uuid, log_type) + elif self.project_name: + response = self.client.logs_by_name(self.project_name, log_type) + else: + self._standard_no_param_error_message() + return if response.status_code == 200: log_messages = response.json() @@ -98,9 +153,16 @@ class DeployCommand: else: console.print(response.text, style="bold red") - def remove_crew(self) -> None: + def remove_crew(self, uuid: Optional[str]) -> None: console.print("Removing deployment...", style="bold blue") - response = self._make_request("DELETE", f"crews/by-name/{self.project_name}") + + if uuid: + response = self.client.delete_by_uuid(uuid) + elif self.project_name: + response = self.client.delete_by_name(self.project_name) + else: + self._standard_no_param_error_message() + return if response.status_code == 204: console.print( @@ -113,8 +175,9 @@ class DeployCommand: def signup(self) -> None: console.print("Signing Up", style="bold blue") - response = self._make_request("GET", "signup_link") + response = self.client.signup() + # signup_command(response["signup_link"]) if response.status_code == 200: data = response.json() console.print(f"Temporary credentials: {data['token']}") diff --git a/src/crewai/cli/deploy/utils.py b/src/crewai/cli/deploy/utils.py index 1bb602e7d..b01356ef8 100644 --- a/src/crewai/cli/deploy/utils.py +++ b/src/crewai/cli/deploy/utils.py @@ -42,13 +42,19 @@ def get_project_name(pyproject_path: str = "pyproject.toml"): # Extract the project name project_name = pyproject_content["tool"]["poetry"]["name"] + if "crewai" not in pyproject_content["tool"]["poetry"]["dependencies"]: + raise Exception("crewai is not in the dependencies.") + return project_name + except FileNotFoundError: print(f"Error: {pyproject_path} not found.") except KeyError: - print("Error: 'name' not found in [tool.poetry] section.") + print(f"Error: {pyproject_path} is not a valid pyproject.toml file.") except tomllib.TOMLDecodeError: print(f"Error: {pyproject_path} is not a valid TOML file.") + except Exception as e: + print(f"Error reading the pyproject.toml file: {e}") return None