llamactl 0.3.0a13__tar.gz → 0.3.0a15__tar.gz

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.
Files changed (36) hide show
  1. {llamactl-0.3.0a13 → llamactl-0.3.0a15}/PKG-INFO +3 -3
  2. {llamactl-0.3.0a13 → llamactl-0.3.0a15}/pyproject.toml +3 -3
  3. {llamactl-0.3.0a13 → llamactl-0.3.0a15}/src/llama_deploy/cli/__init__.py +2 -1
  4. {llamactl-0.3.0a13 → llamactl-0.3.0a15}/src/llama_deploy/cli/app.py +4 -7
  5. llamactl-0.3.0a15/src/llama_deploy/cli/client.py +41 -0
  6. llamactl-0.3.0a15/src/llama_deploy/cli/commands/auth.py +377 -0
  7. {llamactl-0.3.0a13 → llamactl-0.3.0a15}/src/llama_deploy/cli/commands/deployment.py +24 -36
  8. llamactl-0.3.0a15/src/llama_deploy/cli/commands/env.py +206 -0
  9. llamactl-0.3.0a15/src/llama_deploy/cli/config/_config.py +385 -0
  10. llamactl-0.3.0a15/src/llama_deploy/cli/config/auth_service.py +68 -0
  11. llamactl-0.3.0a15/src/llama_deploy/cli/config/env_service.py +64 -0
  12. llamactl-0.3.0a15/src/llama_deploy/cli/config/schema.py +31 -0
  13. llamactl-0.3.0a15/src/llama_deploy/cli/interactive_prompts/utils.py +20 -0
  14. {llamactl-0.3.0a13 → llamactl-0.3.0a15}/src/llama_deploy/cli/options.py +0 -9
  15. llamactl-0.3.0a13/src/llama_deploy/cli/client.py +0 -55
  16. llamactl-0.3.0a13/src/llama_deploy/cli/commands/auth.py +0 -382
  17. llamactl-0.3.0a13/src/llama_deploy/cli/config.py +0 -241
  18. llamactl-0.3.0a13/src/llama_deploy/cli/interactive_prompts/utils.py +0 -59
  19. llamactl-0.3.0a13/src/llama_deploy/cli/textual/api_key_profile_form.py +0 -563
  20. {llamactl-0.3.0a13 → llamactl-0.3.0a15}/README.md +0 -0
  21. {llamactl-0.3.0a13 → llamactl-0.3.0a15}/src/llama_deploy/cli/commands/aliased_group.py +0 -0
  22. {llamactl-0.3.0a13 → llamactl-0.3.0a15}/src/llama_deploy/cli/commands/init.py +0 -0
  23. {llamactl-0.3.0a13 → llamactl-0.3.0a15}/src/llama_deploy/cli/commands/serve.py +0 -0
  24. {llamactl-0.3.0a13 → llamactl-0.3.0a15}/src/llama_deploy/cli/debug.py +0 -0
  25. {llamactl-0.3.0a13 → llamactl-0.3.0a15}/src/llama_deploy/cli/env.py +0 -0
  26. {llamactl-0.3.0a13 → llamactl-0.3.0a15}/src/llama_deploy/cli/interactive_prompts/session_utils.py +0 -0
  27. {llamactl-0.3.0a13 → llamactl-0.3.0a15}/src/llama_deploy/cli/platform_client.py +0 -0
  28. {llamactl-0.3.0a13 → llamactl-0.3.0a15}/src/llama_deploy/cli/py.typed +0 -0
  29. {llamactl-0.3.0a13 → llamactl-0.3.0a15}/src/llama_deploy/cli/textual/deployment_form.py +0 -0
  30. {llamactl-0.3.0a13 → llamactl-0.3.0a15}/src/llama_deploy/cli/textual/deployment_help.py +0 -0
  31. {llamactl-0.3.0a13 → llamactl-0.3.0a15}/src/llama_deploy/cli/textual/deployment_monitor.py +0 -0
  32. {llamactl-0.3.0a13 → llamactl-0.3.0a15}/src/llama_deploy/cli/textual/git_validation.py +0 -0
  33. {llamactl-0.3.0a13 → llamactl-0.3.0a15}/src/llama_deploy/cli/textual/github_callback_server.py +0 -0
  34. {llamactl-0.3.0a13 → llamactl-0.3.0a15}/src/llama_deploy/cli/textual/llama_loader.py +0 -0
  35. {llamactl-0.3.0a13 → llamactl-0.3.0a15}/src/llama_deploy/cli/textual/secrets_form.py +0 -0
  36. {llamactl-0.3.0a13 → llamactl-0.3.0a15}/src/llama_deploy/cli/textual/styles.tcss +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: llamactl
3
- Version: 0.3.0a13
3
+ Version: 0.3.0a15
4
4
  Summary: A command-line interface for managing LlamaDeploy projects and deployments
5
5
  Author: Adrian Lyjak
6
6
  Author-email: Adrian Lyjak <adrianlyjak@gmail.com>
7
7
  License: MIT
8
- Requires-Dist: llama-deploy-core[client]>=0.3.0a13,<0.4.0
9
- Requires-Dist: llama-deploy-appserver>=0.3.0a13,<0.4.0
8
+ Requires-Dist: llama-deploy-core[client]>=0.3.0a15,<0.4.0
9
+ Requires-Dist: llama-deploy-appserver>=0.3.0a15,<0.4.0
10
10
  Requires-Dist: httpx>=0.24.0
11
11
  Requires-Dist: rich>=13.0.0
12
12
  Requires-Dist: questionary>=2.0.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "llamactl"
3
- version = "0.3.0a13"
3
+ version = "0.3.0a15"
4
4
  description = "A command-line interface for managing LlamaDeploy projects and deployments"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -9,8 +9,8 @@ authors = [
9
9
  ]
10
10
  requires-python = ">=3.11, <4"
11
11
  dependencies = [
12
- "llama-deploy-core[client]>=0.3.0a13,<0.4.0",
13
- "llama-deploy-appserver>=0.3.0a13,<0.4.0",
12
+ "llama-deploy-core[client]>=0.3.0a15,<0.4.0",
13
+ "llama-deploy-appserver>=0.3.0a15,<0.4.0",
14
14
  "httpx>=0.24.0",
15
15
  "rich>=13.0.0",
16
16
  "questionary>=2.0.0",
@@ -1,5 +1,6 @@
1
1
  from llama_deploy.cli.commands.auth import auth
2
2
  from llama_deploy.cli.commands.deployment import deployments
3
+ from llama_deploy.cli.commands.env import env_group
3
4
  from llama_deploy.cli.commands.init import init
4
5
  from llama_deploy.cli.commands.serve import serve
5
6
 
@@ -11,7 +12,7 @@ def main() -> None:
11
12
  app()
12
13
 
13
14
 
14
- __all__ = ["app", "deployments", "auth", "serve", "init"]
15
+ __all__ = ["app", "deployments", "auth", "serve", "init", "env_group"]
15
16
 
16
17
 
17
18
  if __name__ == "__main__":
@@ -1,11 +1,9 @@
1
- import asyncio
2
1
  from importlib.metadata import PackageNotFoundError
3
2
  from importlib.metadata import version as pkg_version
4
3
 
5
4
  import click
6
- from llama_deploy.cli.client import get_control_plane_client
7
5
  from llama_deploy.cli.commands.aliased_group import AliasedGroup
8
- from llama_deploy.cli.config import config_manager
6
+ from llama_deploy.cli.config.env_service import service
9
7
  from llama_deploy.cli.options import global_options
10
8
  from rich import print as rprint
11
9
  from rich.console import Console
@@ -23,11 +21,10 @@ def print_version(ctx: click.Context, param: click.Option, value: bool) -> None:
23
21
  console.print(Text.assemble("client version: ", (ver, "green")))
24
22
 
25
23
  # If there is an active profile, attempt to query server version
26
- profile = config_manager.get_current_profile()
27
- if profile and profile.api_url:
24
+ auth_service = service.current_auth_service()
25
+ if auth_service:
28
26
  try:
29
- cp_client = get_control_plane_client()
30
- data = asyncio.run(cp_client.server_version())
27
+ data = auth_service.fetch_server_version()
31
28
  server_ver = data.version
32
29
  console.print(
33
30
  Text.assemble(
@@ -0,0 +1,41 @@
1
+ from contextlib import asynccontextmanager
2
+ from typing import AsyncGenerator
3
+
4
+ from llama_deploy.cli.config.env_service import service
5
+ from llama_deploy.core.client.manage_client import ControlPlaneClient, ProjectClient
6
+ from rich import print as rprint
7
+
8
+
9
+ def get_control_plane_client() -> ControlPlaneClient:
10
+ profile = service.current_auth_service().get_current_profile()
11
+ if profile:
12
+ resolved_base_url = profile.api_url.rstrip("/")
13
+ resolved_api_key = profile.api_key
14
+ return ControlPlaneClient(resolved_base_url, resolved_api_key)
15
+
16
+ # Fallback: allow env-scoped client construction for env operations
17
+ env = service.get_current_environment()
18
+ resolved_base_url = env.api_url.rstrip("/")
19
+ return ControlPlaneClient(resolved_base_url, None)
20
+
21
+
22
+ def get_project_client() -> ProjectClient:
23
+ profile = service.current_auth_service().get_current_profile()
24
+ if not profile:
25
+ rprint("\n[bold red]No profile configured![/bold red]")
26
+ rprint("\nTo get started, create a profile with:")
27
+ rprint("[cyan]llamactl auth token[/cyan]")
28
+ raise SystemExit(1)
29
+ return ProjectClient(profile.api_url, profile.project_id, profile.api_key)
30
+
31
+
32
+ @asynccontextmanager
33
+ async def project_client_context() -> AsyncGenerator[ProjectClient, None]:
34
+ client = get_project_client()
35
+ try:
36
+ yield client
37
+ finally:
38
+ try:
39
+ await client.aclose()
40
+ except Exception:
41
+ pass
@@ -0,0 +1,377 @@
1
+ import asyncio
2
+
3
+ import click
4
+ import questionary
5
+ from llama_deploy.cli.client import get_control_plane_client
6
+ from llama_deploy.cli.config.auth_service import AuthService
7
+ from llama_deploy.cli.config.env_service import service
8
+ from llama_deploy.core.client.manage_client import ClientError, ControlPlaneClient
9
+ from llama_deploy.core.schema.projects import ProjectSummary
10
+ from rich import print as rprint
11
+ from rich.table import Table
12
+ from rich.text import Text
13
+
14
+ from ..app import app, console
15
+ from ..config.schema import Auth
16
+ from ..options import global_options, interactive_option
17
+
18
+
19
+ # Create sub-applications for organizing commands
20
+ @app.group(
21
+ help="Login to llama cloud control plane to manage deployments",
22
+ no_args_is_help=True,
23
+ )
24
+ @global_options
25
+ def auth() -> None:
26
+ """Login to llama cloud control plane"""
27
+ pass
28
+
29
+
30
+ @auth.command("token")
31
+ @global_options
32
+ @click.option(
33
+ "--project-id",
34
+ help="Project ID to use for the login when creating non-interactively",
35
+ )
36
+ @click.option(
37
+ "--api-key",
38
+ help="API key to use for the login when creating non-interactively",
39
+ )
40
+ @interactive_option
41
+ def create_api_key_profile(
42
+ project_id: str | None,
43
+ api_key: str | None,
44
+ interactive: bool,
45
+ ) -> None:
46
+ """Authenticate with an API key and create a profile in the current environment."""
47
+ try:
48
+ auth_svc = service.current_auth_service()
49
+
50
+ # Non-interactive mode: require both api-key and project-id
51
+ if not interactive:
52
+ if not api_key or not project_id:
53
+ raise click.ClickException(
54
+ "--api-key and --project-id are required in non-interactive mode"
55
+ )
56
+ created = auth_svc.create_profile_from_token(project_id, api_key)
57
+ rprint(
58
+ f"[green]Created API key profile '{created.name}' and set as current[/green]"
59
+ )
60
+ return
61
+
62
+ # Interactive mode: prompt for token (masked) and validate
63
+ token_value = api_key or _prompt_for_api_key()
64
+ projects = _validate_token_and_list_projects(auth_svc, token_value)
65
+
66
+ # Select or enter project ID
67
+ selected_project_id = project_id or _select_or_enter_project(
68
+ projects, auth_svc.env.requires_auth
69
+ )
70
+ if not selected_project_id:
71
+ rprint("[yellow]No project selected[/yellow]")
72
+ return
73
+
74
+ # Create and set profile
75
+ created = auth_svc.create_profile_from_token(selected_project_id, token_value)
76
+ rprint(
77
+ f"[green]Created API key profile '{created.name}' and set as current[/green]"
78
+ )
79
+ except Exception as e:
80
+ rprint(f"[red]Error: {e}[/red]")
81
+ raise click.Abort()
82
+
83
+
84
+ @auth.command("list")
85
+ @global_options
86
+ def list_profiles() -> None:
87
+ """List all logged in profiles"""
88
+ try:
89
+ auth_svc = service.current_auth_service()
90
+ profiles = auth_svc.list_profiles()
91
+ current = auth_svc.get_current_profile()
92
+
93
+ if not profiles:
94
+ rprint("[yellow]No profiles found[/yellow]")
95
+ rprint("Create one with: [cyan]llamactl auth token[/cyan]")
96
+ return
97
+
98
+ table = Table(show_edge=False, box=None, header_style="bold cornflower_blue")
99
+ table.add_column(" Name")
100
+ table.add_column("Active Project", style="grey46")
101
+
102
+ for profile in profiles:
103
+ text = Text()
104
+ if profile == current:
105
+ text.append("* ", style="magenta")
106
+ else:
107
+ text.append(" ")
108
+ text.append(profile.name)
109
+ active_project = profile.project_id or "-"
110
+ table.add_row(
111
+ text,
112
+ active_project,
113
+ )
114
+
115
+ console.print(table)
116
+
117
+ except Exception as e:
118
+ rprint(f"[red]Error: {e}[/red]")
119
+ raise click.Abort()
120
+
121
+
122
+ @auth.command("switch")
123
+ @global_options
124
+ @click.argument("name", required=False)
125
+ @interactive_option
126
+ def switch_profile(name: str | None, interactive: bool) -> None:
127
+ """Switch to a different profile"""
128
+ auth_svc = service.current_auth_service()
129
+ try:
130
+ selected_auth = _select_profile(auth_svc, name, interactive)
131
+ if not selected_auth:
132
+ rprint("[yellow]No profile selected[/yellow]")
133
+ return
134
+
135
+ auth_svc.set_current_profile(selected_auth.name)
136
+ rprint(f"[green]Switched to profile '{selected_auth.name}'[/green]")
137
+
138
+ except Exception as e:
139
+ rprint(f"[red]Error: {e}[/red]")
140
+ raise click.Abort()
141
+
142
+
143
+ @auth.command("logout")
144
+ @global_options
145
+ @click.argument("name", required=False)
146
+ @interactive_option
147
+ def delete_profile(name: str | None, interactive: bool) -> None:
148
+ """Logout from a profile and wipe all associated data"""
149
+ try:
150
+ auth_svc = service.current_auth_service()
151
+ auth = _select_profile(auth_svc, name, interactive)
152
+ if not auth:
153
+ rprint("[yellow]No profile selected[/yellow]")
154
+ return
155
+
156
+ if auth_svc.delete_profile(auth.name):
157
+ rprint(f"[green]Logged out from '{auth.name}'[/green]")
158
+ else:
159
+ rprint(f"[red]Profile '{auth.name}' not found[/red]")
160
+
161
+ except Exception as e:
162
+ rprint(f"[red]Error: {e}[/red]")
163
+ raise click.Abort()
164
+
165
+
166
+ # Projects commands
167
+ @auth.command("project")
168
+ @click.argument("project_id", required=False)
169
+ @interactive_option
170
+ @global_options
171
+ def change_project(project_id: str | None, interactive: bool) -> None:
172
+ """Change the active project for the current profile"""
173
+ profile = validate_authenticated_profile(interactive)
174
+ if project_id and profile.project_id == project_id:
175
+ return
176
+ auth_svc = service.current_auth_service()
177
+ if project_id:
178
+ auth_svc.set_project(profile.name, project_id)
179
+ rprint(f"[green]Set active project to '{project_id}'[/green]")
180
+ return
181
+ if not interactive:
182
+ raise click.ClickException(
183
+ "No --project-id provided. Run `llamactl auth project --help` for more information."
184
+ )
185
+ try:
186
+ client = get_control_plane_client()
187
+ projects = asyncio.run(client.list_projects())
188
+
189
+ if not projects:
190
+ rprint("[yellow]No projects found[/yellow]")
191
+ return
192
+ result = questionary.select(
193
+ "Select a project",
194
+ choices=[
195
+ questionary.Choice(
196
+ title=f"{project.project_name} ({project.deployment_count} deployments)",
197
+ value=project.project_id,
198
+ )
199
+ for project in projects
200
+ ]
201
+ + (
202
+ [questionary.Choice(title="Create new project", value="__CREATE__")]
203
+ if not auth_svc.env.requires_auth
204
+ else []
205
+ ),
206
+ ).ask()
207
+ if result == "__CREATE__":
208
+ project_id = questionary.text("Enter project ID").ask()
209
+ result = project_id
210
+ if result:
211
+ auth_svc.set_project(profile.name, result)
212
+ rprint(f"[green]Set active project to '{result}'[/green]")
213
+ else:
214
+ rprint("[yellow]No project selected[/yellow]")
215
+ except Exception as e:
216
+ rprint(f"[red]Error: {e}[/red]")
217
+ raise click.Abort()
218
+
219
+
220
+ def validate_authenticated_profile(interactive: bool) -> Auth:
221
+ """Validate that the user is authenticated within the current environment.
222
+
223
+ - If there is a current profile, return it.
224
+ - If multiple profiles exist in the current environment, prompt to select in interactive mode.
225
+ - If none exist:
226
+ - If environment requires_auth: run token flow inline.
227
+ - Else: create profile without token after selecting a project.
228
+ """
229
+
230
+ auth_svc = service.current_auth_service()
231
+ existing = auth_svc.get_current_profile()
232
+ if existing:
233
+ return existing
234
+
235
+ if not interactive:
236
+ raise click.ClickException(
237
+ "No profile configured. Run `llamactl auth token` to create a profile."
238
+ )
239
+
240
+ # Filter profiles by current environment
241
+ env_profiles = auth_svc.list_profiles()
242
+ current_env = auth_svc.env
243
+
244
+ if len(env_profiles) > 1:
245
+ # Prompt to select
246
+ choice: Auth | None = questionary.select(
247
+ "Select profile",
248
+ choices=[questionary.Choice(title=p.name, value=p) for p in env_profiles],
249
+ ).ask()
250
+ if not choice:
251
+ raise click.ClickException("No profile selected")
252
+ auth_svc.set_current_profile(choice.name)
253
+ return choice
254
+ if len(env_profiles) == 1:
255
+ only = env_profiles[0]
256
+ auth_svc.set_current_profile(only.name)
257
+ return only
258
+
259
+ # No profiles exist for this env
260
+ if current_env.requires_auth:
261
+ # Inline token flow
262
+ created = _token_flow_for_env(auth_svc)
263
+ return created
264
+ else:
265
+ # No auth required: select project and create a default profile without token
266
+ project_id: str | None = questionary.text("Enter project ID").ask()
267
+ if not project_id:
268
+ raise click.ClickException("No project ID provided")
269
+ created = auth_svc.create_profile_from_token(project_id, None)
270
+ return created
271
+
272
+
273
+ # -----------------------------
274
+ # Helpers for token/profile flow
275
+ # -----------------------------
276
+
277
+
278
+ def _prompt_for_api_key() -> str:
279
+ while True:
280
+ entered = questionary.password("Enter API key token").ask()
281
+ if entered:
282
+ return entered.strip()
283
+ rprint("[yellow]API key is required[/yellow]")
284
+
285
+
286
+ def _validate_token_and_list_projects(
287
+ auth_svc: AuthService, api_key: str
288
+ ) -> list[ProjectSummary]:
289
+ async def _run():
290
+ async with ControlPlaneClient.ctx(auth_svc.env.api_url, api_key) as client:
291
+ return await client.list_projects()
292
+
293
+ try:
294
+ return asyncio.run(_run())
295
+ except ClientError as e:
296
+ if getattr(e, "status_code", None) == 401:
297
+ rprint("[red]Invalid API key. Please try again.[/red]")
298
+ return _validate_token_and_list_projects(auth_svc, _prompt_for_api_key())
299
+ if getattr(e, "status_code", None) == 403:
300
+ rprint("[red]This environment requires a valid API key.[/red]")
301
+ return _validate_token_and_list_projects(auth_svc, _prompt_for_api_key())
302
+ raise
303
+ except Exception as e:
304
+ raise click.ClickException(f"Failed to validate API key: {e}")
305
+
306
+
307
+ def _select_or_enter_project(
308
+ projects: list[ProjectSummary], requires_auth: bool
309
+ ) -> str | None:
310
+ if not projects:
311
+ return None
312
+ # select the only authorized project if there is only one
313
+ elif len(projects) == 1 and requires_auth:
314
+ return projects[0].project_id
315
+ else:
316
+ choice = questionary.select(
317
+ "Select a project",
318
+ choices=[
319
+ questionary.Choice(
320
+ title=f"{p.project_name} ({p.deployment_count} deployments)",
321
+ value=p.project_id,
322
+ )
323
+ for p in projects
324
+ ],
325
+ ).ask()
326
+ return choice
327
+
328
+
329
+ def _token_flow_for_env(auth_service: AuthService) -> Auth:
330
+ token_value = _prompt_for_api_key()
331
+ projects = _validate_token_and_list_projects(auth_service, token_value)
332
+ project_id = _select_or_enter_project(projects, auth_service.env.requires_auth)
333
+ if not project_id:
334
+ raise click.ClickException("No project selected")
335
+ created = auth_service.create_profile_from_token(project_id, token_value)
336
+ return created
337
+
338
+
339
+ def _select_profile(
340
+ auth_svc: AuthService, profile_name: str | None, is_interactive: bool
341
+ ) -> Auth | None:
342
+ """
343
+ Select a profile interactively if name not provided.
344
+ Returns the selected profile name or None if cancelled.
345
+
346
+ In non-interactive sessions, returns None if profile_name is not provided.
347
+ """
348
+ if profile_name:
349
+ profile = auth_svc.get_profile(profile_name)
350
+ if profile:
351
+ return profile
352
+
353
+ # Don't attempt interactive selection in non-interactive sessions
354
+ if not is_interactive:
355
+ return None
356
+
357
+ try:
358
+ profiles = auth_svc.list_profiles()
359
+
360
+ if not profiles:
361
+ rprint("[yellow]No profiles found[/yellow]")
362
+ return None
363
+
364
+ choices = []
365
+ current = auth_svc.get_current_profile()
366
+
367
+ for profile in profiles:
368
+ title = f"{profile.name} ({profile.api_url})"
369
+ if profile == current:
370
+ title += " [current]"
371
+ choices.append(questionary.Choice(title=title, value=profile))
372
+
373
+ return questionary.select("Select profile:", choices=choices).ask()
374
+
375
+ except Exception as e:
376
+ rprint(f"[red]Error loading profiles: {e}[/red]")
377
+ return None
@@ -14,6 +14,7 @@ from llama_deploy.cli.commands.auth import validate_authenticated_profile
14
14
  from llama_deploy.core.schema.deployments import DeploymentUpdate
15
15
  from rich import print as rprint
16
16
  from rich.table import Table
17
+ from rich.text import Text
17
18
 
18
19
  from ..app import app, console
19
20
  from ..client import get_project_client
@@ -55,36 +56,23 @@ def list_deployments(interactive: bool) -> None:
55
56
  )
56
57
  return
57
58
 
58
- table = Table(title=f"Deployments for project {client.project_id}")
59
- table.add_column("Name", style="cyan")
60
- table.add_column("ID", style="yellow")
61
- table.add_column("Status", style="green")
62
- table.add_column("Repository", style="blue")
63
- table.add_column("Deployment File", style="magenta")
64
- table.add_column("Git Ref", style="white")
65
- table.add_column("PAT", style="red")
66
- table.add_column("Secrets", style="bright_green")
59
+ table = Table(show_edge=False, box=None, header_style="bold cornflower_blue")
60
+ table.add_column("Name")
61
+ table.add_column("Status", style="grey46")
62
+ table.add_column("Repository", style="grey46")
67
63
 
68
64
  for deployment in deployments:
69
- name = deployment.name
70
- deployment_id = deployment.id
65
+ name = deployment.id
71
66
  status = deployment.status
72
67
  repo_url = deployment.repo_url
73
- deployment_file_path = deployment.deployment_file_path
74
- git_ref = deployment.git_ref
75
- has_pat = "" if deployment.has_personal_access_token else "-"
76
- secret_names = deployment.secret_names
77
- secrets_display = str(len(secret_names)) if secret_names else "-"
68
+ gh = "https://github.com/"
69
+ if repo_url.startswith(gh):
70
+ repo_url = "gh:" + repo_url.removeprefix(gh)
78
71
 
79
72
  table.add_row(
80
73
  name,
81
- deployment_id,
82
74
  status,
83
75
  repo_url,
84
- deployment_file_path,
85
- git_ref,
86
- has_pat,
87
- secrets_display,
88
76
  )
89
77
 
90
78
  console.print(table)
@@ -114,25 +102,25 @@ def get_deployment(deployment_id: str | None, interactive: bool) -> None:
114
102
 
115
103
  deployment = asyncio.run(client.get_deployment(deployment_id))
116
104
 
117
- table = Table(title=f"Deployment: {deployment.name}")
118
- table.add_column("Property", style="cyan")
119
- table.add_column("Value", style="green")
105
+ table = Table(show_edge=False, box=None, header_style="bold cornflower_blue")
106
+ table.add_column("Property", style="grey46", justify="right")
107
+ table.add_column("Value")
120
108
 
121
- table.add_row("ID", deployment.id)
122
- table.add_row("Project ID", deployment.project_id)
123
- table.add_row("Status", deployment.status)
124
- table.add_row("Repository", deployment.repo_url)
125
- table.add_row("Deployment File", deployment.deployment_file_path)
126
- table.add_row("Git Ref", deployment.git_ref)
127
- table.add_row("Has PAT", str(deployment.has_personal_access_token))
109
+ table.add_row("ID", Text(deployment.id))
110
+ table.add_row("Project ID", Text(deployment.project_id))
111
+ table.add_row("Status", Text(deployment.status))
112
+ table.add_row("Repository", Text(deployment.repo_url))
113
+ table.add_row("Deployment File", Text(deployment.deployment_file_path))
114
+ table.add_row("Git Ref", Text(deployment.git_ref or "-"))
128
115
 
129
116
  apiserver_url = deployment.apiserver_url
130
- if apiserver_url:
131
- table.add_row("API Server URL", str(apiserver_url))
117
+ table.add_row(
118
+ "API Server URL",
119
+ Text(str(apiserver_url) if apiserver_url else "-"),
120
+ )
132
121
 
133
- secret_names = deployment.secret_names
134
- if secret_names:
135
- table.add_row("Secrets", ", ".join(secret_names))
122
+ secret_names = deployment.secret_names or []
123
+ table.add_row("Secrets", Text("\n".join(secret_names), style="italic"))
136
124
 
137
125
  console.print(table)
138
126