From a44fe386071810a13b0cfa7d4e7edc17a4f25fba Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Tue, 20 Jan 2026 23:27:19 +0000 Subject: [PATCH 1/2] Add project name support in deployment process - Updated README to include project name configuration. - Enhanced deploy function to accept a project name via command line or environment variable. - Implemented project ID resolution based on project name in the SDK client. - Adjusted deployment methods to incorporate project ID for deployments. --- README.md | 3 +- src/celesto/deployment.py | 18 +++++++++++- src/celesto/sdk/client.py | 59 +++++++++++++++++++++++++++++++++++---- 3 files changed, 73 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9e2e12b..5ab2882 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,13 @@ Set your API key in the environment: ```bash export CELESTO_API_KEY="your-key" +export CELESTO_PROJECT_NAME="your-project-name" ``` ## CLI ```bash -celesto deploy +celesto deploy --project "My Project" celesto ls celesto a2a get-card --agent http://localhost:8000 ``` diff --git a/src/celesto/deployment.py b/src/celesto/deployment.py index 5c2550b..cf32fad 100644 --- a/src/celesto/deployment.py +++ b/src/celesto/deployment.py @@ -107,6 +107,12 @@ def deploy( "-e", help='Environment variables as comma-separated key=value pairs (e.g., "API_KEY=xyz,DEBUG=true")', ), + project_name: Optional[str] = typer.Option( + None, + "--project", + "-p", + help="Celesto project name (or set CELESTO_PROJECT_NAME env var)", + ), api_key: Optional[str] = typer.Option( None, "--api-key", @@ -127,6 +133,12 @@ def deploy( # Get API key final_api_key = _get_api_key(api_key, ignore_env_file, "CELESTO_API_KEY") + resolved_project_name = project_name or os.environ.get("CELESTO_PROJECT_NAME") + if not resolved_project_name: + console.print("❌ [bold red]Error:[/bold red] Project name not found.") + console.print("Provide it via [bold]--project[/bold] or set [bold]CELESTO_PROJECT_NAME[/bold].") + raise typer.Exit(1) + # Validate folder path folder_path = Path(folder).resolve() if not folder_path.exists(): @@ -151,7 +163,11 @@ def deploy( client = CelestoSDK(final_api_key) result = client.deployment.deploy( - folder=folder_path, name=name, description=description, envs=env_dict + folder=folder_path, + name=name, + description=description, + envs=env_dict, + project_name=resolved_project_name, ) console.print("✅ [bold green]Deployment successful![/bold green]") diff --git a/src/celesto/sdk/client.py b/src/celesto/sdk/client.py index 9cf96f1..90124bf 100644 --- a/src/celesto/sdk/client.py +++ b/src/celesto/sdk/client.py @@ -241,7 +241,8 @@ class Deployment(_BaseClient): folder=Path("./my-agent"), name="my-agent", description="My AI assistant", - envs={"OPENAI_API_KEY": "sk-..."} + envs={"OPENAI_API_KEY": "sk-..."}, + project_name="My Project" ) print(f"Deployment ID: {result['id']}") @@ -249,8 +250,41 @@ class Deployment(_BaseClient): deployments = client.deployment.list() """ + def _resolve_project_id(self, project_name: str) -> str: + """Resolve a project ID from a project name.""" + skip = 0 + limit = 100 + while True: + response = self._request( + "GET", + "/projects", + params={"skip": skip, "limit": limit}, + ) + projects = response.get("data") or [] + for project in projects: + if project.get("name") == project_name: + project_id = project.get("id") + if not project_id: + raise CelestoValidationError( + f"Project '{project_name}' missing id in response." + ) + return project_id + total = response.get("total") + if total is None: + break + skip += limit + if skip >= total: + break + + raise CelestoValidationError(f"Project '{project_name}' not found.") + def _create_deployment( - self, bundle: Path, name: str, description: str, envs: dict[str, str] + self, + bundle: Path, + name: str, + description: str, + envs: dict[str, str], + project_id: str, ) -> dict: """Internal method to upload and create a deployment.""" if bundle.exists() and not bundle.is_file(): @@ -263,6 +297,7 @@ def _create_deployment( form_data = { "name": name, "description": description, + "project_id": project_id, "config": json.dumps(config), } @@ -277,6 +312,7 @@ def deploy( name: str, description: Optional[str] = None, envs: Optional[dict[str, str]] = None, + project_name: Optional[str] = None, ) -> dict: """Deploy an agent from a local folder. @@ -289,6 +325,7 @@ def deploy( name: Unique name for the deployment description: Human-readable description (optional) envs: Environment variables to inject (optional) + project_name: Project name to scope the deployment (required) Returns: Deployment result with 'id', 'status', and other metadata @@ -301,7 +338,8 @@ def deploy( folder=Path("./my-agent"), name="weather-bot", description="A bot that provides weather information", - envs={"API_KEY": "secret123"} + envs={"API_KEY": "secret123"}, + project_name="My Project" ) print(f"Status: {result['status']}") # "READY" or "BUILDING" """ @@ -310,6 +348,14 @@ def deploy( if not folder.is_dir(): raise CelestoValidationError(f"Folder {folder} is not a directory") + resolved_project_name = project_name or os.environ.get("CELESTO_PROJECT_NAME") + if not resolved_project_name: + raise CelestoValidationError( + "project_name is required. Pass project_name or set CELESTO_PROJECT_NAME." + ) + + resolved_project_id = self._resolve_project_id(resolved_project_name) + # Create tar.gz archive (Nixpacks expects tar.gz format) with tempfile.NamedTemporaryFile(delete=False, suffix=".tar.gz") as temp_file: with tarfile.open(temp_file.name, "w:gz") as tar: @@ -318,7 +364,9 @@ def deploy( bundle = Path(temp_file.name) try: - return self._create_deployment(bundle, name, description, envs) + return self._create_deployment( + bundle, name, description, envs, resolved_project_id + ) finally: bundle.unlink() @@ -657,7 +705,8 @@ class CelestoSDK(_BaseConnection): # Deploy an agent result = client.deployment.deploy( folder=Path("./my-app"), - name="My App" + name="My App", + project_name="My Project" ) # Manage delegated access From 83c8a511733f0f3892c2f6e6224edfaed6e8cbac Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Tue, 20 Jan 2026 23:28:15 +0000 Subject: [PATCH 2/2] Refactor project name handling in deployment process - Updated deploy function to clarify project name usage, now optional and defaults to the first project. - Introduced _resolve_first_project_id method to retrieve the first available project ID if no project name is provided. - Removed strict requirement for project name in deployment methods, enhancing flexibility. --- src/celesto/deployment.py | 6 +----- src/celesto/sdk/client.py | 31 ++++++++++++++++++++++++------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/celesto/deployment.py b/src/celesto/deployment.py index cf32fad..834108f 100644 --- a/src/celesto/deployment.py +++ b/src/celesto/deployment.py @@ -111,7 +111,7 @@ def deploy( None, "--project", "-p", - help="Celesto project name (or set CELESTO_PROJECT_NAME env var)", + help="Celesto project name (optional; defaults to first project)", ), api_key: Optional[str] = typer.Option( None, @@ -134,10 +134,6 @@ def deploy( final_api_key = _get_api_key(api_key, ignore_env_file, "CELESTO_API_KEY") resolved_project_name = project_name or os.environ.get("CELESTO_PROJECT_NAME") - if not resolved_project_name: - console.print("❌ [bold red]Error:[/bold red] Project name not found.") - console.print("Provide it via [bold]--project[/bold] or set [bold]CELESTO_PROJECT_NAME[/bold].") - raise typer.Exit(1) # Validate folder path folder_path = Path(folder).resolve() diff --git a/src/celesto/sdk/client.py b/src/celesto/sdk/client.py index 90124bf..2158c8f 100644 --- a/src/celesto/sdk/client.py +++ b/src/celesto/sdk/client.py @@ -278,6 +278,25 @@ def _resolve_project_id(self, project_name: str) -> str: raise CelestoValidationError(f"Project '{project_name}' not found.") + def _resolve_first_project_id(self) -> str: + """Resolve the first available project ID.""" + response = self._request( + "GET", + "/projects", + params={"skip": 0, "limit": 1}, + ) + projects = response.get("data") or [] + if not projects: + raise CelestoValidationError( + "No projects found. Create a project or specify project_name." + ) + project_id = projects[0].get("id") + if not project_id: + raise CelestoValidationError( + "First project missing id in response." + ) + return project_id + def _create_deployment( self, bundle: Path, @@ -325,7 +344,7 @@ def deploy( name: Unique name for the deployment description: Human-readable description (optional) envs: Environment variables to inject (optional) - project_name: Project name to scope the deployment (required) + project_name: Project name to scope the deployment (optional; defaults to first project) Returns: Deployment result with 'id', 'status', and other metadata @@ -349,12 +368,10 @@ def deploy( raise CelestoValidationError(f"Folder {folder} is not a directory") resolved_project_name = project_name or os.environ.get("CELESTO_PROJECT_NAME") - if not resolved_project_name: - raise CelestoValidationError( - "project_name is required. Pass project_name or set CELESTO_PROJECT_NAME." - ) - - resolved_project_id = self._resolve_project_id(resolved_project_name) + if resolved_project_name: + resolved_project_id = self._resolve_project_id(resolved_project_name) + else: + resolved_project_id = self._resolve_first_project_id() # Create tar.gz archive (Nixpacks expects tar.gz format) with tempfile.NamedTemporaryFile(delete=False, suffix=".tar.gz") as temp_file: