"""Desktop (Electron) app: build/stamp, stage-and-swap pack, exe integrity gate, macOS signing/TCC, Linux sandbox, launch (hermes gui/desktop). Split out of ``hermes_cli/main.py``. Names that still live in main (``PROJECT_ROOT``, ...) are imported lazily inside the functions that use them (avoids an import cycle). """ import logging import contextlib import argparse import hashlib import json import os import platform import re import shlex import shutil import stat import subprocess import sys import tempfile import time as _time_mod from pathlib import Path from typing import Callable, Optional from hermes_cli.desktop_console import desktop_console_output, desktop_launch_notice from hermes_platform.host import facts # Log-record parity with the origin module. logger = logging.getLogger("hermes_cli.main") _PREVIOUS_APP_KEPT = " ↩ The previous desktop app was left untouched and still works." def _desktop_dist_exists(desktop_dir: Path) -> bool: """Return True when a local desktop renderer build is present.""" return (desktop_dir / "dist" / "index.html").exists() def _renderer_bundle_dir(desktop_dir: Path, *, source_mode: bool) -> Optional[Path]: """The renderer ``dist`` a launch loads: ``apps/desktop/dist`` in source mode, else the ``app.asar.unpacked/dist`` copy (the only real directory, and the one an interrupted replace tears).""" if source_mode: return desktop_dir / "dist" executable = _desktop_packaged_executable(desktop_dir) if executable is None: return None # macOS: …/Hermes.app/Contents/MacOS/Hermes → …/Contents/Resources resources = ( executable.parent.parent / "Resources" if sys.platform == "darwin" else executable.parent / "resources" ) return resources / "app.asar.unpacked" / "dist" # The module files the renderer fetches before any app code runs: Vite emits # them as `