diff --git a/docs/en/concepts/cli.mdx b/docs/en/concepts/cli.mdx
index 804f9d767..e14c264a9 100644
--- a/docs/en/concepts/cli.mdx
+++ b/docs/en/concepts/cli.mdx
@@ -88,7 +88,7 @@ crewai replay [OPTIONS]
- `-t, --task_id TEXT`: Replay the crew from this task ID, including all subsequent tasks
Example:
-```shell Terminal
+```shell Terminal
crewai replay -t task_123456
```
@@ -134,7 +134,7 @@ crewai test [OPTIONS]
- `-m, --model TEXT`: LLM Model to run the tests on the Crew (default: "gpt-4o-mini")
Example:
-```shell Terminal
+```shell Terminal
crewai test -n 5 -m gpt-3.5-turbo
```
@@ -151,7 +151,7 @@ Starting from version 0.103.0, the `crewai run` command can be used to run both
-Make sure to run these commands from the directory where your CrewAI project is set up.
+Make sure to run these commands from the directory where your CrewAI project is set up.
Some commands may require additional configuration or setup within your project structure.
@@ -235,7 +235,7 @@ You must be authenticated to CrewAI Enterprise to use these organization managem
- **Deploy the Crew**: Once you are authenticated, you can deploy your crew or flow to CrewAI Enterprise.
```shell Terminal
crewai deploy push
- ```
+ ```
- Initiates the deployment process on the CrewAI Enterprise platform.
- Upon successful initiation, it will output the Deployment created successfully! message along with the Deployment Name and a unique Deployment ID (UUID).
@@ -309,3 +309,82 @@ When you select a provider, the CLI will prompt you to enter the Key name and th
See the following link for each provider's key name:
* [LiteLLM Providers](https://docs.litellm.ai/docs/providers)
+
+### 12. Configuration Management
+
+Manage CLI configuration settings for CrewAI.
+
+```shell Terminal
+crewai config [COMMAND] [OPTIONS]
+```
+
+#### Commands:
+
+- `list`: Display all CLI configuration parameters
+```shell Terminal
+crewai config list
+```
+
+- `set`: Set a CLI configuration parameter
+```shell Terminal
+crewai config set
+```
+
+- `reset`: Reset all CLI configuration parameters to default values
+```shell Terminal
+crewai config reset
+```
+
+#### Available Configuration Parameters
+
+- `enterprise_base_url`: Base URL of the CrewAI Enterprise instance
+- `oauth2_provider`: OAuth2 provider used for authentication (e.g., workos, okta, auth0)
+- `oauth2_audience`: OAuth2 audience value, typically used to identify the target API or resource
+- `oauth2_client_id`: OAuth2 client ID issued by the provider, used during authentication requests
+- `oauth2_domain`: OAuth2 provider's domain (e.g., your-org.auth0.com) used for issuing tokens
+
+#### Examples
+
+Display current configuration:
+```shell Terminal
+crewai config list
+```
+
+Example output:
+```
+CrewAI CLI Configuration
+┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+┃ Setting ┃ Value ┃ Description ┃
+┡━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
+│ enterprise_base_url│ https://app.crewai.com │ Base URL of the CrewAI Enterprise instance │
+│ org_name │ Not set │ Name of the currently active organization │
+│ org_uuid │ Not set │ UUID of the currently active organization │
+│ oauth2_provider │ workos │ OAuth2 provider used for authentication (e.g., workos, okta, auth0). │
+│ oauth2_audience │ client_01YYY │ OAuth2 audience value, typically used to identify the target API or resource. │
+│ oauth2_client_id │ client_01XXX │ OAuth2 client ID issued by the provider, used during authentication requests. │
+│ oauth2_domain │ login.crewai.com │ OAuth2 provider's domain (e.g., your-org.auth0.com) used for issuing tokens. │
+```
+
+Set the enterprise base URL:
+```shell Terminal
+crewai config set enterprise_base_url https://my-enterprise.crewai.com
+```
+
+Set OAuth2 provider:
+```shell Terminal
+crewai config set oauth2_provider auth0
+```
+
+Set OAuth2 domain:
+```shell Terminal
+crewai config set oauth2_domain my-company.auth0.com
+```
+
+Reset all configuration to defaults:
+```shell Terminal
+crewai config reset
+```
+
+
+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.
+
diff --git a/docs/en/mcp/overview.mdx b/docs/en/mcp/overview.mdx
index a5c18b106..6b2268454 100644
--- a/docs/en/mcp/overview.mdx
+++ b/docs/en/mcp/overview.mdx
@@ -44,6 +44,19 @@ The `MCPServerAdapter` class from `crewai-tools` is the primary way to connect t
Using a Python context manager (`with` statement) is the **recommended approach** for `MCPServerAdapter`. It automatically handles starting and stopping the connection to the MCP server.
+## Connection Configuration
+
+The `MCPServerAdapter` supports several configuration options to customize the connection behavior:
+
+- **`connect_timeout`** (optional): Maximum time in seconds to wait for establishing a connection to the MCP server. Defaults to 30 seconds if not specified. This is particularly useful for remote servers that may have variable response times.
+
+```python
+# Example with custom connection timeout
+with MCPServerAdapter(server_params, connect_timeout=60) as tools:
+ # Connection will timeout after 60 seconds if not established
+ pass
+```
+
```python
from crewai import Agent
from crewai_tools import MCPServerAdapter
@@ -70,7 +83,7 @@ server_params = {
}
# Example usage (uncomment and adapt once server_params is set):
-with MCPServerAdapter(server_params) as mcp_tools:
+with MCPServerAdapter(server_params, connect_timeout=60) as mcp_tools:
print(f"Available tools: {[tool.name for tool in mcp_tools]}")
my_agent = Agent(
@@ -95,7 +108,7 @@ There are two ways to filter tools:
### Accessing a specific tool using dictionary-style indexing.
```python
-with MCPServerAdapter(server_params) as mcp_tools:
+with MCPServerAdapter(server_params, connect_timeout=60) as mcp_tools:
print(f"Available tools: {[tool.name for tool in mcp_tools]}")
my_agent = Agent(
@@ -112,7 +125,7 @@ with MCPServerAdapter(server_params) as mcp_tools:
### Pass a list of tool names to the `MCPServerAdapter` constructor.
```python
-with MCPServerAdapter(server_params, "tool_name") as mcp_tools:
+with MCPServerAdapter(server_params, "tool_name", connect_timeout=60) as mcp_tools:
print(f"Available tools: {[tool.name for tool in mcp_tools]}")
my_agent = Agent(
diff --git a/docs/pt-BR/concepts/cli.mdx b/docs/pt-BR/concepts/cli.mdx
index bc5c36a51..277abd50b 100644
--- a/docs/pt-BR/concepts/cli.mdx
+++ b/docs/pt-BR/concepts/cli.mdx
@@ -324,3 +324,82 @@ Ao escolher um provedor, o CLI solicitará que você informe o nome da chave e a
Veja o seguinte link para o nome de chave de cada provedor:
* [LiteLLM Providers](https://docs.litellm.ai/docs/providers)
+
+### 12. Gerenciamento de Configuração
+
+Gerencie as configurações do CLI para CrewAI.
+
+```shell Terminal
+crewai config [COMANDO] [OPÇÕES]
+```
+
+#### Comandos:
+
+- `list`: Exibir todos os parâmetros de configuração do CLI
+```shell Terminal
+crewai config list
+```
+
+- `set`: Definir um parâmetro de configuração do CLI
+```shell Terminal
+crewai config set
+```
+
+- `reset`: Redefinir todos os parâmetros de configuração do CLI para valores padrão
+```shell Terminal
+crewai config reset
+```
+
+#### Parâmetros de Configuração Disponíveis
+
+- `enterprise_base_url`: URL base da instância CrewAI Enterprise
+- `oauth2_provider`: Provedor OAuth2 usado para autenticação (ex: workos, okta, auth0)
+- `oauth2_audience`: Valor de audiência OAuth2, tipicamente usado para identificar a API ou recurso de destino
+- `oauth2_client_id`: ID do cliente OAuth2 emitido pelo provedor, usado durante solicitações de autenticação
+- `oauth2_domain`: Domínio do provedor OAuth2 (ex: sua-org.auth0.com) usado para emissão de tokens
+
+#### Exemplos
+
+Exibir configuração atual:
+```shell Terminal
+crewai config list
+```
+
+Exemplo de saída:
+```
+CrewAI CLI Configuration
+┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+┃ Setting ┃ Value ┃ Description ┃
+┡━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
+│ enterprise_base_url│ https://app.crewai.com │ Base URL of the CrewAI Enterprise instance │
+│ org_name │ Not set │ Name of the currently active organization │
+│ org_uuid │ Not set │ UUID of the currently active organization │
+│ oauth2_provider │ workos │ OAuth2 provider used for authentication (e.g., workos, okta, auth0). │
+│ oauth2_audience │ client_01YYY │ OAuth2 audience value, typically used to identify the target API or resource. │
+│ oauth2_client_id │ client_01XXX │ OAuth2 client ID issued by the provider, used during authentication requests. │
+│ oauth2_domain │ login.crewai.com │ OAuth2 provider's domain (e.g., your-org.auth0.com) used for issuing tokens. │
+```
+
+Definir a URL base do enterprise:
+```shell Terminal
+crewai config set enterprise_base_url https://minha-empresa.crewai.com
+```
+
+Definir provedor OAuth2:
+```shell Terminal
+crewai config set oauth2_provider auth0
+```
+
+Definir domínio OAuth2:
+```shell Terminal
+crewai config set oauth2_domain minha-empresa.auth0.com
+```
+
+Redefinir todas as configurações para padrões:
+```shell Terminal
+crewai config reset
+```
+
+
+As configurações são armazenadas em `~/.config/crewai/settings.json`. Algumas configurações como nome da organização e UUID são somente leitura e gerenciadas através de comandos de autenticação e organização. Configurações relacionadas ao repositório de ferramentas são ocultas e não podem ser definidas diretamente pelo usuário.
+
diff --git a/docs/pt-BR/mcp/overview.mdx b/docs/pt-BR/mcp/overview.mdx
index 5ce4444c9..5ceaaa144 100644
--- a/docs/pt-BR/mcp/overview.mdx
+++ b/docs/pt-BR/mcp/overview.mdx
@@ -44,6 +44,19 @@ A classe `MCPServerAdapter` da `crewai-tools` é a principal forma de conectar-s
O uso de um gerenciador de contexto Python (`with`) é a **abordagem recomendada** para o `MCPServerAdapter`. Ele lida automaticamente com a abertura e o fechamento da conexão com o servidor MCP.
+## Configuração de Conexão
+
+O `MCPServerAdapter` suporta várias opções de configuração para personalizar o comportamento da conexão:
+
+- **`connect_timeout`** (opcional): Tempo máximo em segundos para aguardar o estabelecimento de uma conexão com o servidor MCP. O padrão é 30 segundos se não especificado. Isso é particularmente útil para servidores remotos que podem ter tempos de resposta variáveis.
+
+```python
+# Exemplo com timeout personalizado para conexão
+with MCPServerAdapter(server_params, connect_timeout=60) as tools:
+ # A conexão terá timeout após 60 segundos se não estabelecida
+ pass
+```
+
```python
from crewai import Agent
from crewai_tools import MCPServerAdapter
@@ -70,7 +83,7 @@ server_params = {
}
# Exemplo de uso (descomente e adapte após definir server_params):
-with MCPServerAdapter(server_params) as mcp_tools:
+with MCPServerAdapter(server_params, connect_timeout=60) as mcp_tools:
print(f"Available tools: {[tool.name for tool in mcp_tools]}")
meu_agente = Agent(
@@ -88,7 +101,7 @@ Este padrão geral mostra como integrar ferramentas. Para exemplos específicos
## Filtrando Ferramentas
```python
-with MCPServerAdapter(server_params) as mcp_tools:
+with MCPServerAdapter(server_params, connect_timeout=60) as mcp_tools:
print(f"Available tools: {[tool.name for tool in mcp_tools]}")
meu_agente = Agent(
diff --git a/src/crewai/cli/authentication/constants.py b/src/crewai/cli/authentication/constants.py
index 0616d5407..22a212822 100644
--- a/src/crewai/cli/authentication/constants.py
+++ b/src/crewai/cli/authentication/constants.py
@@ -1,8 +1,6 @@
ALGORITHMS = ["RS256"]
+
+#TODO: The AUTH0 constants should be removed after WorkOS migration is completed
AUTH0_DOMAIN = "crewai.us.auth0.com"
AUTH0_CLIENT_ID = "DEVC5Fw6NlRoSzmDCcOhVq85EfLBjKa8"
AUTH0_AUDIENCE = "https://crewai.us.auth0.com/api/v2/"
-
-WORKOS_DOMAIN = "login.crewai.com"
-WORKOS_CLI_CONNECT_APP_ID = "client_01JYT06R59SP0NXYGD994NFXXX"
-WORKOS_ENVIRONMENT_ID = "client_01JNJQWBJ4SPFN3SWJM5T7BDG8"
diff --git a/src/crewai/cli/authentication/main.py b/src/crewai/cli/authentication/main.py
index 303c7a1fe..26a354aea 100644
--- a/src/crewai/cli/authentication/main.py
+++ b/src/crewai/cli/authentication/main.py
@@ -1,76 +1,92 @@
import time
import webbrowser
-from typing import Any, Dict
+from typing import Any, Dict, Optional
import requests
from rich.console import Console
+from pydantic import BaseModel, Field
-from .constants import (
- AUTH0_AUDIENCE,
- AUTH0_CLIENT_ID,
- AUTH0_DOMAIN,
- WORKOS_DOMAIN,
- WORKOS_CLI_CONNECT_APP_ID,
- WORKOS_ENVIRONMENT_ID,
-)
from .utils import TokenManager, validate_jwt_token
from urllib.parse import quote
from crewai.cli.plus_api import PlusAPI
from crewai.cli.config import Settings
+from crewai.cli.authentication.constants import (
+ AUTH0_AUDIENCE,
+ AUTH0_CLIENT_ID,
+ AUTH0_DOMAIN,
+)
console = Console()
+class Oauth2Settings(BaseModel):
+ provider: str = Field(description="OAuth2 provider used for authentication (e.g., workos, okta, auth0).")
+ client_id: str = Field(description="OAuth2 client ID issued by the provider, used during authentication requests.")
+ domain: str = Field(description="OAuth2 provider's domain (e.g., your-org.auth0.com) used for issuing tokens.")
+ audience: Optional[str] = Field(description="OAuth2 audience value, typically used to identify the target API or resource.", default=None)
+
+ @classmethod
+ def from_settings(cls):
+ settings = Settings()
+
+ return cls(
+ provider=settings.oauth2_provider,
+ domain=settings.oauth2_domain,
+ client_id=settings.oauth2_client_id,
+ audience=settings.oauth2_audience,
+ )
+
+
+class ProviderFactory:
+ @classmethod
+ def from_settings(cls, settings: Optional[Oauth2Settings] = None):
+ settings = settings or Oauth2Settings.from_settings()
+
+ import importlib
+ module = importlib.import_module(f"crewai.cli.authentication.providers.{settings.provider.lower()}")
+ provider = getattr(module, f"{settings.provider.capitalize()}Provider")
+
+ return provider(settings)
+
class AuthenticationCommand:
- AUTH0_DEVICE_CODE_URL = f"https://{AUTH0_DOMAIN}/oauth/device/code"
- AUTH0_TOKEN_URL = f"https://{AUTH0_DOMAIN}/oauth/token"
-
- WORKOS_DEVICE_CODE_URL = f"https://{WORKOS_DOMAIN}/oauth2/device_authorization"
- WORKOS_TOKEN_URL = f"https://{WORKOS_DOMAIN}/oauth2/token"
-
def __init__(self):
self.token_manager = TokenManager()
- # TODO: WORKOS - This variable is temporary until migration to WorkOS is complete.
- self.user_provider = "workos"
+ self.oauth2_provider = ProviderFactory.from_settings()
def login(self) -> None:
"""Sign up to CrewAI+"""
-
- device_code_url = self.WORKOS_DEVICE_CODE_URL
- token_url = self.WORKOS_TOKEN_URL
- client_id = WORKOS_CLI_CONNECT_APP_ID
- audience = None
-
console.print("Signing in to CrewAI Enterprise...\n", style="bold blue")
# TODO: WORKOS - Next line and conditional are temporary until migration to WorkOS is complete.
user_provider = self._determine_user_provider()
if user_provider == "auth0":
- device_code_url = self.AUTH0_DEVICE_CODE_URL
- token_url = self.AUTH0_TOKEN_URL
- client_id = AUTH0_CLIENT_ID
- audience = AUTH0_AUDIENCE
- self.user_provider = "auth0"
+ settings = Oauth2Settings(
+ provider="auth0",
+ client_id=AUTH0_CLIENT_ID,
+ domain=AUTH0_DOMAIN,
+ audience=AUTH0_AUDIENCE
+ )
+ self.oauth2_provider = ProviderFactory.from_settings(settings)
# End of temporary code.
- device_code_data = self._get_device_code(client_id, device_code_url, audience)
+ device_code_data = self._get_device_code()
self._display_auth_instructions(device_code_data)
- return self._poll_for_token(device_code_data, client_id, token_url)
+ return self._poll_for_token(device_code_data)
def _get_device_code(
- self, client_id: str, device_code_url: str, audience: str | None = None
+ self
) -> Dict[str, Any]:
"""Get the device code to authenticate the user."""
device_code_payload = {
- "client_id": client_id,
+ "client_id": self.oauth2_provider.get_client_id(),
"scope": "openid",
- "audience": audience,
+ "audience": self.oauth2_provider.get_audience(),
}
response = requests.post(
- url=device_code_url, data=device_code_payload, timeout=20
+ url=self.oauth2_provider.get_authorize_url(), data=device_code_payload, timeout=20
)
response.raise_for_status()
return response.json()
@@ -82,21 +98,21 @@ class AuthenticationCommand:
webbrowser.open(device_code_data["verification_uri_complete"])
def _poll_for_token(
- self, device_code_data: Dict[str, Any], client_id: str, token_poll_url: str
+ self, device_code_data: Dict[str, Any]
) -> None:
"""Polls the server for the token until it is received, or max attempts are reached."""
token_payload = {
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
"device_code": device_code_data["device_code"],
- "client_id": client_id,
+ "client_id": self.oauth2_provider.get_client_id(),
}
console.print("\nWaiting for authentication... ", style="bold blue", end="")
attempts = 0
while True and attempts < 10:
- response = requests.post(token_poll_url, data=token_payload, timeout=30)
+ response = requests.post(self.oauth2_provider.get_token_url(), data=token_payload, timeout=30)
token_data = response.json()
if response.status_code == 200:
@@ -128,19 +144,14 @@ class AuthenticationCommand:
"""Validates the JWT token and saves the token to the token manager."""
jwt_token = token_data["access_token"]
+ issuer = self.oauth2_provider.get_issuer()
jwt_token_data = {
"jwt_token": jwt_token,
- "jwks_url": f"https://{WORKOS_DOMAIN}/oauth2/jwks",
- "issuer": f"https://{WORKOS_DOMAIN}",
- "audience": WORKOS_ENVIRONMENT_ID,
+ "jwks_url": self.oauth2_provider.get_jwks_url(),
+ "issuer": issuer,
+ "audience": self.oauth2_provider.get_audience(),
}
- # TODO: WORKOS - The following conditional is temporary until migration to WorkOS is complete.
- if self.user_provider == "auth0":
- jwt_token_data["jwks_url"] = f"https://{AUTH0_DOMAIN}/.well-known/jwks.json"
- jwt_token_data["issuer"] = f"https://{AUTH0_DOMAIN}/"
- jwt_token_data["audience"] = AUTH0_AUDIENCE
-
decoded_token = validate_jwt_token(**jwt_token_data)
expires_at = decoded_token.get("exp", 0)
diff --git a/src/crewai/cli/authentication/providers/auth0.py b/src/crewai/cli/authentication/providers/auth0.py
new file mode 100644
index 000000000..8538550db
--- /dev/null
+++ b/src/crewai/cli/authentication/providers/auth0.py
@@ -0,0 +1,26 @@
+from crewai.cli.authentication.providers.base_provider import BaseProvider
+
+class Auth0Provider(BaseProvider):
+ def get_authorize_url(self) -> str:
+ return f"https://{self._get_domain()}/oauth/device/code"
+
+ def get_token_url(self) -> str:
+ return f"https://{self._get_domain()}/oauth/token"
+
+ def get_jwks_url(self) -> str:
+ return f"https://{self._get_domain()}/.well-known/jwks.json"
+
+ def get_issuer(self) -> str:
+ return f"https://{self._get_domain()}/"
+
+ def get_audience(self) -> str:
+ assert self.settings.audience is not None, "Audience is required"
+ return self.settings.audience
+
+ def get_client_id(self) -> str:
+ assert self.settings.client_id is not None, "Client ID is required"
+ return self.settings.client_id
+
+ def _get_domain(self) -> str:
+ assert self.settings.domain is not None, "Domain is required"
+ return self.settings.domain
diff --git a/src/crewai/cli/authentication/providers/base_provider.py b/src/crewai/cli/authentication/providers/base_provider.py
new file mode 100644
index 000000000..c321de9f7
--- /dev/null
+++ b/src/crewai/cli/authentication/providers/base_provider.py
@@ -0,0 +1,30 @@
+from abc import ABC, abstractmethod
+from crewai.cli.authentication.main import Oauth2Settings
+
+class BaseProvider(ABC):
+ def __init__(self, settings: Oauth2Settings):
+ self.settings = settings
+
+ @abstractmethod
+ def get_authorize_url(self) -> str:
+ ...
+
+ @abstractmethod
+ def get_token_url(self) -> str:
+ ...
+
+ @abstractmethod
+ def get_jwks_url(self) -> str:
+ ...
+
+ @abstractmethod
+ def get_issuer(self) -> str:
+ ...
+
+ @abstractmethod
+ def get_audience(self) -> str:
+ ...
+
+ @abstractmethod
+ def get_client_id(self) -> str:
+ ...
diff --git a/src/crewai/cli/authentication/providers/okta.py b/src/crewai/cli/authentication/providers/okta.py
new file mode 100644
index 000000000..14227ae2b
--- /dev/null
+++ b/src/crewai/cli/authentication/providers/okta.py
@@ -0,0 +1,22 @@
+from crewai.cli.authentication.providers.base_provider import BaseProvider
+
+class OktaProvider(BaseProvider):
+ def get_authorize_url(self) -> str:
+ return f"https://{self.settings.domain}/oauth2/default/v1/device/authorize"
+
+ def get_token_url(self) -> str:
+ return f"https://{self.settings.domain}/oauth2/default/v1/token"
+
+ def get_jwks_url(self) -> str:
+ return f"https://{self.settings.domain}/oauth2/default/v1/keys"
+
+ def get_issuer(self) -> str:
+ return f"https://{self.settings.domain}/oauth2/default"
+
+ def get_audience(self) -> str:
+ assert self.settings.audience is not None
+ return self.settings.audience
+
+ def get_client_id(self) -> str:
+ assert self.settings.client_id is not None
+ return self.settings.client_id
diff --git a/src/crewai/cli/authentication/providers/workos.py b/src/crewai/cli/authentication/providers/workos.py
new file mode 100644
index 000000000..8cf475a4d
--- /dev/null
+++ b/src/crewai/cli/authentication/providers/workos.py
@@ -0,0 +1,25 @@
+from crewai.cli.authentication.providers.base_provider import BaseProvider
+
+class WorkosProvider(BaseProvider):
+ def get_authorize_url(self) -> str:
+ return f"https://{self._get_domain()}/oauth2/device_authorization"
+
+ def get_token_url(self) -> str:
+ return f"https://{self._get_domain()}/oauth2/token"
+
+ def get_jwks_url(self) -> str:
+ return f"https://{self._get_domain()}/oauth2/jwks"
+
+ def get_issuer(self) -> str:
+ return f"https://{self._get_domain()}"
+
+ def get_audience(self) -> str:
+ return self.settings.audience or ""
+
+ def get_client_id(self) -> str:
+ assert self.settings.client_id is not None, "Client ID is required"
+ return self.settings.client_id
+
+ def _get_domain(self) -> str:
+ assert self.settings.domain is not None, "Domain is required"
+ return self.settings.domain
diff --git a/src/crewai/cli/config.py b/src/crewai/cli/config.py
index f2a87792e..a8da400a8 100644
--- a/src/crewai/cli/config.py
+++ b/src/crewai/cli/config.py
@@ -4,7 +4,13 @@ from typing import Optional
from pydantic import BaseModel, Field
-from crewai.cli.constants import DEFAULT_CREWAI_ENTERPRISE_URL
+from crewai.cli.constants import (
+ DEFAULT_CREWAI_ENTERPRISE_URL,
+ CREWAI_ENTERPRISE_DEFAULT_OAUTH2_PROVIDER,
+ CREWAI_ENTERPRISE_DEFAULT_OAUTH2_AUDIENCE,
+ CREWAI_ENTERPRISE_DEFAULT_OAUTH2_CLIENT_ID,
+ CREWAI_ENTERPRISE_DEFAULT_OAUTH2_DOMAIN,
+)
DEFAULT_CONFIG_PATH = Path.home() / ".config" / "crewai" / "settings.json"
@@ -19,11 +25,19 @@ USER_SETTINGS_KEYS = [
# Settings that are related to the CLI
CLI_SETTINGS_KEYS = [
"enterprise_base_url",
+ "oauth2_provider",
+ "oauth2_audience",
+ "oauth2_client_id",
+ "oauth2_domain",
]
# Default values for CLI settings
DEFAULT_CLI_SETTINGS = {
"enterprise_base_url": DEFAULT_CREWAI_ENTERPRISE_URL,
+ "oauth2_provider": CREWAI_ENTERPRISE_DEFAULT_OAUTH2_PROVIDER,
+ "oauth2_audience": CREWAI_ENTERPRISE_DEFAULT_OAUTH2_AUDIENCE,
+ "oauth2_client_id": CREWAI_ENTERPRISE_DEFAULT_OAUTH2_CLIENT_ID,
+ "oauth2_domain": CREWAI_ENTERPRISE_DEFAULT_OAUTH2_DOMAIN,
}
# Readonly settings - cannot be set by the user
@@ -39,10 +53,9 @@ HIDDEN_SETTINGS_KEYS = [
"tool_repository_password",
]
-
class Settings(BaseModel):
enterprise_base_url: Optional[str] = Field(
- default=DEFAULT_CREWAI_ENTERPRISE_URL,
+ default=DEFAULT_CLI_SETTINGS["enterprise_base_url"],
description="Base URL of the CrewAI Enterprise instance",
)
tool_repository_username: Optional[str] = Field(
@@ -59,6 +72,26 @@ class Settings(BaseModel):
)
config_path: Path = Field(default=DEFAULT_CONFIG_PATH, frozen=True, exclude=True)
+ oauth2_provider: str = Field(
+ description="OAuth2 provider used for authentication (e.g., workos, okta, auth0).",
+ default=DEFAULT_CLI_SETTINGS["oauth2_provider"]
+ )
+
+ oauth2_audience: Optional[str] = Field(
+ description="OAuth2 audience value, typically used to identify the target API or resource.",
+ default=DEFAULT_CLI_SETTINGS["oauth2_audience"]
+ )
+
+ oauth2_client_id: str = Field(
+ default=DEFAULT_CLI_SETTINGS["oauth2_client_id"],
+ description="OAuth2 client ID issued by the provider, used during authentication requests.",
+ )
+
+ oauth2_domain: str = Field(
+ description="OAuth2 provider's domain (e.g., your-org.auth0.com) used for issuing tokens.",
+ default=DEFAULT_CLI_SETTINGS["oauth2_domain"]
+ )
+
def __init__(self, config_path: Path = DEFAULT_CONFIG_PATH, **data):
"""Load Settings from config path"""
config_path.parent.mkdir(parents=True, exist_ok=True)
@@ -105,4 +138,4 @@ class Settings(BaseModel):
def _reset_cli_settings(self) -> None:
"""Reset all CLI settings to default values"""
for key in CLI_SETTINGS_KEYS:
- setattr(self, key, DEFAULT_CLI_SETTINGS[key])
+ setattr(self, key, DEFAULT_CLI_SETTINGS.get(key))
diff --git a/src/crewai/cli/constants.py b/src/crewai/cli/constants.py
index 06a02bee5..d0e867c41 100644
--- a/src/crewai/cli/constants.py
+++ b/src/crewai/cli/constants.py
@@ -1,4 +1,8 @@
DEFAULT_CREWAI_ENTERPRISE_URL = "https://app.crewai.com"
+CREWAI_ENTERPRISE_DEFAULT_OAUTH2_PROVIDER = "workos"
+CREWAI_ENTERPRISE_DEFAULT_OAUTH2_AUDIENCE = "client_01JNJQWBJ4SPFN3SWJM5T7BDG8"
+CREWAI_ENTERPRISE_DEFAULT_OAUTH2_CLIENT_ID = "client_01JYT06R59SP0NXYGD994NFXXX"
+CREWAI_ENTERPRISE_DEFAULT_OAUTH2_DOMAIN = "login.crewai.com"
ENV_VARS = {
"openai": [
diff --git a/src/crewai/crew.py b/src/crewai/crew.py
index e9d4b8d0a..5c6fba27a 100644
--- a/src/crewai/crew.py
+++ b/src/crewai/crew.py
@@ -133,7 +133,7 @@ class Crew(FlowTrackable, BaseModel):
default_factory=TaskOutputStorageHandler
)
- name: Optional[str] = Field(default=None)
+ name: Optional[str] = Field(default="crew")
cache: bool = Field(default=True)
tasks: List[Task] = Field(default_factory=list)
agents: List[BaseAgent] = Field(default_factory=list)
@@ -575,7 +575,7 @@ class Crew(FlowTrackable, BaseModel):
crewai_event_bus.emit(
self,
CrewTrainStartedEvent(
- crew_name=self.name or "crew",
+ crew_name=self.name,
n_iterations=n_iterations,
filename=filename,
inputs=inputs,
@@ -602,7 +602,7 @@ class Crew(FlowTrackable, BaseModel):
crewai_event_bus.emit(
self,
CrewTrainCompletedEvent(
- crew_name=self.name or "crew",
+ crew_name=self.name,
n_iterations=n_iterations,
filename=filename,
),
@@ -610,7 +610,7 @@ class Crew(FlowTrackable, BaseModel):
except Exception as e:
crewai_event_bus.emit(
self,
- CrewTrainFailedEvent(error=str(e), crew_name=self.name or "crew"),
+ CrewTrainFailedEvent(error=str(e), crew_name=self.name),
)
self._logger.log("error", f"Training failed: {e}", color="red")
CrewTrainingHandler(TRAINING_DATA_FILE).clear()
@@ -634,7 +634,7 @@ class Crew(FlowTrackable, BaseModel):
crewai_event_bus.emit(
self,
- CrewKickoffStartedEvent(crew_name=self.name or "crew", inputs=inputs),
+ CrewKickoffStartedEvent(crew_name=self.name, inputs=inputs),
)
# Starts the crew to work on its assigned tasks.
@@ -683,7 +683,7 @@ class Crew(FlowTrackable, BaseModel):
except Exception as e:
crewai_event_bus.emit(
self,
- CrewKickoffFailedEvent(error=str(e), crew_name=self.name or "crew"),
+ CrewKickoffFailedEvent(error=str(e), crew_name=self.name),
)
raise
finally:
@@ -1077,7 +1077,7 @@ class Crew(FlowTrackable, BaseModel):
crewai_event_bus.emit(
self,
CrewKickoffCompletedEvent(
- crew_name=self.name or "crew", output=final_task_output
+ crew_name=self.name, output=final_task_output
),
)
return CrewOutput(
@@ -1325,7 +1325,7 @@ class Crew(FlowTrackable, BaseModel):
crewai_event_bus.emit(
self,
CrewTestStartedEvent(
- crew_name=self.name or "crew",
+ crew_name=self.name,
n_iterations=n_iterations,
eval_llm=llm_instance,
inputs=inputs,
@@ -1344,13 +1344,13 @@ class Crew(FlowTrackable, BaseModel):
crewai_event_bus.emit(
self,
CrewTestCompletedEvent(
- crew_name=self.name or "crew",
+ crew_name=self.name,
),
)
except Exception as e:
crewai_event_bus.emit(
self,
- CrewTestFailedEvent(error=str(e), crew_name=self.name or "crew"),
+ CrewTestFailedEvent(error=str(e), crew_name=self.name),
)
raise
diff --git a/src/crewai/lite_agent.py b/src/crewai/lite_agent.py
index e14f4576f..0e99fb563 100644
--- a/src/crewai/lite_agent.py
+++ b/src/crewai/lite_agent.py
@@ -147,7 +147,7 @@ class LiteAgent(FlowTrackable, BaseModel):
default=15, description="Maximum number of iterations for tool usage"
)
max_execution_time: Optional[int] = Field(
- default=None, description="Maximum execution time in seconds"
+ default=None, description=". Maximum execution time in seconds"
)
respect_context_window: bool = Field(
default=True,
@@ -622,4 +622,4 @@ class LiteAgent(FlowTrackable, BaseModel):
def _append_message(self, text: str, role: str = "assistant") -> None:
"""Append a message to the message list with the given role."""
- self._messages.append(format_message_for_llm(text, role=role))
\ No newline at end of file
+ self._messages.append(format_message_for_llm(text, role=role))
diff --git a/src/crewai/utilities/events/utils/console_formatter.py b/src/crewai/utilities/events/utils/console_formatter.py
index 24f92e386..3088603bb 100644
--- a/src/crewai/utilities/events/utils/console_formatter.py
+++ b/src/crewai/utilities/events/utils/console_formatter.py
@@ -1387,6 +1387,7 @@ class ConsoleFormatter:
theme="monokai",
line_numbers=False,
background_color="default",
+ word_wrap=True,
)
content.append("\n")
diff --git a/tests/cli/authentication/providers/__init__.py b/tests/cli/authentication/providers/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/cli/authentication/providers/test_auth0.py b/tests/cli/authentication/providers/test_auth0.py
new file mode 100644
index 000000000..e513a1fb7
--- /dev/null
+++ b/tests/cli/authentication/providers/test_auth0.py
@@ -0,0 +1,91 @@
+import pytest
+from crewai.cli.authentication.main import Oauth2Settings
+from crewai.cli.authentication.providers.auth0 import Auth0Provider
+
+
+
+class TestAuth0Provider:
+
+ @pytest.fixture(autouse=True)
+ def setup_method(self):
+ self.valid_settings = Oauth2Settings(
+ provider="auth0",
+ domain="test-domain.auth0.com",
+ client_id="test-client-id",
+ audience="test-audience"
+ )
+ self.provider = Auth0Provider(self.valid_settings)
+
+ def test_initialization_with_valid_settings(self):
+ provider = Auth0Provider(self.valid_settings)
+ assert provider.settings == self.valid_settings
+ assert provider.settings.provider == "auth0"
+ assert provider.settings.domain == "test-domain.auth0.com"
+ assert provider.settings.client_id == "test-client-id"
+ assert provider.settings.audience == "test-audience"
+
+ def test_get_authorize_url(self):
+ expected_url = "https://test-domain.auth0.com/oauth/device/code"
+ assert self.provider.get_authorize_url() == expected_url
+
+ def test_get_authorize_url_with_different_domain(self):
+ settings = Oauth2Settings(
+ provider="auth0",
+ domain="my-company.auth0.com",
+ client_id="test-client",
+ audience="test-audience"
+ )
+ provider = Auth0Provider(settings)
+ expected_url = "https://my-company.auth0.com/oauth/device/code"
+ assert provider.get_authorize_url() == expected_url
+
+ def test_get_token_url(self):
+ expected_url = "https://test-domain.auth0.com/oauth/token"
+ assert self.provider.get_token_url() == expected_url
+
+ def test_get_token_url_with_different_domain(self):
+ settings = Oauth2Settings(
+ provider="auth0",
+ domain="another-domain.auth0.com",
+ client_id="test-client",
+ audience="test-audience"
+ )
+ provider = Auth0Provider(settings)
+ expected_url = "https://another-domain.auth0.com/oauth/token"
+ assert provider.get_token_url() == expected_url
+
+ def test_get_jwks_url(self):
+ expected_url = "https://test-domain.auth0.com/.well-known/jwks.json"
+ assert self.provider.get_jwks_url() == expected_url
+
+ def test_get_jwks_url_with_different_domain(self):
+ settings = Oauth2Settings(
+ provider="auth0",
+ domain="dev.auth0.com",
+ client_id="test-client",
+ audience="test-audience"
+ )
+ provider = Auth0Provider(settings)
+ expected_url = "https://dev.auth0.com/.well-known/jwks.json"
+ assert provider.get_jwks_url() == expected_url
+
+ def test_get_issuer(self):
+ expected_issuer = "https://test-domain.auth0.com/"
+ assert self.provider.get_issuer() == expected_issuer
+
+ def test_get_issuer_with_different_domain(self):
+ settings = Oauth2Settings(
+ provider="auth0",
+ domain="prod.auth0.com",
+ client_id="test-client",
+ audience="test-audience"
+ )
+ provider = Auth0Provider(settings)
+ expected_issuer = "https://prod.auth0.com/"
+ assert provider.get_issuer() == expected_issuer
+
+ def test_get_audience(self):
+ assert self.provider.get_audience() == "test-audience"
+
+ def test_get_client_id(self):
+ assert self.provider.get_client_id() == "test-client-id"
diff --git a/tests/cli/authentication/providers/test_okta.py b/tests/cli/authentication/providers/test_okta.py
new file mode 100644
index 000000000..b952464ba
--- /dev/null
+++ b/tests/cli/authentication/providers/test_okta.py
@@ -0,0 +1,102 @@
+import pytest
+from crewai.cli.authentication.main import Oauth2Settings
+from crewai.cli.authentication.providers.okta import OktaProvider
+
+
+class TestOktaProvider:
+
+ @pytest.fixture(autouse=True)
+ def setup_method(self):
+ self.valid_settings = Oauth2Settings(
+ provider="okta",
+ domain="test-domain.okta.com",
+ client_id="test-client-id",
+ audience="test-audience"
+ )
+ self.provider = OktaProvider(self.valid_settings)
+
+ def test_initialization_with_valid_settings(self):
+ provider = OktaProvider(self.valid_settings)
+ assert provider.settings == self.valid_settings
+ assert provider.settings.provider == "okta"
+ assert provider.settings.domain == "test-domain.okta.com"
+ assert provider.settings.client_id == "test-client-id"
+ assert provider.settings.audience == "test-audience"
+
+ def test_get_authorize_url(self):
+ expected_url = "https://test-domain.okta.com/oauth2/default/v1/device/authorize"
+ assert self.provider.get_authorize_url() == expected_url
+
+ def test_get_authorize_url_with_different_domain(self):
+ settings = Oauth2Settings(
+ provider="okta",
+ domain="my-company.okta.com",
+ client_id="test-client",
+ audience="test-audience"
+ )
+ provider = OktaProvider(settings)
+ expected_url = "https://my-company.okta.com/oauth2/default/v1/device/authorize"
+ assert provider.get_authorize_url() == expected_url
+
+ def test_get_token_url(self):
+ expected_url = "https://test-domain.okta.com/oauth2/default/v1/token"
+ assert self.provider.get_token_url() == expected_url
+
+ def test_get_token_url_with_different_domain(self):
+ settings = Oauth2Settings(
+ provider="okta",
+ domain="another-domain.okta.com",
+ client_id="test-client",
+ audience="test-audience"
+ )
+ provider = OktaProvider(settings)
+ expected_url = "https://another-domain.okta.com/oauth2/default/v1/token"
+ assert provider.get_token_url() == expected_url
+
+ def test_get_jwks_url(self):
+ expected_url = "https://test-domain.okta.com/oauth2/default/v1/keys"
+ assert self.provider.get_jwks_url() == expected_url
+
+ def test_get_jwks_url_with_different_domain(self):
+ settings = Oauth2Settings(
+ provider="okta",
+ domain="dev.okta.com",
+ client_id="test-client",
+ audience="test-audience"
+ )
+ provider = OktaProvider(settings)
+ expected_url = "https://dev.okta.com/oauth2/default/v1/keys"
+ assert provider.get_jwks_url() == expected_url
+
+ def test_get_issuer(self):
+ expected_issuer = "https://test-domain.okta.com/oauth2/default"
+ assert self.provider.get_issuer() == expected_issuer
+
+ def test_get_issuer_with_different_domain(self):
+ settings = Oauth2Settings(
+ provider="okta",
+ domain="prod.okta.com",
+ client_id="test-client",
+ audience="test-audience"
+ )
+ provider = OktaProvider(settings)
+ expected_issuer = "https://prod.okta.com/oauth2/default"
+ assert provider.get_issuer() == expected_issuer
+
+ def test_get_audience(self):
+ assert self.provider.get_audience() == "test-audience"
+
+ def test_get_audience_assertion_error_when_none(self):
+ settings = Oauth2Settings(
+ provider="okta",
+ domain="test-domain.okta.com",
+ client_id="test-client-id",
+ audience=None
+ )
+ provider = OktaProvider(settings)
+
+ with pytest.raises(AssertionError):
+ provider.get_audience()
+
+ def test_get_client_id(self):
+ assert self.provider.get_client_id() == "test-client-id"
diff --git a/tests/cli/authentication/providers/test_workos.py b/tests/cli/authentication/providers/test_workos.py
new file mode 100644
index 000000000..7eda774d6
--- /dev/null
+++ b/tests/cli/authentication/providers/test_workos.py
@@ -0,0 +1,100 @@
+import pytest
+from crewai.cli.authentication.main import Oauth2Settings
+from crewai.cli.authentication.providers.workos import WorkosProvider
+
+
+class TestWorkosProvider:
+
+ @pytest.fixture(autouse=True)
+ def setup_method(self):
+ self.valid_settings = Oauth2Settings(
+ provider="workos",
+ domain="login.company.com",
+ client_id="test-client-id",
+ audience="test-audience"
+ )
+ self.provider = WorkosProvider(self.valid_settings)
+
+ def test_initialization_with_valid_settings(self):
+ provider = WorkosProvider(self.valid_settings)
+ assert provider.settings == self.valid_settings
+ assert provider.settings.provider == "workos"
+ assert provider.settings.domain == "login.company.com"
+ assert provider.settings.client_id == "test-client-id"
+ assert provider.settings.audience == "test-audience"
+
+ def test_get_authorize_url(self):
+ expected_url = "https://login.company.com/oauth2/device_authorization"
+ assert self.provider.get_authorize_url() == expected_url
+
+ def test_get_authorize_url_with_different_domain(self):
+ settings = Oauth2Settings(
+ provider="workos",
+ domain="login.example.com",
+ client_id="test-client",
+ audience="test-audience"
+ )
+ provider = WorkosProvider(settings)
+ expected_url = "https://login.example.com/oauth2/device_authorization"
+ assert provider.get_authorize_url() == expected_url
+
+ def test_get_token_url(self):
+ expected_url = "https://login.company.com/oauth2/token"
+ assert self.provider.get_token_url() == expected_url
+
+ def test_get_token_url_with_different_domain(self):
+ settings = Oauth2Settings(
+ provider="workos",
+ domain="api.workos.com",
+ client_id="test-client",
+ audience="test-audience"
+ )
+ provider = WorkosProvider(settings)
+ expected_url = "https://api.workos.com/oauth2/token"
+ assert provider.get_token_url() == expected_url
+
+ def test_get_jwks_url(self):
+ expected_url = "https://login.company.com/oauth2/jwks"
+ assert self.provider.get_jwks_url() == expected_url
+
+ def test_get_jwks_url_with_different_domain(self):
+ settings = Oauth2Settings(
+ provider="workos",
+ domain="auth.enterprise.com",
+ client_id="test-client",
+ audience="test-audience"
+ )
+ provider = WorkosProvider(settings)
+ expected_url = "https://auth.enterprise.com/oauth2/jwks"
+ assert provider.get_jwks_url() == expected_url
+
+ def test_get_issuer(self):
+ expected_issuer = "https://login.company.com"
+ assert self.provider.get_issuer() == expected_issuer
+
+ def test_get_issuer_with_different_domain(self):
+ settings = Oauth2Settings(
+ provider="workos",
+ domain="sso.company.com",
+ client_id="test-client",
+ audience="test-audience"
+ )
+ provider = WorkosProvider(settings)
+ expected_issuer = "https://sso.company.com"
+ assert provider.get_issuer() == expected_issuer
+
+ def test_get_audience(self):
+ assert self.provider.get_audience() == "test-audience"
+
+ def test_get_audience_fallback_to_default(self):
+ settings = Oauth2Settings(
+ provider="workos",
+ domain="login.company.com",
+ client_id="test-client-id",
+ audience=None
+ )
+ provider = WorkosProvider(settings)
+ assert provider.get_audience() == ""
+
+ def test_get_client_id(self):
+ assert self.provider.get_client_id() == "test-client-id"
diff --git a/tests/cli/authentication/test_auth_main.py b/tests/cli/authentication/test_auth_main.py
index 61511b5a1..d608c9ba4 100644
--- a/tests/cli/authentication/test_auth_main.py
+++ b/tests/cli/authentication/test_auth_main.py
@@ -6,10 +6,12 @@ from crewai.cli.authentication.main import AuthenticationCommand
from crewai.cli.authentication.constants import (
AUTH0_AUDIENCE,
AUTH0_CLIENT_ID,
- AUTH0_DOMAIN,
- WORKOS_DOMAIN,
- WORKOS_CLI_CONNECT_APP_ID,
- WORKOS_ENVIRONMENT_ID,
+ AUTH0_DOMAIN
+)
+from crewai.cli.constants import (
+ CREWAI_ENTERPRISE_DEFAULT_OAUTH2_DOMAIN,
+ CREWAI_ENTERPRISE_DEFAULT_OAUTH2_CLIENT_ID,
+ CREWAI_ENTERPRISE_DEFAULT_OAUTH2_AUDIENCE,
)
@@ -27,14 +29,17 @@ class TestAuthenticationCommand:
"token_url": f"https://{AUTH0_DOMAIN}/oauth/token",
"client_id": AUTH0_CLIENT_ID,
"audience": AUTH0_AUDIENCE,
+ "domain": AUTH0_DOMAIN,
},
),
(
"workos",
{
- "device_code_url": f"https://{WORKOS_DOMAIN}/oauth2/device_authorization",
- "token_url": f"https://{WORKOS_DOMAIN}/oauth2/token",
- "client_id": WORKOS_CLI_CONNECT_APP_ID,
+ "device_code_url": f"https://{CREWAI_ENTERPRISE_DEFAULT_OAUTH2_DOMAIN}/oauth2/device_authorization",
+ "token_url": f"https://{CREWAI_ENTERPRISE_DEFAULT_OAUTH2_DOMAIN}/oauth2/token",
+ "client_id": CREWAI_ENTERPRISE_DEFAULT_OAUTH2_CLIENT_ID,
+ "audience": CREWAI_ENTERPRISE_DEFAULT_OAUTH2_AUDIENCE,
+ "domain": CREWAI_ENTERPRISE_DEFAULT_OAUTH2_DOMAIN,
},
),
],
@@ -70,19 +75,16 @@ class TestAuthenticationCommand:
"Signing in to CrewAI Enterprise...\n", style="bold blue"
)
mock_determine_provider.assert_called_once()
- mock_get_device.assert_called_once_with(
- expected_urls["client_id"],
- expected_urls["device_code_url"],
- expected_urls.get("audience", None),
- )
+ mock_get_device.assert_called_once()
mock_display.assert_called_once_with(
{"device_code": "test_code", "user_code": "123456"}
)
mock_poll.assert_called_once_with(
{"device_code": "test_code", "user_code": "123456"},
- expected_urls["client_id"],
- expected_urls["token_url"],
)
+ assert self.auth_command.oauth2_provider.get_client_id() == expected_urls["client_id"]
+ assert self.auth_command.oauth2_provider.get_audience() == expected_urls["audience"]
+ assert self.auth_command.oauth2_provider._get_domain() == expected_urls["domain"]
@patch("crewai.cli.authentication.main.webbrowser")
@patch("crewai.cli.authentication.main.console.print")
@@ -115,9 +117,9 @@ class TestAuthenticationCommand:
(
"workos",
{
- "jwks_url": f"https://{WORKOS_DOMAIN}/oauth2/jwks",
- "issuer": f"https://{WORKOS_DOMAIN}",
- "audience": WORKOS_ENVIRONMENT_ID,
+ "jwks_url": f"https://{CREWAI_ENTERPRISE_DEFAULT_OAUTH2_DOMAIN}/oauth2/jwks",
+ "issuer": f"https://{CREWAI_ENTERPRISE_DEFAULT_OAUTH2_DOMAIN}",
+ "audience": CREWAI_ENTERPRISE_DEFAULT_OAUTH2_AUDIENCE,
},
),
],
@@ -133,7 +135,15 @@ class TestAuthenticationCommand:
jwt_config,
has_expiration,
):
- self.auth_command.user_provider = user_provider
+ from crewai.cli.authentication.providers.auth0 import Auth0Provider
+ from crewai.cli.authentication.providers.workos import WorkosProvider
+ from crewai.cli.authentication.main import Oauth2Settings
+
+ if user_provider == "auth0":
+ self.auth_command.oauth2_provider = Auth0Provider(settings=Oauth2Settings(provider=user_provider, client_id="test-client-id", domain=AUTH0_DOMAIN, audience=jwt_config["audience"]))
+ elif user_provider == "workos":
+ self.auth_command.oauth2_provider = WorkosProvider(settings=Oauth2Settings(provider=user_provider, client_id="test-client-id", domain=CREWAI_ENTERPRISE_DEFAULT_OAUTH2_DOMAIN, audience=jwt_config["audience"]))
+
token_data = {"access_token": "test_access_token", "id_token": "test_id_token"}
if has_expiration:
@@ -311,11 +321,12 @@ class TestAuthenticationCommand:
}
mock_post.return_value = mock_response
- result = self.auth_command._get_device_code(
- client_id="test_client",
- device_code_url="https://example.com/device",
- audience="test_audience",
- )
+ self.auth_command.oauth2_provider = MagicMock()
+ self.auth_command.oauth2_provider.get_client_id.return_value = "test_client"
+ self.auth_command.oauth2_provider.get_authorize_url.return_value = "https://example.com/device"
+ self.auth_command.oauth2_provider.get_audience.return_value = "test_audience"
+
+ result = self.auth_command._get_device_code()
mock_post.assert_called_once_with(
url="https://example.com/device",
@@ -354,8 +365,12 @@ class TestAuthenticationCommand:
self.auth_command, "_login_to_tool_repository"
) as mock_tool_login,
):
+ self.auth_command.oauth2_provider = MagicMock()
+ self.auth_command.oauth2_provider.get_token_url.return_value = "https://example.com/token"
+ self.auth_command.oauth2_provider.get_client_id.return_value = "test_client"
+
self.auth_command._poll_for_token(
- device_code_data, "test_client", "https://example.com/token"
+ device_code_data
)
mock_post.assert_called_once_with(
@@ -392,7 +407,7 @@ class TestAuthenticationCommand:
}
self.auth_command._poll_for_token(
- device_code_data, "test_client", "https://example.com/token"
+ device_code_data
)
mock_console_print.assert_any_call(
@@ -415,5 +430,14 @@ class TestAuthenticationCommand:
with pytest.raises(requests.HTTPError):
self.auth_command._poll_for_token(
- device_code_data, "test_client", "https://example.com/token"
+ device_code_data
)
+ # @patch(
+ # "crewai.cli.authentication.main.AuthenticationCommand._determine_user_provider"
+ # )
+ # def test_login_with_auth0(self, mock_determine_provider):
+ # from crewai.cli.authentication.providers.auth0 import Auth0Provider
+ # from crewai.cli.authentication.main import Oauth2Settings
+
+ # self.auth_command.oauth2_provider = Auth0Provider(settings=Oauth2Settings(provider="auth0", client_id=AUTH0_CLIENT_ID, domain=AUTH0_DOMAIN, audience=AUTH0_AUDIENCE))
+ # self.auth_command.login()
diff --git a/tests/cli/config_test.py b/tests/cli/config_test.py
index 06cbfcf2c..a492da54a 100644
--- a/tests/cli/config_test.py
+++ b/tests/cli/config_test.py
@@ -79,7 +79,7 @@ class TestSettings(unittest.TestCase):
for key in user_settings.keys():
self.assertEqual(getattr(settings, key), None)
for key in cli_settings.keys():
- self.assertEqual(getattr(settings, key), DEFAULT_CLI_SETTINGS[key])
+ self.assertEqual(getattr(settings, key), DEFAULT_CLI_SETTINGS.get(key))
def test_dump_new_settings(self):
settings = Settings(
diff --git a/tests/cli/test_settings_command.py b/tests/cli/test_settings_command.py
index 71d016a52..f15deb821 100644
--- a/tests/cli/test_settings_command.py
+++ b/tests/cli/test_settings_command.py
@@ -81,11 +81,10 @@ class TestSettingsCommand(unittest.TestCase):
self.settings_command.reset_all_settings()
- print(USER_SETTINGS_KEYS)
for key in USER_SETTINGS_KEYS:
self.assertEqual(getattr(self.settings_command.settings, key), None)
for key in CLI_SETTINGS_KEYS:
self.assertEqual(
- getattr(self.settings_command.settings, key), DEFAULT_CLI_SETTINGS[key]
+ getattr(self.settings_command.settings, key), DEFAULT_CLI_SETTINGS.get(key)
)
diff --git a/tests/crew_test.py b/tests/crew_test.py
index f91528b4a..eb45d1e37 100644
--- a/tests/crew_test.py
+++ b/tests/crew_test.py
@@ -4756,3 +4756,13 @@ def test_reset_agent_knowledge_with_only_agent_knowledge(researcher, writer):
mock_reset_agent_knowledge.assert_called_once_with(
[mock_ks_research, mock_ks_writer]
)
+
+def test_default_crew_name(researcher, writer):
+ crew = Crew(
+ agents=[researcher, writer],
+ tasks=[
+ Task(description="Task 1", expected_output="output", agent=researcher),
+ Task(description="Task 2", expected_output="output", agent=writer),
+ ],
+ )
+ assert crew.name == "crew"