Completed new latest CLI (#2426)
This commit is contained in:
committed by
GitHub
parent
74cd8f7b45
commit
2216ce66d3
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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]")
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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
48
skyvern/cli/status.py
Normal 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())
|
||||
@@ -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"))
|
||||
|
||||
@@ -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"))
|
||||
|
||||
Reference in New Issue
Block a user