celesto 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
celesto/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ """Celesto SDK package."""
2
+
3
+ from .main import app
4
+
5
+ __version__ = "0.0.1"
6
+
7
+ __all__ = ["app", "__version__"]
celesto/a2a.py ADDED
@@ -0,0 +1,127 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from typing import Tuple
5
+ from uuid import uuid4
6
+
7
+ import httpx
8
+ import typer
9
+ from a2a.client import Client, ClientConfig, ClientFactory
10
+ from a2a.types import Message, TextPart
11
+ from rich.console import Console
12
+
13
+ app = typer.Typer()
14
+ console = Console()
15
+ DEFAULT_TIMEOUT_SECONDS = 120.0
16
+
17
+
18
+ async def _connect_client(
19
+ agent: str, timeout: float
20
+ ) -> Tuple[Client, httpx.AsyncClient]:
21
+ """
22
+ Connect to the remote agent with a ClientConfig that uses an httpx timeout.
23
+ Returns the client and the httpx.AsyncClient to close after the call completes.
24
+ """
25
+ http_client = httpx.AsyncClient(timeout=timeout)
26
+ client_config = ClientConfig(httpx_client=http_client)
27
+
28
+ try:
29
+ client = await ClientFactory.connect(agent=agent, client_config=client_config)
30
+ return client, http_client
31
+ except Exception: # noqa: BLE001
32
+ await http_client.aclose()
33
+ raise
34
+
35
+
36
+ async def _get_card(agent: str, timeout: float):
37
+ client, http_client = await _connect_client(agent, timeout)
38
+ try:
39
+ card = await client.get_card()
40
+ console.print(card.model_dump(mode="json"))
41
+ finally:
42
+ await http_client.aclose()
43
+
44
+
45
+ @app.command()
46
+ def get_card(
47
+ agent: str = typer.Option(
48
+ "http://localhost:8000", help="The URL of the agent to connect to"
49
+ ),
50
+ timeout: float = typer.Option(
51
+ DEFAULT_TIMEOUT_SECONDS,
52
+ help="Timeout in seconds for connecting to the agent",
53
+ ),
54
+ ):
55
+ if timeout <= 0:
56
+ raise typer.BadParameter(
57
+ "Timeout must be greater than zero.", param_name="timeout"
58
+ )
59
+ asyncio.run(_get_card(agent, timeout))
60
+
61
+
62
+ async def _send_message(agent: str, input: str, timeout: float):
63
+ import sys
64
+ import traceback
65
+
66
+ http_client = None
67
+ try:
68
+ client, http_client = await _connect_client(agent, timeout)
69
+
70
+ message = Message(
71
+ role="user",
72
+ parts=[TextPart(text=input)],
73
+ message_id=str(uuid4()),
74
+ task_id=str(uuid4()),
75
+ context_id=str(uuid4()),
76
+ )
77
+ response_stream = client.send_message(message)
78
+
79
+ async for event in response_stream:
80
+ try:
81
+ if isinstance(event, Message):
82
+ console.print("[bold green]Response:[/bold green]")
83
+ for part in event.parts:
84
+ if hasattr(part.root, "text"):
85
+ console.print(part.root.text)
86
+ else:
87
+ task, update = event
88
+ if update is not None:
89
+ if hasattr(update, "artifact") and update.artifact:
90
+ for part in update.artifact.parts:
91
+ if hasattr(part.root, "text"):
92
+ console.print(part.root.text, end="")
93
+ elif hasattr(update, "status"):
94
+ console.print(
95
+ f"\n[dim]Task status: {update.status.state}[/dim]"
96
+ )
97
+ except Exception as e: # noqa: BLE001
98
+ print(f"\n[ERROR] Failed to process event: {e}", file=sys.stderr)
99
+ print(f"Event type: {type(event)}", file=sys.stderr)
100
+ print(f"Event: {event}", file=sys.stderr)
101
+ traceback.print_exc(file=sys.stderr)
102
+ raise
103
+ except Exception as e: # noqa: BLE001
104
+ print(f"\n[ERROR] Stream failed: {e}", file=sys.stderr)
105
+ traceback.print_exc(file=sys.stderr)
106
+ raise
107
+ finally:
108
+ if http_client is not None:
109
+ await http_client.aclose()
110
+
111
+
112
+ @app.command()
113
+ def chat(
114
+ agent: str = typer.Option(
115
+ "http://localhost:8000", help="The URL of the agent to connect to"
116
+ ),
117
+ message: str = typer.Option(..., help="The message to send to the agent"),
118
+ timeout: float = typer.Option(
119
+ DEFAULT_TIMEOUT_SECONDS,
120
+ help="Timeout in seconds for connecting to the agent",
121
+ ),
122
+ ):
123
+ if timeout <= 0:
124
+ raise typer.BadParameter(
125
+ "Timeout must be greater than zero.", param_name="timeout"
126
+ )
127
+ asyncio.run(_send_message(agent, message, timeout))
celesto/deployment.py ADDED
@@ -0,0 +1,244 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from datetime import datetime
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ import typer
9
+ from dotenv.main import DotEnv
10
+ from rich.console import Console
11
+ from typing_extensions import Annotated
12
+
13
+ from .sdk.client import CelestoSDK
14
+
15
+ console = Console()
16
+
17
+
18
+ def _get_secrets_from_env_file(
19
+ env_file: Optional[str] = None, secret_name: Optional[str] = None
20
+ ) -> Optional[str]:
21
+ if not env_file:
22
+ env_file = ".env"
23
+ if not secret_name:
24
+ secret_name = "CELESTO_API_KEY"
25
+ dotenv_path = Path(env_file)
26
+ dot_env = DotEnv(dotenv_path, verbose=True, encoding="utf-8")
27
+ return dot_env.get(secret_name)
28
+
29
+
30
+ def _get_api_key(
31
+ api_key: Optional[str] = None,
32
+ ignore_env_file: Optional[bool] = False,
33
+ secret_name: Optional[str] = None,
34
+ ) -> str:
35
+ """Get API key from argument or environment variable."""
36
+ if not ignore_env_file:
37
+ final_api_key = api_key or _get_secrets_from_env_file(secret_name=secret_name)
38
+ else:
39
+ final_api_key = api_key
40
+ if not final_api_key:
41
+ console.print("āŒ [bold red]Error:[/bold red] API key not found.")
42
+ console.print(
43
+ "Please provide it via [bold]--api-key[/bold] or set [bold]CELESTO_API_KEY[/bold] environment variable."
44
+ )
45
+ console.print("\n[bold cyan]To get your API key:[/bold cyan]")
46
+ console.print("1. Log in to https://celesto.ai")
47
+ console.print("2. Navigate to Settings → Security")
48
+ console.print("3. Copy your API key")
49
+ raise typer.Exit(1)
50
+ return final_api_key
51
+
52
+
53
+ def _resolve_envs(
54
+ folder_path: Path, envs: Optional[str], ignore_env_file: bool
55
+ ) -> dict[str, str]:
56
+ """Build environment dictionary from .env file and CLI overrides."""
57
+ env_dict: dict[str, str] = {}
58
+ if not ignore_env_file:
59
+ env_file_path = folder_path / ".env"
60
+ if env_file_path.exists():
61
+ dotenv = DotEnv(env_file_path, verbose=True, encoding="utf-8")
62
+ for key, value in dotenv.dict().items():
63
+ if value:
64
+ env_dict[key] = value
65
+
66
+ if envs:
67
+ for pair in envs.split(","):
68
+ pair = pair.strip()
69
+ if not pair:
70
+ continue
71
+ if "=" not in pair:
72
+ console.print(
73
+ f"āŒ [bold red]Error:[/bold red] Invalid env pair: '{pair}'. Expected format: key=value"
74
+ )
75
+ raise typer.Exit(1)
76
+ key, value = pair.split("=", 1)
77
+ env_dict[key.strip()] = value.strip()
78
+
79
+ return env_dict
80
+
81
+
82
+ def deploy(
83
+ folder: Annotated[
84
+ str,
85
+ typer.Argument(
86
+ ...,
87
+ help="Path to the folder containing your agent code",
88
+ default_factory=os.getcwd,
89
+ ),
90
+ ],
91
+ name: Annotated[
92
+ str,
93
+ typer.Option(
94
+ ...,
95
+ "--name",
96
+ "-n",
97
+ help="Name for your deployment",
98
+ default_factory=lambda: f"my-agent-{datetime.now().strftime('%Y%m%d%H%M%S')}",
99
+ ),
100
+ ],
101
+ description: Optional[str] = typer.Option(
102
+ None, "--description", "-d", help="Description of your agent"
103
+ ),
104
+ envs: Optional[str] = typer.Option(
105
+ None,
106
+ "--envs",
107
+ "-e",
108
+ help='Environment variables as comma-separated key=value pairs (e.g., "API_KEY=xyz,DEBUG=true")',
109
+ ),
110
+ api_key: Optional[str] = typer.Option(
111
+ None,
112
+ "--api-key",
113
+ "-k",
114
+ help="Celesto API key (or set CELESTO_API_KEY env var)",
115
+ ),
116
+ ignore_env_file: Optional[bool] = typer.Option(
117
+ False,
118
+ "--ignore-env-file",
119
+ "-i",
120
+ help="Ignore environment file",
121
+ ),
122
+ ):
123
+ """Deploy an agent to the Celesto AI platform.
124
+
125
+ It automatically loads the .env file and injects the environment variables into the deployment. To ignore the .env file, use the --ignore-env-file flag.
126
+ """
127
+ # Get API key
128
+ final_api_key = _get_api_key(api_key, ignore_env_file, "CELESTO_API_KEY")
129
+
130
+ # Validate folder path
131
+ folder_path = Path(folder).resolve()
132
+ if not folder_path.exists():
133
+ console.print(
134
+ f"āŒ [bold red]Error:[/bold red] Folder '{folder_path}' does not exist."
135
+ )
136
+ raise typer.Exit(1)
137
+ if not folder_path.is_dir():
138
+ console.print(
139
+ f"āŒ [bold red]Error:[/bold red] '{folder_path}' is not a directory."
140
+ )
141
+ raise typer.Exit(1)
142
+
143
+ # Parse environment variables
144
+ env_dict = _resolve_envs(folder_path, envs, bool(ignore_env_file))
145
+
146
+ # Deploy
147
+ try:
148
+ console.print(
149
+ f"šŸš€ [bold cyan]Deploying[/bold cyan] '{name}' from {folder_path}..."
150
+ )
151
+
152
+ client = CelestoSDK(final_api_key)
153
+ result = client.deployment.deploy(
154
+ folder=folder_path, name=name, description=description, envs=env_dict
155
+ )
156
+ console.print("āœ… [bold green]Deployment successful![/bold green]")
157
+
158
+ # Show deployment details
159
+ deployment_id = result.get("id")
160
+ deployment_name = result.get("name")
161
+ status = result.get("status")
162
+ console.print(f"\n[bold]Deployment ID:[/bold] {deployment_id}")
163
+ console.print(f"[bold]Status:[/bold] {status}")
164
+
165
+ # Show URL once ready
166
+ if status == "READY":
167
+ if not deployment_name:
168
+ console.print(
169
+ "[yellow]āš ļø Unable to determine app name; deployment URL unavailable.[/yellow]"
170
+ )
171
+ else:
172
+ cloud_url = (
173
+ f"https://api.celesto.ai/v1/deploy/apps/{deployment_name}/chat"
174
+ )
175
+ console.print(f"[bold]URL:[/bold] [link={cloud_url}]{cloud_url}[/link]")
176
+ else:
177
+ console.print(
178
+ "[yellow]ā³ Building... Run 'celesto ls' to check status[/yellow]"
179
+ )
180
+
181
+ except Exception as e: # noqa: BLE001
182
+ console.print(f"āŒ [bold red]Deployment failed:[/bold red] {e}")
183
+ raise typer.Exit(1) from e
184
+
185
+
186
+ def list_deployments(
187
+ api_key: Optional[str] = typer.Option(
188
+ None, "--api-key", "-k", help="Celesto API key (or set CELESTO_API_KEY env var)"
189
+ ),
190
+ ):
191
+ """List all deployments."""
192
+ from rich.table import Table
193
+
194
+ # Get API key
195
+ final_api_key = _get_api_key(api_key, secret_name="CELESTO_API_KEY")
196
+
197
+ # List deployments
198
+ try:
199
+ client = CelestoSDK(final_api_key)
200
+ deployments = client.deployment.list()
201
+
202
+ if not deployments:
203
+ console.print("šŸ“­ [yellow]No deployments found.[/yellow]")
204
+ return
205
+
206
+ # Create a table
207
+ table = Table(
208
+ title="šŸš€ Deployments", show_header=True, header_style="bold cyan"
209
+ )
210
+ table.add_column("Name", style="green")
211
+ table.add_column("Status", style="cyan")
212
+ table.add_column("Created At", style="magenta")
213
+ table.add_column("URL", style="blue")
214
+
215
+ for deployment in deployments:
216
+ # Construct the cloud URL
217
+ deployment_name = deployment.get("name")
218
+ status = deployment.get("status")
219
+ if deployment_name and status == "READY":
220
+ cloud_url = (
221
+ f"https://api.celesto.ai/v1/deploy/apps/{deployment_name}/chat"
222
+ )
223
+ elif status == "FAILED":
224
+ cloud_url = "-"
225
+ elif status == "READY":
226
+ cloud_url = "Name unavailable"
227
+ else:
228
+ cloud_url = "Pending"
229
+
230
+ table.add_row(
231
+ deployment.get("name", "N/A"),
232
+ deployment.get("status", "N/A"),
233
+ deployment.get("created_at", "N/A").split("T")[0]
234
+ if deployment.get("created_at")
235
+ else "N/A", # Just date
236
+ cloud_url,
237
+ )
238
+
239
+ console.print(table)
240
+ console.print(f"\nšŸ“Š Total deployments: [bold]{len(deployments)}[/bold]")
241
+
242
+ except Exception as e: # noqa: BLE001
243
+ console.print(f"āŒ [bold red]Failed to list deployments:[/bold red] {e}")
244
+ raise typer.Exit(1) from e
celesto/main.py ADDED
@@ -0,0 +1,34 @@
1
+ from __future__ import annotations
2
+
3
+ import typer
4
+ from rich import print
5
+
6
+ from . import a2a, deployment, proxy
7
+
8
+ app = typer.Typer()
9
+ app.add_typer(proxy.app)
10
+ app.add_typer(a2a.app, name="a2a")
11
+
12
+ # Add deployment commands at top level
13
+ app.command("deploy")(deployment.deploy)
14
+ app.command("list")(deployment.list_deployments)
15
+ app.command("ls")(deployment.list_deployments) # Alias for list
16
+
17
+
18
+ @app.callback(invoke_without_command=True)
19
+ def main(ctx: typer.Context):
20
+ if ctx.invoked_subcommand is None:
21
+ print(
22
+ """[orange_red1]
23
+ ╭──────────────────────────────────────────────────────────────────────╮
24
+ │ Fastest way to build, prototype and deploy AI Agents. │
25
+ │ [bold][link=https://celesto.ai]by Celesto AI[/link][/bold] │
26
+ ā•°ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
27
+ [range_red1]
28
+ """
29
+ )
30
+ typer.echo(ctx.get_help())
31
+
32
+
33
+ if __name__ == "__main__":
34
+ app()
celesto/proxy.py ADDED
@@ -0,0 +1,18 @@
1
+ from __future__ import annotations
2
+
3
+ import typer
4
+
5
+ app = typer.Typer(help="Manage MCP proxy connections.")
6
+
7
+
8
+ @app.command("create-proxy")
9
+ def create_proxy(remote_url: str, name: str | None = None):
10
+ """Create a proxy to a remote MCP server."""
11
+ try:
12
+ from fastmcp import FastMCP
13
+
14
+ mcp_server = FastMCP.as_proxy(remote_url, name=name)
15
+ mcp_server.run()
16
+ except Exception as exc: # noqa: BLE001
17
+ typer.echo(f"Error creating proxy: {exc}", err=True)
18
+ raise typer.Exit(1) from exc
@@ -0,0 +1,44 @@
1
+ from .client import CelestoSDK
2
+ from .exceptions import (
3
+ CelestoAuthenticationError,
4
+ CelestoError,
5
+ CelestoNetworkError,
6
+ CelestoNotFoundError,
7
+ CelestoRateLimitError,
8
+ CelestoServerError,
9
+ CelestoValidationError,
10
+ )
11
+ from .types import (
12
+ AccessRules,
13
+ ConnectionInfo,
14
+ ConnectionListResponse,
15
+ ConnectionResponse,
16
+ ConnectionStatus,
17
+ DeploymentInfo,
18
+ DeploymentResponse,
19
+ DriveFile,
20
+ DriveFilesResponse,
21
+ )
22
+
23
+ __all__ = [
24
+ # Main client
25
+ "CelestoSDK",
26
+ # Exceptions
27
+ "CelestoError",
28
+ "CelestoAuthenticationError",
29
+ "CelestoNotFoundError",
30
+ "CelestoValidationError",
31
+ "CelestoRateLimitError",
32
+ "CelestoServerError",
33
+ "CelestoNetworkError",
34
+ # Types
35
+ "DeploymentInfo",
36
+ "DeploymentResponse",
37
+ "ConnectionStatus",
38
+ "ConnectionResponse",
39
+ "ConnectionInfo",
40
+ "ConnectionListResponse",
41
+ "DriveFile",
42
+ "DriveFilesResponse",
43
+ "AccessRules",
44
+ ]