Skyvern/skyvern/cli/commands.py
Suchintan ff57f9977c
Update docs plus init (#2073)
Co-authored-by: Shuchang Zheng <wintonzheng0325@gmail.com>
2025-04-03 00:46:57 -04:00

753 lines
28 KiB
Python

import asyncio
import json
import os
import shutil
import subprocess
import time
import uuid
from pathlib import Path
from typing import Optional
import typer
import uvicorn
from dotenv import load_dotenv, set_key
from mcp.server.fastmcp import FastMCP
from skyvern.agent import SkyvernAgent
from skyvern.config import settings
from skyvern.forge import app
from skyvern.forge.sdk.db.enums import OrganizationAuthTokenType
from skyvern.utils import detect_os, get_windows_appdata_roaming, migrate_db
load_dotenv()
cli_app = typer.Typer()
run_app = typer.Typer()
cli_app.add_typer(run_app, name="run")
mcp = FastMCP("Skyvern")
@mcp.tool()
async def skyvern_run_task(prompt: str, url: str) -> str:
"""Browse the internet using a browser to achieve a user goal.
Args:
prompt: brief description of what the user wants to accomplish
url: the target website for the user goal
"""
skyvern_agent = SkyvernAgent(
base_url=settings.SKYVERN_BASE_URL,
api_key=settings.SKYVERN_API_KEY,
extra_headers={"X-User-Agent": "skyvern-mcp"},
)
res = await skyvern_agent.run_task(prompt=prompt, url=url)
return res.model_dump()["output"]
def command_exists(command: str) -> bool:
return shutil.which(command) is not None
def run_command(command: str, check: bool = True) -> tuple[Optional[str], Optional[int]]:
try:
result = subprocess.run(command, shell=True, check=check, capture_output=True, text=True)
return result.stdout.strip(), result.returncode
except subprocess.CalledProcessError as e:
return None, e.returncode
def is_postgres_running() -> bool:
if command_exists("pg_isready"):
result, _ = run_command("pg_isready")
return result is not None and "accepting connections" in result
return False
def database_exists(dbname: str, user: str) -> bool:
check_db_command = f'psql {dbname} -U {user} -c "\\q"'
output, _ = run_command(check_db_command, check=False)
return output is not None
def create_database_and_user() -> None:
print("Creating database user and database...")
run_command("createuser skyvern")
run_command("createdb skyvern -O skyvern")
print("Database and user created successfully.")
def is_docker_running() -> bool:
if not command_exists("docker"):
return False
_, code = run_command("docker info", check=False)
return code == 0
def is_postgres_running_in_docker() -> bool:
_, code = run_command("docker ps | grep -q postgresql-container", check=False)
return code == 0
def is_postgres_container_exists() -> bool:
_, code = run_command("docker ps -a | grep -q postgresql-container", check=False)
return code == 0
def setup_postgresql() -> None:
print("Setting up PostgreSQL...")
if command_exists("psql") and is_postgres_running():
print("PostgreSQL is already running locally.")
if database_exists("skyvern", "skyvern"):
print("Database and user exist.")
else:
create_database_and_user()
return
if not is_docker_running():
print("Docker is not running or not installed. Please install or start Docker and try again.")
exit(1)
if is_postgres_running_in_docker():
print("PostgreSQL is already running in a Docker container.")
else:
print("Attempting to install PostgreSQL via Docker...")
if not is_postgres_container_exists():
run_command(
"docker run --name postgresql-container -e POSTGRES_HOST_AUTH_METHOD=trust -d -p 5432:5432 postgres:14"
)
else:
run_command("docker start postgresql-container")
print("PostgreSQL has been installed and started using Docker.")
print("Waiting for PostgreSQL to start...")
time.sleep(20)
_, code = run_command('docker exec postgresql-container psql -U postgres -c "\\du" | grep -q skyvern', check=False)
if code == 0:
print("Database user exists.")
else:
print("Creating database user...")
run_command("docker exec postgresql-container createuser -U postgres skyvern")
_, code = run_command(
"docker exec postgresql-container psql -U postgres -lqt | cut -d \\| -f 1 | grep -qw skyvern", check=False
)
if code == 0:
print("Database exists.")
else:
print("Creating database...")
run_command("docker exec postgresql-container createdb -U postgres skyvern -O skyvern")
print("Database and user created successfully.")
def update_or_add_env_var(key: str, value: str) -> None:
"""Update or add environment variable in .env file."""
env_path = Path(".env")
if not env_path.exists():
env_path.touch()
# Write default environment variables using dotenv
defaults = {
"ENV": "local",
"ENABLE_OPENAI": "false",
"OPENAI_API_KEY": "",
"ENABLE_ANTHROPIC": "false",
"ANTHROPIC_API_KEY": "",
"ENABLE_AZURE": "false",
"AZURE_DEPLOYMENT": "",
"AZURE_API_KEY": "",
"AZURE_API_BASE": "",
"AZURE_API_VERSION": "",
"ENABLE_AZURE_GPT4O_MINI": "false",
"AZURE_GPT4O_MINI_DEPLOYMENT": "",
"AZURE_GPT4O_MINI_API_KEY": "",
"AZURE_GPT4O_MINI_API_BASE": "",
"AZURE_GPT4O_MINI_API_VERSION": "",
"ENABLE_GEMINI": "false",
"GEMINI_API_KEY": "",
"ENABLE_NOVITA": "false",
"NOVITA_API_KEY": "",
"LLM_KEY": "",
"SECONDARY_LLM_KEY": "",
"BROWSER_TYPE": "chromium-headful",
"MAX_SCRAPING_RETRIES": "0",
"VIDEO_PATH": "./videos",
"BROWSER_ACTION_TIMEOUT_MS": "5000",
"MAX_STEPS_PER_RUN": "50",
"LOG_LEVEL": "INFO",
"DATABASE_STRING": "postgresql+psycopg://skyvern@localhost/skyvern",
"PORT": "8000",
"ANALYTICS_ID": "anonymous",
"ENABLE_LOG_ARTIFACTS": "false",
}
for k, v in defaults.items():
set_key(env_path, k, v)
load_dotenv(env_path)
set_key(env_path, key, value)
def setup_llm_providers() -> None:
"""Configure Large Language Model (LLM) Providers."""
print("Configuring Large Language Model (LLM) Providers...")
print("Note: All information provided here will be stored only on your local machine.")
model_options = []
# OpenAI Configuration
print("To enable OpenAI, you must have an OpenAI API key.")
enable_openai = input("Do you want to enable OpenAI (y/n)? ").lower() == "y"
if enable_openai:
openai_api_key = input("Enter your OpenAI API key: ")
if not openai_api_key:
print("Error: OpenAI API key is required.")
print("OpenAI will not be enabled.")
else:
update_or_add_env_var("OPENAI_API_KEY", openai_api_key)
update_or_add_env_var("ENABLE_OPENAI", "true")
model_options.extend(["OPENAI_GPT4O"])
else:
update_or_add_env_var("ENABLE_OPENAI", "false")
# Anthropic Configuration
print("To enable Anthropic, you must have an Anthropic API key.")
enable_anthropic = input("Do you want to enable Anthropic (y/n)? ").lower() == "y"
if enable_anthropic:
anthropic_api_key = input("Enter your Anthropic API key: ")
if not anthropic_api_key:
print("Error: Anthropic API key is required.")
print("Anthropic will not be enabled.")
else:
update_or_add_env_var("ANTHROPIC_API_KEY", anthropic_api_key)
update_or_add_env_var("ENABLE_ANTHROPIC", "true")
model_options.extend(
[
"ANTHROPIC_CLAUDE3_OPUS",
"ANTHROPIC_CLAUDE3_HAIKU",
"ANTHROPIC_CLAUDE3.5_SONNET",
"ANTHROPIC_CLAUDE3.7_SONNET",
]
)
else:
update_or_add_env_var("ENABLE_ANTHROPIC", "false")
# Azure Configuration
print("To enable Azure, you must have an Azure deployment name, API key, base URL, and API version.")
enable_azure = input("Do you want to enable Azure (y/n)? ").lower() == "y"
if enable_azure:
azure_deployment = input("Enter your Azure deployment name: ")
azure_api_key = input("Enter your Azure API key: ")
azure_api_base = input("Enter your Azure API base URL: ")
azure_api_version = input("Enter your Azure API version: ")
if not all([azure_deployment, azure_api_key, azure_api_base, azure_api_version]):
print("Error: All Azure fields must be populated.")
print("Azure will not be enabled.")
else:
update_or_add_env_var("AZURE_DEPLOYMENT", azure_deployment)
update_or_add_env_var("AZURE_API_KEY", azure_api_key)
update_or_add_env_var("AZURE_API_BASE", azure_api_base)
update_or_add_env_var("AZURE_API_VERSION", azure_api_version)
update_or_add_env_var("ENABLE_AZURE", "true")
model_options.append("AZURE_OPENAI_GPT4O")
else:
update_or_add_env_var("ENABLE_AZURE", "false")
# Gemini Configuration
print("To enable Gemini, you must have an Gemini API key.")
enable_gemini = input("Do you want to enable Gemini (y/n)? ").lower() == "y"
if enable_gemini:
gemini_api_key = input("Enter your Gemini API key: ")
if not gemini_api_key:
print("Error: Gemini API key is required.")
print("Gemini will not be enabled.")
else:
update_or_add_env_var("GEMINI_API_KEY", gemini_api_key)
update_or_add_env_var("ENABLE_GEMINI", "true")
model_options.extend(["GEMINI_FLASH_2_0", "GEMINI_FLASH_2_0_LITE", "GEMINI_PRO"])
else:
update_or_add_env_var("ENABLE_GEMINI", "false")
# Novita AI Configuration
print("To enable Novita AI, you must have an Novita AI API key.")
enable_novita = input("Do you want to enable Novita AI (y/n)? ").lower() == "y"
if enable_novita:
novita_api_key = input("Enter your Novita AI API key: ")
if not novita_api_key:
print("Error: Novita AI API key is required.")
print("Novita AI will not be enabled.")
else:
update_or_add_env_var("NOVITA_API_KEY", novita_api_key)
update_or_add_env_var("ENABLE_NOVITA", "true")
model_options.extend(
[
"NOVITA_DEEPSEEK_R1",
"NOVITA_DEEPSEEK_V3",
"NOVITA_LLAMA_3_3_70B",
"NOVITA_LLAMA_3_2_1B",
"NOVITA_LLAMA_3_2_3B",
"NOVITA_LLAMA_3_2_11B_VISION",
"NOVITA_LLAMA_3_1_8B",
"NOVITA_LLAMA_3_1_70B",
"NOVITA_LLAMA_3_1_405B",
"NOVITA_LLAMA_3_8B",
"NOVITA_LLAMA_3_70B",
]
)
else:
update_or_add_env_var("ENABLE_NOVITA", "false")
# Model Selection
if not model_options:
print(
"No LLM providers enabled. You won't be able to run Skyvern unless you enable at least one provider. You can re-run this script to enable providers or manually update the .env file."
)
else:
print("Available LLM models based on your selections:")
for i, model in enumerate(model_options, 1):
print(f"{i}. {model}")
while True:
try:
model_choice = int(input(f"Choose a model by number (e.g., 1 for {model_options[0]}): "))
if 1 <= model_choice <= len(model_options):
break
print(f"Please enter a number between 1 and {len(model_options)}")
except ValueError:
print("Please enter a valid number")
chosen_model = model_options[model_choice - 1]
print(f"Chosen LLM Model: {chosen_model}")
update_or_add_env_var("LLM_KEY", chosen_model)
print("LLM provider configurations updated in .env.")
def get_default_chrome_location(host_system: str) -> str:
"""Get the default Chrome/Chromium location based on OS."""
if host_system == "darwin":
return "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
elif host_system == "linux":
# Common Linux locations
chrome_paths = ["/usr/bin/google-chrome", "/usr/bin/chromium", "/usr/bin/chromium-browser"]
for path in chrome_paths:
if os.path.exists(path):
return path
return "/usr/bin/google-chrome" # default if not found
elif host_system == "wsl":
return "/mnt/c/Program Files/Google/Chrome/Application/chrome.exe"
else:
return "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
def setup_browser_config() -> tuple[str, Optional[str], Optional[str]]:
"""Configure browser settings for Skyvern."""
print("\nConfiguring web browser for scraping...")
browser_types = ["chromium-headless", "chromium-headful", "cdp-connect"]
for i, browser_type in enumerate(browser_types, 1):
print(f"{i}. {browser_type}")
if browser_type == "chromium-headless":
print(" - Runs Chrome in headless mode (no visible window)")
elif browser_type == "chromium-headful":
print(" - Runs Chrome with visible window")
elif browser_type == "cdp-connect":
print(" - Connects to an existing Chrome instance")
print(" - Requires Chrome to be running with remote debugging enabled")
while True:
try:
choice = int(input("\nChoose browser type (1-3): "))
if 1 <= choice <= len(browser_types):
selected_browser = browser_types[choice - 1]
break
print(f"Please enter a number between 1 and {len(browser_types)}")
except ValueError:
print("Please enter a valid number")
browser_location = None
remote_debugging_url = None
if selected_browser == "cdp-connect":
host_system = detect_os()
default_location = get_default_chrome_location(host_system)
print(f"\nDefault Chrome location for your system: {default_location}")
browser_location = input("Enter Chrome executable location (press Enter to use default): ").strip()
if not browser_location:
browser_location = default_location
if not os.path.exists(browser_location):
print(f"Warning: Chrome not found at {browser_location}. Please verify the location is correct.")
print("\nTo use CDP connection, Chrome must be running with remote debugging enabled.")
print("Example: chrome --remote-debugging-port=9222")
print("Default debugging URL: http://localhost:9222")
remote_debugging_url = input("Enter remote debugging URL (press Enter for default): ").strip()
if not remote_debugging_url:
remote_debugging_url = "http://localhost:9222"
return selected_browser, browser_location, remote_debugging_url
async def _setup_local_organization() -> str:
"""
Returns the API key for the local organization generated
"""
skyvern_agent = SkyvernAgent()
organization = await skyvern_agent.get_organization()
org_auth_token = await app.DATABASE.get_valid_org_auth_token(
organization_id=organization.organization_id,
token_type=OrganizationAuthTokenType.api,
)
return org_auth_token.token if org_auth_token else ""
@cli_app.command(name="migrate")
def migrate() -> None:
migrate_db()
def get_claude_config_path(host_system: str) -> str:
"""Get the Claude Desktop config file path for the current OS."""
if host_system == "wsl":
roaming_path = get_windows_appdata_roaming()
if roaming_path is None:
raise RuntimeError("Could not locate Windows AppData\\Roaming path from WSL")
return os.path.join(str(roaming_path), "Claude", "claude_desktop_config.json")
base_paths = {
"darwin": ["~/Library/Application Support/Claude"],
"linux": ["~/.config/Claude", "~/.local/share/Claude", "~/Claude"],
}
if host_system == "darwin":
base_path = os.path.expanduser(base_paths["darwin"][0])
return os.path.join(base_path, "claude_desktop_config.json")
if host_system == "linux":
for path in base_paths["linux"]:
full_path = os.path.expanduser(path)
if os.path.exists(full_path):
return os.path.join(full_path, "claude_desktop_config.json")
raise Exception(f"Unsupported host system: {host_system}")
def get_claude_command_config(
host_system: str, path_to_env: str, path_to_server: str, env_vars: str
) -> tuple[str, list]:
"""Get the command and arguments for Claude Desktop configuration."""
base_env_vars = f"{env_vars} ENABLE_OPENAI=true LOG_LEVEL=CRITICAL"
artifacts_path = os.path.join(os.path.abspath("./"), "artifacts")
if host_system == "wsl":
env_vars = f"{base_env_vars} ARTIFACT_STORAGE_PATH={artifacts_path} BROWSER_TYPE=chromium-headless"
return "wsl.exe", ["bash", "-c", f"{env_vars} {path_to_env} {path_to_server}"]
if host_system in ["linux", "darwin"]:
env_vars = f"{base_env_vars} ARTIFACT_STORAGE_PATH={artifacts_path}"
return path_to_env, [path_to_server]
raise Exception(f"Unsupported host system: {host_system}")
def is_claude_desktop_installed(host_system: str) -> bool:
"""Check if Claude Desktop is installed by looking for its config directory."""
try:
config_path = os.path.dirname(get_claude_config_path(host_system))
return os.path.exists(config_path)
except Exception:
return False
def get_cursor_config_path(host_system: str) -> str:
"""Get the Cursor config file path for the current OS."""
if host_system == "wsl":
roaming_path = get_windows_appdata_roaming()
if roaming_path is None:
raise RuntimeError("Could not locate Windows AppData\\Roaming path from WSL")
return os.path.join(str(roaming_path), ".cursor", "mcp.json")
# For both darwin and linux, use ~/.cursor/mcp.json
return os.path.expanduser("~/.cursor/mcp.json")
def is_cursor_installed(host_system: str) -> bool:
"""Check if Cursor is installed by looking for its config directory."""
try:
config_dir = os.path.expanduser("~/.cursor")
return os.path.exists(config_dir)
except Exception:
return False
def is_windsurf_installed(host_system: str) -> bool:
"""Check if Windsurf is installed by looking for its config directory."""
try:
config_dir = os.path.expanduser("~/.codeium/windsurf")
return os.path.exists(config_dir)
except Exception:
return False
def get_windsurf_config_path(host_system: str) -> str:
"""Get the Windsurf config file path for the current OS."""
return os.path.expanduser("~/.codeium/windsurf/mcp_config.json")
def setup_windsurf_config(host_system: str, path_to_env: str) -> bool:
"""Set up Windsurf configuration for Skyvern MCP."""
if not is_windsurf_installed(host_system):
return False
load_dotenv(".env")
skyvern_base_url = os.environ.get("SKYVERN_BASE_URL", "")
skyvern_api_key = os.environ.get("SKYVERN_API_KEY", "")
if not skyvern_base_url or not skyvern_api_key:
print(
"Error: SKYVERN_BASE_URL and SKYVERN_API_KEY must be set in .env file to set up Windsurf MCP. Please open {path_windsurf_config} and set the these variables manually."
)
try:
path_windsurf_config = get_windsurf_config_path(host_system)
os.makedirs(os.path.dirname(path_windsurf_config), exist_ok=True)
if not os.path.exists(path_windsurf_config):
with open(path_windsurf_config, "w") as f:
json.dump({"mcpServers": {}}, f, indent=2)
windsurf_config: dict = {"mcpServers": {}}
if os.path.exists(path_windsurf_config):
try:
with open(path_windsurf_config, "r") as f:
windsurf_config = json.load(f)
windsurf_config["mcpServers"].pop("Skyvern", None)
windsurf_config["mcpServers"]["Skyvern"] = {
"env": {
"SKYVERN_BASE_URL": skyvern_base_url,
"SKYVERN_API_KEY": skyvern_api_key,
},
"command": path_to_env,
"args": ["-m", "skyvern", "run", "mcp"],
}
except json.JSONDecodeError:
print(
f"JSONDecodeError when reading Error configuring Windsurf. Please open {path_windsurf_config} and fix the json config first."
)
return False
with open(path_windsurf_config, "w") as f:
json.dump(windsurf_config, f, indent=2)
except Exception as e:
print(f"Error configuring Windsurf: {e}")
return False
print(f"Windsurf MCP configuration updated successfully at {path_windsurf_config}.")
return True
def setup_mcp_config() -> str:
"""
return the path to the python environment
"""
python_path = shutil.which("python")
if python_path:
use_default = typer.prompt(f"Found Python at {python_path}. Use this path? (y/n)").lower() == "y"
if use_default:
path_to_env = python_path
else:
path_to_env = typer.prompt("Enter the full path to your configured python environment")
return path_to_env
def setup_claude_desktop_config(host_system: str, path_to_env: str) -> bool:
"""Set up Claude Desktop configuration with given command and args."""
if not is_claude_desktop_installed(host_system):
return False
try:
path_claude_config = get_claude_config_path(host_system)
os.makedirs(os.path.dirname(path_claude_config), exist_ok=True)
if not os.path.exists(path_claude_config):
with open(path_claude_config, "w") as f:
json.dump({"mcpServers": {}}, f, indent=2)
# Read environment variables from .env file
load_dotenv(".env")
skyvern_base_url = os.environ.get("SKYVERN_BASE_URL", "")
skyvern_api_key = os.environ.get("SKYVERN_API_KEY", "")
if not skyvern_base_url or not skyvern_api_key:
print("Error: SKYVERN_BASE_URL and SKYVERN_API_KEY must be set in .env file")
with open(path_claude_config, "r") as f:
claude_config = json.load(f)
claude_config["mcpServers"].pop("Skyvern", None)
claude_config["mcpServers"]["Skyvern"] = {
"env": {
"SKYVERN_BASE_URL": skyvern_base_url,
"SKYVERN_API_KEY": skyvern_api_key,
},
"command": path_to_env,
"args": ["-m", "skyvern", "run", "mcp"],
}
with open(path_claude_config, "w") as f:
json.dump(claude_config, f, indent=2)
print(f"Claude Desktop MCP configuration updated successfully at {path_claude_config}.")
return True
except Exception as e:
print(f"Error configuring Claude Desktop: {e}")
return False
def setup_cursor_config(host_system: str, path_to_env: str) -> bool:
"""Set up Cursor configuration with given command and args."""
if not is_cursor_installed(host_system):
return False
try:
path_cursor_config = get_cursor_config_path(host_system)
os.makedirs(os.path.dirname(path_cursor_config), exist_ok=True)
if not os.path.exists(path_cursor_config):
with open(path_cursor_config, "w") as f:
json.dump({"mcpServers": {}}, f, indent=2)
load_dotenv(".env")
skyvern_base_url = os.environ.get("SKYVERN_BASE_URL", "")
skyvern_api_key = os.environ.get("SKYVERN_API_KEY", "")
if not skyvern_base_url or not skyvern_api_key:
print(
f"Error: SKYVERN_BASE_URL and SKYVERN_API_KEY must be set in .env file to set up Cursor MCP. Please open {path_cursor_config} and set the these variables manually."
)
cursor_config: dict = {"mcpServers": {}}
if os.path.exists(path_cursor_config):
try:
with open(path_cursor_config, "r") as f:
cursor_config = json.load(f)
cursor_config["mcpServers"].pop("Skyvern", None)
cursor_config["mcpServers"]["Skyvern"] = {
"env": {
"SKYVERN_BASE_URL": skyvern_base_url,
"SKYVERN_API_KEY": skyvern_api_key,
},
"command": path_to_env,
"args": ["-m", "skyvern", "run", "mcp"],
}
except json.JSONDecodeError:
print(
f"JSONDecodeError when reading Error configuring Cursor. Please open {path_cursor_config} and fix the json config first."
)
return False
with open(path_cursor_config, "w") as f:
json.dump(cursor_config, f, indent=2)
print(f"Cursor MCP configuration updated successfully at {path_cursor_config}")
return True
except Exception as e:
print(f"Error configuring Cursor: {e}")
return False
def setup_mcp() -> None:
"""Configure MCP for different Skyvern deployments."""
host_system = detect_os()
path_to_env = setup_mcp_config()
# Configure both Claude Desktop and Cursor
setup_claude_desktop_config(host_system, path_to_env)
setup_cursor_config(host_system, path_to_env)
setup_windsurf_config(host_system, path_to_env)
@run_app.command(name="server")
def run_server() -> None:
load_dotenv()
from skyvern.config import settings
port = settings.PORT
uvicorn.run(
"skyvern.forge.api_app:app",
host="0.0.0.0",
port=port,
log_level="info",
)
@run_app.command(name="mcp")
def run_mcp() -> None:
"""Run the MCP server."""
mcp.run(transport="stdio")
@cli_app.command(name="init")
def init() -> None:
run_local_str = (
input("Would you like to run Skyvern locally or in the cloud? (local/cloud) [cloud]: ").strip().lower()
)
run_local = run_local_str == "local" if run_local_str else False
if run_local:
setup_postgresql()
api_key = asyncio.run(_setup_local_organization())
if os.path.exists(".env"):
print(".env file already exists, skipping initialization.")
redo_llm_setup = input("Do you want to go through LLM provider setup again (y/n)? ")
if redo_llm_setup.lower() != "y":
return
print("Initializing .env file...")
setup_llm_providers()
# Configure browser settings
browser_type, browser_location, remote_debugging_url = setup_browser_config()
update_or_add_env_var("BROWSER_TYPE", browser_type)
if browser_location:
update_or_add_env_var("CHROME_EXECUTABLE_PATH", browser_location)
if remote_debugging_url:
update_or_add_env_var("BROWSER_REMOTE_DEBUGGING_URL", remote_debugging_url)
print("Defaulting Skyvern Base URL to: http://localhost:8000")
update_or_add_env_var("SKYVERN_BASE_URL", "http://localhost:8000")
else:
base_url = input("Enter Skyvern base URL (press Enter for https://api.skyvern.com): ").strip()
if not base_url:
base_url = "https://api.skyvern.com"
print("To get your API key:")
print("1. Create an account at https://app.skyvern.com")
print("2. Go to Settings")
print("3. Copy your API key")
api_key = input("Enter your Skyvern API key: ").strip()
if not api_key:
print("API key is required")
api_key = input("Enter your Skyvern API key: ").strip()
update_or_add_env_var("SKYVERN_BASE_URL", base_url)
# Ask for email or generate UUID
analytics_id = input("Please enter your email for analytics (press enter to skip): ")
if not analytics_id:
analytics_id = str(uuid.uuid4())
update_or_add_env_var("ANALYTICS_ID", analytics_id)
update_or_add_env_var("SKYVERN_API_KEY", api_key)
print(".env file has been initialized.")
# Ask if user wants to configure MCP server
configure_mcp = input("\nWould you like to configure the MCP server (y/n)? ").lower() == "y"
if configure_mcp:
setup_mcp()
print("\nMCP server configuration completed.")