"""First-run launcher. Bootstraps a clean Python 3.11 environment via `uv`, regardless of the system Python the user invoked us with. Keeps the user on a single supported runtime while the AI ecosystem stabilizes around newer Python versions. uv install strategy (in order): 1. Direct binary download from GitHub releases (no shell, no admin). 2. PowerShell / shell installer. 3. pip fallback, invoked as `python -m uv`. """ from __future__ import annotations import io import json import os import platform import shutil import subprocess import sys import urllib.request import zipfile from pathlib import Path ROOT = Path(__file__).parent.resolve() VENV_DIR = ROOT / "venv" MARKER = VENV_DIR / ".kawai_ready" HARDWARE_CACHE = ROOT / "config.local.json" UV_CACHE_DIR = ROOT / ".tools" PYTHON_TARGET = "3.11" def venv_python() -> Path: if os.name == "nt": return VENV_DIR / "Scripts" / "python.exe" return VENV_DIR / "bin" / "python" # --- uv discovery / install ------------------------------------------------ def _local_uv_path() -> Path: return UV_CACHE_DIR / ("uv.exe" if os.name == "nt" else "uv") def _uv_argv() -> list[str] | None: """Return argv prefix to invoke uv. None if not available.""" local = _local_uv_path() if local.exists(): return [str(local)] found = shutil.which("uv") if found: return [found] # Installed via pip into current Python (works as module). try: subprocess.check_output( [sys.executable, "-m", "uv", "--version"], stderr=subprocess.STDOUT, timeout=10, ) return [sys.executable, "-m", "uv"] except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired): return None def _uv_release_asset() -> str | None: """Pick the GitHub release asset name for this OS+arch.""" machine = platform.machine().lower() arch_win = "x86_64" if machine in ("amd64", "x86_64") else "aarch64" if "arm" in machine else None if os.name == "nt" and arch_win: return f"uv-{arch_win}-pc-windows-msvc.zip" if sys.platform == "darwin": arch = "aarch64" if "arm" in machine else "x86_64" return f"uv-{arch}-apple-darwin.tar.gz" if sys.platform.startswith("linux"): arch = "aarch64" if "aarch64" in machine or "arm64" in machine else "x86_64" return f"uv-{arch}-unknown-linux-gnu.tar.gz" return None def _download_uv() -> bool: """Download uv binary directly from GitHub releases. Most reliable path.""" asset = _uv_release_asset() if asset is None: return False url = f"https://github.com/astral-sh/uv/releases/latest/download/{asset}" UV_CACHE_DIR.mkdir(parents=True, exist_ok=True) print(f"[kawai] Downloading uv from {url}") try: with urllib.request.urlopen(url, timeout=60) as resp: data = resp.read() except Exception as e: print(f"[kawai] download failed: {e}") return False try: if asset.endswith(".zip"): with zipfile.ZipFile(io.BytesIO(data)) as z: for name in z.namelist(): if name.endswith("uv.exe") or name.endswith("/uv"): target = _local_uv_path() target.write_bytes(z.read(name)) if os.name != "nt": target.chmod(0o755) return target.exists() else: import tarfile with tarfile.open(fileobj=io.BytesIO(data), mode="r:gz") as t: for member in t.getmembers(): if member.name.endswith("/uv") or member.name == "uv": f = t.extractfile(member) if f is None: continue target = _local_uv_path() target.write_bytes(f.read()) target.chmod(0o755) return target.exists() except Exception as e: print(f"[kawai] extract failed: {e}") return False return False def _install_uv_via_shell() -> bool: """Use astral.sh installer scripts. Often blocked on locked-down systems.""" UV_CACHE_DIR.mkdir(parents=True, exist_ok=True) env = os.environ.copy() env["UV_INSTALL_DIR"] = str(UV_CACHE_DIR) env["UV_NO_MODIFY_PATH"] = "1" try: if os.name == "nt": subprocess.check_call( [ "powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", "irm https://astral.sh/uv/install.ps1 | iex", ], env=env, ) else: subprocess.check_call( ["bash", "-c", "curl -LsSf https://astral.sh/uv/install.sh | sh"], env=env, ) except (subprocess.CalledProcessError, FileNotFoundError): return False return _local_uv_path().exists() def _install_uv_via_pip() -> bool: print("[kawai] Installing uv via pip...") try: subprocess.check_call([sys.executable, "-m", "pip", "install", "--user", "--upgrade", "uv"]) except subprocess.CalledProcessError: return False # Verify it's invokable as a module. try: subprocess.check_output( [sys.executable, "-m", "uv", "--version"], stderr=subprocess.STDOUT, timeout=10, ) return True except Exception: return False def _ensure_uv() -> list[str]: argv = _uv_argv() if argv: return argv print("[kawai] Installing uv...") if _download_uv(): return _uv_argv() or [] if _install_uv_via_shell(): argv = _uv_argv() if argv: return argv if _install_uv_via_pip(): argv = _uv_argv() if argv: return argv raise RuntimeError( "Failed to install uv. Install manually from https://astral.sh/uv " "and rerun this launcher." ) # --- venv + deps ----------------------------------------------------------- def _create_venv(uv: list[str]) -> None: if venv_python().exists(): return print(f"[kawai] Creating venv with Python {PYTHON_TARGET} (uv will download it if needed)...") subprocess.check_call([*uv, "venv", str(VENV_DIR), "--python", PYTHON_TARGET]) def _uv_pip(uv: list[str], args: list[str]) -> None: cmd = [*uv, "pip", "install", "--python", str(venv_python()), *args] print(f"[kawai] uv pip install {' '.join(args)}") subprocess.check_call(cmd) def detect_and_install(uv: list[str]) -> dict: sys.path.insert(0, str(ROOT)) from backends import hardware info = hardware.detect() print(f"[kawai] Detected: {info.vendor} / {info.device_name} / {info.vram_gb:.1f} GB / tier={info.tier}") _uv_pip(uv, hardware.torch_install_args(info)) _uv_pip(uv, ["-r", str(ROOT / "requirements.txt")]) payload = { "vendor": info.vendor, "backend": info.backend, "device_name": info.device_name, "vram_gb": info.vram_gb, "tier": info.tier, } HARDWARE_CACHE.write_text(json.dumps(payload, indent=2)) MARKER.write_text("ok") return payload def already_in_venv() -> bool: try: return Path(sys.executable).resolve() == venv_python().resolve() except OSError: return False def relaunch_in_venv() -> None: """Re-exec the launcher inside the venv. Use subprocess on Windows because os.execv mangles argv with spaces in paths.""" print("[kawai] Relaunching inside venv...") py = str(venv_python()) script = str(ROOT / "launcher.py") if os.name == "nt": result = subprocess.run([py, script]) sys.exit(result.returncode) else: os.execv(py, [py, script]) def main() -> None: if already_in_venv(): if not MARKER.exists(): uv = _ensure_uv() detect_and_install(uv) from app import run run() return uv = _ensure_uv() _create_venv(uv) if not MARKER.exists(): detect_and_install(uv) relaunch_in_venv() if __name__ == "__main__": main()