#!/usr/bin/env python r"""YahBible launcher -- the entry point PyInstaller freezes into YahBible.exe. Out of the box: double-click. On first run it downloads the study library from Hugging Face (OhBeOneKeyNoBe/YahBible) into a local folder, then starts the local server and opens the app in your browser. Subsequent runs are instant. Works run-in-place OR installed -- the library lives in %LOCALAPPDATA%\YahBible so it persists either way. The library mirrors the app's expected layout under one BASE dir, and YAHBIBLE_BASE points the server at it (the server falls back to D:\ only on the dev machine). """ import os import sys import threading import time import webbrowser REPO = "OhBeOneKeyNoBe/YahBible" PORT = 41537 # study library: HF path -> subpath under BASE (mirrors the app's expected tree) LIB = { "data/watchman.db": "watchman/watchman.db", "data/reflected_red_basic.sqlite": "Holorites_data/daeos/reflected_red_basic.sqlite", "data/reflected_red.sqlite": "Holorites_data/reflected_red.sqlite", "data/taviel_versions.sqlite": "Holorites_data/daeos/taviel_versions.sqlite", "data/taviel_corpus.sqlite": "Holorites_data/daeos/taviel_corpus.sqlite", "data/english_dict.sqlite": "Holorites_data/daeos/english_dict.sqlite", "data/piper/en_US-lessac-medium.onnx": "Holorites_data/daeos/piper/en_US-lessac-medium.onnx", "data/piper/en_US-lessac-medium.onnx.json": "Holorites_data/daeos/piper/en_US-lessac-medium.onnx.json", "data/reflected_vocal.sqlite": "Holorites/torus_upgrades/reflected_vocal.sqlite", "data/elaniel_yt.sqlite": "Holorites_data/daeos/elaniel_yt.sqlite", "data/taviel_redletter.sqlite": "Holorites_data/daeos/taviel_redletter.sqlite", "data/taviel_doctrine.sqlite": "Holorites_data/daeos/taviel_doctrine.sqlite", } # the big lexicon (interlinear + Strong's + dictionary). Optional first-run: it is 70GB, so we # fetch it in the background after the app is already usable, unless YAHBIBLE_SKIP_LEX is set. BIG = "data/reflected_red.sqlite" # --- CODE auto-update ------------------------------------------------------- # The app CODE (server + UI + study data) lives in the HF repo under code/ and is pulled on every # launch so fixes reach users WITHOUT shipping a new exe. Downloaded modules are loaded over the # frozen copies; any failure falls back to the frozen build, so the app always starts. CODE_FILES = ["o_taviel_ui.py", "o_taviel_server.py", "taviel_commandments.json", "taviel_repentance.json", "taviel_news.json", # the Gauntlet: served by o_taviel_server, so installed desktops pull these too "taviel_apologetics.py", "taviel_reason.py", "gauntlet_kb.json", "taviel_doctrine.py", # THE JEV FRONT DOOR. taviel_reason.serve() routes through these # when the decision server and the embedder are both reachable, # answering identity, vetted, verse, clarify and decline with no # generated tokens at all. Uploading them to the repo was not # enough: this list is what an installed desktop actually # fetches, so a module missing from here is a module that never # arrives, and the front door would sit in the repo doing # nothing. They are SAFE to ship to a desktop with no fork # binary -- serve() gates on a cheap probe and falls back to the # verbatim gauntlet exactly as before. "taviel_frontdoor.py", "taviel_decide.py", "taviel_person.py", "taviel_contrast.py", "taviel_verify.py", # completed interlinear for verses the lexicon lacks (KJV<->MT versification # divergences + NT doxologies) -- so installed desktops match web/mobile "canon_fill.sqlite", # BlackBox split-key auth: o_taviel_server imports these to publish the two # encrypted credential halves (HF + GitHub) on signup / password change "yahbible_auth.py", "auth_sync.py"] def ensure_code(base): cdir = os.path.join(base, "code") os.makedirs(cdir, exist_ok=True) try: from huggingface_hub import hf_hub_download except Exception: return cdir import shutil for fn in CODE_FILES: for _ in range(3): try: cached = hf_hub_download(repo_id=REPO, filename="code/" + fn, repo_type="model") tmp = os.path.join(cdir, fn + ".tmp") shutil.copy2(cached, tmp) os.replace(tmp, os.path.join(cdir, fn)) break except Exception as e: print(" code sync skip %s (%s)" % (fn, str(e)[:40]), flush=True) time.sleep(2) return cdir def load_updated_code(cdir): """Load the freshly-downloaded o_taviel_ui / o_taviel_server over the frozen copies.""" import importlib.util for mod in ("o_taviel_ui", "o_taviel_server"): p = os.path.join(cdir, mod + ".py") if not (os.path.exists(p) and os.path.getsize(p) > 0): continue try: spec = importlib.util.spec_from_file_location(mod, p) m = importlib.util.module_from_spec(spec) sys.modules[mod] = m spec.loader.exec_module(m) print(" updated %s loaded from repo" % mod, flush=True) except Exception as e: print(" updated %s failed, using built-in (%s)" % (mod, str(e)[:60]), flush=True) sys.modules.pop(mod, None) def base_dir(): b = os.environ.get("YAHBIBLE_BASE") if b: return b root = os.environ.get("LOCALAPPDATA") or os.path.expanduser("~") b = os.path.join(root, "YahBible", "library") os.makedirs(b, exist_ok=True) return b def have(base, sub): p = os.path.join(base, sub.replace("/", os.sep)) return os.path.exists(p) and os.path.getsize(p) > 0 def fetch(base, hf_path, sub): from huggingface_hub import hf_hub_download dst = os.path.join(base, sub.replace("/", os.sep)) os.makedirs(os.path.dirname(dst), exist_ok=True) print(" downloading %s ..." % sub, flush=True) cached = hf_hub_download(repo_id=REPO, filename=hf_path, repo_type="model") # copy out of the HF cache into our mirrored layout (hardlink if possible) import shutil try: if os.path.exists(dst): os.remove(dst) os.link(cached, dst) except Exception: shutil.copy2(cached, dst) def ensure_library(base): small = {k: v for k, v in LIB.items() if k != BIG} missing = [(k, v) for k, v in small.items() if not have(base, v)] if missing: print("First run: fetching the YahBible study library (~1.2 GB) ...", flush=True) for k, v in missing: for _ in range(4): try: fetch(base, k, v) break except Exception as e: print(" retry (%s)" % str(e)[:60], flush=True) time.sleep(4) # the 70GB lexicon: fetch in the background so the app is usable immediately if not have(base, LIB[BIG]) and not os.environ.get("YAHBIBLE_SKIP_LEX"): def _bg(): for _ in range(6): try: print("Background: fetching the full lexicon (interlinear + Strong's, ~70 GB)...", flush=True) fetch(base, BIG, LIB[BIG]) print("Lexicon ready -- interlinear + Strong's now enabled.", flush=True) break except Exception as e: print(" lexicon retry (%s)" % str(e)[:60], flush=True) time.sleep(10) threading.Thread(target=_bg, daemon=True).start() def main(): # when frozen, our bundled modules sit next to the exe / in the PyInstaller temp here = os.path.dirname(os.path.abspath(sys.argv[0])) if getattr(sys, "_MEIPASS", None): here = sys._MEIPASS if here not in sys.path: sys.path.insert(0, here) base = base_dir() os.environ["YAHBIBLE_BASE"] = base print("YahBible -- library at:", base, flush=True) ensure_library(base) # pull the latest app code from the repo so fixes apply without a new exe try: cdir = ensure_code(base) os.environ["YAHBIBLE_CODE"] = cdir # so the server's _res() finds updated study data if cdir not in sys.path: sys.path.insert(0, cdir) load_updated_code(cdir) except Exception as e: print(" code auto-update skipped (%s)" % str(e)[:60], flush=True) # start the server (import after YAHBIBLE_BASE is set so its path constants resolve) import o_taviel_server as S threading.Thread(target=S.main, daemon=True).start() # wait for it to bind, then open the browser import socket for _ in range(120): s = socket.socket(); s.settimeout(0.4) try: s.connect(("127.0.0.1", PORT)); s.close(); break except Exception: time.sleep(0.5) webbrowser.open("http://127.0.0.1:%d/" % PORT) print("YahBible is running at http://127.0.0.1:%d/ (close this window to quit)" % PORT, flush=True) try: while True: time.sleep(3600) except KeyboardInterrupt: pass if __name__ == "__main__": main()