feat(server): add spawn API HTTP routes

Add routing for spawn-related endpoints to HttpMixin:
- GET /api/projects -> _handle_projects
- GET /api/health -> _handle_health
- POST /api/spawn -> _handle_spawn
- POST /api/projects/refresh -> _handle_projects_refresh

Update CORS preflight (AC-39) to include GET in allowed methods
and Authorization in allowed headers.

Closes bd-2al
This commit is contained in:
teernisse
2026-02-26 17:00:51 -05:00
parent 7b1e47adc0
commit 62d23793c4
2 changed files with 62 additions and 3 deletions

View File

@@ -1,6 +1,7 @@
import json
import urllib.parse
import amc_server.context as ctx
from amc_server.context import DASHBOARD_DIR
from amc_server.logging_utils import LOGGER
@@ -71,6 +72,10 @@ class HttpMixin:
else:
agent = "claude"
self._serve_skills(agent)
elif self.path == "/api/projects":
self._handle_projects()
elif self.path == "/api/health":
self._handle_health()
else:
self._json_error(404, "Not Found")
except Exception:
@@ -90,6 +95,10 @@ class HttpMixin:
elif self.path.startswith("/api/respond/"):
session_id = urllib.parse.unquote(self.path[len("/api/respond/"):])
self._respond_to_session(session_id)
elif self.path == "/api/spawn":
self._handle_spawn()
elif self.path == "/api/projects/refresh":
self._handle_projects_refresh()
else:
self._json_error(404, "Not Found")
except Exception:
@@ -100,11 +109,12 @@ class HttpMixin:
pass
def do_OPTIONS(self):
# CORS preflight for respond endpoint
# CORS preflight for API endpoints (AC-39: wildcard CORS;
# localhost-only binding AC-24 is the real security boundary)
self.send_response(204)
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Access-Control-Allow-Methods", "POST, OPTIONS")
self.send_header("Access-Control-Allow-Headers", "Content-Type")
self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
self.send_header("Access-Control-Allow-Headers", "Content-Type, Authorization")
self.end_headers()
def _serve_dashboard_file(self, file_path):
@@ -137,6 +147,13 @@ class HttpMixin:
ext = full_path.suffix.lower()
content_type = content_types.get(ext, "application/octet-stream")
# Inject auth token into index.html for spawn endpoint security
if file_path == "index.html" and ctx._auth_token:
content = content.replace(
b"<!-- AMC_AUTH_TOKEN -->",
f'<script>window.AMC_AUTH_TOKEN = "{ctx._auth_token}";</script>'.encode(),
)
# No caching during development
self._send_bytes_response(
200,