"""Launch the local Phill Swarm App. Portable launcher: py launch_swarm_app.py --install python scripts/launch_swarm_app.py --install --config configs/phill_swarm_app.json The same file works from a source checkout or from a packaged HF snapshot. """ import argparse import importlib.util import os from pathlib import Path import shutil import subprocess import sys def _detect_root() -> Path: here = Path(__file__).resolve() candidates = [here.parent, Path.cwd()] if len(here.parents) > 1: candidates.append(here.parents[1]) for root in candidates: if (root / "swarm_app_config.example.json").exists() or (root / "config.json").exists(): return root.resolve() if (root / "configs" / "phill_swarm_app.json").exists(): return root.resolve() if (root / "src" / "swarm_moe_model").exists(): return root.resolve() return here.parent.resolve() PROJECT_ROOT = _detect_root() SRC_ROOT = PROJECT_ROOT / "src" if SRC_ROOT.exists(): sys.path.insert(0, str(SRC_ROOT)) from swarm_moe_model.swarm_app import SwarmAppConfig, run_app # noqa: E402 def _run(cmd, cwd=None, timeout=600): print(f"[swarm-app] run: {' '.join(str(x) for x in cmd)}") return subprocess.run(cmd, cwd=str(cwd) if cwd else None, check=True, timeout=timeout) def _default_config() -> Path: candidates = [ PROJECT_ROOT / "swarm_app_config.example.json", Path.cwd() / "swarm_app_config.example.json", PROJECT_ROOT / "configs" / "phill_swarm_app.json", ] for path in candidates: if path.exists(): return path.resolve() return candidates[0] def _tools_dir(explicit: str | None = None) -> Path: if explicit: return Path(os.path.expandvars(explicit)).expanduser().resolve() candidates = [ PROJECT_ROOT / "src" / "swarm_moe_model" / "Tools", PROJECT_ROOT / "swarm_moe_model" / "Tools", ] spec = importlib.util.find_spec("swarm_moe_model") if spec and spec.submodule_search_locations: candidates.append(Path(list(spec.submodule_search_locations)[0]) / "Tools") for path in candidates: if path.exists(): return path.resolve() return candidates[0].resolve() def ensure_python_extras(include_browser: bool = True): packages = [] if importlib.util.find_spec("ddgs") is None and importlib.util.find_spec("duckduckgo_search") is None: packages.append("ddgs") if include_browser and importlib.util.find_spec("playwright") is None: packages.append("playwright") if importlib.util.find_spec("yaml") is None: packages.append("pyyaml") if packages: _run([sys.executable, "-m", "pip", "install", *packages], cwd=PROJECT_ROOT) if include_browser: try: import playwright # noqa: F401 _run([sys.executable, "-m", "playwright", "install", "chromium"], cwd=PROJECT_ROOT, timeout=1200) except Exception as exc: print(f"[swarm-app] WARN playwright browser install skipped/failed: {exc}") def ensure_npm_tools(tools_dir: Path): npm = shutil.which("npm.cmd" if os.name == "nt" else "npm") or shutil.which("npm") if npm is None: print("[swarm-app] WARN npm not found; Python tools still work, Wrench TypeScript tools remain catalogued.") return if not (tools_dir / "package.json").exists(): print("[swarm-app] WARN Tools/package.json not found; skipping npm install.") return command = "ci" if (tools_dir / "package-lock.json").exists() else "install" _run([npm, command], cwd=tools_dir, timeout=1200) def main(): ap = argparse.ArgumentParser() ap.add_argument("--config", default=str(_default_config())) ap.add_argument("--host", default=None) ap.add_argument("--port", type=int, default=None) ap.add_argument("--model", default=None) ap.add_argument("--permission-mode", choices=["default", "yolo"], default=None) ap.add_argument("--install", action="store_true", help="install optional DDGS/Playwright/npm tool dependencies before launch") ap.add_argument("--no-browser-install", action="store_true") ap.add_argument("--no-npm-install", action="store_true") ap.add_argument("--tools-dir", default=None, help="optional Tools directory for npm install") ap.add_argument("--headless", action="store_true", help="run Playwright browser tools headless to save memory") ap.add_argument("--visible-browser", action="store_true", help="force visible Playwright browser windows for browser tools") ap.add_argument("--capture-dir", default=None, help="browser snapshot directory; defaults to state/browser_captures") ap.add_argument("--no-open", action="store_true") args = ap.parse_args() if args.install: ensure_python_extras(include_browser=not args.no_browser_install) if not args.no_npm_install: ensure_npm_tools(_tools_dir(args.tools_dir)) cfg = SwarmAppConfig.from_json(args.config) if args.host: cfg.host = args.host if args.port: cfg.port = args.port if args.model: cfg.model_path = args.model cfg.swarm_config = None if args.permission_mode: cfg.permission_mode = args.permission_mode if args.headless: cfg.browser_headless = True if args.visible_browser: cfg.browser_headless = False if args.capture_dir: cfg.browser_capture_dir = args.capture_dir if args.no_open: cfg.auto_open = False run_app(cfg, project_root=PROJECT_ROOT) if __name__ == "__main__": main()