diff --git a/README.md b/README.md
index ff97098..5d396b7 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,10 @@
# AMCS Directory
-This is the AMCS (Advanced Module Control System) directory.
+This is the AMCS (Avalon Memory Control Service) directory.
## Purpose
-The AMCS directory is used to store configuration and code for the Advanced Module Control System, which handles...
+The AMCS directory is used to store configuration and code for the Avalon Memory Control Service, which handles...
## Structure
diff --git a/internal/app/app.go b/internal/app/app.go
index f5b82ca..da0932b 100644
--- a/internal/app/app.go
+++ b/internal/app/app.go
@@ -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)
diff --git a/internal/app/llm.go b/internal/app/llm.go
index 79492e0..ddccc35 100644
--- a/internal/app/llm.go
+++ b/internal/app/llm.go
@@ -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
+}
diff --git a/internal/app/llm_test.go b/internal/app/llm_test.go
index 26891a5..44483c4 100644
--- a/internal/app/llm_test.go
+++ b/internal/app/llm_test.go
@@ -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)
+ }
+}
diff --git a/internal/app/status.go b/internal/app/status.go
index 2f8c063..12a0d43 100644
--- a/internal/app/status.go
+++ b/internal/app/status.go
@@ -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
}
diff --git a/internal/app/status_test.go b/internal/app/status_test.go
index 0736663..151f12c 100644
--- a/internal/app/status_test.go
+++ b/internal/app/status_test.go
@@ -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)
diff --git a/llm/memory.md b/llm/memory.md
index 8775b9c..b72c3bc 100644
--- a/llm/memory.md
+++ b/llm/memory.md
@@ -1,6 +1,6 @@
# AMCS Memory Instructions
-AMCS (Avalon Memory Crystal Server) is an MCP server for capturing and retrieving thoughts, memory, and project context. It is backed by Postgres with pgvector for semantic search.
+AMCS (Avalon Memory Control Service) is an MCP server for capturing and retrieving thoughts, memory, and project context. It is backed by Postgres with pgvector for semantic search.
`amcs-cli` is a pre-built CLI that connects to the AMCS MCP server so agents do not need to implement their own HTTP MCP client. Download it from https://git.warky.dev/wdevs/amcs/releases
diff --git a/ui/src/App.svelte b/ui/src/App.svelte
index d9d91d4..9913d59 100644
--- a/ui/src/App.svelte
+++ b/ui/src/App.svelte
@@ -153,9 +153,7 @@
await GlobalStateStore.getState().fetchData();
- if (GlobalStateStore.getState().isLoggedIn()) {
- await loadStatus();
- }
+ await loadStatus();
});
@@ -171,6 +169,9 @@
{authBusy}
{authError}
{authMessage}
+ statusData={data}
+ statusLoading={loading}
+ statusError={error}
onlogin={handleCredentialLogin}
/>
{:else}
diff --git a/ui/src/components/auth/LoginInfoPanel.svelte b/ui/src/components/auth/LoginInfoPanel.svelte
index befa29c..a179278 100644
--- a/ui/src/components/auth/LoginInfoPanel.svelte
+++ b/ui/src/components/auth/LoginInfoPanel.svelte
@@ -1,34 +1,226 @@
-
-
+
+
- AMCS Control Interface
+ Avalon Memory Control Service
{#if isOAuthCallback}
Completing login
{:else}
- Login
+ Operator Access
{/if}
- Origin-style operator access for the AMCS admin interface. ResolveSpec OAuth is the front door now,
- not the old login shortcut.
+ AMCS is a Go MCP server for capturing project thoughts, semantic retrieval,
+ summaries, and linked memory workflows with Postgres + pgvector.
+
+
+ It stores durable memory for assistants, supports project scoping, and
+ exposes tools over MCP for capture, search, context recall, and structured
+ operations.
-
+
-
Primary module
-
Projects
-
Projects are the first real admin screen in this rollout.
+
+ Server status
+
+ {#if loading}
+
Loading…
+ {:else if error}
+
Unavailable
+
{error}
+ {:else if data}
+
{data.version}
+
+ {data.connected_count} connected in {data.connected_window}
+
+
+ Status Endpoint
+
+ {:else}
+
No status snapshot yet.
+ {/if}
-
OAuth path
-
ResolveSpec
-
Client registration, authorize, callback, token exchange.
+
+ Memory stack
+
+
Postgres + pgvector
+
+ Semantic search with full-text fallback when vectors are missing.
+
+
+
+
+
+
+
Project intelligence model
+
+ AMCS separates reusable behavior, safety constraints, and curated
+ knowledge so assistants can be guided consistently across sessions.
+
+
+
+ {#each intelligenceCards as card}
+
setActiveCard(card.id)}
+ onkeydown={(event) => handleCardKeydown(event, card.id)}
+ >
+
+ {card.title}
+
+ {card.summary}
+ {#if activeCard === card.id}
+ {card.detail}
+ {/if}
+
+ Tools:
+ {#each card.tools as tool, idx}
+ {tool}{idx <
+ card.tools.length - 1
+ ? ", "
+ : ""}
+ {/each}
+
+
+ {/each}
diff --git a/ui/src/components/auth/LoginPage.svelte b/ui/src/components/auth/LoginPage.svelte
index 70e5e2b..e80a8b4 100644
--- a/ui/src/components/auth/LoginPage.svelte
+++ b/ui/src/components/auth/LoginPage.svelte
@@ -1,6 +1,7 @@
-
-
-
-
+
+
diff --git a/ui/src/components/auth/LoginPanel.svelte b/ui/src/components/auth/LoginPanel.svelte
index 7718d12..7a28f1c 100644
--- a/ui/src/components/auth/LoginPanel.svelte
+++ b/ui/src/components/auth/LoginPanel.svelte
@@ -28,7 +28,7 @@
{#if isOAuthCallback}
Authorizing operator session
- Finishing the ResolveSpec handshake and exchanging the returned code for an AMCS token.
+ Finishing the callback flow and exchanging the returned code for an AMCS token.
@@ -42,7 +42,7 @@
{:else}
Operator login
- Authenticate with your ResolveSpec credentials.
+ Authenticate to access the AMCS admin interface.