llamactl 0.3.11__tar.gz → 0.3.12__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.11 → llamactl-0.3.12}/PKG-INFO +3 -4
  2. {llamactl-0.3.11 → llamactl-0.3.12}/pyproject.toml +3 -4
  3. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/auth/client.py +6 -2
  4. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/commands/init.py +128 -42
  5. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/commands/serve.py +9 -1
  6. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/options.py +47 -12
  7. {llamactl-0.3.11 → llamactl-0.3.12}/README.md +0 -0
  8. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/__init__.py +0 -0
  9. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/app.py +0 -0
  10. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/client.py +0 -0
  11. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/commands/aliased_group.py +0 -0
  12. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/commands/auth.py +0 -0
  13. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/commands/deployment.py +0 -0
  14. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/commands/env.py +0 -0
  15. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/config/_config.py +0 -0
  16. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/config/_migrations.py +0 -0
  17. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/config/auth_service.py +0 -0
  18. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/config/env_service.py +0 -0
  19. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/config/migrations/0001_init.sql +0 -0
  20. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/config/migrations/0002_add_auth_fields.sql +0 -0
  21. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/config/migrations/__init__.py +0 -0
  22. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/config/schema.py +0 -0
  23. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/debug.py +0 -0
  24. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/env.py +0 -0
  25. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/interactive_prompts/session_utils.py +0 -0
  26. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/interactive_prompts/utils.py +0 -0
  27. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/py.typed +0 -0
  28. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/styles.py +0 -0
  29. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/textual/deployment_form.py +0 -0
  30. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/textual/deployment_help.py +0 -0
  31. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/textual/deployment_monitor.py +0 -0
  32. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/textual/git_validation.py +0 -0
  33. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/textual/github_callback_server.py +0 -0
  34. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/textual/llama_loader.py +0 -0
  35. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/textual/secrets_form.py +0 -0
  36. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/textual/styles.tcss +0 -0
  37. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/utils/env_inject.py +0 -0
  38. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/utils/redact.py +0 -0
  39. {llamactl-0.3.11 → llamactl-0.3.12}/src/llama_deploy/cli/utils/version.py +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: llamactl
3
- Version: 0.3.11
3
+ Version: 0.3.12
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.11,<0.4.0
9
- Requires-Dist: llama-deploy-appserver>=0.3.11,<0.4.0
8
+ Requires-Dist: llama-deploy-core[client]>=0.3.12,<0.4.0
9
+ Requires-Dist: llama-deploy-appserver>=0.3.12,<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
@@ -17,7 +17,6 @@ Requires-Dist: textual>=6.0.0
17
17
  Requires-Dist: aiohttp>=3.12.14
18
18
  Requires-Dist: copier>=9.9.0
19
19
  Requires-Dist: pyjwt[crypto]>=2.10.1
20
- Requires-Dist: vibe-llama>=0.4.4,<0.5.0
21
20
  Requires-Python: >=3.11, <4
22
21
  Description-Content-Type: text/markdown
23
22
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "llamactl"
3
- version = "0.3.11"
3
+ version = "0.3.12"
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.11,<0.4.0",
13
- "llama-deploy-appserver>=0.3.11,<0.4.0",
12
+ "llama-deploy-core[client]>=0.3.12,<0.4.0",
13
+ "llama-deploy-appserver>=0.3.12,<0.4.0",
14
14
  "httpx>=0.24.0,<1.0.0",
15
15
  "rich>=13.0.0",
16
16
  "questionary>=2.0.0",
@@ -21,7 +21,6 @@ dependencies = [
21
21
  "aiohttp>=3.12.14",
22
22
  "copier>=9.9.0",
23
23
  "pyjwt[crypto]>=2.10.1",
24
- "vibe-llama>=0.4.4,<0.5.0",
25
24
  ]
26
25
 
27
26
  [project.scripts]
@@ -9,6 +9,7 @@ import httpx
9
9
  import jwt
10
10
  from jwt.algorithms import RSAAlgorithm # type: ignore[possibly-unbound-import]
11
11
  from llama_deploy.cli.config.schema import DeviceOIDC
12
+ from llama_deploy.core.client.ssl_util import get_httpx_verify_param
12
13
  from pydantic import BaseModel
13
14
 
14
15
  logger = logging.getLogger(__name__)
@@ -57,10 +58,13 @@ class AuthMeResponse(BaseModel):
57
58
  class ClientContextManager(AsyncContextManager):
58
59
  def __init__(self, base_url: str | None, auth: httpx.Auth | None = None) -> None:
59
60
  self.base_url = base_url.rstrip("/") if base_url else None
61
+ verify = get_httpx_verify_param()
60
62
  if self.base_url:
61
- self.client = httpx.AsyncClient(base_url=self.base_url, auth=auth)
63
+ self.client = httpx.AsyncClient(
64
+ base_url=self.base_url, auth=auth, verify=verify
65
+ )
62
66
  else:
63
- self.client = httpx.AsyncClient(auth=auth)
67
+ self.client = httpx.AsyncClient(auth=auth, verify=verify)
64
68
 
65
69
  async def close(self) -> None:
66
70
  try:
@@ -9,12 +9,18 @@ from pathlib import Path
9
9
 
10
10
  import click
11
11
  import copier
12
+ import httpx
12
13
  import questionary
13
14
  from click.exceptions import Exit
14
15
  from llama_deploy.cli.app import app
15
- from llama_deploy.cli.options import global_options
16
+ from llama_deploy.cli.options import (
17
+ global_options,
18
+ interactive_option,
19
+ )
16
20
  from llama_deploy.cli.styles import HEADER_COLOR_HEX
21
+ from llama_deploy.core.client.ssl_util import get_httpx_verify_param
17
22
  from rich import print as rprint
23
+ from rich.text import Text
18
24
 
19
25
 
20
26
  @app.command()
@@ -40,23 +46,24 @@ from rich import print as rprint
40
46
  help="Force overwrite the directory if it exists",
41
47
  )
42
48
  @global_options
49
+ @interactive_option
43
50
  def init(
44
51
  update: bool,
45
52
  template: str | None,
46
53
  dir: Path | None,
47
54
  force: bool,
55
+ interactive: bool,
48
56
  ) -> None:
49
57
  """Create a new app repository from a template"""
50
58
  if update:
51
59
  _update()
52
60
  else:
53
- _create(template, dir, force)
54
-
61
+ _create(template, dir, force, interactive)
55
62
 
56
- def _create(template: str | None, dir: Path | None, force: bool) -> None:
57
- # defer loading to improve cli startup time
58
- from vibe_llama.sdk import VibeLlamaStarter
59
63
 
64
+ def _create(
65
+ template: str | None, dir: Path | None, force: bool, interactive: bool
66
+ ) -> None:
60
67
  @dataclass
61
68
  class TemplateOption:
62
69
  id: str
@@ -165,7 +172,23 @@ def _create(template: str | None, dir: Path | None, force: bool) -> None:
165
172
  ),
166
173
  ]
167
174
 
168
- if template is None:
175
+ # Initialize git repository if git is available
176
+ has_git = False
177
+ git_initialized = False
178
+ try:
179
+ subprocess.run(["git", "--version"], check=True, capture_output=True)
180
+ has_git = True
181
+ except (subprocess.CalledProcessError, FileNotFoundError):
182
+ # git is not available or broken; continue without git
183
+ has_git = False
184
+
185
+ if not has_git:
186
+ rprint(
187
+ "git is required to initialize a template. Make sure you have it installed and available in your PATH."
188
+ )
189
+ raise Exit(1)
190
+
191
+ if template is None and interactive:
169
192
  rprint(
170
193
  "[bold]Select a template to start from.[/bold] Either with javascript frontend UI, or just a python workflow that can be used as an API."
171
194
  )
@@ -191,16 +214,26 @@ def _create(template: str | None, dir: Path | None, force: bool) -> None:
191
214
  ),
192
215
  ).ask()
193
216
  if template is None:
194
- rprint("No template selected")
217
+ options = [o.id for o in ui_options + headless_options]
218
+ rprint(
219
+ Text(
220
+ f"No template selected. Select a template or pass a template name with --template <{'|'.join(options)}>"
221
+ )
222
+ )
195
223
  raise Exit(1)
196
224
  if dir is None:
197
- dir_str = questionary.text(
198
- "Enter the directory to create the new app in", default=template
199
- ).ask()
200
- if not dir_str:
201
- rprint("No directory provided")
202
- raise Exit(1)
203
- dir = Path(dir_str)
225
+ if interactive:
226
+ dir_str = questionary.text(
227
+ "Enter the directory to create the new app in", default=template
228
+ ).ask()
229
+ if dir_str:
230
+ dir = Path(dir_str)
231
+ else:
232
+ return
233
+ else:
234
+ rprint(f"[yellow]No directory provided. Defaulting to {template}[/]")
235
+ dir = Path(template)
236
+
204
237
  resolved_template: TemplateOption | None = next(
205
238
  (o for o in ui_options + headless_options if o.id == template), None
206
239
  )
@@ -208,49 +241,44 @@ def _create(template: str | None, dir: Path | None, force: bool) -> None:
208
241
  rprint(f"Template {template} not found")
209
242
  raise Exit(1)
210
243
  if dir.exists():
211
- is_ok = (
212
- force
213
- or questionary.confirm("Directory exists. Overwrite?", default=False).ask()
244
+ is_ok = force or (
245
+ interactive
246
+ and questionary.confirm("Directory exists. Overwrite?", default=False).ask()
214
247
  )
248
+
215
249
  if not is_ok:
250
+ rprint(
251
+ f"[yellow]Try again with another directory or pass --force to overwrite the existing directory '{str(dir)}'[/]"
252
+ )
216
253
  raise Exit(1)
217
254
  else:
218
255
  shutil.rmtree(dir, ignore_errors=True)
219
256
 
220
257
  copier.run_copy(
221
- resolved_template.source.url,
222
- dir,
223
- quiet=True,
258
+ resolved_template.source.url, dir, quiet=True, defaults=not interactive
224
259
  )
225
- # Initialize git repository if git is available
226
- has_git = False
227
- git_initialized = False
228
- try:
229
- subprocess.run(["git", "--version"], check=True, capture_output=True)
230
- has_git = True
231
- except (subprocess.CalledProcessError, FileNotFoundError):
232
- # git is not available or broken; continue without git
233
- has_git = False
234
260
 
235
261
  # Change to the new directory and initialize git repo
236
262
  original_cwd = Path.cwd()
237
263
  os.chdir(dir)
238
264
 
239
265
  try:
240
- # Dump in a bunch of docs for AI agents
241
-
242
- vibe_llama_starter = VibeLlamaStarter(
243
- agents=["OpenAI Codex CLI"], # AGENTS.md, supported by Cursor,
244
- services=["LlamaDeploy", "LlamaIndex", "llama-index-workflows"]
245
- + (["LlamaCloud Services"] if resolved_template.llama_cloud else []),
266
+ # Dump in a bunch of docs for AI agents (best-effort)
267
+ docs_downloaded = asyncio.run(
268
+ _download_and_write_agents_md(
269
+ include_llama_cloud=resolved_template.llama_cloud
270
+ )
246
271
  )
247
- asyncio.run(vibe_llama_starter.write_instructions(overwrite=True))
248
272
  # Create symlink for Claude.md to point to AGENTS.md
249
- for alternate in ["CLAUDE.md", "GEMINI.md"]: # don't support AGENTS.md (yet?)
250
- claude_path = Path(alternate) # not supported yet
251
- agents_path = Path("AGENTS.md")
252
- if agents_path.exists() and not claude_path.exists():
253
- claude_path.symlink_to("AGENTS.md")
273
+ if docs_downloaded:
274
+ for alternate in [
275
+ "CLAUDE.md",
276
+ "GEMINI.md",
277
+ ]: # don't support AGENTS.md (yet?)
278
+ claude_path = Path(alternate) # not supported yet
279
+ agents_path = Path("AGENTS.md")
280
+ if agents_path.exists() and not claude_path.exists():
281
+ claude_path.symlink_to("AGENTS.md")
254
282
 
255
283
  # Initialize a git repo (best-effort). If anything fails, show a friendly note and continue.
256
284
  if has_git:
@@ -369,3 +397,61 @@ def _update():
369
397
  except (subprocess.CalledProcessError, FileNotFoundError):
370
398
  # Git not available or not in a git repo - continue silently
371
399
  pass
400
+
401
+
402
+ async def _download_and_write_agents_md(include_llama_cloud: bool) -> bool:
403
+ """Fetch a small set of reference docs and write AGENTS.md.
404
+
405
+ Replaces the previous vibe-llama usage with direct HTTP downloads.
406
+
407
+ Returns True if any documentation was fetched, False otherwise.
408
+ """
409
+ BASE_URL = "https://raw.githubusercontent.com/run-llama/vibe-llama/main"
410
+
411
+ services: dict[str, str] = {
412
+ "LlamaIndex": f"{BASE_URL}/documentation/llamaindex.md",
413
+ "LlamaCloud Services": f"{BASE_URL}/documentation/llamacloud.md",
414
+ "llama-index-workflows": f"{BASE_URL}/documentation/llama-index-workflows.md",
415
+ "LlamaDeploy": f"{BASE_URL}/documentation/llamadeploy.md",
416
+ }
417
+
418
+ selected_services: list[str] = [
419
+ "LlamaDeploy",
420
+ "LlamaIndex",
421
+ "llama-index-workflows",
422
+ ]
423
+ if include_llama_cloud:
424
+ selected_services.append("LlamaCloud Services")
425
+
426
+ urls: list[str] = [(s, u) for s in selected_services if (u := services.get(s))]
427
+
428
+ contents: list[str] = []
429
+ timeout = httpx.Timeout(5.0)
430
+ async with httpx.AsyncClient(
431
+ timeout=timeout, verify=get_httpx_verify_param()
432
+ ) as client:
433
+
434
+ async def get_docs(service: str, url: str) -> str | None:
435
+ try:
436
+ resp = await client.get(url)
437
+ resp.raise_for_status()
438
+ text = resp.text.strip()
439
+ if text:
440
+ return text
441
+ except Exception:
442
+ # best-effort: skip failures
443
+ rprint(
444
+ f"[yellow]Failed to fetch documentation for {service}, skipping[/]"
445
+ )
446
+ return None
447
+
448
+ results = await asyncio.gather(
449
+ *[get_docs(service, url) for service, url in urls]
450
+ )
451
+ contents = [r for r in results if r is not None]
452
+
453
+ if contents:
454
+ agents_md = "\n\n---\n\n".join(contents) + "\n"
455
+ Path("AGENTS.md").write_text(agents_md, encoding="utf-8")
456
+
457
+ return bool(contents)
@@ -10,7 +10,7 @@ from click.exceptions import Abort, Exit
10
10
  from llama_deploy.cli.commands.auth import validate_authenticated_profile
11
11
  from llama_deploy.cli.config.env_service import service
12
12
  from llama_deploy.cli.config.schema import Auth
13
- from llama_deploy.cli.options import interactive_option
13
+ from llama_deploy.cli.options import global_options, interactive_option
14
14
  from llama_deploy.cli.styles import WARNING
15
15
  from llama_deploy.cli.utils.redact import redact_api_key
16
16
  from llama_deploy.core.client.manage_client import ControlPlaneClient
@@ -72,7 +72,13 @@ logger = logging.getLogger(__name__)
72
72
  type=click.Path(dir_okay=True, resolve_path=True, path_type=Path),
73
73
  help="The path to the sqlite database to use for the workflow server if using local persistence",
74
74
  )
75
+ @click.option(
76
+ "--host",
77
+ type=str,
78
+ help="The host to run the API server on. Default is 127.0.0.1. Use 0.0.0.0 to allow remote access.",
79
+ )
75
80
  @interactive_option
81
+ @global_options
76
82
  def serve(
77
83
  deployment_file: Path,
78
84
  no_install: bool,
@@ -85,6 +91,7 @@ def serve(
85
91
  log_format: str | None = None,
86
92
  persistence: Literal["memory", "local", "cloud"] | None = None,
87
93
  local_persistence_path: Path | None = None,
94
+ host: str | None = None,
88
95
  interactive: bool = False,
89
96
  ) -> None:
90
97
  """Run llama_deploy API Server in the foreground. Reads the deployment configuration from the current directory. Can optionally specify a deployment file path."""
@@ -141,6 +148,7 @@ def serve(
141
148
  cloud_persistence_name=f"_public:serve_workflows_{deployment_config.name}"
142
149
  if persistence == "cloud"
143
150
  else None,
151
+ host=host,
144
152
  )
145
153
 
146
154
  except (Exit, Abort):
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ import os
2
3
  from typing import Callable, ParamSpec, TypeVar
3
4
 
4
5
  import click
@@ -13,6 +14,52 @@ R = TypeVar("R")
13
14
  def global_options(f: Callable[P, R]) -> Callable[P, R]:
14
15
  """Common decorator to add global options to command groups"""
15
16
 
17
+ return native_tls_option(file_logging(f))
18
+
19
+
20
+ def interactive_option(f: Callable[P, R]) -> Callable[P, R]:
21
+ """Add an interactive option to the command"""
22
+
23
+ default = is_interactive_session()
24
+ return click.option(
25
+ "--interactive/--no-interactive",
26
+ help="Run in interactive mode. If not provided, will default to the current session's interactive state.",
27
+ is_flag=True,
28
+ default=default,
29
+ )(f)
30
+
31
+
32
+ def native_tls_option(f: Callable[P, R]) -> Callable[P, R]:
33
+ """Enable native TLS to trust system configured trust store rather than python bundled trust stores.
34
+
35
+ When enabled, we set:
36
+ - UV_NATIVE_TLS=1 to instruct uv to use the platform trust store
37
+ - LLAMA_DEPLOY_USE_TRUSTSTORE=1 to use system certificate store for Python httpx clients
38
+ """
39
+
40
+ def _enable_native_tls(
41
+ ctx: click.Context, param: click.Parameter, value: bool
42
+ ) -> bool:
43
+ if value:
44
+ # Don't override if user explicitly set a value
45
+ os.environ.setdefault("UV_NATIVE_TLS", "1")
46
+ os.environ.setdefault("LLAMA_DEPLOY_USE_TRUSTSTORE", "1")
47
+ return value
48
+
49
+ return click.option(
50
+ "--native-tls",
51
+ is_flag=True,
52
+ help=(
53
+ "Enable native TLS mode to use system certificate store rather than runtime defaults. Can be set via LLAMACTL_NATIVE_TLS=1"
54
+ ),
55
+ callback=_enable_native_tls,
56
+ expose_value=False,
57
+ is_eager=True,
58
+ envvar=["LLAMACTL_NATIVE_TLS"],
59
+ )(f)
60
+
61
+
62
+ def file_logging(f: Callable[P, R]) -> Callable[P, R]:
16
63
  def debug_callback(ctx: click.Context, param: click.Parameter, value: str) -> str:
17
64
  if value:
18
65
  setup_file_logging(level=logging._nameToLevel.get(value, logging.INFO))
@@ -29,15 +76,3 @@ def global_options(f: Callable[P, R]) -> Callable[P, R]:
29
76
  is_eager=True,
30
77
  hidden=True,
31
78
  )(f)
32
-
33
-
34
- def interactive_option(f: Callable[P, R]) -> Callable[P, R]:
35
- """Add an interactive option to the command"""
36
-
37
- default = is_interactive_session()
38
- return click.option(
39
- "--interactive/--no-interactive",
40
- help="Run in interactive mode. If not provided, will default to the current session's interactive state.",
41
- is_flag=True,
42
- default=default,
43
- )(f)
File without changes