Completed new latest CLI (#2426)

This commit is contained in:
Prakash Maheshwaran
2025-05-22 03:12:32 -04:00
committed by GitHub
parent 74cd8f7b45
commit 2216ce66d3
9 changed files with 260 additions and 46 deletions

View File

@@ -102,7 +102,13 @@ This quickstart guide will walk you through getting Skyvern up and running on yo
skyvern run ui
```
5. **Run task**
5. **Check component status**
```bash
skyvern status
```
6. **Run task**
Run a skyvern task locally:
```python

View File

@@ -2,24 +2,32 @@ import typer
from dotenv import load_dotenv
from .docs import docs_app
from .init_command import init, init_browser, init_mcp
from .init_command import init, init_browser
from .run_commands import run_app
from .setup_commands import setup_mcp_command
from .status import status_app
from .tasks import tasks_app
from .workflow import workflow_app
cli_app = typer.Typer()
cli_app.add_typer(run_app, name="run")
cli_app.add_typer(workflow_app, name="workflow")
cli_app.add_typer(tasks_app, name="tasks")
cli_app.add_typer(docs_app, name="docs")
setup_app = typer.Typer()
cli_app.add_typer(setup_app, name="setup")
init_app = typer.Typer(invoke_without_command=True)
cli_app = typer.Typer(
help=("""[bold]Skyvern CLI[/bold]\nManage and run your local Skyvern environment."""),
no_args_is_help=True,
rich_markup_mode="rich",
)
cli_app.add_typer(
run_app,
name="run",
help="Run Skyvern services like the API server, UI, and MCP.",
)
cli_app.add_typer(workflow_app, name="workflow", help="Workflow management commands.")
cli_app.add_typer(tasks_app, name="tasks", help="Task management commands.")
cli_app.add_typer(docs_app, name="docs", help="Open Skyvern documentation.")
cli_app.add_typer(status_app, name="status", help="Check if Skyvern services are running.")
init_app = typer.Typer(
invoke_without_command=True,
help="Interactively configure Skyvern and its dependencies.",
)
cli_app.add_typer(init_app, name="init")
setup_app.command(name="mcp")(setup_mcp_command)
@init_app.callback()
def init_callback(
@@ -37,12 +45,6 @@ def init_browser_command() -> None:
init_browser()
@init_app.command(name="mcp")
def init_mcp_command() -> None:
"""Initialize only the MCP server configuration."""
init_mcp()
if __name__ == "__main__": # pragma: no cover - manual CLI invocation
load_dotenv()
cli_app()

View File

@@ -1,13 +1,31 @@
"""Documentation-related CLI helpers."""
import webbrowser
import typer
from rich.panel import Panel
from .console import console
docs_app = typer.Typer()
DOCS_URL = "https://docs.skyvern.com"
docs_app = typer.Typer(
invoke_without_command=True,
help="Open Skyvern documentation in your browser.",
)
@docs_app.command()
def placeholder() -> None:
"""Placeholder command for documentation actions."""
console.print("Documentation commands are not yet implemented.")
@docs_app.callback()
def docs_callback(ctx: typer.Context) -> None:
"""Open the Skyvern documentation in a browser."""
if ctx.invoked_subcommand is None:
console.print(
Panel(
f"[bold blue]Opening Skyvern docs at [link={DOCS_URL}]{DOCS_URL}[/link][/bold blue]",
border_style="cyan",
)
)
try:
webbrowser.open(DOCS_URL)
except Exception as exc: # pragma: no cover - CLI safeguard
console.print(f"[red]Failed to open documentation: {exc}[/red]")

View File

@@ -142,8 +142,3 @@ def init_browser() -> None:
progress.add_task("[bold blue]Downloading Chromium, this may take a moment...", total=None)
subprocess.run(["playwright", "install", "chromium"], check=True)
console.print("✅ [green]Chromium installation complete.[/green]")
def init_mcp() -> None:
"""Initialize only the MCP server configuration."""
setup_mcp()

View File

@@ -16,7 +16,7 @@ from skyvern.utils import detect_os
from .console import console
run_app = typer.Typer()
run_app = typer.Typer(help="Commands to run Skyvern services such as the API server or UI.")
mcp = FastMCP("Skyvern")

View File

@@ -1,6 +0,0 @@
from .mcp import setup_mcp
def setup_mcp_command() -> None:
"""Wrapper command to configure the MCP server."""
setup_mcp()

48
skyvern/cli/status.py Normal file
View File

@@ -0,0 +1,48 @@
import os
import socket
import typer
from rich.table import Table
from .console import console
status_app = typer.Typer(help="Check status of Skyvern components.", invoke_without_command=True)
def _check_port(port: int) -> bool:
"""Return True if a local port is accepting connections."""
try:
with socket.create_connection(("localhost", port), timeout=0.5):
return True
except OSError:
return False
def _status_table() -> Table:
api_port = int(os.getenv("PORT", 8000))
ui_port = 8080
db_port = 5432
components = [
("API server", _check_port(api_port), "skyvern run server"),
("UI server", _check_port(ui_port), "skyvern run ui"),
("PostgreSQL", _check_port(db_port), "skyvern init --no-postgres false"),
]
table = Table(title="Skyvern Component Status")
table.add_column("Component", style="bold")
table.add_column("Running")
table.add_column("Start Command")
for name, running, cmd in components:
status = "[green]Yes[/green]" if running else "[red]No[/red]"
table.add_row(name, status, cmd)
return table
@status_app.callback(invoke_without_command=True)
def status_callback(ctx: typer.Context) -> None:
"""Show status for API server, UI, and database."""
if ctx.invoked_subcommand is None:
console.print(_status_table())

View File

@@ -1,13 +1,62 @@
"""Task-related CLI helpers."""
from __future__ import annotations
import json
import os
from typing import Optional
import typer
from dotenv import load_dotenv
from rich.panel import Panel
from skyvern.client import Skyvern
from skyvern.config import settings
from .console import console
tasks_app = typer.Typer()
tasks_app = typer.Typer(help="Manage Skyvern tasks and operations.")
@tasks_app.command()
def placeholder() -> None:
"""Placeholder command for task management."""
console.print("Task operations are not yet implemented.")
@tasks_app.callback()
def tasks_callback(
ctx: typer.Context,
api_key: Optional[str] = typer.Option(
None,
"--api-key",
help="Skyvern API key",
envvar="SKYVERN_API_KEY",
),
) -> None:
"""Store API key in Typer context."""
ctx.obj = {"api_key": api_key}
def _get_client(api_key: Optional[str] = None) -> Skyvern:
"""Instantiate a Skyvern SDK client using environment variables."""
load_dotenv()
load_dotenv(".env")
key = api_key or os.getenv("SKYVERN_API_KEY") or settings.SKYVERN_API_KEY
return Skyvern(base_url=settings.SKYVERN_BASE_URL, api_key=key)
def _list_workflow_tasks(client: Skyvern, run_id: str) -> list[dict]:
"""Return tasks for the given workflow run."""
resp = client.agent._client_wrapper.httpx_client.request(
"api/v1/tasks",
method="GET",
params={"workflow_run_id": run_id, "page_size": 100, "page": 1},
)
resp.raise_for_status()
return resp.json()
@tasks_app.command("list")
def list_tasks(
ctx: typer.Context,
workflow_run_id: str = typer.Option(..., "--workflow-run-id", "-r", help="Workflow run ID"),
) -> None:
"""List tasks for a workflow run."""
client = _get_client(ctx.obj.get("api_key") if ctx.obj else None)
tasks = _list_workflow_tasks(client, workflow_run_id)
console.print(Panel(json.dumps(tasks, indent=2), border_style="cyan"))

View File

@@ -1,13 +1,115 @@
"""Workflow-related CLI helpers."""
from __future__ import annotations
import json
import os
from typing import Optional
import typer
from dotenv import load_dotenv
from rich.panel import Panel
from skyvern.client import Skyvern
from skyvern.config import settings
from .console import console
from .tasks import _list_workflow_tasks
workflow_app = typer.Typer()
workflow_app = typer.Typer(help="Manage Skyvern workflows.")
@workflow_app.command()
def placeholder() -> None:
"""Placeholder command for workflow operations."""
console.print("Workflow operations are not yet implemented.")
@workflow_app.callback()
def workflow_callback(
ctx: typer.Context,
api_key: Optional[str] = typer.Option(
None,
"--api-key",
help="Skyvern API key",
envvar="SKYVERN_API_KEY",
),
) -> None:
"""Store the provided API key in the Typer context."""
ctx.obj = {"api_key": api_key}
def _get_client(api_key: Optional[str] = None) -> Skyvern:
"""Instantiate a Skyvern SDK client using environment variables."""
load_dotenv()
load_dotenv(".env")
key = api_key or os.getenv("SKYVERN_API_KEY") or settings.SKYVERN_API_KEY
return Skyvern(base_url=settings.SKYVERN_BASE_URL, api_key=key)
@workflow_app.command("start")
def start_workflow(
ctx: typer.Context,
workflow_id: str = typer.Argument(..., help="Workflow permanent ID"),
parameters: str = typer.Option("{}", "--parameters", "-p", help="JSON parameters for the workflow"),
title: Optional[str] = typer.Option(None, "--title", help="Title for the workflow run"),
max_steps: Optional[int] = typer.Option(None, "--max-steps", help="Override the workflow max steps"),
) -> None:
"""Dispatch a workflow run."""
try:
params_dict = json.loads(parameters) if parameters else {}
except json.JSONDecodeError:
console.print(f"[red]Invalid JSON for parameters: {parameters}[/red]")
raise typer.Exit(code=1)
client = _get_client(ctx.obj.get("api_key") if ctx.obj else None)
run_resp = client.agent.run_workflow(
workflow_id=workflow_id,
parameters=params_dict,
title=title,
max_steps_override=max_steps,
)
console.print(
Panel(
f"Started workflow run [bold]{run_resp.run_id}[/bold]",
border_style="green",
)
)
@workflow_app.command("stop")
def stop_workflow(
ctx: typer.Context,
run_id: str = typer.Argument(..., help="ID of the workflow run"),
) -> None:
"""Cancel a running workflow."""
client = _get_client(ctx.obj.get("api_key") if ctx.obj else None)
client.agent.cancel_run(run_id=run_id)
console.print(Panel(f"Stop signal sent for run {run_id}", border_style="red"))
@workflow_app.command("status")
def workflow_status(
ctx: typer.Context,
run_id: str = typer.Argument(..., help="ID of the workflow run"),
tasks: bool = typer.Option(False, "--tasks", help="Show task executions"),
) -> None:
"""Retrieve status information for a workflow run."""
client = _get_client(ctx.obj.get("api_key") if ctx.obj else None)
run = client.agent.get_run(run_id=run_id)
console.print(Panel(run.model_dump_json(indent=2), border_style="cyan"))
if tasks:
task_list = _list_workflow_tasks(client, run_id)
console.print(Panel(json.dumps(task_list, indent=2), border_style="magenta"))
@workflow_app.command("list")
def list_workflows(
ctx: typer.Context,
page: int = typer.Option(1, "--page", help="Page number"),
page_size: int = typer.Option(10, "--page-size", help="Number of workflows to return"),
template: bool = typer.Option(False, "--template", help="List template workflows"),
) -> None:
"""List workflows for the organization."""
client = _get_client(ctx.obj.get("api_key") if ctx.obj else None)
resp = client.agent._client_wrapper.httpx_client.request(
"api/v1/workflows",
method="GET",
params={"page": page, "page_size": page_size, "template": template},
)
resp.raise_for_status()
console.print(Panel(json.dumps(resp.json(), indent=2), border_style="cyan"))