"""Thin wrappers around the store modules + helpers the GUI pages share. Putting these here means each page imports `from gui import services as svc` rather than importing internal store modules directly. If we later swap an implementation (e.g. switching memory backend), only this file changes. """ import os import sys import subprocess # Make sure project root is on sys.path when launched as `python -m gui` # from inside C:\Jarvis\python\. _PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if _PROJECT_ROOT not in sys.path: sys.path.insert(0, _PROJECT_ROOT) import memory_store import profiles_store import macros_store import scheduler_store import plugins_store import llm_backend # ---------- memory ---------- def memory_all() -> list[dict]: return memory_store.all_facts() def memory_remember(key: str, value: str) -> None: memory_store.remember(key, value) def memory_forget(key: str) -> bool: return memory_store.forget(key) def memory_search(query: str, limit: int = 5) -> list[dict]: return memory_store.search(query, limit) # ---------- profiles ---------- def profile_list() -> list[str]: return profiles_store.list_names() def profile_active() -> str: return profiles_store.active_name() def profile_set(name: str) -> dict: return profiles_store.set_active(name) # ---------- macros ---------- def macros_all() -> list[dict]: out = [] for name in macros_store.list_names(): m = macros_store.get(name) or {} out.append({ "name": name, "steps_count": len(m.get("steps", [])), "steps": m.get("steps", []), "created_at": m.get("created_at", 0), "last_run": m.get("last_run"), }) return out def macros_delete(name: str) -> bool: return macros_store.delete(name) def macros_recording() -> tuple[bool, str | None]: return macros_store.is_recording(), macros_store.recording_name() # ---------- scheduler ---------- def scheduler_all() -> list[dict]: return scheduler_store.list_all() def scheduler_remove(task_id: str) -> bool: return scheduler_store.remove(task_id) # ---------- plugins ---------- def plugins_all() -> list[dict]: return plugins_store.list_packs() def plugins_open_folder() -> str: p = plugins_store.plugins_dir() if os.name == "nt": try: subprocess.Popen(["explorer.exe", p]) except OSError: pass return p def plugins_set_enabled(name: str, enabled: bool) -> tuple[bool, str]: return plugins_store.set_enabled(name, enabled) # ---------- LLM ---------- def llm_active() -> tuple[str, str]: return llm_backend.current_backend(), llm_backend.current_model() def llm_swap(target: str) -> str: return llm_backend.swap_to(target) # ---------- assistant launcher ---------- def assistant_run() -> bool: """Spawn main.py in a detached subprocess. Returns True on launch.""" try: main_py = os.path.join(_PROJECT_ROOT, "main.py") # CREATE_NEW_PROCESS_GROUP so it doesn't get killed with the GUI creationflags = 0 if os.name == "nt": creationflags = 0x00000200 # CREATE_NEW_PROCESS_GROUP subprocess.Popen( [sys.executable, main_py], cwd=_PROJECT_ROOT, creationflags=creationflags, ) return True except OSError as e: print(f"[gui] failed to launch assistant: {e}") return False def assistant_is_running() -> bool: """Crude check: look for a python process running main.py from our dir. Implementation note: we deliberately avoid pulling psutil — keeps the GUI install footprint small. The Rust fork uses sysinfo; here we just scan `tasklist` on Windows. """ if os.name != "nt": return False try: out = subprocess.run( ["tasklist", "/FI", "IMAGENAME eq python.exe", "/V", "/FO", "CSV"], capture_output=True, text=True, timeout=2, ) except (OSError, subprocess.SubprocessError): return False return "main.py" in out.stdout