llamactl 0.3.10__tar.gz → 0.3.11__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 (39) hide show
  1. {llamactl-0.3.10 → llamactl-0.3.11}/PKG-INFO +3 -3
  2. {llamactl-0.3.10 → llamactl-0.3.11}/pyproject.toml +3 -3
  3. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/commands/init.py +14 -2
  4. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/commands/serve.py +10 -0
  5. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/textual/deployment_form.py +100 -6
  6. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/textual/deployment_help.py +6 -0
  7. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/textual/styles.tcss +112 -0
  8. llamactl-0.3.11/src/llama_deploy/cli/utils/version.py +11 -0
  9. {llamactl-0.3.10 → llamactl-0.3.11}/README.md +0 -0
  10. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/__init__.py +0 -0
  11. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/app.py +0 -0
  12. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/auth/client.py +0 -0
  13. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/client.py +0 -0
  14. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/commands/aliased_group.py +0 -0
  15. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/commands/auth.py +0 -0
  16. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/commands/deployment.py +0 -0
  17. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/commands/env.py +0 -0
  18. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/config/_config.py +0 -0
  19. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/config/_migrations.py +0 -0
  20. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/config/auth_service.py +0 -0
  21. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/config/env_service.py +0 -0
  22. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/config/migrations/0001_init.sql +0 -0
  23. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/config/migrations/0002_add_auth_fields.sql +0 -0
  24. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/config/migrations/__init__.py +0 -0
  25. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/config/schema.py +0 -0
  26. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/debug.py +0 -0
  27. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/env.py +0 -0
  28. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/interactive_prompts/session_utils.py +0 -0
  29. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/interactive_prompts/utils.py +0 -0
  30. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/options.py +0 -0
  31. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/py.typed +0 -0
  32. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/styles.py +0 -0
  33. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/textual/deployment_monitor.py +0 -0
  34. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/textual/git_validation.py +0 -0
  35. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/textual/github_callback_server.py +0 -0
  36. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/textual/llama_loader.py +0 -0
  37. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/textual/secrets_form.py +0 -0
  38. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/utils/env_inject.py +0 -0
  39. {llamactl-0.3.10 → llamactl-0.3.11}/src/llama_deploy/cli/utils/redact.py +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: llamactl
3
- Version: 0.3.10
3
+ Version: 0.3.11
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.10,<0.4.0
9
- Requires-Dist: llama-deploy-appserver>=0.3.10,<0.4.0
8
+ Requires-Dist: llama-deploy-core[client]>=0.3.11,<0.4.0
9
+ Requires-Dist: llama-deploy-appserver>=0.3.11,<0.4.0
10
10
  Requires-Dist: httpx>=0.24.0,<1.0.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.10"
3
+ version = "0.3.11"
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.10,<0.4.0",
13
- "llama-deploy-appserver>=0.3.10,<0.4.0",
12
+ "llama-deploy-core[client]>=0.3.11,<0.4.0",
13
+ "llama-deploy-appserver>=0.3.11,<0.4.0",
14
14
  "httpx>=0.24.0,<1.0.0",
15
15
  "rich>=13.0.0",
16
16
  "questionary>=2.0.0",
@@ -79,6 +79,15 @@ def _create(template: str | None, dir: Path | None, force: bool) -> None:
79
79
  ),
80
80
  llama_cloud=False,
81
81
  ),
82
+ TemplateOption(
83
+ id="showcase",
84
+ name="Showcase",
85
+ description="A collection of workflow and UI patterns to build LlamaDeploy apps",
86
+ source=GithubTemplateRepo(
87
+ url="https://github.com/run-llama/template-workflow-showcase"
88
+ ),
89
+ llama_cloud=False,
90
+ ),
82
91
  TemplateOption(
83
92
  id="document-qa",
84
93
  name="Document Question & Answer",
@@ -215,6 +224,7 @@ def _create(template: str | None, dir: Path | None, force: bool) -> None:
215
224
  )
216
225
  # Initialize git repository if git is available
217
226
  has_git = False
227
+ git_initialized = False
218
228
  try:
219
229
  subprocess.run(["git", "--version"], check=True, capture_output=True)
220
230
  has_git = True
@@ -252,6 +262,7 @@ def _create(template: str | None, dir: Path | None, force: bool) -> None:
252
262
  check=True,
253
263
  capture_output=True,
254
264
  )
265
+ git_initialized = True
255
266
  except (subprocess.CalledProcessError, FileNotFoundError) as e:
256
267
  # Extract a short error message if present
257
268
  err_msg = ""
@@ -295,7 +306,8 @@ def _create(template: str | None, dir: Path | None, force: bool) -> None:
295
306
  rprint(" [orange3]uvx[/] llamactl serve")
296
307
  rprint("")
297
308
  rprint("[bold]To deploy:[/]")
298
- if has_git:
309
+ # Only show manual git init steps if repository failed to initialize earlier
310
+ if not git_initialized:
299
311
  rprint(" [orange3]git[/] init")
300
312
  rprint(" [orange3]git[/] add .")
301
313
  rprint(" [orange3]git[/] commit -m 'Initial commit'")
@@ -306,7 +318,7 @@ def _create(template: str | None, dir: Path | None, force: bool) -> None:
306
318
  rprint(" [orange3]git[/] push -u origin main")
307
319
  rprint("")
308
320
  # rprint(" [orange3]uvx[/] llamactl login")
309
- rprint(" [orange3]uvx[/] llamactl deploy")
321
+ rprint(" [orange3]uvx[/] llamactl deploy create")
310
322
  rprint("")
311
323
 
312
324
 
@@ -92,6 +92,16 @@ def serve(
92
92
  rprint(f"[red]Deployment file '{deployment_file}' not found[/red]")
93
93
  raise click.Abort()
94
94
 
95
+ # Early check: appserver requires a pyproject.toml in the config directory
96
+ config_dir = deployment_file if deployment_file.is_dir() else deployment_file.parent
97
+ if not (config_dir / "pyproject.toml").exists():
98
+ rprint(
99
+ "[red]No pyproject.toml found at[/red] "
100
+ f"[bold]{config_dir}[/bold].\n"
101
+ "Add a pyproject.toml to your project and re-run 'llamactl serve'."
102
+ )
103
+ raise click.Abort()
104
+
95
105
  try:
96
106
  # Pre-check: if the template requires llama cloud access, ensure credentials
97
107
  _maybe_inject_llama_cloud_credentials(
@@ -5,6 +5,7 @@ import logging
5
5
  import re
6
6
  from dataclasses import dataclass, field
7
7
  from pathlib import Path
8
+ from textwrap import dedent
8
9
  from urllib.parse import urlsplit
9
10
 
10
11
  from llama_deploy.cli.client import get_project_client as get_client
@@ -23,6 +24,7 @@ from llama_deploy.cli.textual.git_validation import (
23
24
  ValidationResultMessage,
24
25
  )
25
26
  from llama_deploy.cli.textual.secrets_form import SecretsWidget
27
+ from llama_deploy.cli.utils.version import get_installed_appserver_version
26
28
  from llama_deploy.core.deployment_config import (
27
29
  DEFAULT_DEPLOYMENT_NAME,
28
30
  read_deployment_config,
@@ -40,6 +42,7 @@ from llama_deploy.core.schema.deployments import (
40
42
  DeploymentResponse,
41
43
  DeploymentUpdate,
42
44
  )
45
+ from packaging.version import Version
43
46
  from textual import events
44
47
  from textual.app import App, ComposeResult
45
48
  from textual.containers import Container, HorizontalGroup, Widget
@@ -47,7 +50,7 @@ from textual.content import Content
47
50
  from textual.message import Message
48
51
  from textual.reactive import reactive
49
52
  from textual.validation import Length
50
- from textual.widgets import Button, Input, Label, Static
53
+ from textual.widgets import Button, Input, Label, Select, Static
51
54
 
52
55
 
53
56
  @dataclass
@@ -75,11 +78,20 @@ class DeploymentForm:
75
78
  warnings: list[str] = field(default_factory=list)
76
79
  # env info
77
80
  env_info_messages: str | None = None
81
+ # appserver version fields
82
+ installed_appserver_version: str | None = None
83
+ existing_llama_deploy_version: str | None = None
84
+ selected_appserver_version: str | None = None
78
85
 
79
86
  @classmethod
80
87
  def from_deployment(cls, deployment: DeploymentResponse) -> "DeploymentForm":
81
88
  secret_names = deployment.secret_names or []
82
89
 
90
+ installed = get_installed_appserver_version()
91
+ existing = deployment.llama_deploy_version
92
+ # If versions match (or existing is None), treat as non-editable like create
93
+ selected = existing or installed
94
+
83
95
  return DeploymentForm(
84
96
  name=deployment.name,
85
97
  id=deployment.id,
@@ -91,8 +103,15 @@ class DeploymentForm:
91
103
  secrets={},
92
104
  initial_secrets=set(secret_names),
93
105
  is_editing=True,
106
+ installed_appserver_version=installed,
107
+ existing_llama_deploy_version=existing,
108
+ selected_appserver_version=selected,
94
109
  )
95
110
 
111
+ @staticmethod
112
+ def appserver_version() -> str | None:
113
+ return get_installed_appserver_version()
114
+
96
115
  def to_update(self) -> DeploymentUpdate:
97
116
  """Convert form data to API format"""
98
117
 
@@ -100,6 +119,8 @@ class DeploymentForm:
100
119
  for secret in self.removed_secrets:
101
120
  secrets[secret] = None
102
121
 
122
+ appserver_version = self.selected_appserver_version
123
+
103
124
  data = DeploymentUpdate(
104
125
  repo_url=self.repo_url,
105
126
  git_ref=self.git_ref or "main",
@@ -110,12 +131,14 @@ class DeploymentForm:
110
131
  else self.personal_access_token
111
132
  ),
112
133
  secrets=secrets,
134
+ llama_deploy_version=appserver_version,
113
135
  )
114
136
 
115
137
  return data
116
138
 
117
139
  def to_create(self) -> DeploymentCreate:
118
140
  """Convert form data to API format"""
141
+ appserver_version = self.selected_appserver_version
119
142
 
120
143
  return DeploymentCreate(
121
144
  name=self.name,
@@ -124,6 +147,7 @@ class DeploymentForm:
124
147
  git_ref=self.git_ref or "main",
125
148
  personal_access_token=self.personal_access_token,
126
149
  secrets=self.secrets,
150
+ llama_deploy_version=appserver_version,
127
151
  )
128
152
 
129
153
 
@@ -149,12 +173,23 @@ class DeploymentFormWidget(Widget):
149
173
  def compose(self) -> ComposeResult:
150
174
  title = "Edit Deployment" if self.form_data.is_editing else "Create Deployment"
151
175
 
152
- yield Static(
153
- Content.from_markup(
154
- f"{title} [italic][@click=app.show_help()]More info[/][/italic]"
155
- ),
176
+ with HorizontalGroup(
156
177
  classes="primary-message",
157
- )
178
+ ):
179
+ yield Static(
180
+ Content.from_markup(
181
+ f"{title} [italic][@click=app.show_help()]More info[/][/]"
182
+ ),
183
+ classes="w-1fr",
184
+ )
185
+ yield Static(
186
+ Content.from_markup(
187
+ dedent("""
188
+ [italic]Tab or click to navigate.[/]
189
+ """).strip()
190
+ ),
191
+ classes="text-right w-1fr",
192
+ )
158
193
  yield Static(
159
194
  self.error_message,
160
195
  id="error-message",
@@ -200,6 +235,11 @@ class DeploymentFormWidget(Widget):
200
235
  compact=True,
201
236
  )
202
237
 
238
+ yield Static(classes="full-width")
239
+ yield Static(
240
+ Content.from_markup("[italic]Advanced[/]"),
241
+ classes="text-center full-width",
242
+ )
203
243
  yield Label("Config File:", classes="form-label", shrink=True)
204
244
  yield Input(
205
245
  value=self.form_data.deployment_file_path,
@@ -207,6 +247,7 @@ class DeploymentFormWidget(Widget):
207
247
  id="deployment_file_path",
208
248
  compact=True,
209
249
  )
250
+
210
251
  yield Label("Personal Access Token:", classes="form-label", shrink=True)
211
252
  if self.form_data.has_existing_pat:
212
253
  yield Button(
@@ -226,6 +267,45 @@ class DeploymentFormWidget(Widget):
226
267
  compact=True,
227
268
  )
228
269
 
270
+ # Appserver version display/selector
271
+ yield Label("Appserver Version:", classes="form-label", shrink=True)
272
+ versions_differ = (
273
+ self.form_data.is_editing
274
+ and self.form_data.installed_appserver_version
275
+ and self.form_data.existing_llama_deploy_version
276
+ and self.form_data.installed_appserver_version
277
+ != self.form_data.existing_llama_deploy_version
278
+ )
279
+ if versions_differ:
280
+ # Show dropdown selector for version choice
281
+ installed_version = self.form_data.installed_appserver_version
282
+ existing_version = self.form_data.existing_llama_deploy_version
283
+ current_selection = (
284
+ self.form_data.selected_appserver_version
285
+ or existing_version
286
+ or installed_version
287
+ )
288
+ is_upgrade = Version(installed_version) > Version(existing_version)
289
+ label = "Upgrade" if is_upgrade else "Downgrade"
290
+ yield Select(
291
+ [
292
+ (f"{label} to {installed_version}", installed_version),
293
+ (f"Keep {existing_version}", existing_version),
294
+ ],
295
+ value=current_selection,
296
+ id="appserver_version_select",
297
+ allow_blank=False,
298
+ compact=True,
299
+ )
300
+ else:
301
+ # Non-editable display of version
302
+ readonly_version = (
303
+ self.form_data.installed_appserver_version
304
+ or self.form_data.existing_llama_deploy_version
305
+ or "unknown"
306
+ )
307
+ yield Static(readonly_version, id="appserver_version_readonly")
308
+
229
309
  # Secrets section
230
310
  yield SecretsWidget(
231
311
  initial_secrets=self.form_data.secrets,
@@ -249,6 +329,13 @@ class DeploymentFormWidget(Widget):
249
329
  # Post message to parent app to handle cancel
250
330
  self.post_message(CancelFormMessage())
251
331
 
332
+ def on_select_changed(self, event: Select.Changed) -> None:
333
+ """Handle version selection changes"""
334
+ if event.select.id == "appserver_version_select" and event.value:
335
+ updated_form = dataclasses.replace(self.resolve_form_data())
336
+ updated_form.selected_appserver_version = str(event.value)
337
+ self.form_data = updated_form
338
+
252
339
  def _save(self) -> None:
253
340
  self.form_data = self.resolve_form_data()
254
341
  if self._validate_form():
@@ -318,6 +405,9 @@ class DeploymentFormWidget(Widget):
318
405
  removed_secrets=self.original_form_data.initial_secrets.difference(
319
406
  updated_prior_secrets
320
407
  ),
408
+ installed_appserver_version=self.form_data.installed_appserver_version,
409
+ existing_llama_deploy_version=self.form_data.existing_llama_deploy_version,
410
+ selected_appserver_version=self.form_data.selected_appserver_version,
321
411
  )
322
412
 
323
413
 
@@ -583,6 +673,8 @@ def _initialize_deployment_data() -> DeploymentForm:
583
673
  if len(secrets) > 0:
584
674
  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
675
 
676
+ installed = get_installed_appserver_version()
677
+
586
678
  form = DeploymentForm(
587
679
  name=name or "",
588
680
  repo_url=repo_url or "",
@@ -591,6 +683,8 @@ def _initialize_deployment_data() -> DeploymentForm:
591
683
  deployment_file_path=config_file_path or "",
592
684
  warnings=warnings,
593
685
  env_info_messages=env_info_message,
686
+ installed_appserver_version=installed,
687
+ selected_appserver_version=installed,
594
688
  )
595
689
  return form
596
690
 
@@ -39,6 +39,12 @@ class DeploymentHelpWidget(Widget):
39
39
  [b]Config File[/b]
40
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
41
 
42
+ [b]Personal Access Token[/b]
43
+ A personal access token to access the git repository. Can be used instead of the github integration.
44
+
45
+ [b]Appserver Version[/b]
46
+ The version of the appserver to deploy. Affects features and functionality. By default this is set to the current llamactl version, and then retained until manually upgraded.
47
+
42
48
  [b]Secrets[/b]
43
49
  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
50
 
@@ -60,6 +60,10 @@ Input.disabled {
60
60
  /* UTILITIES, MESSAGES & NOTIFICATIONS */
61
61
  /* =============================================== */
62
62
 
63
+ .mb-0 {
64
+ margin-bottom: 0 !important;
65
+ }
66
+
63
67
  .mb-1 {
64
68
  margin-bottom: 1;
65
69
  }
@@ -68,6 +72,10 @@ Input.disabled {
68
72
  margin-bottom: 2;
69
73
  }
70
74
 
75
+ .mt-0 {
76
+ margin-top: 0;
77
+ }
78
+
71
79
  .mt-1 {
72
80
  margin-top: 1;
73
81
  }
@@ -76,6 +84,10 @@ Input.disabled {
76
84
  margin-top: 2;
77
85
  }
78
86
 
87
+ .m-0 {
88
+ margin: 0;
89
+ }
90
+
79
91
  .m-1 {
80
92
  margin: 1;
81
93
  }
@@ -102,6 +114,7 @@ Input.disabled {
102
114
  padding: 0 0 0 1
103
115
  }
104
116
 
117
+
105
118
  .error-message {
106
119
  color: $text-error;
107
120
  background: $error-muted;
@@ -201,3 +214,102 @@ Button.secondary {
201
214
  align: left middle;
202
215
  }
203
216
 
217
+
218
+ .top-left {
219
+ align: left top;
220
+ }
221
+ .top-center {
222
+ align: left middle;
223
+ }
224
+ .top-right {
225
+ align: right top;
226
+ }
227
+ .middle-left {
228
+ align: left middle;
229
+ }
230
+ .middle-center {
231
+ align: center middle;
232
+ }
233
+ .middle-right {
234
+ align: right middle;
235
+ }
236
+ .bottom-left {
237
+ align: left bottom;
238
+ }
239
+ .bottom-center {
240
+ align: right bottom;
241
+ }
242
+ .bottom-right {
243
+ align: right bottom;
244
+ }
245
+
246
+
247
+ .text-right {
248
+ text-align: right;
249
+ }
250
+ .text-left {
251
+ text-align: left;
252
+ }
253
+ .text-center {
254
+ text-align: center;
255
+ }
256
+ .text-justify {
257
+ text-align: justify;
258
+ }
259
+
260
+ .background-red {
261
+ background: red;
262
+ }
263
+
264
+
265
+ .w-1 {
266
+ width: 1;
267
+ }
268
+
269
+ .w-2 {
270
+ width: 2;
271
+ }
272
+
273
+ .w-3 {
274
+ width: 3;
275
+ }
276
+
277
+ .w-1fr {
278
+ width: 1fr;
279
+ }
280
+
281
+ .w-100 {
282
+ width: 100%;
283
+ }
284
+
285
+ .w-2fr {
286
+ width: 2fr;
287
+ }
288
+
289
+ .h-1 {
290
+ height: 1;
291
+ }
292
+
293
+ .h-2 {
294
+ height: 2;
295
+ }
296
+
297
+ .h-3 {
298
+ height: 3;
299
+ }
300
+
301
+ .h-1fr {
302
+ height: 1fr;
303
+ }
304
+
305
+ .h-100 {
306
+ height: 100%;
307
+ }
308
+
309
+ .h-2fr {
310
+ height: 2fr;
311
+ }
312
+
313
+ .h-100 {
314
+ height: 100%;
315
+ }
@@ -0,0 +1,11 @@
1
+ """Version utilities shared across CLI components."""
2
+
3
+ from importlib import metadata as importlib_metadata
4
+
5
+
6
+ def get_installed_appserver_version() -> str | None:
7
+ """Return the installed version of `llama-deploy-appserver`, if available."""
8
+ try:
9
+ return importlib_metadata.version("llama-deploy-appserver")
10
+ except Exception:
11
+ return None
File without changes