Compare commits
2 Commits
b17241b928
...
f6a86e3933
| Author | SHA1 | Date | |
|---|---|---|---|
| f6a86e3933 | |||
| a4193b295a |
@@ -1,10 +1,10 @@
|
|||||||
# AMCS Directory
|
# AMCS Directory
|
||||||
|
|
||||||
This is the AMCS (Advanced Module Control System) directory.
|
This is the AMCS (Avalon Memory Control Service) directory.
|
||||||
|
|
||||||
## Purpose
|
## 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
|
## Structure
|
||||||
|
|
||||||
|
|||||||
@@ -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/project.jpg", serveHomeImage)
|
||||||
mux.HandleFunc("/images/icon.png", serveIcon)
|
mux.HandleFunc("/images/icon.png", serveIcon)
|
||||||
mux.HandleFunc("/llm", serveLLMInstructions)
|
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("/api/status", statusAPIHandler(info, accessTracker, oauthEnabled))
|
||||||
|
mux.HandleFunc("/status", statusAPIHandler(info, accessTracker, oauthEnabled))
|
||||||
|
|
||||||
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
amcsllm "git.warky.dev/wdevs/amcs/llm"
|
amcsllm "git.warky.dev/wdevs/amcs/llm"
|
||||||
)
|
)
|
||||||
@@ -20,3 +22,74 @@ func serveLLMInstructions(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
_, _ = w.Write(amcsllm.MemoryInstructions)
|
_, _ = 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
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package app
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
amcsllm "git.warky.dev/wdevs/amcs/llm"
|
amcsllm "git.warky.dev/wdevs/amcs/llm"
|
||||||
@@ -29,3 +30,70 @@ func TestServeLLMInstructions(t *testing.T) {
|
|||||||
t.Fatalf("body = %q, want embedded instructions", body)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ func fallback(value, defaultValue string) string {
|
|||||||
|
|
||||||
func statusAPIHandler(info buildinfo.Info, tracker *auth.AccessTracker, oauthEnabled bool) http.HandlerFunc {
|
func statusAPIHandler(info buildinfo.Info, tracker *auth.AccessTracker, oauthEnabled bool) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
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)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
func TestHomeHandlerAllowsHead(t *testing.T) {
|
||||||
handler := homeHandler(buildinfo.Info{Version: "v1"}, auth.NewAccessTracker(), false)
|
handler := homeHandler(buildinfo.Info{Version: "v1"}, auth.NewAccessTracker(), false)
|
||||||
req := httptest.NewRequest(http.MethodHead, "/", nil)
|
req := httptest.NewRequest(http.MethodHead, "/", nil)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# AMCS Memory Instructions
|
# 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
|
`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
|
||||||
|
|
||||||
|
|||||||
@@ -153,9 +153,7 @@
|
|||||||
|
|
||||||
await GlobalStateStore.getState().fetchData();
|
await GlobalStateStore.getState().fetchData();
|
||||||
|
|
||||||
if (GlobalStateStore.getState().isLoggedIn()) {
|
await loadStatus();
|
||||||
await loadStatus();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -171,6 +169,9 @@
|
|||||||
{authBusy}
|
{authBusy}
|
||||||
{authError}
|
{authError}
|
||||||
{authMessage}
|
{authMessage}
|
||||||
|
statusData={data}
|
||||||
|
statusLoading={loading}
|
||||||
|
statusError={error}
|
||||||
onlogin={handleCredentialLogin}
|
onlogin={handleCredentialLogin}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
|
|||||||
@@ -1,34 +1,226 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
const { isOAuthCallback }: { isOAuthCallback: boolean } = $props();
|
import type { StatusResponse } from "../../types";
|
||||||
|
type IntelligenceCard = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
accentClass: string;
|
||||||
|
summary: string;
|
||||||
|
detail: string;
|
||||||
|
tools: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
isOAuthCallback,
|
||||||
|
data,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
}: {
|
||||||
|
isOAuthCallback: boolean;
|
||||||
|
data: StatusResponse | null;
|
||||||
|
loading: boolean;
|
||||||
|
error: string;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
const intelligenceCards: IntelligenceCard[] = [
|
||||||
|
{
|
||||||
|
id: "projects",
|
||||||
|
title: "Projects",
|
||||||
|
accentClass: "text-indigo-200",
|
||||||
|
summary:
|
||||||
|
"Named containers that scope memory and operations so retrieval stays focused on the right workstream.",
|
||||||
|
detail:
|
||||||
|
"Project context can be resolved explicitly or from active session scope depending on client behavior.",
|
||||||
|
tools: ["list_projects", "create_project", "get_project_context"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "thoughts",
|
||||||
|
title: "Thoughts",
|
||||||
|
accentClass: "text-violet-200",
|
||||||
|
summary:
|
||||||
|
"Core memory records with metadata and links that power search, recall, summaries, and relationship traversal.",
|
||||||
|
detail:
|
||||||
|
"Thought capture can include metadata enrichment and link-based navigation for richer retrieval.",
|
||||||
|
tools: ["capture_thought", "search_thoughts", "related_thoughts"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "skills",
|
||||||
|
title: "Skills",
|
||||||
|
accentClass: "text-cyan-200",
|
||||||
|
summary:
|
||||||
|
"Reusable agent instructions and capabilities that can be linked to a project and loaded with it.",
|
||||||
|
detail:
|
||||||
|
"Use project-linked skills to keep behavior consistent across sessions and assistants.",
|
||||||
|
tools: ["list_project_skills", "add_skill", "add_project_skill"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "guardrails",
|
||||||
|
title: "Guardrails",
|
||||||
|
accentClass: "text-amber-200",
|
||||||
|
summary:
|
||||||
|
"Safety and policy constraints with severity levels that can be enforced globally or per project.",
|
||||||
|
detail:
|
||||||
|
"Guardrails provide stable operational boundaries for memory and tool usage behavior.",
|
||||||
|
tools: [
|
||||||
|
"list_project_guardrails",
|
||||||
|
"add_guardrail",
|
||||||
|
"add_project_guardrail",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "learnings",
|
||||||
|
title: "Learnings",
|
||||||
|
accentClass: "text-emerald-200",
|
||||||
|
summary:
|
||||||
|
"Curated records for durable lessons and decisions, separate from raw thoughts for cleaner review.",
|
||||||
|
detail:
|
||||||
|
"Structured fields such as status, priority, and confidence support operational follow-through.",
|
||||||
|
tools: ["add_learning", "list_learnings", "get_learning"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "vector-metadata-build",
|
||||||
|
title: "Vector + Metadata Build",
|
||||||
|
accentClass: "text-fuchsia-200",
|
||||||
|
summary:
|
||||||
|
"Backfill and repair flows for embeddings and metadata so retrieval quality stays healthy over time.",
|
||||||
|
detail:
|
||||||
|
"Use dry runs for safe audits, then run updates to regenerate missing vectors or retry failed metadata.",
|
||||||
|
tools: [
|
||||||
|
"backfill_embeddings",
|
||||||
|
"reparse_thought_metadata",
|
||||||
|
"retry_failed_metadata",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let activeCard = $state("projects");
|
||||||
|
|
||||||
|
function setActiveCard(id: string) {
|
||||||
|
activeCard = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCardKeydown(event: KeyboardEvent, id: string) {
|
||||||
|
if (event.key === "Enter" || event.key === " ") {
|
||||||
|
event.preventDefault();
|
||||||
|
setActiveCard(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="rounded-3xl border border-cyan-400/20 bg-slate-900/80 p-8 shadow-2xl shadow-slate-950/40">
|
<div
|
||||||
<div class="inline-flex items-center gap-2 rounded-full border border-cyan-400/20 bg-cyan-400/10 px-3 py-1 text-sm font-medium text-cyan-200">
|
class="rounded-3xl border border-cyan-400/20 bg-slate-900/80 p-8 shadow-2xl shadow-slate-950/40"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="inline-flex items-center gap-2 rounded-full border border-cyan-400/20 bg-cyan-400/10 px-3 py-1 text-sm font-medium text-cyan-200"
|
||||||
|
>
|
||||||
<span class="h-2 w-2 rounded-full bg-emerald-400"></span>
|
<span class="h-2 w-2 rounded-full bg-emerald-400"></span>
|
||||||
AMCS Control Interface
|
AMCS (Avalon Memory Control Service)
|
||||||
</div>
|
</div>
|
||||||
<h1 class="mt-6 text-4xl font-semibold tracking-tight text-white">
|
<h1 class="mt-6 text-4xl font-semibold tracking-tight text-white">
|
||||||
{#if isOAuthCallback}
|
{#if isOAuthCallback}
|
||||||
Completing login
|
Completing login...
|
||||||
{:else}
|
{:else}
|
||||||
Login
|
Avalon Memory Control Service
|
||||||
{/if}
|
{/if}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="mt-3 max-w-2xl text-base leading-7 text-slate-300">
|
<p class="mt-3 max-w-2xl text-base leading-7 text-slate-300">
|
||||||
Origin-style operator access for the AMCS admin interface. ResolveSpec OAuth is the front door now,
|
AMCS is a Go MCP server for capturing project thoughts, semantic retrieval,
|
||||||
not the old login shortcut.
|
summaries, and linked memory workflows with Postgres + pgvector.
|
||||||
|
</p>
|
||||||
|
<p class="mt-2 max-w-2xl text-sm leading-6 text-slate-400">
|
||||||
|
It stores durable memory for assistants, supports project scoping, and
|
||||||
|
exposes tools over MCP for capture, search, context recall, and structured
|
||||||
|
operations.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="mt-8 grid gap-4 sm:grid-cols-2">
|
<div class="mt-8 grid gap-4 sm:grid-cols-3">
|
||||||
<div class="rounded-2xl border border-white/10 bg-white/5 p-5">
|
<div class="rounded-2xl border border-white/10 bg-white/5 p-5">
|
||||||
<p class="text-sm uppercase tracking-[0.2em] text-slate-400">Primary module</p>
|
<p class="text-sm uppercase tracking-[0.2em] text-slate-400">
|
||||||
<p class="mt-2 text-2xl font-semibold text-white">Projects</p>
|
Server status
|
||||||
<p class="mt-2 text-sm text-slate-400">Projects are the first real admin screen in this rollout.</p>
|
</p>
|
||||||
|
{#if loading}
|
||||||
|
<p class="mt-2 text-lg font-semibold text-white">Loading…</p>
|
||||||
|
{:else if error}
|
||||||
|
<p class="mt-2 text-sm font-medium text-rose-300">Unavailable</p>
|
||||||
|
<p class="mt-2 text-xs text-rose-200/80">{error}</p>
|
||||||
|
{:else if data}
|
||||||
|
<p class="mt-2 text-2xl font-semibold text-white">{data.version}</p>
|
||||||
|
<p class="mt-2 text-sm text-slate-400">
|
||||||
|
{data.connected_count} connected in {data.connected_window}
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="/status"
|
||||||
|
class="mt-3 inline-flex items-center rounded-lg border border-emerald-300/30 bg-emerald-400/10 px-3 py-1.5 text-xs font-semibold uppercase tracking-[0.12em] text-emerald-100 transition hover:border-emerald-300/50 hover:bg-emerald-400/20"
|
||||||
|
>
|
||||||
|
Status Endpoint
|
||||||
|
</a>
|
||||||
|
{:else}
|
||||||
|
<p class="mt-2 text-sm text-slate-400">No status snapshot yet.</p>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-2xl border border-white/10 bg-white/5 p-5">
|
<div class="rounded-2xl border border-white/10 bg-white/5 p-5">
|
||||||
<p class="text-sm uppercase tracking-[0.2em] text-slate-400">OAuth path</p>
|
<p class="text-sm uppercase tracking-[0.2em] text-slate-400">
|
||||||
<p class="mt-2 text-2xl font-semibold text-white">ResolveSpec</p>
|
Memory stack
|
||||||
<p class="mt-2 text-sm text-slate-400">Client registration, authorize, callback, token exchange.</p>
|
</p>
|
||||||
|
<p class="mt-2 text-2xl font-semibold text-white">Postgres + pgvector</p>
|
||||||
|
<p class="mt-2 text-sm text-slate-400">
|
||||||
|
Semantic search with full-text fallback when vectors are missing.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-2xl border border-white/10 bg-white/5 p-5">
|
||||||
|
<p class="text-sm uppercase tracking-[0.2em] text-slate-400">
|
||||||
|
Operator docs
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="/llm"
|
||||||
|
class="mt-2 inline-flex items-center rounded-lg border border-cyan-300/30 bg-cyan-400/10 px-3 py-2 text-sm font-semibold text-cyan-100 transition hover:border-cyan-300/50 hover:bg-cyan-400/20"
|
||||||
|
>
|
||||||
|
Open LLM Instructions
|
||||||
|
</a>
|
||||||
|
<p class="mt-2 text-sm text-slate-400">
|
||||||
|
Tool behavior, workflows, and MCP guidance for assistants.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-6 rounded-2xl border border-white/10 bg-slate-950/35 p-5">
|
||||||
|
<h2 class="text-lg font-semibold text-white">Project intelligence model</h2>
|
||||||
|
<p class="mt-2 text-sm text-slate-400">
|
||||||
|
AMCS separates reusable behavior, safety constraints, and curated
|
||||||
|
knowledge so assistants can be guided consistently across sessions.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="mt-4 grid gap-4 md:grid-cols-2 xl:grid-cols-3">
|
||||||
|
{#each intelligenceCards as card}
|
||||||
|
<article
|
||||||
|
class={`rounded-xl border p-4 transition ${
|
||||||
|
activeCard === card.id
|
||||||
|
? "border-cyan-300/40 bg-cyan-400/[0.08] shadow-lg shadow-cyan-950/30"
|
||||||
|
: "border-white/10 bg-white/[0.03] hover:border-white/20 hover:bg-white/[0.05]"
|
||||||
|
}`}
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
aria-pressed={activeCard === card.id}
|
||||||
|
onclick={() => setActiveCard(card.id)}
|
||||||
|
onkeydown={(event) => handleCardKeydown(event, card.id)}
|
||||||
|
>
|
||||||
|
<p class={`text-xs uppercase tracking-[0.16em] ${card.accentClass}`}>
|
||||||
|
{card.title}
|
||||||
|
</p>
|
||||||
|
<p class="mt-2 text-sm text-slate-300">{card.summary}</p>
|
||||||
|
{#if activeCard === card.id}
|
||||||
|
<p class="mt-2 text-xs text-slate-300/90">{card.detail}</p>
|
||||||
|
{/if}
|
||||||
|
<p class="mt-3 text-xs text-slate-400">
|
||||||
|
Tools:
|
||||||
|
{#each card.tools as tool, idx}
|
||||||
|
<code class="text-slate-200">{tool}</code>{idx <
|
||||||
|
card.tools.length - 1
|
||||||
|
? ", "
|
||||||
|
: ""}
|
||||||
|
{/each}
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import LoginInfoPanel from './LoginInfoPanel.svelte';
|
import LoginInfoPanel from './LoginInfoPanel.svelte';
|
||||||
import LoginPanel from './LoginPanel.svelte';
|
import LoginPanel from './LoginPanel.svelte';
|
||||||
|
import type { StatusResponse } from '../../types';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isOAuthCallback,
|
isOAuthCallback,
|
||||||
@@ -8,6 +9,9 @@
|
|||||||
authBusy,
|
authBusy,
|
||||||
authError,
|
authError,
|
||||||
authMessage,
|
authMessage,
|
||||||
|
statusData,
|
||||||
|
statusLoading,
|
||||||
|
statusError,
|
||||||
onlogin
|
onlogin
|
||||||
}: {
|
}: {
|
||||||
isOAuthCallback: boolean;
|
isOAuthCallback: boolean;
|
||||||
@@ -15,20 +19,51 @@
|
|||||||
authBusy: boolean;
|
authBusy: boolean;
|
||||||
authError: string;
|
authError: string;
|
||||||
authMessage: string;
|
authMessage: string;
|
||||||
|
statusData: StatusResponse | null;
|
||||||
|
statusLoading: boolean;
|
||||||
|
statusError: string;
|
||||||
onlogin: (username: string, password: string) => void;
|
onlogin: (username: string, password: string) => void;
|
||||||
} = $props();
|
} = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main class="mx-auto flex min-h-screen max-w-6xl items-center px-4 py-10 sm:px-6 lg:px-8">
|
<main class="mx-auto min-h-screen w-full max-w-6xl px-4 py-8 sm:px-6 lg:px-8">
|
||||||
<section class="grid w-full gap-8 lg:grid-cols-[1.15fr_0.85fr]">
|
<section class="flex min-h-[calc(100vh-4rem)] flex-col gap-8">
|
||||||
<LoginInfoPanel {isOAuthCallback} />
|
<LoginInfoPanel {isOAuthCallback} data={statusData} loading={statusLoading} error={statusError} />
|
||||||
<LoginPanel
|
<div class="mt-auto">
|
||||||
{isOAuthCallback}
|
<LoginPanel
|
||||||
{callbackBusy}
|
{isOAuthCallback}
|
||||||
{authBusy}
|
{callbackBusy}
|
||||||
{authError}
|
{authBusy}
|
||||||
{authMessage}
|
{authError}
|
||||||
{onlogin}
|
{authMessage}
|
||||||
/>
|
{onlogin}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 flex flex-wrap items-center justify-center gap-3 text-xs text-slate-400">
|
||||||
|
<a
|
||||||
|
href="/llms.txt"
|
||||||
|
class="inline-flex items-center rounded-md border border-white/10 bg-white/[0.03] px-2.5 py-1.5 transition hover:border-cyan-300/40 hover:text-cyan-100"
|
||||||
|
>
|
||||||
|
llms.txt
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="/robots.txt"
|
||||||
|
class="inline-flex items-center rounded-md border border-white/10 bg-white/[0.03] px-2.5 py-1.5 transition hover:border-cyan-300/40 hover:text-cyan-100"
|
||||||
|
>
|
||||||
|
robots.txt
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="/.well-known/oauth-authorization-server"
|
||||||
|
class="inline-flex items-center rounded-md border border-white/10 bg-white/[0.03] px-2.5 py-1.5 transition hover:border-cyan-300/40 hover:text-cyan-100"
|
||||||
|
>
|
||||||
|
OAuth Discovery
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="/llm"
|
||||||
|
class="inline-flex items-center rounded-md border border-white/10 bg-white/[0.03] px-2.5 py-1.5 transition hover:border-cyan-300/40 hover:text-cyan-100"
|
||||||
|
>
|
||||||
|
LLM Docs
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
{#if isOAuthCallback}
|
{#if isOAuthCallback}
|
||||||
<h2 class="text-xl font-semibold text-white">Authorizing operator session</h2>
|
<h2 class="text-xl font-semibold text-white">Authorizing operator session</h2>
|
||||||
<p class="mt-2 text-sm leading-6 text-slate-400">
|
<p class="mt-2 text-sm leading-6 text-slate-400">
|
||||||
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.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="mt-6 rounded-2xl border border-cyan-400/20 bg-cyan-400/5 px-4 py-6 text-sm text-cyan-100">
|
<div class="mt-6 rounded-2xl border border-cyan-400/20 bg-cyan-400/5 px-4 py-6 text-sm text-cyan-100">
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<h2 class="text-xl font-semibold text-white">Operator login</h2>
|
<h2 class="text-xl font-semibold text-white">Operator login</h2>
|
||||||
<p class="mt-1 text-sm text-slate-400">Authenticate with your ResolveSpec credentials.</p>
|
<p class="mt-1 text-sm text-slate-400">Authenticate to access the AMCS admin interface.</p>
|
||||||
|
|
||||||
<form class="mt-6 space-y-4" onsubmit={handleSubmit}>
|
<form class="mt-6 space-y-4" onsubmit={handleSubmit}>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
Reference in New Issue
Block a user