Clean up unused BMAD workflow, agent, and command files across all IDE configurations (.agent, .clinerules, .cursor, .gemini, .github, .kilocode, .opencode) and internal module files (_bmad/bmb, _bmad/bmm). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
132 lines
4.6 KiB
Python
132 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Minimal backend for Circuit Builder UI.
|
|
Serves the static UI and runs entropyk-cli validate/run on POST.
|
|
Run from repository root: python tools/circuit-builder-ui/server.py
|
|
Then open http://localhost:8765
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import tempfile
|
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
|
from pathlib import Path
|
|
from urllib.parse import parse_qs, urlparse
|
|
|
|
PORT = 8765
|
|
# Path to entropyk-cli from repo root (run server from repo root)
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent.parent
|
|
CLI_PATH = REPO_ROOT / "target" / "release" / "entropyk-cli"
|
|
UI_DIR = Path(__file__).resolve().parent
|
|
|
|
|
|
def run_cli(subcommand, config_json_str, run_output=None):
|
|
"""Run entropyk-cli validate or run; config_json_str written to temp file."""
|
|
if not CLI_PATH.exists():
|
|
return False, f"CLI not found. Build first: cargo build --release -p entropyk-cli\nExpected: {CLI_PATH}", ""
|
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
|
f.write(config_json_str)
|
|
tmp_path = f.name
|
|
try:
|
|
cmd = [str(CLI_PATH), subcommand, "--config", tmp_path]
|
|
if run_output is not None:
|
|
cmd.extend(["-o", run_output])
|
|
r = subprocess.run(
|
|
cmd,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=60,
|
|
cwd=str(REPO_ROOT),
|
|
)
|
|
stdout = r.stdout or ""
|
|
stderr = r.stderr or ""
|
|
if r.returncode != 0:
|
|
return False, stdout or stderr or f"Exit code {r.returncode}", stderr
|
|
return True, stdout, stderr
|
|
except subprocess.TimeoutExpired:
|
|
return False, "Timeout (60s)", ""
|
|
except Exception as e:
|
|
return False, str(e), ""
|
|
finally:
|
|
try:
|
|
os.unlink(tmp_path)
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
class Handler(BaseHTTPRequestHandler):
|
|
def do_GET(self):
|
|
path = urlparse(self.path).path
|
|
if path == "/" or path == "/index.html":
|
|
self.serve_file(UI_DIR / "index.html", "text/html")
|
|
elif path == "/examples/simple_working.json":
|
|
self.serve_file(REPO_ROOT / "crates" / "cli" / "examples" / "simple_working.json", "application/json")
|
|
elif path == "/examples/chiller_r410a_minimal.json":
|
|
self.serve_file(REPO_ROOT / "crates" / "cli" / "examples" / "chiller_r410a_minimal.json", "application/json")
|
|
else:
|
|
self.send_error(404)
|
|
|
|
def serve_file(self, filepath, content_type):
|
|
try:
|
|
with open(filepath, "rb") as f:
|
|
data = f.read()
|
|
except FileNotFoundError:
|
|
self.send_error(404)
|
|
return
|
|
self.send_response(200)
|
|
self.send_header("Content-Type", content_type)
|
|
self.send_header("Access-Control-Allow-Origin", "*")
|
|
self.send_header("Content-Length", str(len(data)))
|
|
self.end_headers()
|
|
self.wfile.write(data)
|
|
|
|
def do_POST(self):
|
|
path = urlparse(self.path).path
|
|
if path != "/validate" and path != "/run":
|
|
self.send_error(404)
|
|
return
|
|
content_len = int(self.headers.get("Content-Length", 0))
|
|
body = self.rfile.read(content_len)
|
|
try:
|
|
config_str = body.decode("utf-8")
|
|
json.loads(config_str)
|
|
except Exception as e:
|
|
self.send_json(400, {"ok": False, "error": f"Invalid JSON: {e}"})
|
|
return
|
|
if path == "/validate":
|
|
ok, out, err = run_cli("validate", config_str)
|
|
else:
|
|
ok, out, err = run_cli("run", config_str, run_output=os.path.devnull)
|
|
self.send_json(200, {"ok": ok, "stdout": out, "stderr": err})
|
|
|
|
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("Access-Control-Allow-Origin", "*")
|
|
self.send_header("Content-Length", str(len(body)))
|
|
self.end_headers()
|
|
self.wfile.write(body)
|
|
|
|
def log_message(self, format, *args):
|
|
print(f"[{self.log_date_time_string()}] {format % args}")
|
|
|
|
|
|
def main():
|
|
os.chdir(REPO_ROOT)
|
|
print(f"Entropyk Circuit Builder UI")
|
|
print(f" UI: http://localhost:{PORT}/")
|
|
print(f" CLI: {CLI_PATH} {'(found)' if CLI_PATH.exists() else '(not found — run: cargo build --release -p entropyk-cli)'}")
|
|
print(f" Root: {REPO_ROOT}")
|
|
server = HTTPServer(("", PORT), Handler)
|
|
try:
|
|
server.serve_forever()
|
|
except KeyboardInterrupt:
|
|
print("\nBye.")
|
|
server.server_close()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|