llamactl 0.2.7a1__py3-none-any.whl → 0.3.0__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.
Files changed (41) hide show
  1. llama_deploy/cli/__init__.py +9 -22
  2. llama_deploy/cli/app.py +69 -0
  3. llama_deploy/cli/auth/client.py +362 -0
  4. llama_deploy/cli/client.py +47 -170
  5. llama_deploy/cli/commands/aliased_group.py +33 -0
  6. llama_deploy/cli/commands/auth.py +696 -0
  7. llama_deploy/cli/commands/deployment.py +300 -0
  8. llama_deploy/cli/commands/env.py +211 -0
  9. llama_deploy/cli/commands/init.py +313 -0
  10. llama_deploy/cli/commands/serve.py +239 -0
  11. llama_deploy/cli/config/_config.py +390 -0
  12. llama_deploy/cli/config/_migrations.py +65 -0
  13. llama_deploy/cli/config/auth_service.py +130 -0
  14. llama_deploy/cli/config/env_service.py +67 -0
  15. llama_deploy/cli/config/migrations/0001_init.sql +35 -0
  16. llama_deploy/cli/config/migrations/0002_add_auth_fields.sql +24 -0
  17. llama_deploy/cli/config/migrations/__init__.py +7 -0
  18. llama_deploy/cli/config/schema.py +61 -0
  19. llama_deploy/cli/env.py +5 -3
  20. llama_deploy/cli/interactive_prompts/session_utils.py +37 -0
  21. llama_deploy/cli/interactive_prompts/utils.py +6 -72
  22. llama_deploy/cli/options.py +27 -5
  23. llama_deploy/cli/py.typed +0 -0
  24. llama_deploy/cli/styles.py +10 -0
  25. llama_deploy/cli/textual/deployment_form.py +263 -36
  26. llama_deploy/cli/textual/deployment_help.py +53 -0
  27. llama_deploy/cli/textual/deployment_monitor.py +466 -0
  28. llama_deploy/cli/textual/git_validation.py +20 -21
  29. llama_deploy/cli/textual/github_callback_server.py +17 -14
  30. llama_deploy/cli/textual/llama_loader.py +13 -1
  31. llama_deploy/cli/textual/secrets_form.py +28 -8
  32. llama_deploy/cli/textual/styles.tcss +49 -8
  33. llama_deploy/cli/utils/env_inject.py +23 -0
  34. {llamactl-0.2.7a1.dist-info → llamactl-0.3.0.dist-info}/METADATA +9 -6
  35. llamactl-0.3.0.dist-info/RECORD +38 -0
  36. {llamactl-0.2.7a1.dist-info → llamactl-0.3.0.dist-info}/WHEEL +1 -1
  37. llama_deploy/cli/commands.py +0 -549
  38. llama_deploy/cli/config.py +0 -173
  39. llama_deploy/cli/textual/profile_form.py +0 -171
  40. llamactl-0.2.7a1.dist-info/RECORD +0 -19
  41. {llamactl-0.2.7a1.dist-info → llamactl-0.3.0.dist-info}/entry_points.txt +0 -0
@@ -1,28 +1,53 @@
1
1
  """Textual-based deployment forms for CLI interactions"""
2
2
 
3
- from dataclasses import dataclass, field
4
3
  import dataclasses
5
4
  import logging
5
+ import re
6
+ from dataclasses import dataclass, field
6
7
  from pathlib import Path
8
+ from urllib.parse import urlsplit
7
9
 
8
- from llama_deploy.cli.textual.secrets_form import SecretsWidget
10
+ from llama_deploy.cli.client import get_project_client as get_client
11
+ from llama_deploy.cli.env import load_env_secrets_from_string
12
+ from llama_deploy.cli.textual.deployment_help import (
13
+ DeploymentHelpBackMessage,
14
+ DeploymentHelpWidget,
15
+ )
16
+ from llama_deploy.cli.textual.deployment_monitor import (
17
+ DeploymentMonitorWidget,
18
+ MonitorCloseMessage,
19
+ )
9
20
  from llama_deploy.cli.textual.git_validation import (
10
21
  GitValidationWidget,
11
- ValidationResultMessage,
12
22
  ValidationCancelMessage,
23
+ ValidationResultMessage,
24
+ )
25
+ from llama_deploy.cli.textual.secrets_form import SecretsWidget
26
+ from llama_deploy.core.deployment_config import (
27
+ DEFAULT_DEPLOYMENT_NAME,
28
+ read_deployment_config,
29
+ )
30
+ from llama_deploy.core.git.git_util import (
31
+ get_current_branch,
32
+ get_git_root,
33
+ get_unpushed_commits_count,
34
+ is_git_repo,
35
+ list_remotes,
36
+ working_tree_has_changes,
13
37
  )
14
38
  from llama_deploy.core.schema.deployments import (
15
39
  DeploymentCreate,
16
40
  DeploymentResponse,
17
41
  DeploymentUpdate,
18
42
  )
43
+ from textual import events
19
44
  from textual.app import App, ComposeResult
20
45
  from textual.containers import Container, HorizontalGroup, Widget
46
+ from textual.content import Content
47
+ from textual.message import Message
48
+ from textual.reactive import reactive
21
49
  from textual.validation import Length
22
50
  from textual.widgets import Button, Input, Label, Static
23
- from textual.reactive import reactive
24
- from llama_deploy.cli.client import get_client
25
- from textual.message import Message
26
51
 
27
52
 
28
53
  @dataclass
@@ -34,7 +59,7 @@ class DeploymentForm:
34
59
  id: str | None = None
35
60
  repo_url: str = ""
36
61
  git_ref: str = "main"
37
- deployment_file_path: str = "llama_deploy.yaml"
62
+ deployment_file_path: str = ""
38
63
  personal_access_token: str = ""
39
64
  # indicates if the deployment has a personal access token (value is unknown)
40
65
  has_existing_pat: bool = False
@@ -46,6 +71,10 @@ class DeploymentForm:
46
71
  removed_secrets: set[str] = field(default_factory=set)
47
72
  # if the deployment is being edited
48
73
  is_editing: bool = False
74
+ # warnings shown to the user
75
+ warnings: list[str] = field(default_factory=list)
76
+ # env info
77
+ env_info_messages: str | None = None
49
78
 
50
79
  @classmethod
51
80
  def from_deployment(cls, deployment: DeploymentResponse) -> "DeploymentForm":
@@ -56,7 +85,7 @@ class DeploymentForm:
56
85
  id=deployment.id,
57
86
  repo_url=deployment.repo_url,
58
87
  git_ref=deployment.git_ref or "main",
59
- deployment_file_path=deployment.deployment_file_path or "llama_deploy.yaml",
88
+ deployment_file_path=deployment.deployment_file_path,
60
89
  personal_access_token="", # Always start empty for security
61
90
  has_existing_pat=deployment.has_personal_access_token,
62
91
  secrets={},
@@ -74,7 +103,7 @@ class DeploymentForm:
74
103
  data = DeploymentUpdate(
75
104
  repo_url=self.repo_url,
76
105
  git_ref=self.git_ref or "main",
77
- deployment_file_path=self.deployment_file_path or "llama_deploy.yaml",
106
+ deployment_file_path=self.deployment_file_path or None,
78
107
  personal_access_token=(
79
108
  ""
80
109
  if self.personal_access_token is None and not self.has_existing_pat
@@ -91,7 +120,7 @@ class DeploymentForm:
91
120
  return DeploymentCreate(
92
121
  name=self.name,
93
122
  repo_url=self.repo_url,
94
- deployment_file_path=self.deployment_file_path or "llama_deploy.yaml",
123
+ deployment_file_path=self.deployment_file_path or None,
95
124
  git_ref=self.git_ref or "main",
96
125
  personal_access_token=self.personal_access_token,
97
126
  secrets=self.secrets,
@@ -119,8 +148,11 @@ class DeploymentFormWidget(Widget):
119
148
 
120
149
  def compose(self) -> ComposeResult:
121
150
  title = "Edit Deployment" if self.form_data.is_editing else "Create Deployment"
151
+
122
152
  yield Static(
123
- title,
153
+ Content.from_markup(
154
+ f"{title} [italic][@click=app.show_help()]More info[/][/italic]"
155
+ ),
124
156
  classes="primary-message",
125
157
  )
126
158
  yield Static(
@@ -128,6 +160,13 @@ class DeploymentFormWidget(Widget):
128
160
  id="error-message",
129
161
  classes="error-message " + ("visible" if self.error_message else "hidden"),
130
162
  )
163
+ # Top-of-form warnings banner
164
+ yield Static(
165
+ "Note: " + " ".join(f"{w}" for w in self.form_data.warnings),
166
+ id="warning-list",
167
+ classes="warning-message mb-1 hidden "
168
+ + ("visible" if self.form_data.warnings else ""),
169
+ )
131
170
 
132
171
  # Main deployment fields
133
172
  with Widget(classes="two-column-form-grid"):
@@ -161,10 +200,10 @@ class DeploymentFormWidget(Widget):
161
200
  compact=True,
162
201
  )
163
202
 
164
- yield Label("Deployment File:", classes="form-label", shrink=True)
203
+ yield Label("Config File:", classes="form-label", shrink=True)
165
204
  yield Input(
166
205
  value=self.form_data.deployment_file_path,
167
- placeholder="llama_deploy.yaml",
206
+ placeholder="Optional path to config dir/file",
168
207
  id="deployment_file_path",
169
208
  compact=True,
170
209
  )
@@ -191,6 +230,7 @@ class DeploymentFormWidget(Widget):
191
230
  yield SecretsWidget(
192
231
  initial_secrets=self.form_data.secrets,
193
232
  prior_secrets=self.form_data.initial_secrets,
233
+ info_message=self.form_data.env_info_messages,
194
234
  )
195
235
 
196
236
  with HorizontalGroup(classes="button-row"):
@@ -201,7 +241,7 @@ class DeploymentFormWidget(Widget):
201
241
  if event.button.id == "save":
202
242
  self._save()
203
243
  elif event.button.id == "change_pat":
204
- updated_form = dataclasses.replace(self._get_form_data())
244
+ updated_form = dataclasses.replace(self.resolve_form_data())
205
245
  updated_form.has_existing_pat = False
206
246
  updated_form.personal_access_token = ""
207
247
  self.form_data = updated_form
@@ -210,7 +250,7 @@ class DeploymentFormWidget(Widget):
210
250
  self.post_message(CancelFormMessage())
211
251
 
212
252
  def _save(self) -> None:
213
- self.form_data = self._get_form_data()
253
+ self.form_data = self.resolve_form_data()
214
254
  if self._validate_form():
215
255
  # Post message to parent app to start validation
216
256
  self.post_message(StartValidationMessage(self.form_data))
@@ -245,7 +285,7 @@ class DeploymentFormWidget(Widget):
245
285
  """Show an error message"""
246
286
  self.error_message = message
247
287
 
248
- def _get_form_data(self) -> DeploymentForm:
288
+ def resolve_form_data(self) -> DeploymentForm:
249
289
  """Extract form data from inputs"""
250
290
  name_input = self.query_one("#name", Input)
251
291
  repo_url_input = self.query_one("#repo_url", Input)
@@ -269,8 +309,7 @@ class DeploymentFormWidget(Widget):
269
309
  id=self.form_data.id,
270
310
  repo_url=repo_url_input.value.strip(),
271
311
  git_ref=git_ref_input.value.strip() or "main",
272
- deployment_file_path=deployment_file_input.value.strip()
273
- or "llama_deploy.yaml",
312
+ deployment_file_path=deployment_file_input.value.strip(),
274
313
  personal_access_token=pat_value,
275
314
  secrets=updated_secrets,
276
315
  initial_secrets=self.original_form_data.initial_secrets,
@@ -299,15 +338,26 @@ class StartValidationMessage(Message):
299
338
  self.form_data = form_data
300
339
 
301
340
 
341
+ class ShowHelpMessage(Message):
342
+ def __init__(self, form_data: DeploymentForm):
343
+ super().__init__()
344
+ self.form_data = form_data
345
+
346
+
347
+ class HelpBackMessage(Message):
348
+ pass
349
+
350
+
302
351
  class DeploymentEditApp(App[DeploymentResponse | None]):
303
352
  """Textual app for editing/creating deployments"""
304
353
 
305
354
  CSS_PATH = Path(__file__).parent / "styles.tcss"
306
355
 
307
- # App states: 'form' or 'validation'
356
+ # App states: 'form', 'validation', 'help', or 'monitor'
308
357
  current_state: reactive[str] = reactive("form", recompose=True)
309
358
  form_data: reactive[DeploymentForm] = reactive(DeploymentForm())
310
359
  save_error: reactive[str] = reactive("", recompose=True)
360
+ saved_deployment = reactive[DeploymentResponse | None](None, recompose=True)
311
361
 
312
362
  def __init__(self, initial_data: DeploymentForm):
313
363
  super().__init__()
@@ -317,13 +367,17 @@ class DeploymentEditApp(App[DeploymentResponse | None]):
317
367
  def on_mount(self) -> None:
318
368
  self.theme = "tokyo-night"
319
369
 
320
- def on_key(self, event) -> None:
370
+ def on_key(self, event: events.Key) -> None:
321
371
  """Handle key events, including Ctrl+C"""
322
372
  if event.key == "ctrl+c":
323
- self.exit(None)
373
+ if self.current_state == "monitor" and self.saved_deployment is not None:
374
+ self.exit(self.saved_deployment)
375
+ else:
376
+ self.exit(None)
324
377
 
325
378
  def compose(self) -> ComposeResult:
326
- with Container(classes="form-container"):
379
+ is_slim = self.current_state != "monitor"
380
+ with Container(classes="form-container" if is_slim else ""):
327
381
  if self.current_state == "form":
328
382
  yield DeploymentFormWidget(self.form_data, self.save_error)
329
383
  elif self.current_state == "validation":
@@ -336,6 +390,28 @@ class DeploymentEditApp(App[DeploymentResponse | None]):
336
390
  if self.form_data.personal_access_token
337
391
  else None,
338
392
  )
393
+ elif self.current_state == "help":
394
+ yield DeploymentHelpWidget()
395
+ elif self.current_state == "monitor":
396
+ deployment_id = (
397
+ self.saved_deployment.id if self.saved_deployment else ""
398
+ )
399
+ yield DeploymentMonitorWidget(deployment_id)
400
+ else:
401
+ yield Static("Unknown state: " + self.current_state)
402
+
403
+ def action_show_help(self) -> None:
404
+ widget = self.query("DeploymentFormWidget")
405
+ if widget:
406
+ typed_widget: DeploymentFormWidget = widget[0]
407
+ self.form_data = typed_widget.resolve_form_data()
408
+
409
+ self.current_state = "help"
410
+
411
+ def on_deployment_help_back_message(
412
+ self, message: DeploymentHelpBackMessage
413
+ ) -> None:
414
+ self.current_state = "form"
339
415
 
340
416
  def on_start_validation_message(self, message: StartValidationMessage) -> None:
341
417
  """Handle validation start message from form widget"""
@@ -343,7 +419,9 @@ class DeploymentEditApp(App[DeploymentResponse | None]):
343
419
  self.save_error = "" # Clear any previous errors
344
420
  self.current_state = "validation"
345
421
 
346
- def on_validation_result_message(self, message: ValidationResultMessage) -> None:
422
+ async def on_validation_result_message(
423
+ self, message: ValidationResultMessage
424
+ ) -> None:
347
425
  """Handle validation success from git validation widget"""
348
426
  logging.info("validation result message", message)
349
427
  # Update form data with validated PAT if provided
@@ -355,30 +433,44 @@ class DeploymentEditApp(App[DeploymentResponse | None]):
355
433
  updated_form.has_existing_pat = False
356
434
  self.form_data = updated_form
357
435
 
358
- # Proceed with save
359
- self._perform_save()
436
+ # Proceed with save (async)
437
+ await self._perform_save()
360
438
 
361
439
  def on_validation_cancel_message(self, message: ValidationCancelMessage) -> None:
362
440
  """Handle validation cancellation from git validation widget"""
363
441
  # Return to form, clearing any save error
364
- print("DEBUG: on_validation_cancel_message")
365
442
  self.save_error = ""
366
443
  self.current_state = "form"
367
444
 
368
- def _perform_save(self) -> None:
445
+ def on_show_help_message(self, message: ShowHelpMessage) -> None:
446
+ """Navigate to help view, preserving current form state."""
447
+ self.form_data = message.form_data
448
+ self.current_state = "help"
449
+
450
+ def on_help_back_message(self, message: HelpBackMessage) -> None:
451
+ """Return from help to form, keeping form state intact."""
452
+ self.current_state = "form"
453
+
454
+ async def _perform_save(self) -> None:
369
455
  """Actually save the deployment after validation"""
370
456
  logging.info("saving form data", self.form_data)
371
457
  result = self.form_data
372
458
  client = get_client()
373
459
  try:
374
- if result.is_editing:
375
- update_deployment = client.update_deployment(
376
- result.id, result.to_update()
377
- )
378
- else:
379
- update_deployment = client.create_deployment(result.to_create())
380
- # Exit with result
381
- self.exit(update_deployment)
460
+ update_deployment = (
461
+ await client.update_deployment(result.id, result.to_update())
462
+ if result.is_editing
463
+ else await client.create_deployment(result.to_create())
464
+ )
465
+ # Save and navigate to embedded monitor screen
466
+ self.saved_deployment = update_deployment
467
+ # Ensure form_data carries the new ID for any subsequent operations
468
+ if not result.is_editing and update_deployment.id:
469
+ updated_form = dataclasses.replace(self.form_data)
470
+ updated_form.id = update_deployment.id
471
+ updated_form.is_editing = True
472
+ self.form_data = updated_form
473
+ self.current_state = "monitor"
382
474
  except Exception as e:
383
475
  # Return to form and show error
384
476
  self.save_error = f"Error saving deployment: {e}"
@@ -392,6 +484,10 @@ class DeploymentEditApp(App[DeploymentResponse | None]):
392
484
  """Handle cancel message from form widget"""
393
485
  self.exit(None)
394
486
 
487
+ def on_monitor_close_message(self, _: MonitorCloseMessage) -> None:
488
+ """Handle close from embedded monitor by exiting with saved deployment."""
489
+ self.exit(self.saved_deployment)
490
+
395
491
 
396
492
  def edit_deployment_form(
397
493
  deployment: DeploymentResponse,
@@ -404,6 +500,137 @@ def edit_deployment_form(
404
500
 
405
501
  def create_deployment_form() -> DeploymentResponse | None:
406
502
  """Launch deployment creation form and return result"""
407
- initial_data = DeploymentForm()
503
+ initial_data = _initialize_deployment_data()
408
504
  app = DeploymentEditApp(initial_data)
409
505
  return app.run()
506
+
507
+
508
+ def _initialize_deployment_data() -> DeploymentForm:
509
+ """
510
+ initialize the deployment form data from the current git repo and .env file
511
+ """
512
+
513
+ repo_url: str | None = None
514
+ git_ref: str | None = None
515
+ secrets: dict[str, str] = {}
516
+ name: str | None = None
517
+ config_file_path: str | None = None
518
+ warnings: list[str] = []
519
+ has_git = is_git_repo()
520
+ has_no_workflows = False
521
+ try:
522
+ config = read_deployment_config(Path("."), Path("."))
523
+ if config.name != DEFAULT_DEPLOYMENT_NAME:
524
+ name = config.name
525
+ has_no_workflows = config.has_no_workflows()
526
+ except Exception:
527
+ warnings.append("Could not parse local deployment config. It may be invalid.")
528
+ if not has_git and has_no_workflows:
529
+ warnings = [
530
+ "Run from within a git repository to automatically generate a deployment config."
531
+ ]
532
+ elif has_no_workflows:
533
+ warnings = [
534
+ "The current project has no workflows configured. It may be invalid."
535
+ ]
536
+ elif not has_git:
537
+ warnings.append(
538
+ "Current directory is not a git repository. If you are trying to deploy this directory, you will need to create a git repository and push it before creating a deployment."
539
+ )
540
+ else:
541
+ seen = set[str]()
542
+ remotes = list_remotes()
543
+ candidate_origins = []
544
+ for remote in remotes:
545
+ normalized_url = _normalize_to_http(remote)
546
+ if normalized_url not in seen:
547
+ candidate_origins.append(normalized_url)
548
+ seen.add(normalized_url)
549
+ preferred_origin = sorted(
550
+ candidate_origins, key=lambda x: "github.com" in x, reverse=True
551
+ )
552
+ if preferred_origin:
553
+ repo_url = preferred_origin[0]
554
+ git_ref = get_current_branch()
555
+ root = get_git_root()
556
+ if root != Path.cwd():
557
+ config_file_path = str(Path.cwd().relative_to(root))
558
+
559
+ if not preferred_origin:
560
+ warnings.append(
561
+ "No git remote was found. You will need to push your changes to a remote repository before creating a deployment from this repository."
562
+ )
563
+ else:
564
+ # Working tree changes
565
+ if working_tree_has_changes() and preferred_origin:
566
+ warnings.append(
567
+ "Working tree has uncommitted or untracked changes. You may want to push them before creating a deployment from this branch."
568
+ )
569
+ else:
570
+ # Unpushed commits (ahead of upstream)
571
+ ahead = get_unpushed_commits_count()
572
+ if ahead is None:
573
+ warnings.append(
574
+ "Current branch has no upstream configured. You will need to push them or choose a different branch."
575
+ )
576
+ elif ahead > 0:
577
+ warnings.append(
578
+ f"There are {ahead} local commits not pushed to upstream. They won't be included in the deployment unless you push them first."
579
+ )
580
+ env_info_message = None
581
+ if Path(".env").exists():
582
+ secrets = load_env_secrets_from_string(Path(".env").read_text())
583
+ if len(secrets) > 0:
584
+ env_info_message = "Secrets were automatically seeded from your .env file. Remove or change any that should not be set. They must be manually configured after creation."
585
+
586
+ form = DeploymentForm(
587
+ name=name or "",
588
+ repo_url=repo_url or "",
589
+ git_ref=git_ref or "main",
590
+ secrets=secrets,
591
+ deployment_file_path=config_file_path or "",
592
+ warnings=warnings,
593
+ env_info_messages=env_info_message,
594
+ )
595
+ return form
596
+
597
+
598
+ def _normalize_to_http(url: str) -> str:
599
+ """
600
+ normalize a git url to a best guess for a corresponding http(s) url
601
+ """
602
+ candidate = (url or "").strip()
603
+
604
+ # If no scheme, first try scp-like SSH syntax: [user@]host:path
605
+ has_scheme = "://" in candidate
606
+ if not has_scheme:
607
+ scp_match = re.match(
608
+ r"^(?:(?P<user>[^@]+)@)?(?P<host>[^:/\s]+):(?P<path>[^/].+)$",
609
+ candidate,
610
+ )
611
+ if scp_match:
612
+ host = scp_match.group("host")
613
+ path = scp_match.group("path").lstrip("/")
614
+ if path.endswith(".git"):
615
+ path = path[:-4]
616
+ return f"https://{host}/{path}"
617
+
618
+ # If no scheme (and not scp), assume host/path and prepend https
619
+ parsed = urlsplit(candidate if has_scheme else f"https://{candidate}")
620
+
621
+ # Drop credentials from netloc
622
+ netloc = parsed.netloc.split("@", 1)[-1]
623
+
624
+ # Drop explicit port (common for SSH like :7999 which is wrong for https)
625
+ if ":" in netloc:
626
+ netloc = netloc.split(":", 1)[0]
627
+
628
+ # Normalize path and strip .git
629
+ path = parsed.path.lstrip("/")
630
+ if path.endswith(".git"):
631
+ path = path[:-4]
632
+
633
+ if path:
634
+ return f"https://{netloc}/{path}"
635
+ else:
636
+ return f"https://{netloc}"
@@ -0,0 +1,53 @@
1
+ from textwrap import dedent
2
+
3
+ from textual.app import ComposeResult
4
+ from textual.containers import HorizontalGroup, Widget
5
+ from textual.content import Content
6
+ from textual.message import Message
7
+ from textual.widgets import Button, Static
8
+
9
+
10
+ class DeploymentHelpBackMessage(Message):
11
+ pass
12
+
13
+
14
+ class DeploymentHelpWidget(Widget):
15
+ DEFAULT_CSS = """
16
+ DeploymentHelpWidget {
17
+ layout: vertical;
18
+ height: auto;
19
+ }
20
+ """
21
+
22
+ def compose(self) -> ComposeResult:
23
+ yield Static(
24
+ "Deploy your app to llama cloud or your own infrastructure:",
25
+ classes="primary-message",
26
+ )
27
+ yield Static(
28
+ Content.from_markup(
29
+ dedent("""
30
+ [b]Deployment Name[/b]
31
+ A unique name to identify this deployment. Controls the URL where your deployment is accessible. Will have a random suffix appended if not unique.
32
+
33
+ [b]Git Repository[/b]
34
+ A git repository URL to pull code from. If not publically accessible, you will be prompted to install the llama deploy github app. If code is on another platform, either provide a Personal Access Token (basic access credentials) instead.
35
+
36
+ [b]Git Ref[/b]
37
+ The git ref to deploy. This can be a branch, tag, or commit hash. If this is a branch, after deploying, run a `[slategrey reverse]llamactl deploy update[/]` to update the deployment to the latest git ref after you make updates.
38
+
39
+ [b]Config File[/b]
40
+ Path to a directory or file containing a `[slategrey reverse]pyproject.toml[/]` or `[slategrey reverse]llama_deploy.yaml[/]` containing the llama deploy configuration. Only necessary if you have the configuration not at the root of the repo, or you have an unconventional configuration file.
41
+
42
+ [b]Secrets[/b]
43
+ Secrets to add as environment variables to the deployment. e.g. to access a database or an API. Supports adding in `[slategrey reverse].env[/]` file format.
44
+
45
+ """).strip()
46
+ ),
47
+ )
48
+ with HorizontalGroup(classes="button-row"):
49
+ yield Button("Back", variant="primary", id="help_back", compact=True)
50
+
51
+ def on_button_pressed(self, event: Button.Pressed) -> None:
52
+ if event.button.id == "help_back":
53
+ self.post_message(DeploymentHelpBackMessage())