Skip to content

Commit 3b5ee16

Browse files
authored
feat(agentstack-cli): add interactive mode to agentstack add and run (#1682)
* feat(agentstack-cli): add interactive mode to agentstack run Signed-off-by: Jan Pokorný <[email protected]> * feat(agentstack-cli): add interactive mode to agentstack add Signed-off-by: Jan Pokorný <[email protected]> --------- Signed-off-by: Jan Pokorný <[email protected]>
1 parent 3e5ac14 commit 3b5ee16

File tree

2 files changed

+62
-7
lines changed

2 files changed

+62
-7
lines changed

apps/agentstack-cli/src/agentstack_cli/commands/agent.py

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -152,12 +152,50 @@ def short_location(provider: Provider) -> str:
152152

153153
@app.command("add")
154154
async def add_agent(
155-
location: typing.Annotated[str, typer.Argument(help="Agent location (public docker image or github url)")],
155+
location: typing.Annotated[
156+
str | None, typer.Argument(help="Agent location (public docker image or github url)")
157+
] = None,
156158
dockerfile: typing.Annotated[str | None, typer.Option(help="Use custom dockerfile path")] = None,
157159
verbose: typing.Annotated[bool, typer.Option("-v", "--verbose", help="Show verbose output")] = False,
158160
yes: typing.Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation prompts.")] = False,
159161
) -> None:
160162
"""Add a docker image or GitHub repository [aliases: install]"""
163+
if location is None:
164+
repo_input = (
165+
await inquirer.text( # pyright: ignore[reportPrivateImportUsage]
166+
message="Enter GitHub repository (owner/repo or full URL):",
167+
).execute_async()
168+
or ""
169+
)
170+
171+
match = re.search(r"^(?:(?:https?://)?(?:www\.)?github\.com/)?([^/]+)/([^/?&]+)", repo_input)
172+
if not match:
173+
raise ValueError(f"Invalid GitHub URL format: {repo_input}. Expected 'owner/repo' or a full GitHub URL.")
174+
175+
owner, repo = match.group(1), match.group(2).removesuffix(".git")
176+
177+
async with httpx.AsyncClient() as client:
178+
response = await client.get(
179+
f"https://api.github.com/repos/{owner}/{repo}/tags",
180+
headers={"Accept": "application/vnd.github.v3+json"},
181+
)
182+
tags = [tag["name"] for tag in response.json()] if response.status_code == 200 else []
183+
184+
if tags:
185+
selected_tag = await inquirer.fuzzy( # pyright: ignore[reportPrivateImportUsage]
186+
message="Select a tag to use:",
187+
choices=tags,
188+
).execute_async()
189+
else:
190+
selected_tag = (
191+
await inquirer.text( # pyright: ignore[reportPrivateImportUsage]
192+
message="Enter tag to use:",
193+
).execute_async()
194+
or "main"
195+
)
196+
197+
location = f"https://github.com/{owner}/{repo}@{selected_tag}"
198+
161199
url = announce_server_action(f"Installing agent '{location}' for")
162200
await confirm_server_action("Proceed with installing this agent on", url=url, yes=yes)
163201
with verbosity(verbose):
@@ -770,24 +808,41 @@ def handler() -> str:
770808
@app.command("run")
771809
async def run_agent(
772810
search_path: typing.Annotated[
773-
str, typer.Argument(..., help="Short ID, agent name or part of the provider location")
774-
],
811+
str | None,
812+
typer.Argument(
813+
help="Short ID, agent name or part of the provider location",
814+
),
815+
] = None,
775816
input: typing.Annotated[
776817
str | None,
777818
typer.Argument(
778-
default_factory=lambda: None if sys.stdin.isatty() else sys.stdin.read(),
779819
help="Agent input as text or JSON",
780820
),
781-
],
821+
] = None,
782822
dump_files: typing.Annotated[
783823
Path | None, typer.Option(help="Folder path to save any files returned by the agent")
784824
] = None,
785825
) -> None:
786826
"""Run an agent."""
787-
announce_server_action(f"Running agent '{search_path}' on")
827+
if search_path is not None and input is None and sys.stdin.isatty():
828+
input = sys.stdin.read()
788829
async with configuration.use_platform_client():
789830
providers = await Provider.list()
790831
await ensure_llm_provider()
832+
833+
if search_path is None:
834+
if not providers:
835+
err_console.error("No agents found. Add an agent first using 'agentstack agent add'.")
836+
sys.exit(1)
837+
search_path = await inquirer.fuzzy( # pyright: ignore[reportPrivateImportUsage]
838+
message="Select an agent to run:",
839+
choices=[provider.agent_card.name for provider in providers],
840+
).execute_async()
841+
if search_path is None:
842+
err_console.error("No agent selected. Exiting.")
843+
sys.exit(1)
844+
845+
announce_server_action(f"Running agent '{search_path}' on")
791846
provider = select_provider(search_path, providers=providers)
792847

793848
context = await Context.create(

apps/agentstack-cli/src/agentstack_cli/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ async def confirm_server_action(message: str, url: str | None = None, *, yes: bo
142142
return
143143
url = url or require_active_server()
144144
confirmed = await inquirer.confirm( # type: ignore
145-
message=f"{message} [cyan]{url}[/cyan]?", default=False
145+
message=f"{message} {url}?", default=False
146146
).execute_async()
147147
if not confirmed:
148148
console.info("Action cancelled.")

0 commit comments

Comments
 (0)