Open deployment page after CLI deploy (#6343)
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Vulnerability Scan / pip-audit (push) Has been cancelled

* Open deployment page after CLI deploy

* Format deploy browser URL helper

* Handle browser launch failures

* Prefer nested deployment identifiers
This commit is contained in:
João Moura
2026-06-26 14:34:07 -03:00
committed by GitHub
parent f364a7d988
commit e10c17fcf6
2 changed files with 138 additions and 1 deletions

View File

@@ -1,12 +1,15 @@
from pathlib import Path
import subprocess
from typing import Any
from urllib.parse import quote
import webbrowser
from crewai_core.plus_api import CreateCrewPayload
from rich.console import Console
from crewai_cli import git
from crewai_cli.command import BaseCommand, PlusAPIMixin
from crewai_cli.constants import DEFAULT_CREWAI_ENTERPRISE_URL
from crewai_cli.deploy.archive import create_project_zip
from crewai_cli.deploy.validate import DeployValidator, Severity, render_report
from crewai_cli.utils import fetch_and_json_env_file, get_project_name
@@ -14,6 +17,8 @@ from crewai_cli.utils import fetch_and_json_env_file, get_project_name
console = Console()
_MISSING_LOCKFILE_ERROR_CODES = {"missing_lockfile"}
_DEPLOYMENT_ID_KEYS = ("deployment_id", "deploymentId")
_DEPLOYMENT_FALLBACK_IDENTIFIER_KEYS = ("id", "uuid")
def _run_predeploy_validation(
@@ -79,6 +84,39 @@ def _env_summary(env_vars: dict[str, str]) -> str:
return f"{len(env_vars)} env vars: {keys}"
def _deployment_identifier(json_response: dict[str, Any]) -> str | None:
"""Return the best available identifier for a deployment show URL."""
deployment = json_response.get("deployment")
for key in _DEPLOYMENT_ID_KEYS:
value = json_response.get(key)
if value:
return str(value)
if isinstance(deployment, dict):
for key in _DEPLOYMENT_ID_KEYS + _DEPLOYMENT_FALLBACK_IDENTIFIER_KEYS:
value = deployment.get(key)
if value:
return str(value)
for key in _DEPLOYMENT_FALLBACK_IDENTIFIER_KEYS:
value = json_response.get(key)
if value:
return str(value)
return None
def _deployment_page_url(base_url: str, json_response: dict[str, Any]) -> str | None:
"""Build the CrewAI deployment show URL for a response payload."""
identifier = _deployment_identifier(json_response)
if not identifier:
return None
return (
f"{base_url.rstrip('/')}/crewai_plus/deployments/{quote(identifier, safe='')}"
)
def _needs_lockfile_for_deploy(project_root: Path | None = None) -> bool:
"""Return True when deploy should create the project's first lockfile."""
root = project_root or Path.cwd()
@@ -165,6 +203,7 @@ class DeployCommand(BaseCommand, PlusAPIMixin):
console.print("crewai deploy status")
console.print(" or")
console.print(f'crewai deploy status --uuid "{json_response["uuid"]}"')
self._open_deployment_page(json_response)
def _display_logs(self, log_messages: list[dict[str, Any]]) -> None:
"""
@@ -178,6 +217,28 @@ class DeployCommand(BaseCommand, PlusAPIMixin):
f"{log_message['timestamp']} - {log_message['level']}: {log_message['message']}"
)
def _open_deployment_page(self, json_response: dict[str, Any]) -> None:
"""Open the deployment show page in the user's browser when possible."""
base_url = str(
getattr(self.plus_api_client, "base_url", None)
or DEFAULT_CREWAI_ENTERPRISE_URL
)
deployment_url = _deployment_page_url(base_url, json_response)
if not deployment_url:
return
console.print(f"\nOpening deployment page: [blue]{deployment_url}[/blue]")
try:
opened = webbrowser.open(deployment_url)
except Exception:
opened = False
if not opened:
console.print(
"Could not open the deployment page automatically.",
style="yellow",
)
def deploy(self, uuid: str | None = None, skip_validate: bool = False) -> None:
"""
Deploy a crew using either UUID or project name.
@@ -438,6 +499,7 @@ class DeployCommand(BaseCommand, PlusAPIMixin):
console.print("crewai deploy push")
console.print(" or")
console.print(f"crewai deploy push --uuid {json_response['uuid']}")
self._open_deployment_page(json_response)
def list_crews(self) -> None:
"""