CLI - skyvern run code (#3738)
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
from typing import Any, List
|
||||
from typing import Any, List, Optional
|
||||
|
||||
import psutil
|
||||
import typer
|
||||
@@ -12,14 +14,16 @@ from mcp.server.fastmcp import FastMCP
|
||||
from rich.panel import Panel
|
||||
from rich.prompt import Confirm
|
||||
|
||||
from skyvern.cli.console import console
|
||||
from skyvern.cli.utils import start_services
|
||||
from skyvern.config import settings
|
||||
from skyvern.forge.sdk.core import skyvern_context
|
||||
from skyvern.forge.sdk.forge_log import setup_logger
|
||||
from skyvern.library.skyvern import Skyvern
|
||||
from skyvern.services.script_service import run_script
|
||||
from skyvern.utils import detect_os
|
||||
from skyvern.utils.env_paths import resolve_backend_env_path, resolve_frontend_env_path
|
||||
|
||||
from .console import console
|
||||
|
||||
run_app = typer.Typer(help="Commands to run Skyvern services such as the API server or UI.")
|
||||
|
||||
mcp = FastMCP("Skyvern")
|
||||
@@ -163,3 +167,135 @@ def run_mcp() -> None:
|
||||
# This breaks the MCP processing because it expects json output only
|
||||
# console.print(Panel("[bold green]Starting MCP Server...[/bold green]", border_style="green"))
|
||||
mcp.run(transport="stdio")
|
||||
|
||||
|
||||
@run_app.command(
|
||||
name="code",
|
||||
context_settings={"allow_interspersed_args": False},
|
||||
)
|
||||
def run_code(
|
||||
script_path: str = typer.Argument(..., help="Path to the Python script to run"),
|
||||
params: List[str] = typer.Option([], "-p", help="Parameters in format param=value (without leading dash)"),
|
||||
params_json: str = typer.Option(None, "--params", help="JSON string of parameters"),
|
||||
params_file: str = typer.Option(None, "--params-file", help="Path to JSON file with parameters"),
|
||||
ai: Optional[str] = typer.Option(
|
||||
"fallback", "--ai", help="AI mode to use for the script. Options: fallback, proactive or None"
|
||||
),
|
||||
) -> None:
|
||||
"""Run a Python script with parameters.
|
||||
|
||||
Supports three ways to pass parameters (in order of priority):
|
||||
|
||||
1. JSON file (highest priority):
|
||||
skyvern run code main.py --params-file params.json
|
||||
|
||||
2. JSON string:
|
||||
skyvern run code main.py --params '{"param1": "val1", "param2": "val2"}'
|
||||
|
||||
3. Individual flags (lowest priority):
|
||||
skyvern run code main.py -p param1=val1 -p param2=val2
|
||||
|
||||
Note: For backward compatibility, leading dashes in -p values are automatically stripped.
|
||||
"""
|
||||
# Disable LiteLLM loggers
|
||||
os.environ["LITELLM_LOG"] = "CRITICAL"
|
||||
import litellm # noqa: PLC0415
|
||||
|
||||
litellm.suppress_debug_info = True
|
||||
litellm.set_verbose = False
|
||||
|
||||
logging.getLogger("LiteLLM").setLevel(logging.CRITICAL)
|
||||
logging.getLogger("LiteLLM Router").setLevel(logging.CRITICAL)
|
||||
logging.getLogger("LiteLLM Proxy").setLevel(logging.CRITICAL)
|
||||
settings.LOG_LEVEL = "CRITICAL"
|
||||
setup_logger()
|
||||
|
||||
# Validate script path
|
||||
if not script_path:
|
||||
console.print("[red]❌ Error: No script path provided[/red]")
|
||||
console.print("[yellow]→ Action: Provide a path to your Python script[/yellow]")
|
||||
console.print("[blue]Example: skyvern run code main.py -p param1=value1[/blue]")
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
if not os.path.exists(script_path):
|
||||
console.print("[red]❌ Error: Cannot find script file[/red]")
|
||||
console.print(f"[yellow]→ Looked for: {script_path}[/yellow]")
|
||||
console.print("[yellow]→ Action: Check that the file exists and the path is correct[/yellow]")
|
||||
# Show current directory to help user understand relative paths
|
||||
console.print(f"[blue]Current directory: {os.getcwd()}[/blue]")
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
if not script_path.endswith(".py"):
|
||||
console.print("[red]❌ Error: Invalid file type[/red]")
|
||||
console.print(f"[yellow]→ Provided: {script_path}[/yellow]")
|
||||
console.print("[yellow]→ Action: Please provide a Python script file ending with .py[/yellow]")
|
||||
console.print("[blue]Example: skyvern run code my_script.py[/blue]")
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
parameters = {}
|
||||
|
||||
# Priority: params_file > params_json > individual -p flags
|
||||
if params_file:
|
||||
try:
|
||||
with open(params_file) as f:
|
||||
parameters = json.load(f)
|
||||
console.print(f"[blue]✓ Loaded parameters from file: {params_file}[/blue]")
|
||||
except FileNotFoundError:
|
||||
console.print("[red]❌ Error: Cannot find parameters file[/red]")
|
||||
console.print(f"[yellow]→ Looked for: {params_file}[/yellow]")
|
||||
console.print("[yellow]→ Action: Check that the file exists and the path is correct[/yellow]")
|
||||
console.print(f"[blue]Current directory: {os.getcwd()}[/blue]")
|
||||
raise typer.Exit(code=1)
|
||||
except json.JSONDecodeError as e:
|
||||
console.print("[red]❌ Error: Invalid JSON format in parameters file[/red]")
|
||||
console.print(f"[yellow]→ File: {params_file}[/yellow]")
|
||||
console.print(f"[yellow]→ Details: {e}[/yellow]")
|
||||
console.print("[yellow]→ Action: Fix the JSON syntax in your parameters file[/yellow]")
|
||||
console.print('[blue]Expected format: {{"param1": "value1", "param2": "value2"}}[/blue]')
|
||||
raise typer.Exit(code=1)
|
||||
elif params_json:
|
||||
try:
|
||||
parameters = json.loads(params_json)
|
||||
console.print("[blue]✓ Loaded parameters from JSON string[/blue]")
|
||||
except json.JSONDecodeError as e:
|
||||
console.print("[red]❌ Error: Invalid JSON format in --params string[/red]")
|
||||
console.print(f"[yellow]→ Details: {e}[/yellow]")
|
||||
console.print("[yellow]→ Action: Check your JSON syntax (quotes, brackets, commas)[/yellow]")
|
||||
console.print('[blue]Example: --params \'{{"param1": "value1", "param2": "value2"}}\'[/blue]')
|
||||
raise typer.Exit(code=1)
|
||||
elif params:
|
||||
for param in params:
|
||||
# Remove leading dash if present (for backward compatibility)
|
||||
if param.startswith("-"):
|
||||
param = param[1:]
|
||||
|
||||
if "=" in param:
|
||||
key, value = param.split("=", 1)
|
||||
parameters[key] = value
|
||||
else:
|
||||
console.print("[yellow]⚠️ Warning: Skipping invalid parameter format[/yellow]")
|
||||
console.print(f"[yellow]→ Invalid: {param}[/yellow]")
|
||||
console.print("[yellow]→ Expected format: -p param=value[/yellow]")
|
||||
console.print("[blue]Example: -p download_start_date=31/07/2025[/blue]")
|
||||
console.print("[blue]✓ Loaded parameters from command-line flags[/blue]")
|
||||
|
||||
console.print(Panel(f"[bold green]Running script: {script_path}[/bold green]", border_style="green"))
|
||||
if parameters:
|
||||
console.print("[blue]📋 Parameters:[/blue]")
|
||||
console.print(f"[blue]{json.dumps(parameters, indent=2)}[/blue]")
|
||||
else:
|
||||
console.print("[blue]ℹ️ Running script without parameters[/blue]")
|
||||
console.print("[dim]Tip: Add parameters with -p, --params, or --params-file[/dim]")
|
||||
|
||||
# set up skyvern context
|
||||
|
||||
skyvern_context.set(skyvern_context.SkyvernContext(script_mode=True, ai_mode_override=ai))
|
||||
try:
|
||||
asyncio.run(run_script(path=script_path, parameters=parameters))
|
||||
console.print("✅ [green]Script execution completed successfully![/green]")
|
||||
except Exception as e:
|
||||
console.print("[red]❌ Error: Script execution failed[/red]")
|
||||
console.print(f"[yellow]→ Script: {script_path}[/yellow]")
|
||||
console.print(f"[yellow]→ Details: {e}[/yellow]")
|
||||
console.print("[yellow]→ Action: Check the error message above and fix any issues in your script[/yellow]")
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
Reference in New Issue
Block a user