agentstack-cli 0.4.2rc8__tar.gz → 0.4.2rc9__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 (25) hide show
  1. {agentstack_cli-0.4.2rc8 → agentstack_cli-0.4.2rc9}/PKG-INFO +1 -1
  2. {agentstack_cli-0.4.2rc8 → agentstack_cli-0.4.2rc9}/pyproject.toml +1 -1
  3. {agentstack_cli-0.4.2rc8 → agentstack_cli-0.4.2rc9}/src/agentstack_cli/__init__.py +7 -4
  4. {agentstack_cli-0.4.2rc8 → agentstack_cli-0.4.2rc9}/src/agentstack_cli/commands/agent.py +56 -39
  5. {agentstack_cli-0.4.2rc8 → agentstack_cli-0.4.2rc9}/src/agentstack_cli/commands/build.py +60 -47
  6. {agentstack_cli-0.4.2rc8 → agentstack_cli-0.4.2rc9}/src/agentstack_cli/commands/model.py +1 -1
  7. {agentstack_cli-0.4.2rc8 → agentstack_cli-0.4.2rc9}/src/agentstack_cli/commands/platform/__init__.py +5 -5
  8. {agentstack_cli-0.4.2rc8 → agentstack_cli-0.4.2rc9}/src/agentstack_cli/commands/platform/base_driver.py +2 -0
  9. {agentstack_cli-0.4.2rc8 → agentstack_cli-0.4.2rc9}/src/agentstack_cli/commands/self.py +8 -4
  10. agentstack_cli-0.4.2rc9/src/agentstack_cli/data/helm-chart.tgz +0 -0
  11. {agentstack_cli-0.4.2rc8 → agentstack_cli-0.4.2rc9}/src/agentstack_cli/utils.py +27 -4
  12. agentstack_cli-0.4.2rc8/src/agentstack_cli/data/helm-chart.tgz +0 -0
  13. {agentstack_cli-0.4.2rc8 → agentstack_cli-0.4.2rc9}/README.md +0 -0
  14. {agentstack_cli-0.4.2rc8 → agentstack_cli-0.4.2rc9}/src/agentstack_cli/api.py +0 -0
  15. {agentstack_cli-0.4.2rc8 → agentstack_cli-0.4.2rc9}/src/agentstack_cli/async_typer.py +0 -0
  16. {agentstack_cli-0.4.2rc8 → agentstack_cli-0.4.2rc9}/src/agentstack_cli/auth_manager.py +0 -0
  17. {agentstack_cli-0.4.2rc8 → agentstack_cli-0.4.2rc9}/src/agentstack_cli/commands/__init__.py +0 -0
  18. {agentstack_cli-0.4.2rc8 → agentstack_cli-0.4.2rc9}/src/agentstack_cli/commands/mcp.py +0 -0
  19. {agentstack_cli-0.4.2rc8 → agentstack_cli-0.4.2rc9}/src/agentstack_cli/commands/platform/istio.py +0 -0
  20. {agentstack_cli-0.4.2rc8 → agentstack_cli-0.4.2rc9}/src/agentstack_cli/commands/platform/lima_driver.py +0 -0
  21. {agentstack_cli-0.4.2rc8 → agentstack_cli-0.4.2rc9}/src/agentstack_cli/commands/platform/wsl_driver.py +0 -0
  22. {agentstack_cli-0.4.2rc8 → agentstack_cli-0.4.2rc9}/src/agentstack_cli/commands/server.py +0 -0
  23. {agentstack_cli-0.4.2rc8 → agentstack_cli-0.4.2rc9}/src/agentstack_cli/configuration.py +0 -0
  24. {agentstack_cli-0.4.2rc8 → agentstack_cli-0.4.2rc9}/src/agentstack_cli/console.py +0 -0
  25. {agentstack_cli-0.4.2rc8 → agentstack_cli-0.4.2rc9}/src/agentstack_cli/data/.gitignore +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: agentstack-cli
3
- Version: 0.4.2rc8
3
+ Version: 0.4.2rc9
4
4
  Summary: Agent Stack CLI
5
5
  Author: IBM Corp.
6
6
  Requires-Dist: anyio~=4.10.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agentstack-cli"
3
- version = "0.4.2-rc8"
3
+ version = "0.4.2-rc9"
4
4
  description = "Agent Stack CLI"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "IBM Corp." }]
@@ -33,13 +33,14 @@ Usage: agentstack [OPTIONS] COMMAND [ARGS]...
33
33
  ╰────────────────────────────────────────────────────────────────────────────╯
34
34
 
35
35
  ╭─ Agent Management ─────────────────────────────────────────────────────────╮
36
- │ add Install an agent (Docker, GitHub, local)
36
+ │ add Install an agent (Docker, GitHub)
37
37
  │ remove Uninstall an agent │
38
+ │ update Update an agent │
38
39
  │ info Show agent details │
39
40
  │ logs Stream agent execution logs │
40
- │ build Build an agent container image │
41
41
  │ env Manage agent environment variables │
42
- server-side-build [EXPERIMENTAL] Build agents remotely
42
+ │ build Build an agent remotely
43
+ │ client-side-build Build an agent container image locally │
43
44
  ╰────────────────────────────────────────────────────────────────────────────╯
44
45
 
45
46
  ╭─ Platform & Configuration ─────────────────────────────────────────────────╮
@@ -92,7 +93,9 @@ app.add_typer(agent_alias, name="", no_args_is_help=True)
92
93
 
93
94
 
94
95
  @app.command("version")
95
- async def version(verbose: typing.Annotated[bool, typer.Option("-v", help="Show verbose output")] = False):
96
+ async def version(
97
+ verbose: typing.Annotated[bool, typer.Option("-v", "--verbose", help="Show verbose output")] = False,
98
+ ):
96
99
  """Print version of the Agent Stack CLI."""
97
100
  import agentstack_cli.commands.self
98
101
 
@@ -61,7 +61,7 @@ from agentstack_sdk.a2a.extensions.common.form import (
61
61
  TextField,
62
62
  TextFieldValue,
63
63
  )
64
- from agentstack_sdk.platform import ModelProvider, Provider
64
+ from agentstack_sdk.platform import BuildState, ModelProvider, Provider
65
65
  from agentstack_sdk.platform.context import Context, ContextPermissions, ContextToken, Permissions
66
66
  from agentstack_sdk.platform.model_provider import ModelCapability
67
67
  from InquirerPy import inquirer
@@ -73,7 +73,7 @@ from rich.console import ConsoleRenderable, Group, NewLine
73
73
  from rich.panel import Panel
74
74
  from rich.text import Text
75
75
 
76
- from agentstack_cli.commands.build import build
76
+ from agentstack_cli.commands.build import _server_side_build
77
77
  from agentstack_cli.commands.model import ensure_llm_provider
78
78
  from agentstack_cli.configuration import Configuration
79
79
 
@@ -87,7 +87,6 @@ if sys.platform != "win32":
87
87
  from collections.abc import Callable
88
88
  from pathlib import Path
89
89
  from typing import Any
90
- from urllib.parse import urlparse
91
90
 
92
91
  import jsonschema
93
92
  import rich.json
@@ -101,11 +100,11 @@ from agentstack_cli.utils import (
101
100
  announce_server_action,
102
101
  confirm_server_action,
103
102
  generate_schema_example,
103
+ is_github_url,
104
104
  parse_env_var,
105
105
  print_log,
106
106
  prompt_user,
107
107
  remove_nullable,
108
- run_command,
109
108
  status,
110
109
  verbosity,
111
110
  )
@@ -153,49 +152,67 @@ configuration = Configuration()
153
152
 
154
153
  @app.command("add")
155
154
  async def add_agent(
156
- location: typing.Annotated[
157
- str, typer.Argument(help="Agent location (public docker image, local path or github url)")
158
- ],
155
+ location: typing.Annotated[str, typer.Argument(help="Agent location (public docker image or github url)")],
159
156
  dockerfile: typing.Annotated[str | None, typer.Option(help="Use custom dockerfile path")] = None,
160
- vm_name: typing.Annotated[str, typer.Option(hidden=True)] = "agentstack",
161
- verbose: typing.Annotated[bool, typer.Option("-v", help="Show verbose output")] = False,
157
+ verbose: typing.Annotated[bool, typer.Option("-v", "--verbose", help="Show verbose output")] = False,
162
158
  yes: typing.Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation prompts.")] = False,
163
159
  ) -> None:
164
- """Install discovered agent or add public docker image or github repository [aliases: install]"""
160
+ """Add a docker image or GitHub repository [aliases: install]"""
165
161
  url = announce_server_action(f"Installing agent '{location}' for")
166
162
  await confirm_server_action("Proceed with installing this agent on", url=url, yes=yes)
167
- agent_card = None
168
163
  with verbosity(verbose):
169
- if (
170
- process := await run_command(
171
- ["docker", "inspect", location], check=False, message="Inspecting docker images"
172
- )
173
- ).returncode == 0:
174
- console.success(f"Found local image [bold]{location}[/bold]")
175
- manifest = base64.b64decode(
176
- json.loads(process.stdout)[0]["Config"]["Labels"]["beeai.dev.agent.json"]
177
- ).decode()
178
- agent_card = json.loads(manifest)
179
- elif (
180
- Path(location).expanduser().exists()
181
- or location.startswith("git@")
182
- or location.startswith("github.com/")
183
- or location.startswith("www.github.com/")
184
- or location.endswith(".git")
185
- or ((u := urlparse(location)).scheme.startswith("http") and u.netloc.endswith("github.com"))
186
- or u.scheme in {"ssh", "git", "git+ssh"}
187
- ):
188
- console.info(f"Assuming build context, attempting to build agent from [bold]{location}[/bold]")
189
- location, agent_card = await build(location, dockerfile, tag=None, vm_name=vm_name, import_image=True)
164
+ if is_github_url(location):
165
+ console.info(f"Assuming GitHub repository, attempting to build agent from [bold]{location}[/bold]")
166
+ with status("Building agent"):
167
+ build = await _server_side_build(location, dockerfile, add=True, verbose=verbose)
168
+ if build.status != BuildState.COMPLETED:
169
+ error = build.error_message or "see logs above for details"
170
+ raise RuntimeError(f"Agent build failed: {error}")
190
171
  else:
191
- console.info(f"Assuming public docker image, attempting to pull {location}")
172
+ if dockerfile:
173
+ raise ValueError("Dockerfile can be specified only if location is a GitHub url")
174
+ console.info(f"Assuming public docker image or network address, attempting to add {location}")
175
+ with status("Registering agent to platform"):
176
+ async with configuration.use_platform_client():
177
+ await Provider.create(location=location)
178
+ console.success(f"Agent [bold]{location}[/bold] added to platform")
179
+ await list_agents()
192
180
 
193
- with status("Registering agent to platform"):
194
- async with configuration.use_platform_client():
195
- await Provider.create(
196
- location=location,
197
- agent_card=AgentCard.model_validate(agent_card) if agent_card else None,
181
+
182
+ @app.command("update")
183
+ async def update_agent(
184
+ search_path: typing.Annotated[
185
+ str, typer.Argument(..., help="Short ID, agent name or part of the provider location of agent to replace")
186
+ ],
187
+ location: typing.Annotated[str, typer.Argument(help="Agent location (public docker image or github url)")],
188
+ dockerfile: typing.Annotated[str | None, typer.Option(help="Use custom dockerfile path")] = None,
189
+ verbose: typing.Annotated[bool, typer.Option("-v", "--verbose", help="Show verbose output")] = False,
190
+ yes: typing.Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation prompts.")] = False,
191
+ ) -> None:
192
+ """Upgrade agent to a newer docker image or build from GitHub repository"""
193
+ with verbosity(verbose):
194
+ async with configuration.use_platform_client():
195
+ provider = select_provider(search_path, providers=await Provider.list())
196
+
197
+ url = announce_server_action(f"Upgrading agent from '{provider.source}' to {location}")
198
+ await confirm_server_action("Proceed with upgrading agent on", url=url, yes=yes)
199
+
200
+ if is_github_url(location):
201
+ console.info(f"Assuming GitHub repository, attempting to build agent from [bold]{location}[/bold]")
202
+ with status("Building agent"):
203
+ build = await _server_side_build(
204
+ github_url=location, dockerfile=dockerfile, replace=provider.id, verbose=verbose
198
205
  )
206
+ if build.status != BuildState.COMPLETED:
207
+ error = build.error_message or "see logs above for details"
208
+ raise RuntimeError(f"Agent build failed: {error}")
209
+ else:
210
+ if dockerfile:
211
+ raise ValueError("Dockerfile can be specified only if location is a GitHub url")
212
+ console.info(f"Assuming public docker image or network address, attempting to add {location}")
213
+ with status("Upgrading agent in the platform"):
214
+ async with configuration.use_platform_client():
215
+ await provider.patch(location=location)
199
216
  console.success(f"Agent [bold]{location}[/bold] added to platform")
200
217
  await list_agents()
201
218
 
@@ -778,7 +795,7 @@ async def run_agent(
778
795
  metadata={"provider_id": provider.id, "agent_name": provider.agent_card.name},
779
796
  )
780
797
  context_token = await context.generate_token(
781
- grant_global_permissions=Permissions(llm={"*"}, embeddings={"*"}, a2a_proxy={"*"}),
798
+ grant_global_permissions=Permissions(llm={"*"}, embeddings={"*"}, a2a_proxy={"*"}, providers={"read"}),
782
799
  grant_context_permissions=ContextPermissions(files={"*"}, vector_stores={"*"}, context_data={"*"}),
783
800
  )
784
801
 
@@ -8,6 +8,7 @@ import re
8
8
  import sys
9
9
  import typing
10
10
  import uuid
11
+ from asyncio import CancelledError
11
12
  from contextlib import suppress
12
13
  from datetime import timedelta
13
14
  from pathlib import Path
@@ -17,13 +18,13 @@ import anyio.abc
17
18
  import typer
18
19
  from a2a.utils import AGENT_CARD_WELL_KNOWN_PATH
19
20
  from agentstack_sdk.platform import AddProvider, BuildConfiguration, Provider, UpdateProvider
20
- from agentstack_sdk.platform.provider_build import BuildState, ProviderBuild
21
+ from agentstack_sdk.platform.provider_build import ProviderBuild
21
22
  from anyio import open_process
22
23
  from httpx import AsyncClient, HTTPError
23
24
  from tenacity import AsyncRetrying, retry_if_exception_type, stop_after_delay, wait_fixed
24
25
 
25
26
  from agentstack_cli.async_typer import AsyncTyper
26
- from agentstack_cli.console import console
27
+ from agentstack_cli.console import console, err_console
27
28
  from agentstack_cli.utils import (
28
29
  announce_server_action,
29
30
  capture_output,
@@ -47,8 +48,8 @@ async def find_free_port():
47
48
  app = AsyncTyper()
48
49
 
49
50
 
50
- @app.command("build")
51
- async def build(
51
+ @app.command("client-side-build")
52
+ async def client_side_build(
52
53
  context: typing.Annotated[str, typer.Argument(help="Docker context for the agent")] = ".",
53
54
  dockerfile: typing.Annotated[str | None, typer.Option(help="Use custom dockerfile path")] = None,
54
55
  tag: typing.Annotated[str | None, typer.Option(help="Docker tag for the agent")] = None,
@@ -58,8 +59,9 @@ async def build(
58
59
  bool, typer.Option("--import/--no-import", is_flag=True, help="Import the image into Agent Stack platform")
59
60
  ] = True,
60
61
  vm_name: typing.Annotated[str, typer.Option(hidden=True)] = "agentstack",
61
- verbose: typing.Annotated[bool, typer.Option("-v")] = False,
62
+ verbose: typing.Annotated[bool, typer.Option("-v", "--verbose", help="Show verbose output")] = False,
62
63
  ):
64
+ """Build agent locally using Docker."""
63
65
  with verbosity(verbose):
64
66
  await run_command(["which", "docker"], "Checking docker")
65
67
  image_id = "agentstack-agent-build-tmp:latest"
@@ -142,56 +144,67 @@ async def build(
142
144
  return tag, agent_card
143
145
 
144
146
 
145
- @app.command("server-side-build")
146
- async def server_side_build_experimental(
147
+ async def _server_side_build(
148
+ github_url: str,
149
+ dockerfile: str | None = None,
150
+ replace: str | None = None,
151
+ add: bool = False,
152
+ verbose: bool = False,
153
+ ) -> ProviderBuild:
154
+ build = None
155
+ try:
156
+ from agentstack_cli.commands.agent import select_provider
157
+ from agentstack_cli.configuration import Configuration
158
+
159
+ if replace and add:
160
+ raise ValueError("Cannot specify both replace and add options.")
161
+
162
+ build_configuration = None
163
+ if dockerfile:
164
+ build_configuration = BuildConfiguration(dockerfile_path=Path(dockerfile))
165
+
166
+ async with Configuration().use_platform_client():
167
+ on_complete = None
168
+ if replace:
169
+ provider = select_provider(replace, await Provider.list())
170
+ on_complete = UpdateProvider(provider_id=uuid.UUID(provider.id))
171
+ elif add:
172
+ on_complete = AddProvider()
173
+
174
+ build = await ProviderBuild.create(
175
+ location=github_url,
176
+ on_complete=on_complete,
177
+ build_configuration=build_configuration,
178
+ )
179
+ with verbosity(verbose):
180
+ async for message in build.stream_logs():
181
+ print_log(message, ansi_mode=True, out_console=err_console)
182
+ return await build.get()
183
+ except (KeyboardInterrupt, CancelledError):
184
+ if build:
185
+ await build.delete()
186
+ console.error("Build aborted.")
187
+ raise
188
+
189
+
190
+ @app.command("build")
191
+ async def server_side_build(
147
192
  github_url: typing.Annotated[
148
193
  str, typer.Argument(..., help="Github repository URL (public or private if supported by the platform instance)")
149
194
  ],
150
195
  dockerfile: typing.Annotated[
151
196
  str | None, typer.Option(help="Use custom dockerfile path, relative to github url sub-path")
152
197
  ] = None,
153
- replace: typing.Annotated[
154
- str | None, typer.Option(help="Short ID, agent name or part of the provider location")
155
- ] = None,
156
- add: typing.Annotated[bool, typer.Option(help="Add agent to the platform after build")] = False,
198
+ verbose: typing.Annotated[bool, typer.Option("-v", "--verbose", help="Show verbose output")] = False,
157
199
  yes: typing.Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation prompts.")] = False,
158
200
  ):
159
- """EXPERIMENTAL: Build agent from github repository in the platform."""
160
- from agentstack_cli.commands.agent import select_provider
161
- from agentstack_cli.configuration import Configuration
201
+ """Build agent from a GitHub repository in the platform."""
162
202
 
163
- if replace and add:
164
- raise ValueError("Cannot specify both replace and add options.")
165
-
166
- build_configuration = None
167
- if dockerfile:
168
- build_configuration = BuildConfiguration(dockerfile_path=Path(dockerfile))
169
-
170
- url = announce_server_action(f"Starting server-side build for '{github_url}' on")
203
+ url = announce_server_action(f"Starting build for '{github_url}' on")
171
204
  await confirm_server_action("Proceed with building this agent on", url=url, yes=yes)
172
205
 
173
- async with Configuration().use_platform_client():
174
- on_complete = None
175
- if replace:
176
- provider = select_provider(replace, await Provider.list())
177
- on_complete = UpdateProvider(provider_id=uuid.UUID(provider.id))
178
- elif add:
179
- on_complete = AddProvider()
180
-
181
- build = await ProviderBuild.create(
182
- location=github_url,
183
- on_complete=on_complete,
184
- build_configuration=build_configuration,
185
- )
186
- async for message in build.stream_logs():
187
- print_log(message, ansi_mode=True)
188
- build = await build.get()
189
- if build.status == BuildState.COMPLETED:
190
- if add:
191
- message = "Agent added successfully. List agents using [green]agentstack list[/green]"
192
- else:
193
- message = f"Agent built successfully, add it to the platform using: [green]agentstack add {build.destination}[/green]"
194
- console.success(message)
195
- else:
196
- error = build.error_message or "see logs above for details"
197
- console.error(f"Agent build failed: {error}")
206
+ build = await _server_side_build(github_url=github_url, dockerfile=dockerfile, verbose=verbose)
207
+
208
+ console.success(
209
+ f"Agent built successfully, add it to the platform using: [green]agentstack add {build.destination}[/green]"
210
+ )
@@ -413,7 +413,7 @@ async def _reset_configuration(existing_providers: list[ModelProvider] | None =
413
413
  @app.command("setup")
414
414
  async def setup(
415
415
  use_true_localhost: typing.Annotated[bool, typer.Option(hidden=True)] = False,
416
- verbose: typing.Annotated[bool, typer.Option("-v")] = False,
416
+ verbose: typing.Annotated[bool, typer.Option("-v", "--verbose", help="Show verbose output")] = False,
417
417
  ):
418
418
  """Interactive setup for LLM and embedding provider environment variables"""
419
419
  announce_server_action("Configuring model providers for")
@@ -72,7 +72,7 @@ async def start(
72
72
  pathlib.Path | None, typer.Option("-f", help="Set Helm chart values using yaml values file")
73
73
  ] = None,
74
74
  vm_name: typing.Annotated[str, typer.Option(hidden=True)] = "agentstack",
75
- verbose: typing.Annotated[bool, typer.Option("-v", help="Show verbose output")] = False,
75
+ verbose: typing.Annotated[bool, typer.Option("-v", "--verbose", help="Show verbose output")] = False,
76
76
  ):
77
77
  """Start Agent Stack platform."""
78
78
  import agentstack_cli.commands.server
@@ -127,7 +127,7 @@ async def start(
127
127
  @app.command("stop")
128
128
  async def stop(
129
129
  vm_name: typing.Annotated[str, typer.Option(hidden=True)] = "agentstack",
130
- verbose: typing.Annotated[bool, typer.Option("-v", help="Show verbose output")] = False,
130
+ verbose: typing.Annotated[bool, typer.Option("-v", "--verbose", help="Show verbose output")] = False,
131
131
  ):
132
132
  """Stop Agent Stack platform."""
133
133
  with verbosity(verbose):
@@ -142,7 +142,7 @@ async def stop(
142
142
  @app.command("delete")
143
143
  async def delete(
144
144
  vm_name: typing.Annotated[str, typer.Option(hidden=True)] = "agentstack",
145
- verbose: typing.Annotated[bool, typer.Option("-v", help="Show verbose output")] = False,
145
+ verbose: typing.Annotated[bool, typer.Option("-v", "--verbose", help="Show verbose output")] = False,
146
146
  ):
147
147
  """Delete Agent Stack platform."""
148
148
  with verbosity(verbose):
@@ -155,7 +155,7 @@ async def delete(
155
155
  async def import_image_cmd(
156
156
  tag: typing.Annotated[str, typer.Argument(help="Docker image tag to import")],
157
157
  vm_name: typing.Annotated[str, typer.Option(hidden=True)] = "agentstack",
158
- verbose: typing.Annotated[bool, typer.Option("-v", help="Show verbose output")] = False,
158
+ verbose: typing.Annotated[bool, typer.Option("-v", "--verbose", help="Show verbose output")] = False,
159
159
  ):
160
160
  """Import a local docker image into the Agent Stack platform."""
161
161
  with verbosity(verbose):
@@ -170,7 +170,7 @@ async def import_image_cmd(
170
170
  async def exec_cmd(
171
171
  command: typing.Annotated[list[str] | None, typer.Argument()] = None,
172
172
  vm_name: typing.Annotated[str, typer.Option(hidden=True)] = "agentstack",
173
- verbose: typing.Annotated[bool, typer.Option("-v", help="Show verbose output")] = False,
173
+ verbose: typing.Annotated[bool, typer.Option("-v", "--verbose", help="Show verbose output")] = False,
174
174
  ):
175
175
  """For debugging -- execute a command inside the Agent Stack platform VM."""
176
176
  with verbosity(verbose, show_success_status=False):
@@ -117,6 +117,8 @@ class BaseDriver(abc.ABC):
117
117
  "generateConversationTitle": False, # TODO: enable when UI implementation is ready
118
118
  "uiLocalSetup": True,
119
119
  },
120
+ "providerBuilds": {"enabled": True},
121
+ "localDockerRegistry": {"enabled": True},
120
122
  "auth": {"enabled": False},
121
123
  }
122
124
  if values_file:
@@ -40,7 +40,9 @@ def _path() -> str:
40
40
 
41
41
 
42
42
  @app.command("version")
43
- async def version(verbose: typing.Annotated[bool, typer.Option("-v", help="Show verbose output")] = False):
43
+ async def version(
44
+ verbose: typing.Annotated[bool, typer.Option("-v", "--verbose", help="Show verbose output")] = False,
45
+ ):
44
46
  """Print version of the Agent Stack CLI."""
45
47
  with verbosity(verbose=verbose):
46
48
  cli_version = importlib.metadata.version("agentstack-cli")
@@ -78,7 +80,7 @@ async def version(verbose: typing.Annotated[bool, typer.Option("-v", help="Show
78
80
 
79
81
  @app.command("install")
80
82
  async def install(
81
- verbose: typing.Annotated[bool, typer.Option("-v", help="Show verbose output")] = False,
83
+ verbose: typing.Annotated[bool, typer.Option("-v", "--verbose", help="Show verbose output")] = False,
82
84
  ):
83
85
  """Install Agent Stack platform pre-requisites."""
84
86
  with verbosity(verbose=verbose):
@@ -171,7 +173,9 @@ async def install(
171
173
 
172
174
 
173
175
  @app.command("upgrade")
174
- async def upgrade(verbose: typing.Annotated[bool, typer.Option("-v", help="Show verbose output")] = False):
176
+ async def upgrade(
177
+ verbose: typing.Annotated[bool, typer.Option("-v", "--verbose", help="Show verbose output")] = False,
178
+ ):
175
179
  """Upgrade Agent Stack CLI and Platform to the latest version."""
176
180
  if not shutil.which("uv", path=_path()):
177
181
  console.error("Can't self-upgrade because 'uv' was not found.")
@@ -189,7 +193,7 @@ async def upgrade(verbose: typing.Annotated[bool, typer.Option("-v", help="Show
189
193
 
190
194
  @app.command("uninstall")
191
195
  async def uninstall(
192
- verbose: typing.Annotated[bool, typer.Option("-v", help="Show verbose output")] = False,
196
+ verbose: typing.Annotated[bool, typer.Option("-v", "--verbose", help="Show verbose output")] = False,
193
197
  ):
194
198
  """Uninstall Agent Stack CLI and Platform."""
195
199
  if not shutil.which("uv", path=_path()):
@@ -5,6 +5,7 @@ import contextlib
5
5
  import functools
6
6
  import json
7
7
  import os
8
+ import re
8
9
  import subprocess
9
10
  import sys
10
11
  from collections.abc import AsyncIterator
@@ -26,7 +27,7 @@ from jsf import JSF
26
27
  from prompt_toolkit import PromptSession
27
28
  from prompt_toolkit.shortcuts import CompleteStyle
28
29
  from pydantic import BaseModel
29
- from rich.console import Capture
30
+ from rich.console import Capture, Console
30
31
  from rich.text import Text
31
32
 
32
33
  from agentstack_cli.configuration import Configuration
@@ -296,7 +297,7 @@ def verbosity(verbose: bool, show_success_status: bool = True):
296
297
  SHOW_SUCCESS_STATUS.reset(token_command_status)
297
298
 
298
299
 
299
- def print_log(line, ansi_mode=False):
300
+ def print_log(line, ansi_mode=False, out_console: Console | None = None):
300
301
  if "error" in line:
301
302
 
302
303
  class CustomError(Exception): ...
@@ -309,6 +310,28 @@ def print_log(line, ansi_mode=False):
309
310
  return Text.from_ansi(text) if ansi_mode else text
310
311
 
311
312
  if line["stream"] == "stderr":
312
- err_console.print(decode(line["message"]))
313
+ (out_console or err_console).print(decode(line["message"]))
313
314
  elif line["stream"] == "stdout":
314
- console.print(decode(line["message"]))
315
+ (out_console or console).print(decode(line["message"]))
316
+
317
+
318
+ def is_github_url(url: str) -> bool:
319
+ """This pattern is taken from agentstack_server.utils.github.GithubUrl, make sure to keep it in sync"""
320
+
321
+ pattern = r"""
322
+ ^
323
+ (?:git\+)? # Optional git+ prefix
324
+ https?://(?P<host>github(?:\.[^/]+)+)/ # GitHub host (github.com or github.enterprise.com)
325
+ (?P<org>[^/]+)/ # Organization
326
+ (?P<repo>
327
+ (?: # Non-capturing group for repo name
328
+ (?!\.git(?:$|[@#])) # Negative lookahead for .git at end or followed by @#
329
+ [^/@#] # Any char except /@#
330
+ )+ # One or more of these chars
331
+ )
332
+ (?:\.git)? # Optional .git suffix
333
+ (?:@(?P<version>[^#]+))? # Optional version after @
334
+ (?:\#path=(?P<path>.+))? # Optional path after #path=
335
+ $
336
+ """
337
+ return bool(re.match(pattern, url, re.VERBOSE))