fix(ui): update AMCS references and add status handling

* Corrected "Advanced Module Control System" to "Avalon Memory Control Service" in documentation and UI components.
* Added status handling to the LoginInfoPanel and LoginPage components.
* Implemented new endpoints for robots.txt and llms.txt.
This commit is contained in:
2026-04-27 00:04:08 +02:00
parent b17241b928
commit a4193b295a
11 changed files with 427 additions and 34 deletions

View File

@@ -243,7 +243,11 @@ func routes(logger *slog.Logger, cfg *config.Config, info buildinfo.Info, db *st
mux.HandleFunc("/images/project.jpg", serveHomeImage)
mux.HandleFunc("/images/icon.png", serveIcon)
mux.HandleFunc("/llm", serveLLMInstructions)
mux.HandleFunc("/llms.txt", serveLLMSTXT)
mux.HandleFunc("/.well-known/llms.txt", serveLLMSTXT)
mux.HandleFunc("/robots.txt", serveRobotsTXT)
mux.HandleFunc("/api/status", statusAPIHandler(info, accessTracker, oauthEnabled))
mux.HandleFunc("/status", statusAPIHandler(info, accessTracker, oauthEnabled))
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)

View File

@@ -1,7 +1,9 @@
package app
import (
"fmt"
"net/http"
"strings"
amcsllm "git.warky.dev/wdevs/amcs/llm"
)
@@ -20,3 +22,74 @@ func serveLLMInstructions(w http.ResponseWriter, r *http.Request) {
}
_, _ = w.Write(amcsllm.MemoryInstructions)
}
func serveRobotsTXT(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/robots.txt" {
http.NotFound(w, r)
return
}
if r.Method != http.MethodGet && r.Method != http.MethodHead {
w.Header().Set("Allow", "GET, HEAD")
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("Cache-Control", "public, max-age=300")
w.WriteHeader(http.StatusOK)
if r.Method == http.MethodHead {
return
}
body := fmt.Sprintf("User-agent: *\nAllow: /\n\n# LLM-friendly docs\nLLM: %s/llm\nLLMS: %s/llms.txt\n", requestBaseURL(r), requestBaseURL(r))
_, _ = w.Write([]byte(body))
}
func serveLLMSTXT(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/llms.txt" && r.URL.Path != "/.well-known/llms.txt" {
http.NotFound(w, r)
return
}
if r.Method != http.MethodGet && r.Method != http.MethodHead {
w.Header().Set("Allow", "GET, HEAD")
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("Cache-Control", "public, max-age=300")
w.WriteHeader(http.StatusOK)
if r.Method == http.MethodHead {
return
}
base := requestBaseURL(r)
body := fmt.Sprintf(
"# AMCS\n\n> A memory server for AI assistants (MCP tools, semantic retrieval, and structured project memory).\n\n## Endpoints\n- %s/llm\n- %s/status\n- %s/mcp\n- %s/.well-known/oauth-authorization-server\n",
base,
base,
base,
base,
)
_, _ = w.Write([]byte(body))
}
func requestBaseURL(r *http.Request) string {
scheme := "http"
if r != nil && r.TLS != nil {
scheme = "https"
}
if r != nil {
if proto := strings.TrimSpace(r.Header.Get("X-Forwarded-Proto")); proto != "" {
scheme = proto
}
}
host := "localhost"
if r != nil {
if v := strings.TrimSpace(r.Host); v != "" {
host = v
}
}
return scheme + "://" + host
}

View File

@@ -3,6 +3,7 @@ package app
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
amcsllm "git.warky.dev/wdevs/amcs/llm"
@@ -29,3 +30,70 @@ func TestServeLLMInstructions(t *testing.T) {
t.Fatalf("body = %q, want embedded instructions", body)
}
}
func TestServeRobotsTXT(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/robots.txt", nil)
req.Host = "amcs.example.com"
req.Header.Set("X-Forwarded-Proto", "https")
rec := httptest.NewRecorder()
serveRobotsTXT(rec, req)
res := rec.Result()
defer func() {
_ = res.Body.Close()
}()
if res.StatusCode != http.StatusOK {
t.Fatalf("status = %d, want %d", res.StatusCode, http.StatusOK)
}
if got := res.Header.Get("Content-Type"); got != "text/plain; charset=utf-8" {
t.Fatalf("content-type = %q, want %q", got, "text/plain; charset=utf-8")
}
body := rec.Body.String()
if !strings.Contains(body, "LLM: https://amcs.example.com/llm") {
t.Fatalf("body = %q, want LLM link", body)
}
if !strings.Contains(body, "LLMS: https://amcs.example.com/llms.txt") {
t.Fatalf("body = %q, want LLMS link", body)
}
}
func TestServeLLMSTXT(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/llms.txt", nil)
req.Host = "amcs.example.com"
req.Header.Set("X-Forwarded-Proto", "https")
rec := httptest.NewRecorder()
serveLLMSTXT(rec, req)
res := rec.Result()
defer func() {
_ = res.Body.Close()
}()
if res.StatusCode != http.StatusOK {
t.Fatalf("status = %d, want %d", res.StatusCode, http.StatusOK)
}
if got := res.Header.Get("Content-Type"); got != "text/plain; charset=utf-8" {
t.Fatalf("content-type = %q, want %q", got, "text/plain; charset=utf-8")
}
body := rec.Body.String()
if !strings.Contains(body, "https://amcs.example.com/llm") {
t.Fatalf("body = %q, want /llm link", body)
}
if !strings.Contains(body, "https://amcs.example.com/.well-known/oauth-authorization-server") {
t.Fatalf("body = %q, want oauth discovery link", body)
}
}
func TestServeLLMSTXTWellKnownPath(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/.well-known/llms.txt", nil)
rec := httptest.NewRecorder()
serveLLMSTXT(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
}
}

View File

@@ -55,7 +55,7 @@ func fallback(value, defaultValue string) string {
func statusAPIHandler(info buildinfo.Info, tracker *auth.AccessTracker, oauthEnabled bool) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/api/status" {
if r.URL.Path != "/api/status" && r.URL.Path != "/status" {
http.NotFound(w, r)
return
}

View File

@@ -86,6 +86,26 @@ func TestStatusAPIHandlerReturnsJSON(t *testing.T) {
}
}
func TestStatusAPIHandlerSupportsStatusPath(t *testing.T) {
handler := statusAPIHandler(buildinfo.Info{Version: "v1"}, auth.NewAccessTracker(), true)
req := httptest.NewRequest(http.MethodGet, "/status", nil)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
}
var payload statusAPIResponse
if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
t.Fatalf("json.Unmarshal() error = %v", err)
}
if payload.Version != "v1" {
t.Fatalf("version = %q, want %q", payload.Version, "v1")
}
}
func TestHomeHandlerAllowsHead(t *testing.T) {
handler := homeHandler(buildinfo.Info{Version: "v1"}, auth.NewAccessTracker(), false)
req := httptest.NewRequest(http.MethodHead, "/", nil)