chore: env path refactor (#3691)
Co-authored-by: Suchintan <suchintan@users.noreply.github.com> Co-authored-by: Shuchang Zheng <wintonzheng0325@gmail.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import typer
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from skyvern.utils.env_paths import resolve_backend_env_path
|
||||
|
||||
from .docs import docs_app
|
||||
from .init_command import init, init_browser
|
||||
from .quickstart import quickstart_app
|
||||
@@ -54,5 +56,5 @@ def init_browser_command() -> None:
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover - manual CLI invocation
|
||||
load_dotenv()
|
||||
load_dotenv(resolve_backend_env_path())
|
||||
cli_app()
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import asyncio
|
||||
import os
|
||||
import subprocess
|
||||
import uuid
|
||||
|
||||
@@ -10,6 +9,7 @@ from rich.progress import Progress, SpinnerColumn, TextColumn
|
||||
from rich.prompt import Confirm, Prompt
|
||||
|
||||
from skyvern.utils import migrate_db
|
||||
from skyvern.utils.env_paths import resolve_backend_env_path
|
||||
|
||||
from .browser import setup_browser_config
|
||||
from .console import console
|
||||
@@ -50,8 +50,9 @@ def init(
|
||||
else:
|
||||
console.print("[red]Failed to generate local organization API key. Please check server logs.[/red]")
|
||||
|
||||
if os.path.exists(".env"):
|
||||
console.print("💡 [.env] file already exists.", style="yellow")
|
||||
backend_env_path = resolve_backend_env_path()
|
||||
if backend_env_path.exists():
|
||||
console.print(f"💡 [{backend_env_path}] file already exists.", style="yellow")
|
||||
redo_llm_setup = Confirm.ask(
|
||||
"Do you want to go through [bold yellow]LLM provider setup again[/bold yellow]?",
|
||||
default=False,
|
||||
@@ -108,7 +109,7 @@ def init(
|
||||
analytics_id = analytics_id_input if analytics_id_input else str(uuid.uuid4())
|
||||
update_or_add_env_var("ANALYTICS_ID", analytics_id)
|
||||
update_or_add_env_var("SKYVERN_API_KEY", api_key)
|
||||
console.print("✅ [green].env file has been initialized.[/green]")
|
||||
console.print(f"✅ [green]{resolve_backend_env_path()} file has been initialized.[/green]")
|
||||
|
||||
if Confirm.ask("\nWould you like to [bold yellow]configure the MCP server[/bold yellow]?", default=True):
|
||||
setup_mcp()
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
from pathlib import Path
|
||||
|
||||
from dotenv import load_dotenv, set_key
|
||||
from rich.panel import Panel
|
||||
from rich.prompt import Confirm, Prompt
|
||||
|
||||
from skyvern.utils.env_paths import resolve_backend_env_path
|
||||
|
||||
from .console import console
|
||||
|
||||
|
||||
def update_or_add_env_var(key: str, value: str) -> None:
|
||||
"""Update or add environment variable in .env file."""
|
||||
env_path = Path(".env")
|
||||
env_path = resolve_backend_env_path()
|
||||
if not env_path.exists():
|
||||
env_path.touch()
|
||||
defaults = {
|
||||
|
||||
@@ -11,6 +11,7 @@ from skyvern.forge import app
|
||||
from skyvern.forge.sdk.db.enums import OrganizationAuthTokenType
|
||||
from skyvern.library import Skyvern
|
||||
from skyvern.utils import detect_os, get_windows_appdata_roaming
|
||||
from skyvern.utils.env_paths import resolve_backend_env_path
|
||||
|
||||
from .console import console
|
||||
|
||||
@@ -132,12 +133,13 @@ def setup_claude_desktop_config(host_system: str, path_to_env: str) -> bool:
|
||||
with open(path_claude_config, "w") as f:
|
||||
json.dump({"mcpServers": {}}, f, indent=2)
|
||||
|
||||
load_dotenv(".env")
|
||||
backend_env_path = resolve_backend_env_path()
|
||||
load_dotenv(backend_env_path)
|
||||
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:
|
||||
console.print(
|
||||
f"[red]Error: SKYVERN_BASE_URL and SKYVERN_API_KEY must be set in .env file to set up Claude MCP. Please open {path_claude_config} and set these variables manually.[/red]"
|
||||
f"[red]Error: SKYVERN_BASE_URL and SKYVERN_API_KEY must be set in {backend_env_path} to set up Claude MCP. Please open {path_claude_config} and set these variables manually.[/red]"
|
||||
)
|
||||
return False
|
||||
|
||||
@@ -184,12 +186,13 @@ def setup_cursor_config(host_system: str, path_to_env: str) -> bool:
|
||||
with open(path_cursor_config, "w") as f:
|
||||
json.dump({"mcpServers": {}}, f, indent=2)
|
||||
|
||||
load_dotenv(".env")
|
||||
backend_env_path = resolve_backend_env_path()
|
||||
load_dotenv(backend_env_path)
|
||||
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:
|
||||
console.print(
|
||||
f"[red]Error: SKYVERN_BASE_URL and SKYVERN_API_KEY must be set in .env file to set up Cursor MCP. Please open [link]{path_cursor_config}[/link] and set these variables manually.[/red]"
|
||||
f"[red]Error: SKYVERN_BASE_URL and SKYVERN_API_KEY must be set in {backend_env_path} to set up Cursor MCP. Please open [link]{path_cursor_config}[/link] and set these variables manually.[/red]"
|
||||
)
|
||||
return False
|
||||
|
||||
@@ -228,12 +231,13 @@ def setup_windsurf_config(host_system: str, path_to_env: str) -> bool:
|
||||
return False
|
||||
|
||||
path_windsurf_config = get_windsurf_config_path(host_system)
|
||||
load_dotenv(".env")
|
||||
backend_env_path = resolve_backend_env_path()
|
||||
load_dotenv(backend_env_path)
|
||||
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:
|
||||
console.print(
|
||||
f"[red]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 these variables manually.[/red]"
|
||||
f"[red]Error: SKYVERN_BASE_URL and SKYVERN_API_KEY must be set in {backend_env_path} to set up Windsurf MCP. Please open {path_windsurf_config} and set these variables manually.[/red]"
|
||||
)
|
||||
|
||||
try:
|
||||
|
||||
@@ -2,7 +2,6 @@ import asyncio
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Any, List
|
||||
|
||||
import psutil
|
||||
@@ -17,6 +16,7 @@ from skyvern.cli.utils import start_services
|
||||
from skyvern.config import settings
|
||||
from skyvern.library.skyvern import Skyvern
|
||||
from skyvern.utils import detect_os
|
||||
from skyvern.utils.env_paths import resolve_backend_env_path, resolve_frontend_env_path
|
||||
|
||||
from .console import console
|
||||
|
||||
@@ -83,8 +83,7 @@ def kill_pids(pids: List[int]) -> None:
|
||||
@run_app.command(name="server")
|
||||
def run_server() -> None:
|
||||
"""Run the Skyvern API server."""
|
||||
load_dotenv()
|
||||
load_dotenv(".env")
|
||||
load_dotenv(resolve_backend_env_path())
|
||||
from skyvern.config import settings # noqa: PLC0415
|
||||
|
||||
port = settings.PORT
|
||||
@@ -117,68 +116,27 @@ def run_ui() -> None:
|
||||
except Exception as e: # pragma: no cover - CLI safeguards
|
||||
console.print(f"[red]Error checking for process: {e}[/red]")
|
||||
|
||||
# Try multiple methods to find the frontend directory
|
||||
frontend_dir = None
|
||||
|
||||
# Method 1: Relative to current working directory
|
||||
cwd_frontend = Path.cwd() / "skyvern-frontend"
|
||||
if cwd_frontend.exists():
|
||||
frontend_dir = cwd_frontend
|
||||
|
||||
# Method 2: Relative to the module file (original method)
|
||||
if frontend_dir is None:
|
||||
module_based_frontend = Path(__file__).parent.parent.parent / "skyvern-frontend"
|
||||
if module_based_frontend.exists():
|
||||
frontend_dir = module_based_frontend
|
||||
|
||||
# Method 3: Search up the directory tree from current working directory
|
||||
if frontend_dir is None:
|
||||
current = Path.cwd()
|
||||
while current != current.parent: # Stop at filesystem root
|
||||
candidate = current / "skyvern-frontend"
|
||||
if candidate.exists() and candidate.is_dir():
|
||||
frontend_dir = candidate
|
||||
break
|
||||
current = current.parent
|
||||
|
||||
if frontend_dir is None:
|
||||
console.print(
|
||||
f"[bold red]ERROR: Skyvern Frontend directory not found. Searched in:[/bold red]\n"
|
||||
f" • {cwd_frontend}\n"
|
||||
f" • {Path(__file__).parent.parent.parent / 'skyvern-frontend'}\n"
|
||||
f" • Parent directories of {Path.cwd()}\n"
|
||||
f"[bold red]Are you in the right repo?[/bold red]"
|
||||
)
|
||||
frontend_env_path = resolve_frontend_env_path()
|
||||
if frontend_env_path is None:
|
||||
console.print("[bold red]ERROR: Skyvern Frontend directory not found.[/bold red]")
|
||||
return
|
||||
|
||||
frontend_env_path = frontend_dir / ".env"
|
||||
frontend_dir = frontend_env_path.parent
|
||||
if not frontend_env_path.exists():
|
||||
console.print("[bold blue]Setting up frontend .env file...[/bold blue]")
|
||||
shutil.copy(frontend_dir / ".env.example", frontend_env_path)
|
||||
console.print("✅ [green]Successfully set up frontend .env file[/green]")
|
||||
|
||||
# Look for .env file in multiple locations
|
||||
main_env_path = None
|
||||
env_search_paths = [
|
||||
Path.cwd() / ".env",
|
||||
frontend_dir.parent / ".env",
|
||||
Path(__file__).parent.parent.parent / ".env",
|
||||
]
|
||||
|
||||
for env_path in env_search_paths:
|
||||
if env_path.exists():
|
||||
main_env_path = env_path
|
||||
break
|
||||
|
||||
if main_env_path and main_env_path.exists():
|
||||
load_dotenv(main_env_path)
|
||||
backend_env_path = resolve_backend_env_path()
|
||||
if backend_env_path.exists():
|
||||
load_dotenv(backend_env_path)
|
||||
skyvern_api_key = os.getenv("SKYVERN_API_KEY")
|
||||
if skyvern_api_key:
|
||||
set_key(str(frontend_env_path), "VITE_SKYVERN_API_KEY", skyvern_api_key)
|
||||
set_key(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(f"[red]ERROR: Backend .env file not found at {backend_env_path}[/red]")
|
||||
|
||||
os.chdir(frontend_dir)
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ from rich.panel import Panel
|
||||
|
||||
from skyvern.client import Skyvern
|
||||
from skyvern.config import settings
|
||||
from skyvern.utils.env_paths import resolve_backend_env_path
|
||||
|
||||
from .console import console
|
||||
|
||||
@@ -33,8 +34,7 @@ def tasks_callback(
|
||||
|
||||
def _get_client(api_key: str | None = None) -> Skyvern:
|
||||
"""Instantiate a Skyvern SDK client using environment variables."""
|
||||
load_dotenv()
|
||||
load_dotenv(".env")
|
||||
load_dotenv(resolve_backend_env_path())
|
||||
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)
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import sys
|
||||
import typer
|
||||
|
||||
from skyvern.cli.console import console
|
||||
from skyvern.utils.env_paths import resolve_backend_env_path
|
||||
|
||||
|
||||
async def start_services(server_only: bool = False) -> None:
|
||||
@@ -28,7 +29,7 @@ async def start_services(server_only: bool = False) -> None:
|
||||
|
||||
console.print("\n🎉 [bold green]Skyvern is now running![/bold green]")
|
||||
console.print("🌐 [bold]Access the UI at:[/bold] [cyan]http://localhost:8080[/cyan]")
|
||||
console.print("🔑 [bold]Your API key is in your .env file as SKYVERN_API_KEY[/bold]")
|
||||
console.print(f"🔑 [bold]Your API key is in {resolve_backend_env_path()} as SKYVERN_API_KEY[/bold]")
|
||||
|
||||
# Wait for processes to complete (they won't unless killed)
|
||||
if not server_only:
|
||||
|
||||
@@ -11,6 +11,7 @@ from rich.panel import Panel
|
||||
|
||||
from skyvern.client import Skyvern
|
||||
from skyvern.config import settings
|
||||
from skyvern.utils.env_paths import resolve_backend_env_path
|
||||
|
||||
from .console import console
|
||||
from .tasks import _list_workflow_tasks
|
||||
@@ -34,8 +35,7 @@ def workflow_callback(
|
||||
|
||||
def _get_client(api_key: str | None = None) -> Skyvern:
|
||||
"""Instantiate a Skyvern SDK client using environment variables."""
|
||||
load_dotenv()
|
||||
load_dotenv(".env")
|
||||
load_dotenv(resolve_backend_env_path())
|
||||
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)
|
||||
|
||||
|
||||
@@ -2,10 +2,22 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
from skyvern import constants
|
||||
from skyvern.constants import SKYVERN_DIR
|
||||
from skyvern.utils.env_paths import resolve_backend_env_path
|
||||
|
||||
# NOTE: _DEFAULT_ENV_FILES resolves .env paths at import time and assumes
|
||||
# the process has changed dir to the desired project root by this time.
|
||||
# Even if we were to resolve paths at instantiation time, the global `settings`
|
||||
# singleton instantiation at the bottom of this file also runs at import time
|
||||
# and relies on the same assumption.
|
||||
_DEFAULT_ENV_FILES = (
|
||||
resolve_backend_env_path(".env"),
|
||||
resolve_backend_env_path(".env.staging"),
|
||||
resolve_backend_env_path(".env.prod"),
|
||||
)
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
model_config = SettingsConfigDict(env_file=(".env", ".env.staging", ".env.prod"), extra="ignore")
|
||||
model_config = SettingsConfigDict(env_file=_DEFAULT_ENV_FILES, extra="ignore")
|
||||
|
||||
# settings for experimentation
|
||||
ENABLE_EXP_ALL_TEXTUAL_ELEMENTS_INTERACTABLE: bool = False
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import asyncio
|
||||
import os
|
||||
import typing
|
||||
from typing import Any
|
||||
|
||||
@@ -28,6 +27,7 @@ from skyvern.schemas.run_blocks import CredentialType
|
||||
from skyvern.schemas.runs import CUA_ENGINES, ProxyLocation, RunEngine, RunStatus, RunType
|
||||
from skyvern.services import run_service, task_v1_service, task_v2_service
|
||||
from skyvern.utils import migrate_db
|
||||
from skyvern.utils.env_paths import resolve_backend_env_path
|
||||
|
||||
|
||||
class Skyvern(AsyncSkyvern):
|
||||
@@ -54,10 +54,11 @@ class Skyvern(AsyncSkyvern):
|
||||
httpx_client=httpx_client,
|
||||
)
|
||||
if base_url is None and api_key is None:
|
||||
if not os.path.exists(".env"):
|
||||
env_path = resolve_backend_env_path()
|
||||
if not env_path.exists():
|
||||
raise Exception("No .env file found. Please run 'skyvern init' first to set up your environment.")
|
||||
|
||||
load_dotenv(".env")
|
||||
load_dotenv(env_path)
|
||||
migrate_db()
|
||||
|
||||
self._api_key = api_key
|
||||
|
||||
46
skyvern/utils/env_paths.py
Normal file
46
skyvern/utils/env_paths.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
BACKEND_ENV_DEFAULT = ".env"
|
||||
FRONTEND_DIRNAME = "skyvern-frontend"
|
||||
FRONTEND_ENV_FILENAME = ".env"
|
||||
|
||||
|
||||
def resolve_backend_env_path(
|
||||
basename: str = BACKEND_ENV_DEFAULT,
|
||||
) -> Path:
|
||||
"""Return the backend env file path inside the current working directory."""
|
||||
|
||||
env_path = Path.cwd() / basename
|
||||
|
||||
return env_path
|
||||
|
||||
|
||||
def resolve_frontend_env_path() -> Optional[Path]:
|
||||
"""Return the path to the frontend .env file (may not exist)."""
|
||||
|
||||
frontend_root: Optional[Path] = None
|
||||
|
||||
if frontend_root is None:
|
||||
cwd_frontend = Path.cwd() / FRONTEND_DIRNAME
|
||||
if cwd_frontend.exists() and cwd_frontend.is_dir():
|
||||
frontend_root = cwd_frontend
|
||||
|
||||
if frontend_root is None:
|
||||
module_based_frontend = Path(__file__).resolve().parent.parent.parent / FRONTEND_DIRNAME
|
||||
if module_based_frontend.exists() and module_based_frontend.is_dir():
|
||||
frontend_root = module_based_frontend
|
||||
|
||||
if frontend_root is None:
|
||||
for parent in Path.cwd().parents:
|
||||
candidate = parent / FRONTEND_DIRNAME
|
||||
if candidate.exists() and candidate.is_dir():
|
||||
frontend_root = candidate
|
||||
break
|
||||
|
||||
if frontend_root is None:
|
||||
return None
|
||||
|
||||
env_path = frontend_root / FRONTEND_ENV_FILENAME
|
||||
|
||||
return env_path
|
||||
Reference in New Issue
Block a user