"""Mesh-free preview launcher for the MeshAI dashboard API. Stands up ONLY the FastAPI dashboard (NO MeshAI instance, NO mesh connector, NO broadcast loop) so the region-routing UI can be developed and tested without a live mesh radio. Usage: python3 scripts/preview_dashboard.py [config_dir] config_dir defaults to /tmp/meshai-region-preview/config. The directory may be empty — an empty/missing config.yaml yields safe defaults. The script sets app.state.config and app.state.config_path so all config-backed endpoints work. Endpoints that read other app.state attrs (notification_router, env_store, alert_engine, etc.) guard with getattr(..., None) already, so they return graceful empty/404 responses rather than crashing. Run vite separately for the frontend: cd dashboard-frontend && npm run dev Then hit the API at http://localhost:8082/api/... """ import sys from pathlib import Path # Resolve the config directory from the first CLI arg or use the default preview path. cfg_dir = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("/tmp/meshai-region-preview/config") cfg_dir.mkdir(parents=True, exist_ok=True) # Ensure there is a minimal config.yaml so load_config doesn't error. _cfg_yaml = cfg_dir / "config.yaml" if not _cfg_yaml.exists(): _cfg_yaml.write_text("# meshai preview config\n") # Import AFTER path is ready so relative imports resolve correctly. from meshai.dashboard.server import create_app from meshai.config_loader import load_config import uvicorn app = create_app() cfg = load_config(cfg_dir) app.state.config = cfg app.state.config_path = cfg_dir / "config.yaml" # Stub out attrs that endpoints guard with getattr(..., None) to avoid AttributeError # on first access in case FastAPI touches app.state eagerly. for _attr in ("notification_router", "env_store", "alert_engine", "health_engine", "data_store", "connector", "bus", "danger_correlator", "mesh_context", "broadcaster"): if not hasattr(app.state, _attr): setattr(app.state, _attr, None) # PREVIEW-ONLY: coverage is a restart-required section, so a normal deploy keeps # the running config stale until restart. In the preview we want create/delete/save # to reflect immediately, so reload the in-memory config from disk after any # /api/config write. (Prod keeps the restart-gated semantics; this hook is local.) @app.middleware("http") async def _reload_config_after_write(request, call_next): response = await call_next(request) try: if (request.method in ("PUT", "POST", "DELETE", "PATCH") and request.url.path.startswith("/api/config")): app.state.config = load_config(cfg_dir) except Exception: pass return response if __name__ == "__main__": print(f"[preview] config dir : {cfg_dir}") print(f"[preview] config path: {app.state.config_path}") print(f"[preview] listening : http://0.0.0.0:8082") print(f"[preview] regions API: http://localhost:8082/api/notifications/regions") print(f"[preview] matrix API : http://localhost:8082/api/notifications/region-routing") uvicorn.run(app, host="0.0.0.0", port=8082)