"""Local backend process skeleton (U-007): pure stdlib `http.server`, no third-party deps (05 §5.7) -- this process is also where the Graph Engine scheduler will live later (05 §5.4.1), but that's a different Unit. MVP scope here: process start/stop, and `c_pick_folder` (03-C §3.-1) as the one real route -- macOS only for now (`osascript`); Windows/Linux native dialogs are PH3 (U-022). """ import json import platform import subprocess from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer class DialogUnavailableError(Exception): """DIALOG_UNAVAILABLE -- 03-C §3.-1: no native-dialog capability here.""" def pick_folder(): """macOS implementation of c_pick_folder. Returns {"selected_path": str} on success, {"selected_path": None} on user cancel (03-C: not an error). Raises DialogUnavailableError off macOS or if osascript itself fails for a reason other than the user cancelling.""" if platform.system() != "Darwin": raise DialogUnavailableError("native folder picker only implemented for macOS so far (PH3 adds Windows/Linux)") script = 'POSIX path of (choose folder with prompt "Select a graphlemon project folder")' result = subprocess.run(["osascript", "-e", script], capture_output=True, text=True) if result.returncode != 0: if "User canceled" in result.stderr or "(-128)" in result.stderr: return {"selected_path": None} raise DialogUnavailableError(result.stderr.strip()) return {"selected_path": result.stdout.strip()} class _Handler(BaseHTTPRequestHandler): def do_POST(self): if self.path == "/api/system/pick-folder": self._handle_pick_folder() else: self._send_json(404, {"error": "NOT_FOUND"}) def _handle_pick_folder(self): try: self._send_json(200, pick_folder()) except DialogUnavailableError as e: self._send_json(422, {"error": "DIALOG_UNAVAILABLE", "detail": str(e)}) def _send_json(self, status, obj): body = json.dumps(obj).encode("utf-8") self.send_response(status) self.send_header("Content-Type", "application/json") self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) def log_message(self, format, *args): pass # quiets test output; real request logging is a later Unit's concern def create_server(host="127.0.0.1", port=0): """port=0 lets the OS assign a free port -- avoids collisions in tests and lets multiple graphlemon instances run side by side if ever needed.""" return ThreadingHTTPServer((host, port), _Handler)