Files
Dorod-Sky/skyvern/cli/run_commands.py

161 lines
5.9 KiB
Python

import os
import shutil
import subprocess
from pathlib import Path
from typing import List
import psutil
import typer
import uvicorn
from dotenv import load_dotenv, set_key
from mcp.server.fastmcp import FastMCP
from rich.panel import Panel
from rich.prompt import Confirm
from skyvern.config import settings
from skyvern.library.skyvern import Skyvern
from skyvern.utils import detect_os
from .console import console
run_app = typer.Typer(help="Commands to run Skyvern services such as the API server or UI.")
mcp = FastMCP("Skyvern")
@mcp.tool()
async def skyvern_run_task(prompt: str, url: str) -> dict[str, str]:
"""Use Skyvern to execute anything in the browser. Useful for accomplishing tasks that require browser automation.
This tool uses Skyvern's browser automation to navigate websites and perform actions to achieve
the user's intended outcome. It can handle tasks like form filling, clicking buttons, data extraction,
and multi-step workflows.
It can even help you find updated data on the internet if your model information is outdated.
Args:
prompt: A natural language description of what needs to be accomplished (e.g. "Book a flight from
NYC to LA", "Sign up for the newsletter", "Find the price of item X", "Apply to a job")
url: The starting URL of the website where the task should be performed
"""
skyvern_agent = Skyvern(
base_url=settings.SKYVERN_BASE_URL,
api_key=settings.SKYVERN_API_KEY,
)
res = await skyvern_agent.run_task(prompt=prompt, url=url, user_agent="skyvern-mcp", wait_for_completion=True)
# TODO: It would be nice if we could return the task URL here
output = res.model_dump()["output"]
base_url = settings.SKYVERN_BASE_URL
run_history_url = (
"https://app.skyvern.com/history" if "skyvern.com" in base_url else "http://localhost:8080/history"
)
return {"output": output, "run_history_url": run_history_url}
def get_pids_on_port(port: int) -> List[int]:
"""Return a list of PIDs listening on the given port."""
pids = []
try:
for conn in psutil.net_connections(kind="inet"):
if conn.laddr and conn.laddr.port == port and conn.pid:
pids.append(conn.pid)
except Exception:
pass
return list(set(pids))
def kill_pids(pids: List[int]) -> None:
"""Kill the given list of PIDs in a cross-platform way."""
host_system = detect_os()
for pid in pids:
try:
if host_system in {"windows", "wsl"}:
subprocess.run(f"taskkill /PID {pid} /F", shell=True, check=False)
else:
os.kill(pid, 9)
except Exception:
console.print(f"[red]Failed to kill process {pid}[/red]")
@run_app.command(name="server")
def run_server() -> None:
"""Run the Skyvern API server."""
load_dotenv()
load_dotenv(".env")
from skyvern.config import settings
port = settings.PORT
console.print(Panel(f"[bold green]Starting Skyvern API Server on port {port}...", border_style="green"))
uvicorn.run(
"skyvern.forge.api_app:app",
host="0.0.0.0",
port=port,
log_level="info",
)
@run_app.command(name="ui")
def run_ui() -> None:
"""Run the Skyvern UI server."""
console.print(Panel("[bold blue]Starting Skyvern UI Server...[/bold blue]", border_style="blue"))
try:
with console.status("[bold green]Checking for existing process on port 8080...") as status:
pids = get_pids_on_port(8080)
if pids:
status.stop()
response = Confirm.ask("Process already running on port 8080. [yellow]Kill it?[/yellow]")
if response:
kill_pids(pids)
console.print("✅ [green]Process killed.[/green]")
else:
console.print("[yellow]UI server not started. Process already running on port 8080.[/yellow]")
return
status.stop()
except Exception as e: # pragma: no cover - CLI safeguards
console.print(f"[red]Error checking for process: {e}[/red]")
current_dir = Path(__file__).parent.parent.parent
frontend_dir = current_dir / "skyvern-frontend"
if not frontend_dir.exists():
console.print(
f"[bold red]ERROR: Skyvern Frontend directory not found at [path]{frontend_dir}[/path]. Are you in the right repo?[/bold red]"
)
return
if not (frontend_dir / ".env").exists():
console.print("[bold blue]Setting up frontend .env file...[/bold blue]")
shutil.copy(frontend_dir / ".env.example", frontend_dir / ".env")
main_env_path = current_dir / ".env"
if main_env_path.exists():
load_dotenv(main_env_path)
skyvern_api_key = os.getenv("SKYVERN_API_KEY")
if skyvern_api_key:
frontend_env_path = frontend_dir / ".env"
set_key(str(frontend_env_path), "VITE_SKYVERN_API_KEY", skyvern_api_key)
else:
console.print("[red]ERROR: SKYVERN_API_KEY not found in .env file[/red]")
else:
console.print("[red]ERROR: .env file not found[/red]")
console.print("✅ [green]Successfully set up frontend .env file[/green]")
os.chdir(frontend_dir)
try:
console.print("📦 [bold blue]Running npm install...[/bold blue]")
subprocess.run("npm install --silent", shell=True, check=True)
console.print("✅ [green]npm install complete.[/green]")
console.print("🚀 [bold blue]Starting npm UI server...[/bold blue]")
subprocess.run("npm run start", shell=True, check=True)
except subprocess.CalledProcessError as e:
console.print(f"[bold red]Error running UI server: {e}[/bold red]")
return
@run_app.command(name="mcp")
def run_mcp() -> None:
"""Run the MCP server."""
console.print(Panel("[bold green]Starting MCP Server...[/bold green]", border_style="green"))
mcp.run(transport="stdio")