130 lines
5.9 KiB
Python
130 lines
5.9 KiB
Python
"""
|
||
MARK LIV — one-time setup.
|
||
|
||
Installs the Python dependencies for THIS operating system only: the OS-specific
|
||
packages in requirements.txt carry `sys_platform` markers, so a macOS or Linux
|
||
user never pulls Windows-only libraries (and vice-versa). Then it fetches the
|
||
Playwright browsers needed for web automation (current-OS builds only).
|
||
|
||
Two things it deliberately does NOT install:
|
||
* the optional local wake word ("Hey Jarvis") — one-click, opt-in, from
|
||
⚙ → WAKE WORD inside the app;
|
||
* anything for the avatar — the holographic head renders in software on the
|
||
PyQt6 and numpy already listed here. No GPU, no OpenGL, no extra packages.
|
||
"""
|
||
import platform
|
||
import subprocess
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
OS = platform.system() # "Windows" | "Darwin" | "Linux"
|
||
HERE = Path(__file__).resolve().parent
|
||
|
||
MIN_PY = (3, 11) # hard floor: below this the syntax used here won't parse
|
||
MAX_PY = (3, 13) # highest version this is actually tested on
|
||
|
||
|
||
def _run(label: str, args: list[str]) -> None:
|
||
print(f"\n▶ {label}")
|
||
subprocess.run(args, check=True)
|
||
|
||
|
||
def _check_python() -> None:
|
||
"""Fail immediately and clearly rather than deep inside a pip resolver.
|
||
|
||
A wrong interpreter is the single most common way this install goes sideways,
|
||
and the error it produces on its own names a wheel, not the real problem.
|
||
"""
|
||
v = sys.version_info[:2]
|
||
if v > MAX_PY:
|
||
# Newer is a warning, not a wall. Turning away someone who installed
|
||
# today's Python is a worse first impression than a version that
|
||
# turns out to work fine, and if a wheel really is missing pip says
|
||
# so plainly.
|
||
print(f"\n⚠️ Python {v[0]}.{v[1]} is newer than the "
|
||
f"{MAX_PY[0]}.{MAX_PY[1]} this is tested on. Continuing — if a "
|
||
f"package has no wheel yet, install Python "
|
||
f"{MAX_PY[0]}.{MAX_PY[1]} and run setup with that.")
|
||
return
|
||
if v < MIN_PY:
|
||
print(f"\n❌ Python {v[0]}.{v[1]} detected — MARK LIV needs at "
|
||
f"least Python {MIN_PY[0]}.{MIN_PY[1]}.")
|
||
print(" Install a supported version and run setup with it, e.g.:")
|
||
print(f" py -{MIN_PY[0]}.{MIN_PY[1]} setup.py (Windows)")
|
||
print(f" python{MIN_PY[0]}.{MIN_PY[1]} setup.py (macOS / Linux)")
|
||
sys.exit(1)
|
||
|
||
|
||
def _check_assets() -> None:
|
||
"""The avatar's face is a shipped file; a truncated clone should say so."""
|
||
face = HERE / "core" / "face_model.obj"
|
||
if not face.exists() or face.stat().st_size < 4096:
|
||
print(
|
||
"\n⚠️ core/face_model.obj is missing or truncated — the avatar will "
|
||
"fall back to the plain glowing core.\n"
|
||
" Re-clone the repository, or fetch that one file again."
|
||
)
|
||
|
||
|
||
def main() -> None:
|
||
print(f"⚙ MARK LIV setup — detected OS: {OS or 'unknown'}, "
|
||
f"Python {sys.version_info[0]}.{sys.version_info[1]}")
|
||
_check_python()
|
||
|
||
# requirements.txt filters OS-specific extras by itself via pip markers.
|
||
_run("Installing Python dependencies (OS-specific extras auto-filtered)…",
|
||
[sys.executable, "-m", "pip", "install", "-r", "requirements.txt"])
|
||
|
||
# Chromium covers Chrome/Edge/Opera/Brave/Vivaldi; Firefox for Firefox.
|
||
# (Safari automation additionally needs: python -m playwright install webkit)
|
||
# Not fatal: these are a few hundred megabytes from a CDN that a corporate
|
||
# network or a flaky connection can refuse, and everything except browser
|
||
# automation works without them. Failing the whole install there would send
|
||
# a user away from a working app.
|
||
try:
|
||
_run("Installing Playwright browsers (chromium + firefox)…",
|
||
[sys.executable, "-m", "playwright", "install", "chromium", "firefox"])
|
||
except (subprocess.CalledProcessError, FileNotFoundError) as e:
|
||
print(f"\n⚠️ Playwright browsers were not installed ({e}).")
|
||
print(" Everything except browser automation works. Retry later with:")
|
||
print(f' {sys.executable} -m playwright install chromium firefox')
|
||
|
||
_check_assets()
|
||
|
||
# ── OS-specific post-install notes ────────────────────────────────────────
|
||
if OS == "Windows":
|
||
try:
|
||
import win32com.client # noqa: F401
|
||
except ImportError:
|
||
postinstall = Path(sys.executable).parent / "Scripts" / "pywin32_postinstall.py"
|
||
print(
|
||
"\n⚠️ pywin32 did not register correctly — desktop-shortcut "
|
||
"creation will use a slower fallback. To fix it, run:\n"
|
||
f' "{sys.executable}" -m pip install --force-reinstall pywin32\n'
|
||
f' "{sys.executable}" "{postinstall}" -install'
|
||
)
|
||
elif OS == "Linux":
|
||
print(
|
||
"\nℹ️ Linux note — a few voice-controlled OS actions shell out to "
|
||
"native tools. Install the ones you'll use via your package manager:\n"
|
||
" • volume → pulseaudio-utils (pactl)\n"
|
||
" • brightness → brightnessctl\n"
|
||
" • reminders → systemd (systemd-run) or 'at'\n"
|
||
" • open URLs → xdg-utils (xdg-open)"
|
||
)
|
||
elif OS == "Darwin":
|
||
print(
|
||
"\nℹ️ macOS note — volume, brightness and reminders use the built-in "
|
||
"'osascript' / LaunchAgents, so no extra tools are required.\n"
|
||
" For Safari automation only: python -m playwright install webkit"
|
||
)
|
||
|
||
print("\n✅ Setup complete!")
|
||
print(" 1) Launch it: python main.py")
|
||
print(" 2) Paste your free Gemini API key when the setup screen appears.")
|
||
print(" 3) (Optional) Enable 'Hey Jarvis' from ⚙ → WAKE WORD.")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|