llamactl 0.3.19__py3-none-any.whl → 0.3.20__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.
@@ -2,6 +2,7 @@ import warnings
2
2
 
3
3
  from llama_deploy.cli.commands.auth import auth
4
4
  from llama_deploy.cli.commands.deployment import deployments
5
+ from llama_deploy.cli.commands.dev import dev
5
6
  from llama_deploy.cli.commands.env import env_group
6
7
  from llama_deploy.cli.commands.init import init
7
8
  from llama_deploy.cli.commands.pkg import pkg
@@ -23,7 +24,16 @@ def main() -> None:
23
24
  app()
24
25
 
25
26
 
26
- __all__ = ["app", "deployments", "auth", "serve", "init", "env_group", "pkg"]
27
+ __all__ = [
28
+ "app",
29
+ "deployments",
30
+ "auth",
31
+ "serve",
32
+ "init",
33
+ "env_group",
34
+ "pkg",
35
+ "dev",
36
+ ]
27
37
 
28
38
 
29
39
  if __name__ == "__main__":
@@ -119,6 +119,7 @@ def get_deployment(deployment_id: str | None, interactive: bool) -> None:
119
119
  table.add_row("Repository", Text(deployment.repo_url))
120
120
  table.add_row("Deployment File", Text(deployment.deployment_file_path))
121
121
  table.add_row("Git Ref", Text(deployment.git_ref or "-"))
122
+ table.add_row("Last Deployed Commit", Text((deployment.git_sha or "-")[:7]))
122
123
 
123
124
  apiserver_url = deployment.apiserver_url
124
125
  table.add_row(
@@ -0,0 +1,184 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import subprocess
5
+ from pathlib import Path
6
+ from typing import Iterable
7
+
8
+ import click
9
+ from click.exceptions import Abort, Exit
10
+ from llama_deploy.appserver.deployment_config_parser import get_deployment_config
11
+ from llama_deploy.appserver.settings import configure_settings, settings
12
+ from llama_deploy.appserver.workflow_loader import (
13
+ load_environment_variables,
14
+ load_workflows,
15
+ parse_environment_variables,
16
+ validate_required_env_vars,
17
+ )
18
+ from llama_deploy.cli.commands.aliased_group import AliasedGroup
19
+ from llama_deploy.cli.commands.serve import (
20
+ _maybe_inject_llama_cloud_credentials,
21
+ _print_connection_summary,
22
+ )
23
+ from llama_deploy.cli.commands.serve import (
24
+ serve as serve_command,
25
+ )
26
+ from llama_deploy.cli.options import global_options, interactive_option
27
+ from llama_deploy.core.config import DEFAULT_DEPLOYMENT_FILE_PATH
28
+ from llama_deploy.core.deployment_config import DeploymentConfig
29
+ from rich import print as rprint
30
+
31
+ from ..app import app
32
+
33
+
34
+ @app.group(
35
+ name="dev",
36
+ help="Development utilities for llama-deploy projects",
37
+ cls=AliasedGroup,
38
+ no_args_is_help=True,
39
+ )
40
+ @global_options
41
+ def dev() -> None:
42
+ """Collection of development commands."""
43
+
44
+
45
+ dev.add_command(serve_command, name="serve")
46
+
47
+
48
+ @dev.command(
49
+ "validate",
50
+ help="Load configured workflows and run their validation hooks",
51
+ )
52
+ @click.argument(
53
+ "deployment_file",
54
+ required=False,
55
+ default=DEFAULT_DEPLOYMENT_FILE_PATH,
56
+ type=click.Path(dir_okay=True, resolve_path=True, path_type=Path),
57
+ )
58
+ @interactive_option
59
+ @global_options
60
+ def validate_command(deployment_file: Path, interactive: bool) -> None:
61
+ """Validate workflows defined in the deployment configuration."""
62
+ config_dir = _ensure_project_layout(
63
+ deployment_file, command_name="llamactl dev validate"
64
+ )
65
+ try:
66
+ config, _ = _prepare_environment(
67
+ deployment_file, interactive, require_cloud=False
68
+ )
69
+ workflows = load_workflows(config)
70
+ errors: list[tuple[str, Exception]] = _run_validations(workflows.values())
71
+
72
+ if errors:
73
+ for idx, (name, error) in enumerate(errors, start=1):
74
+ rprint(f"[red]Validation {idx} failed for '{name}': {error}[/red]")
75
+ raise click.ClickException("Workflow validation failed")
76
+
77
+ _print_connection_summary()
78
+ rprint(
79
+ f"[green]Validated workflows in {config_dir} successfully ({len(workflows)} found).[/green]"
80
+ )
81
+ except (Exit, Abort):
82
+ raise
83
+ except click.ClickException:
84
+ raise
85
+ except Exception as exc: # pragma: no cover - unexpected errors reported to user
86
+ rprint(f"[red]Failed to validate workflows: {exc}[/red]")
87
+ raise click.Abort()
88
+
89
+
90
+ @dev.command(
91
+ "run",
92
+ help=(
93
+ "Load env configuration and execute a command. Use '--' before the command "
94
+ "to avoid option parsing."
95
+ ),
96
+ context_settings={"ignore_unknown_options": True, "allow_extra_args": True},
97
+ )
98
+ @global_options
99
+ @click.option(
100
+ "deployment_file",
101
+ "--deployment-file",
102
+ default=DEFAULT_DEPLOYMENT_FILE_PATH,
103
+ type=click.Path(dir_okay=True, resolve_path=True, path_type=Path),
104
+ help="The deployment file to use for the command",
105
+ )
106
+ @interactive_option
107
+ @click.argument("cmd", nargs=-1, type=click.UNPROCESSED)
108
+ def run_command(deployment_file: Path, interactive: bool, cmd: tuple[str, ...]) -> None: # type: ignore
109
+ """Execute COMMAND with deployment environment variables applied."""
110
+ _ensure_project_layout(deployment_file, command_name="llamactl dev run")
111
+ if not cmd:
112
+ raise click.ClickException(
113
+ "No command provided. Use '--' before the command arguments if needed."
114
+ )
115
+
116
+ try:
117
+ config, config_parent = _prepare_environment(
118
+ deployment_file, interactive, require_cloud=False
119
+ )
120
+ env_overrides = parse_environment_variables(config, config_parent)
121
+ env = os.environ.copy()
122
+ env.update({k: v for k, v in env_overrides.items() if v is not None})
123
+
124
+ _print_connection_summary()
125
+ result = subprocess.run(cmd, env=env, check=False)
126
+ if result.returncode != 0:
127
+ raise SystemExit(result.returncode)
128
+ except (Exit, Abort, SystemExit, click.ClickException):
129
+ raise
130
+ except FileNotFoundError as exc:
131
+ rprint(f"[red]Command not found: {exc.filename}[/red]")
132
+ raise click.Abort()
133
+ except Exception as exc: # pragma: no cover - unexpected errors reported to user
134
+ rprint(f"[red]Failed to run command: {exc}[/red]")
135
+ raise click.Abort()
136
+
137
+
138
+ def _ensure_project_layout(deployment_file: Path, *, command_name: str) -> Path:
139
+ if not deployment_file.exists():
140
+ rprint(f"[red]Deployment file '{deployment_file}' not found[/red]")
141
+ raise click.Abort()
142
+
143
+ config_dir = deployment_file if deployment_file.is_dir() else deployment_file.parent
144
+ if not (config_dir / "pyproject.toml").exists():
145
+ rprint(
146
+ "[red]No pyproject.toml found at[/red] "
147
+ f"[bold]{config_dir}[/bold].\n"
148
+ f"Add a pyproject.toml to your project and re-run '{command_name}'."
149
+ )
150
+ raise click.Abort()
151
+ return config_dir
152
+
153
+
154
+ def _prepare_environment(
155
+ deployment_file: Path, interactive: bool, *, require_cloud: bool
156
+ ) -> tuple[DeploymentConfig, Path]:
157
+ _maybe_inject_llama_cloud_credentials(
158
+ deployment_file, interactive, require_cloud=require_cloud
159
+ )
160
+ configure_settings(
161
+ deployment_file_path=deployment_file,
162
+ app_root=Path.cwd(),
163
+ )
164
+ config = get_deployment_config()
165
+ config_parent = settings.resolved_config_parent
166
+ load_environment_variables(config, config_parent)
167
+ validate_required_env_vars(config)
168
+ return config, config_parent
169
+
170
+
171
+ def _run_validations(workflows: Iterable[object]) -> list[tuple[str, Exception]]:
172
+ errors: list[tuple[str, Exception]] = []
173
+ for workflow in workflows:
174
+ workflow_name = getattr(workflow, "name", "workflow")
175
+ try:
176
+ validate_method = getattr(workflow, "_validate", None)
177
+ if callable(validate_method):
178
+ validate_method()
179
+ except Exception as exc:
180
+ errors.append((str(workflow_name), exc))
181
+ return errors
182
+
183
+
184
+ __all__ = ["dev", "validate_command", "run_command"]
@@ -279,41 +279,64 @@ def _create(
279
279
  if agents_path.exists() and not claude_path.exists():
280
280
  claude_path.symlink_to("AGENTS.md")
281
281
 
282
- # Initialize a git repo (best-effort). If anything fails, show a friendly note and continue.
282
+ # Initialize a git repo unless we're already inside one.
283
283
  if has_git:
284
+ # Detect whether the target directory is already inside a git work tree
285
+ inside_existing_repo = False
284
286
  try:
285
- subprocess.run(["git", "init"], check=True, capture_output=True)
286
- subprocess.run(["git", "add", "."], check=True, capture_output=True)
287
- subprocess.run(
288
- ["git", "commit", "-m", "Initial commit"],
287
+ result = subprocess.run(
288
+ ["git", "rev-parse", "--is-inside-work-tree"],
289
289
  check=True,
290
290
  capture_output=True,
291
+ text=True,
291
292
  )
292
- git_initialized = True
293
- except (subprocess.CalledProcessError, FileNotFoundError) as e:
294
- # Extract a short error message if present
295
- err_msg = ""
296
- if isinstance(e, subprocess.CalledProcessError):
297
- stderr_bytes = e.stderr or b""
298
- if isinstance(stderr_bytes, (bytes, bytearray)):
299
- try:
300
- stderr_text = stderr_bytes.decode("utf-8", "ignore")
301
- except Exception:
302
- stderr_text = ""
303
- else:
304
- stderr_text = str(stderr_bytes)
305
- if stderr_text.strip():
306
- err_msg = stderr_text.strip().split("\n")[-1]
307
- elif isinstance(e, FileNotFoundError):
308
- err_msg = "git executable not found"
293
+ inside_existing_repo = result.stdout.strip().lower() == "true"
294
+ except (subprocess.CalledProcessError, FileNotFoundError):
295
+ inside_existing_repo = False
309
296
 
310
- rprint("")
311
- rprint("⚠️ [bold]Skipping git initialization due to an error.[/]")
312
- if err_msg:
313
- rprint(f" {err_msg}")
314
- rprint(" You can initialize it manually:")
315
- rprint(" git init && git add . && git commit -m 'Initial commit'")
316
- rprint("")
297
+ if inside_existing_repo:
298
+ # Do not create a nested repo; user likely wants this within the parent repo
299
+ rprint(
300
+ "[yellow]Detected an existing Git repository in a parent directory; skipping git initialization for this app.[/]"
301
+ )
302
+ # Treat as initialized for purposes of what instructions to show later
303
+ git_initialized = True
304
+ else:
305
+ try:
306
+ subprocess.run(["git", "init"], check=True, capture_output=True)
307
+ subprocess.run(["git", "add", "."], check=True, capture_output=True)
308
+ subprocess.run(
309
+ ["git", "commit", "-m", "Initial commit"],
310
+ check=True,
311
+ capture_output=True,
312
+ )
313
+ git_initialized = True
314
+ except (subprocess.CalledProcessError, FileNotFoundError) as e:
315
+ # Extract a short error message if present
316
+ err_msg = ""
317
+ if isinstance(e, subprocess.CalledProcessError):
318
+ stderr_bytes = e.stderr or b""
319
+ if isinstance(stderr_bytes, (bytes, bytearray)):
320
+ try:
321
+ stderr_text = stderr_bytes.decode("utf-8", "ignore")
322
+ except Exception:
323
+ stderr_text = ""
324
+ else:
325
+ stderr_text = str(stderr_bytes)
326
+ if stderr_text.strip():
327
+ err_msg = stderr_text.strip().split("\n")[-1]
328
+ elif isinstance(e, FileNotFoundError):
329
+ err_msg = "git executable not found"
330
+
331
+ rprint("")
332
+ rprint("⚠️ [bold]Skipping git initialization due to an error.[/]")
333
+ if err_msg:
334
+ rprint(f" {err_msg}")
335
+ rprint(" You can initialize it manually:")
336
+ rprint(
337
+ " git init && git add . && git commit -m 'Initial commit'"
338
+ )
339
+ rprint("")
317
340
  finally:
318
341
  os.chdir(original_cwd)
319
342
 
@@ -63,6 +63,7 @@ class DeploymentForm:
63
63
  id: str | None = None
64
64
  repo_url: str = ""
65
65
  git_ref: str = "main"
66
+ git_sha: str | None = None
66
67
  deployment_file_path: str = ""
67
68
  personal_access_token: str = ""
68
69
  # indicates if the deployment has a personal access token (value is unknown)
@@ -100,6 +101,7 @@ class DeploymentForm:
100
101
  id=deployment.id,
101
102
  repo_url=deployment.repo_url,
102
103
  git_ref=deployment.git_ref or "main",
104
+ git_sha=deployment.git_sha or "-",
103
105
  deployment_file_path=deployment.deployment_file_path,
104
106
  personal_access_token="", # Always start empty for security
105
107
  has_existing_pat=deployment.has_personal_access_token,
@@ -242,6 +244,15 @@ class DeploymentFormWidget(Widget):
242
244
  compact=True,
243
245
  )
244
246
 
247
+ yield Label("Last Deployed Commit:", classes="form-label", shrink=True)
248
+ yield Input(
249
+ value=(self.form_data.git_sha or "-")[:7],
250
+ placeholder="-",
251
+ id="git_sha",
252
+ compact=True,
253
+ disabled=True,
254
+ )
255
+
245
256
  yield Static(classes="full-width")
246
257
  yield Static(
247
258
  Content.from_markup("[italic]Advanced[/]"),
@@ -133,6 +133,16 @@ class DeploymentMonitorWidget(Widget):
133
133
  compact=True,
134
134
  variant="default",
135
135
  )
136
+
137
+ with HorizontalGroup(classes=""):
138
+ yield Static(" Last Deployed Commit: ", classes="deployment-link-label")
139
+ yield Button(
140
+ "",
141
+ id="last_deployed_commit",
142
+ classes="deployment-link",
143
+ compact=True,
144
+ variant="default",
145
+ )
136
146
  yield Static("", classes="error-message", id="error_message")
137
147
 
138
148
  # Single-line status bar with colored icon and deployment ID
@@ -168,6 +178,13 @@ class DeploymentMonitorWidget(Widget):
168
178
  self.app.copy_to_clipboard(txt)
169
179
  elif event.button.id == "deployment_link_button":
170
180
  self.action_open_url()
181
+ elif event.button.id == "last_deployed_commit":
182
+ if self.deployment is not None:
183
+ self.action_open_url(
184
+ url=self.deployment.repo_url
185
+ + "/commit/"
186
+ + (self.deployment.git_sha or "")
187
+ )
171
188
 
172
189
  async def _fetch_deployment(self) -> None:
173
190
  try:
@@ -300,11 +317,15 @@ class DeploymentMonitorWidget(Widget):
300
317
  return "●", red
301
318
  return "●", gray
302
319
 
303
- def action_open_url(self) -> None:
304
- if not self.deployment or not self.deployment.apiserver_url:
305
- return
306
- logger.debug(f"Opening URL: {self.deployment.apiserver_url}")
307
- webbrowser.open(str(self.deployment.apiserver_url))
320
+ def action_open_url(self, url: str | None = None) -> None:
321
+ if not url:
322
+ if not self.deployment or not self.deployment.apiserver_url:
323
+ return
324
+ logger.debug(f"Opening URL: {self.deployment.apiserver_url}")
325
+ webbrowser.open(str(self.deployment.apiserver_url))
326
+ else:
327
+ logger.debug(f"Opening URL: {url}")
328
+ webbrowser.open(str(url))
308
329
 
309
330
  def _render_status_line(self) -> Text:
310
331
  phase = self.deployment.status if self.deployment else "Unknown"
@@ -364,10 +385,12 @@ class DeploymentMonitorWidget(Widget):
364
385
  ev_widget = self.query_one("#last_event_status", Static)
365
386
  ev_details_widget = self.query_one("#last_event_details", Static)
366
387
  deployment_link_button = self.query_one("#deployment_link_button", Button)
388
+ last_commit_button = self.query_one("#last_deployed_commit", Button)
367
389
  widget.update(self._render_status_line())
368
390
  deployment_link_button.label = (
369
391
  f"{str(self.deployment.apiserver_url or '') if self.deployment else ''}"
370
392
  )
393
+ last_commit_button.label = f"{(str(self.deployment.git_sha or '-'))[:7]}"
371
394
  # Update last event line
372
395
  ev_widget.update(self._render_last_event_status())
373
396
  ev_details_widget.update(self._render_last_event_details())
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: llamactl
3
- Version: 0.3.19
3
+ Version: 0.3.20
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.19,<0.4.0
9
- Requires-Dist: llama-deploy-appserver>=0.3.19,<0.4.0
8
+ Requires-Dist: llama-deploy-core[client]>=0.3.20,<0.4.0
9
+ Requires-Dist: llama-deploy-appserver>=0.3.20,<0.4.0
10
10
  Requires-Dist: vibe-llama-core>=0.1.0
11
11
  Requires-Dist: rich>=13.0.0
12
12
  Requires-Dist: questionary>=2.0.0
@@ -1,12 +1,13 @@
1
- llama_deploy/cli/__init__.py,sha256=ae22d4cdf686aeef367d80d71938c62d7570d9bddd454c44fa78b3354f25d4f5,834
1
+ llama_deploy/cli/__init__.py,sha256=7c13390f1b6aa0e0970b0e7c661c6ae97e3e6ef266129c0daaa7acc5732388a6,922
2
2
  llama_deploy/cli/app.py,sha256=9170e4f506c482522bd745eb1cdb700a198cfcfd7204c168c94e5ee2b6b43ffa,2199
3
3
  llama_deploy/cli/auth/client.py,sha256=3ebd2526f65f8d576e17d304df1b8a163d07586b88b5628cb36c9fa487a23ef6,11841
4
4
  llama_deploy/cli/client.py,sha256=f4053b5183224cff55c1393e78887d1af2597219135379a851b742c676adc154,1727
5
5
  llama_deploy/cli/commands/aliased_group.py,sha256=101fe7733802dfb448198331818123184523b54cb80a27f166d4ff7010a76e49,1097
6
6
  llama_deploy/cli/commands/auth.py,sha256=48c4cc786e8c4e0fb8c0caaba690cef359cddac9b7fbb0b88505111323c07667,24754
7
- llama_deploy/cli/commands/deployment.py,sha256=abea89e94ed978821a998453792d127605831ba23108df776873852ddd19f97e,14526
7
+ llama_deploy/cli/commands/deployment.py,sha256=2571aa7f220930adc47f17e9aa33147c13156f81acfb911f3455f3800c742720,14611
8
+ llama_deploy/cli/commands/dev.py,sha256=d1ca964d181c71290c6e63b92977a108cb3e707bb5e95c088b9306a51ac699e8,6354
8
9
  llama_deploy/cli/commands/env.py,sha256=36cb1b0abb9e3d1c5546d3e8a3c4c7839c4d6c2abf75763e39efb08376b3eae9,6808
9
- llama_deploy/cli/commands/init.py,sha256=348ec90fb54a6774062b109afe8cc9ac7f3c59144f941fd5697001ea52d5b946,14981
10
+ llama_deploy/cli/commands/init.py,sha256=7254c9ad1824dc546fe33f8592034ef044700ffbaf843b9ee7dc49495ca9166e,16137
10
11
  llama_deploy/cli/commands/pkg.py,sha256=31049a8266fba71a45920187ef983988bb5ba3b9ad81ab4b7bca6042a071a810,4068
11
12
  llama_deploy/cli/commands/serve.py,sha256=e1e91f17e13dce31ebadb4a1b91cda9e2e3eeb45f89f1db0ae3fadd879e8a2ab,12871
12
13
  llama_deploy/cli/config/_config.py,sha256=654a4b6d06542e3503edab7023fc1c3148de510b3e3f6194e28cd4bd3e7c029a,14230
@@ -28,9 +29,9 @@ llama_deploy/cli/pkg/options.py,sha256=540c619a2a11f72161b8e41002446cf9d3f62de60
28
29
  llama_deploy/cli/pkg/utils.py,sha256=b25348ac0f9ddc984ef98edc930aef0648ed182437e660cc60b0daf56172b171,1197
29
30
  llama_deploy/cli/py.typed,sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855,0
30
31
  llama_deploy/cli/styles.py,sha256=15901fb567b0d10470f56a06d863819c4ed00a9f90b2a8c46b4bc2fb1dbdf6c3,307
31
- llama_deploy/cli/textual/deployment_form.py,sha256=d30779c225e5388671d84982a819e78ed0b3a0c3b9f7868483cfc3f3f3212f28,29377
32
+ llama_deploy/cli/textual/deployment_form.py,sha256=a7f6d8fffe2ae066241a397bf920c109aed7503faf5bcbf4c8b5bb46ac27cb6f,29762
32
33
  llama_deploy/cli/textual/deployment_help.py,sha256=991d8cdcc61ae0cf79ddd27715db5452c9902d343ce20775f8651252056eca77,2859
33
- llama_deploy/cli/textual/deployment_monitor.py,sha256=c39abf72b76a46de80403c6986b75b18eeca777a29f62cc847a193ca264ff4ea,16774
34
+ llama_deploy/cli/textual/deployment_monitor.py,sha256=ec638b1450821c40a5db557be649b669f9a015fdad7b6f90942df64a446ab117,17726
34
35
  llama_deploy/cli/textual/git_validation.py,sha256=94c95b61d0cbc490566a406b4886c9c12e1d1793dc14038a5be37119223c9568,13419
35
36
  llama_deploy/cli/textual/github_callback_server.py,sha256=3111cc45b3ff2632255a37e4472c85084670c94bcea25ec428f06b0761dd27bf,7584
36
37
  llama_deploy/cli/textual/llama_loader.py,sha256=33cb32a46dd40bcf889c553e44f2672c410e26bd1d4b17aa6cca6d0a5d59c2c4,1468
@@ -39,7 +40,7 @@ llama_deploy/cli/textual/styles.tcss,sha256=2536f52ea1a654ae1f8990a25d45c845cb31
39
40
  llama_deploy/cli/utils/env_inject.py,sha256=01911758bcc3cf22aad0db0d1ade56aece48d6ad6bdb7186ea213337c67f5a89,688
40
41
  llama_deploy/cli/utils/redact.py,sha256=1e768d76b4a6708230c34f7ce8a5a82ab52795bb3d6ab0387071ab4e8d7e7934,863
41
42
  llama_deploy/cli/utils/version.py,sha256=bf01a6dda948b868cc08c93701ed44cd36b487402404af8451d4c0996a2edb31,364
42
- llamactl-0.3.19.dist-info/WHEEL,sha256=66530aef82d5020ef5af27ae0123c71abb9261377c5bc519376c671346b12918,79
43
- llamactl-0.3.19.dist-info/entry_points.txt,sha256=b67e1eb64305058751a651a80f2d2268b5f7046732268421e796f64d4697f83c,52
44
- llamactl-0.3.19.dist-info/METADATA,sha256=21a1074e9ef4081337b502bbc53fcd68da625680ae427dc10c8e65543414f5c0,3217
45
- llamactl-0.3.19.dist-info/RECORD,,
43
+ llamactl-0.3.20.dist-info/WHEEL,sha256=66530aef82d5020ef5af27ae0123c71abb9261377c5bc519376c671346b12918,79
44
+ llamactl-0.3.20.dist-info/entry_points.txt,sha256=b67e1eb64305058751a651a80f2d2268b5f7046732268421e796f64d4697f83c,52
45
+ llamactl-0.3.20.dist-info/METADATA,sha256=e95a71bcdec2df10da451a5299d2b9974d81d20daf50ecef4299c9e766fc5ec6,3217
46
+ llamactl-0.3.20.dist-info/RECORD,,