#!/usr/bin/env python3 """Open the NexusOS UI in a native window. Uses pywebview, which renders via the WebView2 runtime on Windows (already present on Win10/11) -- a real app window with no browser chrome and none of the Edge --app profile cold-start. Blocks until the window is closed; the launcher waits on this process and stops the services when it exits. The window opens IMMEDIATELY on a loading page and navigates to the app once the backend answers, rather than waiting up to 40s with nothing on screen and then appearing. One window the whole time: nothing pops up and vanishes, and there is no moment where the user is left wondering whether the command did anything. The loading page's status line is updated live (backend -> ready) via evaluate_js, instead of sitting on one static sentence for the whole wait - the point is a user watching the window can tell what stage it's stuck on, without needing to go read a terminal or a log file to find out. Falls back to the default browser if pywebview/WebView2 is unavailable, staying alive so the launcher doesn't tear the services down underneath it. """ import os import sys import time import urllib.request URL = "http://localhost:8000" ICON_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "assets", "NexusOS.ico") # Inline so it renders with no server and no asset files - the whole point is # that it shows before anything is listening. Colours match the app's dark UI so # the swap to the real page is not a flash of a different-looking window. The #

is the hook _set_status() rewrites as boot moves through its stages. LOADING_HTML = """ NexusOS

Starting NexusOS

Starting backend...

""" FAILED_HTML = LOADING_HTML.replace( "
", "" ).replace( "Starting NexusOS", "Backend did not start" ).replace( 'id="status">Starting backend...', 'id="status">Nothing answered on :8000 after 40s. Check: ncp logs -b', ) def _set_status(win, text: str) -> None: # Best-effort: the window can be mid-teardown (user closed it while this # background thread was still polling), and evaluate_js on a dead window # raises - that's not a reason to kill the polling loop. try: win.evaluate_js(f"document.getElementById('status').textContent = {text!r}") except Exception: pass def _http_ok(url: str, timeout: float = 2.0) -> bool: try: with urllib.request.urlopen(url, timeout=timeout) as r: return r.status == 200 except Exception: return False def _wait_for_backend(win, timeout: float = 40.0) -> bool: """Poll the backend, updating the on-screen status as it comes up - so the window never loads before the server is up, which shows a localhost error the webview won't retry.""" status_url = URL.rstrip("/") + "/status" deadline = time.time() + timeout _set_status(win, "Starting backend...") while time.time() < deadline: try: # 5s, not 2: /status probes Ollama, and a wedged Ollama made it # slower than a 2s ceiling - the backend was up and answering 200 # while this loop timed out on every attempt and declared it dead. # The server side is fixed too; this is the margin. with urllib.request.urlopen(status_url, timeout=5) as r: if r.status == 200: _set_status(win, "Opening Nexus...") return True except Exception: pass time.sleep(0.5) return False def main() -> int: try: import webview except Exception as e: # pywebview not installed return _browser_fallback(f"pywebview unavailable ({e})") try: window = webview.create_window( "NexusOS", html=LOADING_HTML, width=1200, height=800, min_size=(900, 600), ) def _swap_in_app(win): """Runs once the GUI loop is up, so the spinner is already on screen while we wait. load_url replaces the loading page in place - there is never a second window to close.""" if _wait_for_backend(win): win.load_url(URL) else: print("[nexus] backend not reachable on :8000 after 40s.", file=sys.stderr) win.load_html(FAILED_HTML) icon = ICON_PATH if os.path.isfile(ICON_PATH) else None webview.start(_swap_in_app, window, icon=icon) # blocks until the window is closed return 0 except Exception as e: # no WebView2 runtime / backend failure return _browser_fallback(f"native window failed ({e})") def _browser_fallback(reason: str) -> int: import webbrowser print(f"[nexus] {reason}; opening default browser instead.", file=sys.stderr) webbrowser.open(URL) # Stay alive so the launcher keeps the services up. The user stops NexusOS # by closing the launcher (or the browser tab, then the services idle out). try: while True: time.sleep(3600) except KeyboardInterrupt: return 0 if __name__ == "__main__": sys.exit(main())