diff --git a/cmd/mcpproxy/doctor_cmd.go b/cmd/mcpproxy/doctor_cmd.go
index 8e85cd4e..e872f60d 100644
--- a/cmd/mcpproxy/doctor_cmd.go
+++ b/cmd/mcpproxy/doctor_cmd.go
@@ -421,6 +421,23 @@ func displaySecurityFeaturesStatus() {
return
}
+ // Routing Mode status (Spec 031)
+ routingMode := cfg.RoutingMode
+ if routingMode == "" {
+ routingMode = config.RoutingModeRetrieveTools
+ }
+ fmt.Printf(" Routing Mode: %s\n", routingMode)
+ switch routingMode {
+ case config.RoutingModeDirect:
+ fmt.Println(" All upstream tools exposed directly via /mcp endpoint")
+ case config.RoutingModeCodeExecution:
+ fmt.Println(" JS orchestration via code_execution tool")
+ default:
+ fmt.Println(" BM25 search via retrieve_tools + call_tool variants")
+ }
+ fmt.Printf(" Endpoints: /mcp/all (direct), /mcp/code (code_execution), /mcp/call (retrieve_tools)\n")
+ fmt.Println()
+
// Sensitive Data Detection status
sddConfig := cfg.SensitiveDataDetection
if sddConfig == nil || sddConfig.IsEnabled() {
diff --git a/cmd/mcpproxy/status_cmd.go b/cmd/mcpproxy/status_cmd.go
index a461e2ac..bc85f169 100644
--- a/cmd/mcpproxy/status_cmd.go
+++ b/cmd/mcpproxy/status_cmd.go
@@ -28,6 +28,7 @@ type StatusInfo struct {
UptimeSeconds float64 `json:"uptime_seconds,omitempty"`
APIKey string `json:"api_key"`
WebUIURL string `json:"web_ui_url"`
+ RoutingMode string `json:"routing_mode"`
Servers *ServerCounts `json:"servers,omitempty"`
SocketPath string `json:"socket_path,omitempty"`
ConfigPath string `json:"config_path,omitempty"`
@@ -158,11 +159,17 @@ func collectStatusFromDaemon(cfg *config.Config, socketPath, configPath string)
defer cancel()
info := &StatusInfo{
- State: "Running",
- Edition: Edition,
- APIKey: cfg.APIKey,
- SocketPath: socketPath,
- ConfigPath: configPath,
+ State: "Running",
+ Edition: Edition,
+ APIKey: cfg.APIKey,
+ RoutingMode: cfg.RoutingMode,
+ SocketPath: socketPath,
+ ConfigPath: configPath,
+ }
+
+ // Apply routing mode default if empty
+ if info.RoutingMode == "" {
+ info.RoutingMode = config.RoutingModeRetrieveTools
}
// Add teams info if available
@@ -220,13 +227,19 @@ func collectStatusFromConfig(cfg *config.Config, socketPath, configPath string)
listenAddr = "127.0.0.1:8080"
}
+ routingMode := cfg.RoutingMode
+ if routingMode == "" {
+ routingMode = config.RoutingModeRetrieveTools
+ }
+
info := &StatusInfo{
- State: "Not running",
- Edition: Edition,
- ListenAddr: listenAddr + " (configured)",
- APIKey: cfg.APIKey,
- WebUIURL: statusBuildWebUIURL(listenAddr, cfg.APIKey),
- ConfigPath: configPath,
+ State: "Not running",
+ Edition: Edition,
+ ListenAddr: listenAddr + " (configured)",
+ APIKey: cfg.APIKey,
+ WebUIURL: statusBuildWebUIURL(listenAddr, cfg.APIKey),
+ RoutingMode: routingMode,
+ ConfigPath: configPath,
}
info.TeamsInfo = collectTeamsInfo(cfg)
@@ -351,6 +364,7 @@ func printStatusTable(info *StatusInfo) {
}
fmt.Printf(" %-12s %s\n", "API Key:", info.APIKey)
+ fmt.Printf(" %-12s %s\n", "Routing:", info.RoutingMode)
fmt.Printf(" %-12s %s\n", "Web UI:", info.WebUIURL)
if info.Servers != nil {
diff --git a/cmd/mcpproxy/status_cmd_test.go b/cmd/mcpproxy/status_cmd_test.go
index c723e9c0..15a6c719 100644
--- a/cmd/mcpproxy/status_cmd_test.go
+++ b/cmd/mcpproxy/status_cmd_test.go
@@ -449,6 +449,129 @@ func TestStatusJSONOutput(t *testing.T) {
}
}
+func TestStatusRoutingModeInTable(t *testing.T) {
+ tests := []struct {
+ name string
+ routingMode string
+ expected string
+ }{
+ {
+ name: "retrieve_tools mode",
+ routingMode: "retrieve_tools",
+ expected: "retrieve_tools",
+ },
+ {
+ name: "direct mode",
+ routingMode: "direct",
+ expected: "direct",
+ },
+ {
+ name: "code_execution mode",
+ routingMode: "code_execution",
+ expected: "code_execution",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ info := &StatusInfo{
+ State: "Running",
+ Edition: "personal",
+ ListenAddr: "127.0.0.1:8080",
+ APIKey: "a1b2****a1b2",
+ WebUIURL: "http://127.0.0.1:8080/ui/?apikey=test",
+ RoutingMode: tt.routingMode,
+ }
+
+ old := os.Stdout
+ r, w, _ := os.Pipe()
+ os.Stdout = w
+
+ printStatusTable(info)
+
+ w.Close()
+ os.Stdout = old
+
+ buf := make([]byte, 4096)
+ n, _ := r.Read(buf)
+ output := string(buf[:n])
+
+ if !strings.Contains(output, "Routing:") {
+ t.Errorf("expected output to contain 'Routing:', output:\n%s", output)
+ }
+ if !strings.Contains(output, tt.expected) {
+ t.Errorf("expected output to contain %q, output:\n%s", tt.expected, output)
+ }
+ })
+ }
+}
+
+func TestStatusRoutingModeInJSON(t *testing.T) {
+ info := &StatusInfo{
+ State: "Running",
+ Edition: "personal",
+ ListenAddr: "127.0.0.1:8080",
+ APIKey: "testkey",
+ WebUIURL: "http://127.0.0.1:8080/ui/",
+ RoutingMode: "direct",
+ }
+
+ old := os.Stdout
+ r, w, _ := os.Pipe()
+ os.Stdout = w
+
+ err := printStatusJSON(info)
+
+ w.Close()
+ os.Stdout = old
+
+ if err != nil {
+ t.Fatalf("printStatusJSON failed: %v", err)
+ }
+
+ buf := make([]byte, 8192)
+ n, _ := r.Read(buf)
+ output := string(buf[:n])
+
+ var result StatusInfo
+ if jsonErr := json.Unmarshal([]byte(output), &result); jsonErr != nil {
+ t.Fatalf("invalid JSON: %v\nOutput: %s", jsonErr, output)
+ }
+
+ if result.RoutingMode != "direct" {
+ t.Errorf("expected routing_mode 'direct', got %q", result.RoutingMode)
+ }
+}
+
+func TestCollectStatusFromConfigRoutingMode(t *testing.T) {
+ t.Run("uses config routing mode", func(t *testing.T) {
+ cfg := &config.Config{
+ Listen: "127.0.0.1:8080",
+ APIKey: "testkey",
+ RoutingMode: "direct",
+ }
+
+ info := collectStatusFromConfig(cfg, "/tmp/test.sock", "/tmp/config.json")
+
+ if info.RoutingMode != "direct" {
+ t.Errorf("expected routing mode 'direct', got %q", info.RoutingMode)
+ }
+ })
+
+ t.Run("defaults to retrieve_tools when empty", func(t *testing.T) {
+ cfg := &config.Config{
+ Listen: "127.0.0.1:8080",
+ APIKey: "testkey",
+ }
+
+ info := collectStatusFromConfig(cfg, "/tmp/test.sock", "/tmp/config.json")
+
+ if info.RoutingMode != config.RoutingModeRetrieveTools {
+ t.Errorf("expected routing mode %q, got %q", config.RoutingModeRetrieveTools, info.RoutingMode)
+ }
+ })
+}
+
// parseTestDuration is a helper to parse duration strings for tests.
func parseTestDuration(s string) (time.Duration, error) {
return time.ParseDuration(s)
diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index d6d6bece..c03ec74d 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -110,6 +110,9 @@ onMounted(async () => {
// Fetch version info
systemStore.fetchInfo()
+
+ // Fetch routing mode info
+ systemStore.fetchRouting()
})
onUnmounted(() => {
diff --git a/frontend/src/components/TopHeader.vue b/frontend/src/components/TopHeader.vue
index 8b7409c2..40937b77 100644
--- a/frontend/src/components/TopHeader.vue
+++ b/frontend/src/components/TopHeader.vue
@@ -64,6 +64,12 @@
Tools
+
+
+ Mode:
+ {{ routingModeLabel }}
+
+
Proxy:
@@ -105,6 +111,18 @@ const authStore = useAuthStore()
const addServerLabel = computed(() => authStore.isTeamsEdition ? 'Add Personal Server' : 'Add Server')
+const routingModeLabel = computed(() => {
+ const mode = systemStore.routingMode
+ switch (mode) {
+ case 'direct':
+ return 'Direct'
+ case 'code_execution':
+ return 'Code Exec'
+ default:
+ return 'Retrieve'
+ }
+})
+
const searchQuery = ref('')
const copyTooltip = ref('Copy MCP address')
const showAddServerModal = ref(false)
diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts
index 86ff6610..40e3d6e3 100644
--- a/frontend/src/services/api.ts
+++ b/frontend/src/services/api.ts
@@ -1,4 +1,4 @@
-import type { APIResponse, Server, Tool, ToolApproval, SearchResult, StatusUpdate, SecretRef, MigrationAnalysis, ConfigSecretsResponse, GetToolCallsResponse, GetToolCallDetailResponse, GetServerToolCallsResponse, GetConfigResponse, ValidateConfigResponse, ConfigApplyResult, ServerTokenMetrics, GetRegistriesResponse, SearchRegistryServersResponse, RepositoryServer, GetSessionsResponse, GetSessionDetailResponse, InfoResponse, ActivityListResponse, ActivityDetailResponse, ActivitySummaryResponse, ImportResponse, AgentTokenInfo, CreateAgentTokenRequest, CreateAgentTokenResponse } from '@/types'
+import type { APIResponse, Server, Tool, ToolApproval, SearchResult, StatusUpdate, SecretRef, MigrationAnalysis, ConfigSecretsResponse, GetToolCallsResponse, GetToolCallDetailResponse, GetServerToolCallsResponse, GetConfigResponse, ValidateConfigResponse, ConfigApplyResult, ServerTokenMetrics, GetRegistriesResponse, SearchRegistryServersResponse, RepositoryServer, GetSessionsResponse, GetSessionDetailResponse, InfoResponse, ActivityListResponse, ActivityDetailResponse, ActivitySummaryResponse, ImportResponse, AgentTokenInfo, CreateAgentTokenRequest, CreateAgentTokenResponse, RoutingInfo } from '@/types'
// Event types for API service
export interface APIAuthEvent {
@@ -205,8 +205,13 @@ class APIService {
}
// Status endpoint
- async getStatus(): Promise
> {
- return this.request<{ edition: string; running: boolean }>('/api/v1/status')
+ async getStatus(): Promise> {
+ return this.request<{ edition: string; running: boolean; routing_mode: string }>('/api/v1/status')
+ }
+
+ // Routing mode endpoint
+ async getRouting(): Promise> {
+ return this.request('/api/v1/routing')
}
// Server endpoints
diff --git a/frontend/src/stores/system.ts b/frontend/src/stores/system.ts
index b6030dca..5e8ffb57 100644
--- a/frontend/src/stores/system.ts
+++ b/frontend/src/stores/system.ts
@@ -1,6 +1,6 @@
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
-import type { StatusUpdate, Theme, Toast, InfoResponse } from '@/types'
+import type { StatusUpdate, Theme, Toast, InfoResponse, RoutingInfo } from '@/types'
import api from '@/services/api'
export const useSystemStore = defineStore('system', () => {
@@ -11,6 +11,7 @@ export const useSystemStore = defineStore('system', () => {
const currentTheme = ref('corporate')
const toasts = ref([])
const info = ref(null)
+ const routing = ref(null)
// Available themes
const themes: Theme[] = [
@@ -59,6 +60,9 @@ export const useSystemStore = defineStore('system', () => {
const updateAvailable = computed(() => info.value?.update?.available ?? false)
const latestVersion = computed(() => info.value?.update?.latest_version ?? '')
+ // Routing mode
+ const routingMode = computed(() => routing.value?.routing_mode ?? status.value?.routing_mode ?? 'retrieve_tools')
+
// Actions
function connectEventSource() {
if (eventSource.value) {
@@ -348,6 +352,17 @@ export const useSystemStore = defineStore('system', () => {
}
}
+ async function fetchRouting() {
+ try {
+ const response = await api.getRouting()
+ if (response.success && response.data) {
+ routing.value = response.data
+ }
+ } catch (error) {
+ console.error('Failed to fetch routing:', error)
+ }
+ }
+
// Initialize theme on store creation
loadTheme()
@@ -359,6 +374,7 @@ export const useSystemStore = defineStore('system', () => {
toasts,
themes,
info,
+ routing,
// Computed
isRunning,
@@ -368,6 +384,7 @@ export const useSystemStore = defineStore('system', () => {
version,
updateAvailable,
latestVersion,
+ routingMode,
// Actions
connectEventSource,
@@ -378,5 +395,6 @@ export const useSystemStore = defineStore('system', () => {
removeToast,
clearToasts,
fetchInfo,
+ fetchRouting,
}
-})
\ No newline at end of file
+})
diff --git a/frontend/src/types/api.ts b/frontend/src/types/api.ts
index c5ce201c..dd3ca82f 100644
--- a/frontend/src/types/api.ts
+++ b/frontend/src/types/api.ts
@@ -115,6 +115,7 @@ export interface SearchResult {
export interface StatusUpdate {
running: boolean
listen_addr: string
+ routing_mode?: string
upstream_stats: {
connected_servers: number
total_servers: number
@@ -124,6 +125,19 @@ export interface StatusUpdate {
timestamp: number
}
+// Routing mode types
+export interface RoutingInfo {
+ routing_mode: string
+ description: string
+ endpoints: {
+ default: string
+ direct: string
+ code_execution: string
+ retrieve_tools: string
+ }
+ available_modes: string[]
+}
+
// Dashboard stats
export interface DashboardStats {
servers: {
diff --git a/internal/config/config.go b/internal/config/config.go
index a47100a4..2d8e1902 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -12,6 +12,11 @@ import (
const (
defaultPort = "127.0.0.1:8080" // Localhost-only binding by default for security
+
+ // Routing mode constants (Spec 031)
+ RoutingModeRetrieveTools = "retrieve_tools" // Default: BM25 search via retrieve_tools + call_tool_read/write/destructive
+ RoutingModeDirect = "direct" // All upstream tools exposed directly with serverName__toolName naming
+ RoutingModeCodeExecution = "code_execution" // JS orchestration via code_execution tool with tool catalog
)
// Duration is a wrapper around time.Duration that can be marshaled to/from JSON.
@@ -123,6 +128,10 @@ type Config struct {
// Sensitive data detection settings (Spec 026)
SensitiveDataDetection *SensitiveDataDetectionConfig `json:"sensitive_data_detection,omitempty" mapstructure:"sensitive-data-detection"`
+ // Routing mode (Spec 031): how MCP tools are exposed to clients
+ // Valid values: "retrieve_tools" (default), "direct", "code_execution"
+ RoutingMode string `json:"routing_mode,omitempty" mapstructure:"routing-mode"`
+
// Tool-level quarantine settings (Spec 032)
// QuarantineEnabled controls whether tool-level quarantine is active.
// When nil (default), quarantine is enabled (secure by default).
@@ -856,6 +865,21 @@ func (c *Config) ValidateDetailed() []ValidationError {
})
}
+ // Validate routing mode (Spec 031)
+ if c.RoutingMode != "" {
+ validRoutingModes := map[string]bool{
+ RoutingModeRetrieveTools: true,
+ RoutingModeDirect: true,
+ RoutingModeCodeExecution: true,
+ }
+ if !validRoutingModes[c.RoutingMode] {
+ errors = append(errors, ValidationError{
+ Field: "routing_mode",
+ Message: fmt.Sprintf("invalid routing mode: %s (must be retrieve_tools, direct, or code_execution)", c.RoutingMode),
+ })
+ }
+ }
+
// Validate server configurations
serverNames := make(map[string]bool)
for i, server := range c.Servers {
@@ -988,6 +1012,11 @@ func (c *Config) Validate() error {
}
// CodeExecutionMaxToolCalls defaults to 0 (unlimited), which is valid
+ // Apply routing mode default (Spec 031)
+ if c.RoutingMode == "" {
+ c.RoutingMode = RoutingModeRetrieveTools
+ }
+
// Then perform detailed validation
errors := c.ValidateDetailed()
if len(errors) > 0 {
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
index 87438cc4..a98309bf 100644
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -843,6 +843,112 @@ func TestConfig_WithSensitiveDataDetection(t *testing.T) {
assert.Equal(t, 4.5, restored.SensitiveDataDetection.EntropyThreshold)
}
+// Tests for routing mode (Spec 031)
+
+func TestRoutingModeDefault(t *testing.T) {
+ cfg := &Config{}
+ err := cfg.Validate()
+ require.NoError(t, err)
+ assert.Equal(t, RoutingModeRetrieveTools, cfg.RoutingMode, "default routing mode should be retrieve_tools")
+}
+
+func TestRoutingModeValidation(t *testing.T) {
+ tests := []struct {
+ name string
+ routingMode string
+ wantErr bool
+ }{
+ {
+ name: "empty defaults to retrieve_tools",
+ routingMode: "",
+ wantErr: false,
+ },
+ {
+ name: "retrieve_tools is valid",
+ routingMode: RoutingModeRetrieveTools,
+ wantErr: false,
+ },
+ {
+ name: "direct is valid",
+ routingMode: RoutingModeDirect,
+ wantErr: false,
+ },
+ {
+ name: "code_execution is valid",
+ routingMode: RoutingModeCodeExecution,
+ wantErr: false,
+ },
+ {
+ name: "invalid mode is rejected",
+ routingMode: "invalid_mode",
+ wantErr: true,
+ },
+ {
+ name: "typo mode is rejected",
+ routingMode: "Direct",
+ wantErr: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ cfg := &Config{
+ RoutingMode: tt.routingMode,
+ }
+ err := cfg.Validate()
+ if tt.wantErr {
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "routing_mode")
+ } else {
+ assert.NoError(t, err)
+ if tt.routingMode == "" {
+ assert.Equal(t, RoutingModeRetrieveTools, cfg.RoutingMode)
+ } else {
+ assert.Equal(t, tt.routingMode, cfg.RoutingMode)
+ }
+ }
+ })
+ }
+}
+
+func TestRoutingModeJSONSerialization(t *testing.T) {
+ cfg := &Config{
+ Listen: "127.0.0.1:8080",
+ RoutingMode: RoutingModeDirect,
+ }
+
+ data, err := json.Marshal(cfg)
+ require.NoError(t, err)
+
+ var restored Config
+ err = json.Unmarshal(data, &restored)
+ require.NoError(t, err)
+ assert.Equal(t, RoutingModeDirect, restored.RoutingMode)
+}
+
+func TestRoutingModeOmittedFromJSON(t *testing.T) {
+ // When routing_mode is empty, it should be omitted from JSON
+ cfg := &Config{
+ Listen: "127.0.0.1:8080",
+ }
+
+ data, err := json.Marshal(cfg)
+ require.NoError(t, err)
+
+ // Parse as map to check key presence
+ var m map[string]interface{}
+ err = json.Unmarshal(data, &m)
+ require.NoError(t, err)
+ _, exists := m["routing_mode"]
+ assert.False(t, exists, "routing_mode should be omitted when empty")
+}
+
+func TestRoutingModeConstants(t *testing.T) {
+ assert.Equal(t, "retrieve_tools", RoutingModeRetrieveTools)
+ assert.Equal(t, "direct", RoutingModeDirect)
+ assert.Equal(t, "code_execution", RoutingModeCodeExecution)
+}
+
// Tests for tool-level quarantine config (Spec 032)
func TestConfig_IsQuarantineEnabled(t *testing.T) {
diff --git a/internal/httpapi/routing_test.go b/internal/httpapi/routing_test.go
new file mode 100644
index 00000000..0257cae4
--- /dev/null
+++ b/internal/httpapi/routing_test.go
@@ -0,0 +1,182 @@
+package httpapi
+
+import (
+ "encoding/json"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/smart-mcp-proxy/mcpproxy-go/internal/config"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "go.uber.org/zap"
+)
+
+// mockRoutingController is a mock controller for routing endpoint tests
+type mockRoutingController struct {
+ baseController
+ apiKey string
+ routingMode string
+}
+
+func (m *mockRoutingController) GetCurrentConfig() any {
+ return &config.Config{
+ APIKey: m.apiKey,
+ }
+}
+
+func (m *mockRoutingController) GetConfig() (*config.Config, error) {
+ return &config.Config{
+ APIKey: m.apiKey,
+ RoutingMode: m.routingMode,
+ }, nil
+}
+
+func TestHandleGetRouting(t *testing.T) {
+ t.Run("returns default retrieve_tools mode", func(t *testing.T) {
+ logger := zap.NewNop().Sugar()
+ mockCtrl := &mockRoutingController{apiKey: "test-key", routingMode: ""}
+ srv := NewServer(mockCtrl, logger, nil)
+
+ req := httptest.NewRequest(http.MethodGet, "/api/v1/routing", nil)
+ req.Header.Set("X-API-Key", "test-key")
+ w := httptest.NewRecorder()
+
+ srv.ServeHTTP(w, req)
+
+ assert.Equal(t, http.StatusOK, w.Code)
+
+ var resp struct {
+ Success bool `json:"success"`
+ Data map[string]interface{} `json:"data"`
+ }
+ err := json.NewDecoder(w.Body).Decode(&resp)
+ require.NoError(t, err)
+ assert.True(t, resp.Success)
+ assert.Equal(t, config.RoutingModeRetrieveTools, resp.Data["routing_mode"])
+ assert.NotEmpty(t, resp.Data["description"])
+ assert.NotNil(t, resp.Data["endpoints"])
+ assert.NotNil(t, resp.Data["available_modes"])
+ })
+
+ t.Run("returns direct mode", func(t *testing.T) {
+ logger := zap.NewNop().Sugar()
+ mockCtrl := &mockRoutingController{apiKey: "test-key", routingMode: "direct"}
+ srv := NewServer(mockCtrl, logger, nil)
+
+ req := httptest.NewRequest(http.MethodGet, "/api/v1/routing", nil)
+ req.Header.Set("X-API-Key", "test-key")
+ w := httptest.NewRecorder()
+
+ srv.ServeHTTP(w, req)
+
+ assert.Equal(t, http.StatusOK, w.Code)
+
+ var resp struct {
+ Success bool `json:"success"`
+ Data map[string]interface{} `json:"data"`
+ }
+ err := json.NewDecoder(w.Body).Decode(&resp)
+ require.NoError(t, err)
+ assert.Equal(t, "direct", resp.Data["routing_mode"])
+ assert.Contains(t, resp.Data["description"], "directly")
+ })
+
+ t.Run("returns code_execution mode", func(t *testing.T) {
+ logger := zap.NewNop().Sugar()
+ mockCtrl := &mockRoutingController{apiKey: "test-key", routingMode: "code_execution"}
+ srv := NewServer(mockCtrl, logger, nil)
+
+ req := httptest.NewRequest(http.MethodGet, "/api/v1/routing", nil)
+ req.Header.Set("X-API-Key", "test-key")
+ w := httptest.NewRecorder()
+
+ srv.ServeHTTP(w, req)
+
+ assert.Equal(t, http.StatusOK, w.Code)
+
+ var resp struct {
+ Success bool `json:"success"`
+ Data map[string]interface{} `json:"data"`
+ }
+ err := json.NewDecoder(w.Body).Decode(&resp)
+ require.NoError(t, err)
+ assert.Equal(t, "code_execution", resp.Data["routing_mode"])
+ assert.Contains(t, resp.Data["description"], "JavaScript")
+ })
+
+ t.Run("includes all endpoints", func(t *testing.T) {
+ logger := zap.NewNop().Sugar()
+ mockCtrl := &mockRoutingController{apiKey: "test-key", routingMode: "retrieve_tools"}
+ srv := NewServer(mockCtrl, logger, nil)
+
+ req := httptest.NewRequest(http.MethodGet, "/api/v1/routing", nil)
+ req.Header.Set("X-API-Key", "test-key")
+ w := httptest.NewRecorder()
+
+ srv.ServeHTTP(w, req)
+
+ assert.Equal(t, http.StatusOK, w.Code)
+
+ var resp struct {
+ Success bool `json:"success"`
+ Data map[string]interface{} `json:"data"`
+ }
+ err := json.NewDecoder(w.Body).Decode(&resp)
+ require.NoError(t, err)
+
+ endpoints, ok := resp.Data["endpoints"].(map[string]interface{})
+ require.True(t, ok, "endpoints should be an object")
+ assert.Equal(t, "/mcp", endpoints["default"])
+ assert.Equal(t, "/mcp/all", endpoints["direct"])
+ assert.Equal(t, "/mcp/code", endpoints["code_execution"])
+ assert.Equal(t, "/mcp/call", endpoints["retrieve_tools"])
+ })
+}
+
+func TestHandleGetStatus_IncludesRoutingMode(t *testing.T) {
+ t.Run("includes routing_mode in status response", func(t *testing.T) {
+ logger := zap.NewNop().Sugar()
+ mockCtrl := &mockRoutingController{apiKey: "test-key", routingMode: "direct"}
+ srv := NewServer(mockCtrl, logger, nil)
+
+ req := httptest.NewRequest(http.MethodGet, "/api/v1/status", nil)
+ req.Header.Set("X-API-Key", "test-key")
+ w := httptest.NewRecorder()
+
+ srv.ServeHTTP(w, req)
+
+ assert.Equal(t, http.StatusOK, w.Code)
+
+ var resp struct {
+ Success bool `json:"success"`
+ Data map[string]interface{} `json:"data"`
+ }
+ err := json.NewDecoder(w.Body).Decode(&resp)
+ require.NoError(t, err)
+ assert.True(t, resp.Success)
+ assert.Equal(t, "direct", resp.Data["routing_mode"])
+ })
+
+ t.Run("defaults routing_mode to retrieve_tools", func(t *testing.T) {
+ logger := zap.NewNop().Sugar()
+ mockCtrl := &mockRoutingController{apiKey: "test-key", routingMode: ""}
+ srv := NewServer(mockCtrl, logger, nil)
+
+ req := httptest.NewRequest(http.MethodGet, "/api/v1/status", nil)
+ req.Header.Set("X-API-Key", "test-key")
+ w := httptest.NewRecorder()
+
+ srv.ServeHTTP(w, req)
+
+ assert.Equal(t, http.StatusOK, w.Code)
+
+ var resp struct {
+ Success bool `json:"success"`
+ Data map[string]interface{} `json:"data"`
+ }
+ err := json.NewDecoder(w.Body).Decode(&resp)
+ require.NoError(t, err)
+ assert.Equal(t, config.RoutingModeRetrieveTools, resp.Data["routing_mode"])
+ })
+}
diff --git a/internal/httpapi/server.go b/internal/httpapi/server.go
index 1279b227..f6eeecce 100644
--- a/internal/httpapi/server.go
+++ b/internal/httpapi/server.go
@@ -447,6 +447,9 @@ func (s *Server) setupRoutes() {
// Info endpoint (server version, web UI URL, etc.)
r.Get("/info", s.handleGetInfo)
+ // Routing mode endpoint
+ r.Get("/routing", s.handleGetRouting)
+
// Server management
r.Get("/servers", s.handleGetServers)
r.Post("/servers", s.handleAddServer) // T001: Add server
@@ -673,18 +676,70 @@ func (s *Server) writeSuccess(w http.ResponseWriter, data interface{}) {
// @Failure 500 {object} contracts.ErrorResponse "Internal server error"
// @Router /api/v1/status [get]
func (s *Server) handleGetStatus(w http.ResponseWriter, _ *http.Request) {
+ // Get routing mode from config
+ routingMode := config.RoutingModeRetrieveTools
+ if cfg, err := s.controller.GetConfig(); err == nil && cfg != nil && cfg.RoutingMode != "" {
+ routingMode = cfg.RoutingMode
+ }
+
response := map[string]interface{}{
"running": s.controller.IsRunning(),
"edition": editionValue,
"listen_addr": s.controller.GetListenAddress(),
"upstream_stats": s.controller.GetUpstreamStats(),
"status": s.controller.GetStatus(),
+ "routing_mode": routingMode,
"timestamp": time.Now().Unix(),
}
s.writeSuccess(w, response)
}
+// handleGetRouting godoc
+// @Summary Get routing mode information
+// @Description Get the current routing mode and available MCP endpoints
+// @Tags status
+// @Produce json
+// @Security ApiKeyAuth
+// @Security ApiKeyQuery
+// @Success 200 {object} contracts.SuccessResponse "Routing mode information"
+// @Router /api/v1/routing [get]
+func (s *Server) handleGetRouting(w http.ResponseWriter, _ *http.Request) {
+ routingMode := config.RoutingModeRetrieveTools
+ if cfg, err := s.controller.GetConfig(); err == nil && cfg != nil && cfg.RoutingMode != "" {
+ routingMode = cfg.RoutingMode
+ }
+
+ // Build mode description
+ var description string
+ switch routingMode {
+ case config.RoutingModeDirect:
+ description = "All upstream tools exposed directly via serverName__toolName naming"
+ case config.RoutingModeCodeExecution:
+ description = "JavaScript orchestration via code_execution tool with tool catalog"
+ default:
+ description = "BM25 search via retrieve_tools + call_tool variants (default)"
+ }
+
+ response := map[string]interface{}{
+ "routing_mode": routingMode,
+ "description": description,
+ "endpoints": map[string]interface{}{
+ "default": "/mcp",
+ "direct": "/mcp/all",
+ "code_execution": "/mcp/code",
+ "retrieve_tools": "/mcp/call",
+ },
+ "available_modes": []string{
+ config.RoutingModeRetrieveTools,
+ config.RoutingModeDirect,
+ config.RoutingModeCodeExecution,
+ },
+ }
+
+ s.writeSuccess(w, response)
+}
+
// handleGetInfo godoc
// @Summary Get server information
// @Description Get essential server metadata including version, web UI URL, endpoint addresses, and update availability
diff --git a/internal/jsruntime/runtime.go b/internal/jsruntime/runtime.go
index 68d4251a..709aaa3b 100644
--- a/internal/jsruntime/runtime.go
+++ b/internal/jsruntime/runtime.go
@@ -12,13 +12,59 @@ import (
// ExecutionOptions contains optional parameters for JavaScript execution
type ExecutionOptions struct {
- Input map[string]interface{} // Input data accessible as global `input` variable
- TimeoutMs int // Execution timeout in milliseconds
- MaxToolCalls int // Maximum number of call_tool() invocations (0 = unlimited)
- AllowedServers []string // Whitelist of allowed server names (empty = all allowed)
- ExecutionID string // Unique execution ID for logging (auto-generated if empty)
+ Input map[string]interface{} // Input data accessible as global `input` variable
+ TimeoutMs int // Execution timeout in milliseconds
+ MaxToolCalls int // Maximum number of call_tool() invocations (0 = unlimited)
+ AllowedServers []string // Whitelist of allowed server names (empty = all allowed)
+ ExecutionID string // Unique execution ID for logging (auto-generated if empty)
+
+ // Auth enforcement (Spec 031)
+ AuthContext *AuthInfo // Auth context for permission enforcement (nil = no restrictions)
+ ToolAnnotationFunc ToolAnnotationLookup // Function to look up tool annotations for permission checking
}
+// AuthInfo carries authentication context for permission enforcement in JS execution.
+// This is a simplified view of the auth.AuthContext to avoid circular imports.
+type AuthInfo struct {
+ Type string // "admin", "agent", "user", etc.
+ AgentName string // Name of the agent token
+ AllowedServers []string // Servers this token can access (nil = all)
+ Permissions []string // Permission tiers: "read", "write", "destructive"
+}
+
+// CanAccessServer checks whether this auth context can access the named server.
+func (a *AuthInfo) CanAccessServer(name string) bool {
+ if a == nil || a.Type == "admin" || a.Type == "admin_user" {
+ return true
+ }
+ if name == "" {
+ return false
+ }
+ for _, s := range a.AllowedServers {
+ if s == "*" || s == name {
+ return true
+ }
+ }
+ return false
+}
+
+// HasPermission checks whether this auth context includes the given permission.
+func (a *AuthInfo) HasPermission(perm string) bool {
+ if a == nil || a.Type == "admin" || a.Type == "admin_user" {
+ return true
+ }
+ for _, p := range a.Permissions {
+ if p == perm {
+ return true
+ }
+ }
+ return false
+}
+
+// ToolAnnotationLookup is a function that returns the permission tier required for a tool.
+// Returns one of "read", "write", "destructive".
+type ToolAnnotationLookup func(serverName, toolName string) string
+
// ToolCaller is an interface for calling upstream MCP tools
type ToolCaller interface {
CallTool(ctx context.Context, serverName, toolName string, args map[string]interface{}) (interface{}, error)
@@ -26,16 +72,21 @@ type ToolCaller interface {
// ExecutionContext tracks the state of a single JavaScript execution
type ExecutionContext struct {
- ExecutionID string
- StartTime time.Time
- EndTime *time.Time
- Status string // "running", "success", "error", "timeout"
- ToolCalls []ToolCallRecord
- ResultValue interface{}
- ErrorDetails *JsError
- toolCaller ToolCaller
- maxToolCalls int
+ ExecutionID string
+ StartTime time.Time
+ EndTime *time.Time
+ Status string // "running", "success", "error", "timeout"
+ ToolCalls []ToolCallRecord
+ ResultValue interface{}
+ ErrorDetails *JsError
+ toolCaller ToolCaller
+ maxToolCalls int
allowedServerMap map[string]bool
+
+ // Auth enforcement (Spec 031)
+ authInfo *AuthInfo
+ toolAnnotationFunc ToolAnnotationLookup
+ maxPermissionLevel string // Tracks highest permission used: read < write < destructive
}
// ToolCallRecord represents a single call_tool() invocation
@@ -59,13 +110,16 @@ func Execute(ctx context.Context, caller ToolCaller, code string, opts Execution
// Create execution context
execCtx := &ExecutionContext{
- ExecutionID: opts.ExecutionID,
- StartTime: time.Now(),
- Status: "running",
- ToolCalls: make([]ToolCallRecord, 0),
- toolCaller: caller,
- maxToolCalls: opts.MaxToolCalls,
- allowedServerMap: make(map[string]bool),
+ ExecutionID: opts.ExecutionID,
+ StartTime: time.Now(),
+ Status: "running",
+ ToolCalls: make([]ToolCallRecord, 0),
+ toolCaller: caller,
+ maxToolCalls: opts.MaxToolCalls,
+ allowedServerMap: make(map[string]bool),
+ authInfo: opts.AuthContext,
+ toolAnnotationFunc: opts.ToolAnnotationFunc,
+ maxPermissionLevel: "",
}
// Build allowed server map for fast lookup
@@ -241,6 +295,39 @@ func (ec *ExecutionContext) makeCallToolFunction(vm *goja.Runtime) func(goja.Fun
})
}
+ // Auth context enforcement (Spec 031)
+ if ec.authInfo != nil {
+ // Check server access
+ if !ec.authInfo.CanAccessServer(serverName) {
+ return vm.ToValue(map[string]interface{}{
+ "ok": false,
+ "error": map[string]interface{}{
+ "code": "ACCESS_DENIED",
+ "message": fmt.Sprintf("token does not have access to server '%s'", serverName),
+ },
+ })
+ }
+
+ // Determine required permission via annotation lookup
+ requiredPerm := "read" // Default to read
+ if ec.toolAnnotationFunc != nil {
+ requiredPerm = ec.toolAnnotationFunc(serverName, toolName)
+ }
+
+ if !ec.authInfo.HasPermission(requiredPerm) {
+ return vm.ToValue(map[string]interface{}{
+ "ok": false,
+ "error": map[string]interface{}{
+ "code": "PERMISSION_DENIED",
+ "message": fmt.Sprintf("token does not have '%s' permission for tool '%s:%s'", requiredPerm, serverName, toolName),
+ },
+ })
+ }
+
+ // Track highest permission level
+ ec.updateMaxPermissionLevel(requiredPerm)
+ }
+
// Record tool call start
record := ToolCallRecord{
ServerName: serverName,
@@ -292,3 +379,22 @@ func validateSerializable(value interface{}) error {
}
return nil
}
+
+// permissionRank maps permission tiers to numeric rank for comparison.
+var permissionRank = map[string]int{
+ "read": 1,
+ "write": 2,
+ "destructive": 3,
+}
+
+// updateMaxPermissionLevel tracks the highest permission level used during execution.
+func (ec *ExecutionContext) updateMaxPermissionLevel(perm string) {
+ if permissionRank[perm] > permissionRank[ec.maxPermissionLevel] {
+ ec.maxPermissionLevel = perm
+ }
+}
+
+// GetMaxPermissionLevel returns the highest permission level used during execution.
+func (ec *ExecutionContext) GetMaxPermissionLevel() string {
+ return ec.maxPermissionLevel
+}
diff --git a/internal/jsruntime/runtime_test.go b/internal/jsruntime/runtime_test.go
index b688785a..29ca9699 100644
--- a/internal/jsruntime/runtime_test.go
+++ b/internal/jsruntime/runtime_test.go
@@ -19,7 +19,10 @@ type mockToolCaller struct {
func newMockToolCaller() *mockToolCaller {
return &mockToolCaller{
- calls: make([]struct{ server, tool string; args map[string]interface{} }, 0),
+ calls: make([]struct {
+ server, tool string
+ args map[string]interface{}
+ }, 0),
results: make(map[string]interface{}),
errors: make(map[string]error),
}
@@ -349,3 +352,241 @@ func TestExecuteSandboxRestrictions(t *testing.T) {
})
}
}
+
+// TestExecuteAuthContext_ServerAccessDenied tests auth enforcement blocks unauthorized server access
+func TestExecuteAuthContext_ServerAccessDenied(t *testing.T) {
+ caller := newMockToolCaller()
+
+ code := `
+ var res = call_tool("secret-server", "get_data", {});
+ ({ ok: res.ok, code: res.error ? res.error.code : null, msg: res.error ? res.error.message : null })
+ `
+ opts := ExecutionOptions{
+ AuthContext: &AuthInfo{
+ Type: "agent",
+ AgentName: "test-bot",
+ AllowedServers: []string{"github"}, // Only github, not secret-server
+ Permissions: []string{"read"},
+ },
+ }
+
+ result := Execute(context.Background(), caller, code, opts)
+ if !result.Ok {
+ t.Fatalf("expected ok=true, got error: %v", result.Error)
+ }
+
+ resultMap := result.Value.(map[string]interface{})
+ if resultMap["ok"] != false {
+ t.Errorf("expected tool call to fail, got ok=%v", resultMap["ok"])
+ }
+ if resultMap["code"] != "ACCESS_DENIED" {
+ t.Errorf("expected ACCESS_DENIED, got %v", resultMap["code"])
+ }
+ if len(caller.calls) != 0 {
+ t.Errorf("expected 0 upstream calls (blocked by auth), got %d", len(caller.calls))
+ }
+}
+
+// TestExecuteAuthContext_PermissionDenied tests auth enforcement blocks insufficient permissions
+func TestExecuteAuthContext_PermissionDenied(t *testing.T) {
+ caller := newMockToolCaller()
+
+ code := `
+ var res = call_tool("github", "delete_repo", {});
+ ({ ok: res.ok, code: res.error ? res.error.code : null })
+ `
+ opts := ExecutionOptions{
+ AuthContext: &AuthInfo{
+ Type: "agent",
+ AgentName: "reader-bot",
+ AllowedServers: []string{"github"},
+ Permissions: []string{"read"}, // Only read, needs destructive
+ },
+ ToolAnnotationFunc: func(serverName, toolName string) string {
+ if toolName == "delete_repo" {
+ return "destructive"
+ }
+ return "read"
+ },
+ }
+
+ result := Execute(context.Background(), caller, code, opts)
+ if !result.Ok {
+ t.Fatalf("expected ok=true, got error: %v", result.Error)
+ }
+
+ resultMap := result.Value.(map[string]interface{})
+ if resultMap["ok"] != false {
+ t.Errorf("expected tool call to fail, got ok=%v", resultMap["ok"])
+ }
+ if resultMap["code"] != "PERMISSION_DENIED" {
+ t.Errorf("expected PERMISSION_DENIED, got %v", resultMap["code"])
+ }
+}
+
+// TestExecuteAuthContext_PermissionAggregation tests that max permission level is tracked
+func TestExecuteAuthContext_PermissionAggregation(t *testing.T) {
+ caller := newMockToolCaller()
+
+ code := `
+ call_tool("github", "list_repos", {});
+ call_tool("github", "create_issue", {});
+ call_tool("github", "list_repos", {});
+ ({ done: true })
+ `
+ opts := ExecutionOptions{
+ AuthContext: &AuthInfo{
+ Type: "agent",
+ AgentName: "writer-bot",
+ AllowedServers: []string{"github"},
+ Permissions: []string{"read", "write", "destructive"},
+ },
+ ToolAnnotationFunc: func(serverName, toolName string) string {
+ if toolName == "create_issue" {
+ return "write"
+ }
+ return "read"
+ },
+ }
+
+ result := Execute(context.Background(), caller, code, opts)
+ if !result.Ok {
+ t.Fatalf("expected ok=true, got error: %v", result.Error)
+ }
+
+ if len(caller.calls) != 3 {
+ t.Errorf("expected 3 tool calls, got %d", len(caller.calls))
+ }
+}
+
+// TestExecuteAuthContext_AdminBypass tests that admin auth bypasses permission checks
+func TestExecuteAuthContext_AdminBypass(t *testing.T) {
+ caller := newMockToolCaller()
+
+ code := `
+ var res = call_tool("secret-server", "delete_all", {});
+ ({ ok: res.ok })
+ `
+ opts := ExecutionOptions{
+ AuthContext: &AuthInfo{
+ Type: "admin",
+ // No AllowedServers or Permissions - admin bypasses everything
+ },
+ ToolAnnotationFunc: func(serverName, toolName string) string {
+ return "destructive"
+ },
+ }
+
+ result := Execute(context.Background(), caller, code, opts)
+ if !result.Ok {
+ t.Fatalf("expected ok=true, got error: %v", result.Error)
+ }
+
+ resultMap := result.Value.(map[string]interface{})
+ if resultMap["ok"] != true {
+ t.Errorf("admin should bypass all auth checks, got ok=%v", resultMap["ok"])
+ }
+ if len(caller.calls) != 1 {
+ t.Errorf("expected 1 tool call (admin bypass), got %d", len(caller.calls))
+ }
+}
+
+// TestExecuteAuthContext_NoAuthContext tests backward compatibility with nil auth
+func TestExecuteAuthContext_NoAuthContext(t *testing.T) {
+ caller := newMockToolCaller()
+
+ code := `
+ var res = call_tool("any-server", "any-tool", {});
+ ({ ok: res.ok })
+ `
+ opts := ExecutionOptions{
+ // No AuthContext - should not enforce any restrictions
+ }
+
+ result := Execute(context.Background(), caller, code, opts)
+ if !result.Ok {
+ t.Fatalf("expected ok=true, got error: %v", result.Error)
+ }
+
+ resultMap := result.Value.(map[string]interface{})
+ if resultMap["ok"] != true {
+ t.Errorf("expected ok=true with no auth context, got %v", resultMap["ok"])
+ }
+}
+
+// TestAuthInfo_CanAccessServer tests the AuthInfo.CanAccessServer method
+func TestAuthInfo_CanAccessServer(t *testing.T) {
+ tests := []struct {
+ name string
+ auth *AuthInfo
+ server string
+ expected bool
+ }{
+ {"nil auth allows all", nil, "anything", true},
+ {"admin allows all", &AuthInfo{Type: "admin"}, "anything", true},
+ {"admin_user allows all", &AuthInfo{Type: "admin_user"}, "anything", true},
+ {"wildcard allows all", &AuthInfo{Type: "agent", AllowedServers: []string{"*"}}, "anything", true},
+ {"specific match", &AuthInfo{Type: "agent", AllowedServers: []string{"github"}}, "github", true},
+ {"no match", &AuthInfo{Type: "agent", AllowedServers: []string{"github"}}, "gitlab", false},
+ {"empty server name", &AuthInfo{Type: "agent", AllowedServers: []string{"github"}}, "", false},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := tt.auth.CanAccessServer(tt.server)
+ if result != tt.expected {
+ t.Errorf("expected %v, got %v", tt.expected, result)
+ }
+ })
+ }
+}
+
+// TestAuthInfo_HasPermission tests the AuthInfo.HasPermission method
+func TestAuthInfo_HasPermission(t *testing.T) {
+ tests := []struct {
+ name string
+ auth *AuthInfo
+ perm string
+ expected bool
+ }{
+ {"nil auth allows all", nil, "destructive", true},
+ {"admin allows all", &AuthInfo{Type: "admin"}, "destructive", true},
+ {"has read", &AuthInfo{Type: "agent", Permissions: []string{"read"}}, "read", true},
+ {"no write", &AuthInfo{Type: "agent", Permissions: []string{"read"}}, "write", false},
+ {"has write", &AuthInfo{Type: "agent", Permissions: []string{"read", "write"}}, "write", true},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := tt.auth.HasPermission(tt.perm)
+ if result != tt.expected {
+ t.Errorf("expected %v, got %v", tt.expected, result)
+ }
+ })
+ }
+}
+
+// TestPermissionLevelTracking tests max permission level tracking
+func TestPermissionLevelTracking(t *testing.T) {
+ ec := &ExecutionContext{}
+
+ ec.updateMaxPermissionLevel("read")
+ if ec.GetMaxPermissionLevel() != "read" {
+ t.Errorf("expected read, got %s", ec.GetMaxPermissionLevel())
+ }
+
+ ec.updateMaxPermissionLevel("write")
+ if ec.GetMaxPermissionLevel() != "write" {
+ t.Errorf("expected write, got %s", ec.GetMaxPermissionLevel())
+ }
+
+ ec.updateMaxPermissionLevel("read") // Lower level shouldn't downgrade
+ if ec.GetMaxPermissionLevel() != "write" {
+ t.Errorf("expected write (should not downgrade), got %s", ec.GetMaxPermissionLevel())
+ }
+
+ ec.updateMaxPermissionLevel("destructive")
+ if ec.GetMaxPermissionLevel() != "destructive" {
+ t.Errorf("expected destructive, got %s", ec.GetMaxPermissionLevel())
+ }
+}
diff --git a/internal/server/mcp.go b/internal/server/mcp.go
index bbd41212..bad7ca26 100644
--- a/internal/server/mcp.go
+++ b/internal/server/mcp.go
@@ -71,6 +71,11 @@ type MCPProxyServer struct {
mainServer *Server // Reference to main server for config persistence
config *config.Config // Add config reference for security checks
+ // Routing mode MCP server instances (Spec 031)
+ // Each instance has different tools registered for its routing mode.
+ directServer *mcpserver.MCPServer // Direct mode: upstream tools with serverName__toolName naming
+ codeExecServer *mcpserver.MCPServer // Code execution mode: code_execution + retrieve_tools
+
// Docker availability cache
dockerAvailableCache *bool
dockerCacheTime time.Time
@@ -216,7 +221,7 @@ func NewMCPProxyServer(
sessionStore: sessionStore,
}
- // Register proxy tools
+ // Register proxy tools for the default (retrieve_tools) server
proxy.registerTools(debugSearch)
// Register prompts if enabled
@@ -224,6 +229,9 @@ func NewMCPProxyServer(
proxy.registerPrompts()
}
+ // Initialize routing mode server instances (Spec 031)
+ proxy.initRoutingModeServers()
+
return proxy
}
diff --git a/internal/server/mcp_code_execution.go b/internal/server/mcp_code_execution.go
index 01b2a18b..a3492bb2 100644
--- a/internal/server/mcp_code_execution.go
+++ b/internal/server/mcp_code_execution.go
@@ -7,6 +7,8 @@ import (
"sync"
"time"
+ "github.com/smart-mcp-proxy/mcpproxy-go/internal/auth"
+ "github.com/smart-mcp-proxy/mcpproxy-go/internal/contracts"
"github.com/smart-mcp-proxy/mcpproxy-go/internal/jsruntime"
"github.com/smart-mcp-proxy/mcpproxy-go/internal/storage"
"github.com/smart-mcp-proxy/mcpproxy-go/internal/upstream"
@@ -103,16 +105,16 @@ func (p *MCPProxyServer) handleCodeExecution(ctx context.Context, request mcp.Ca
// Create tool caller adapter that wraps the upstream manager
toolCaller := &upstreamToolCaller{
- upstreamManager: p.upstreamManager,
- logger: p.logger,
- executionID: options.ExecutionID,
- storage: p.storage,
- configPath: configPath,
- parentCallID: parentCallID,
- sessionID: sessionID,
- clientName: clientName,
- clientVersion: clientVersion,
- mainServer: p.mainServer,
+ upstreamManager: p.upstreamManager,
+ logger: p.logger,
+ executionID: options.ExecutionID,
+ storage: p.storage,
+ configPath: configPath,
+ parentCallID: parentCallID,
+ sessionID: sessionID,
+ clientName: clientName,
+ clientVersion: clientVersion,
+ mainServer: p.mainServer,
}
// Log pool metrics before acquisition
@@ -163,6 +165,18 @@ func (p *MCPProxyServer) handleCodeExecution(ctx context.Context, request mcp.Ca
}()
}
+ // Inject auth context for permission enforcement (Spec 031)
+ if authCtx := auth.AuthContextFromContext(ctx); authCtx != nil {
+ options.AuthContext = &jsruntime.AuthInfo{
+ Type: authCtx.Type,
+ AgentName: authCtx.AgentName,
+ AllowedServers: authCtx.AllowedServers,
+ Permissions: authCtx.Permissions,
+ }
+ // Provide tool annotation lookup function for permission tier resolution
+ options.ToolAnnotationFunc = p.lookupToolPermission
+ }
+
// Execute JavaScript
p.logger.Info("executing JavaScript code",
zap.String("execution_id", options.ExecutionID),
@@ -257,10 +271,10 @@ func (p *MCPProxyServer) handleCodeExecution(ctx context.Context, request mcp.Ca
// Record the parent code_execution call in history
codeExecRecord := &storage.ToolCallRecord{
- ID: parentCallID,
- ServerID: "code_execution", // Special server ID for built-in tool
- ServerName: "mcpproxy", // Built-in tool
- ToolName: "code_execution",
+ ID: parentCallID,
+ ServerID: "code_execution", // Special server ID for built-in tool
+ ServerName: "mcpproxy", // Built-in tool
+ ToolName: "code_execution",
Arguments: map[string]interface{}{
"code": code,
"input": options.Input,
@@ -331,18 +345,18 @@ type toolCallRecord struct {
// upstreamToolCaller adapts the upstream.Manager to implement jsruntime.ToolCaller
type upstreamToolCaller struct {
- upstreamManager *upstream.Manager
- logger *zap.Logger
- executionID string
- storage *storage.Manager
- configPath string
- toolCalls []toolCallRecord
- mu sync.Mutex
- parentCallID string // ID of the parent code_execution call
- sessionID string // MCP session ID
- clientName string // MCP client name
- clientVersion string // MCP client version
- mainServer *Server // Reference to main server for tokenizer access
+ upstreamManager *upstream.Manager
+ logger *zap.Logger
+ executionID string
+ storage *storage.Manager
+ configPath string
+ toolCalls []toolCallRecord
+ mu sync.Mutex
+ parentCallID string // ID of the parent code_execution call
+ sessionID string // MCP session ID
+ clientName string // MCP client name
+ clientVersion string // MCP client version
+ mainServer *Server // Reference to main server for tokenizer access
}
// CallTool implements jsruntime.ToolCaller interface
@@ -530,3 +544,37 @@ func (u *upstreamToolCaller) storeToolCallInHistory(serverName, toolName string,
)
}
}
+
+// lookupToolPermission returns the required permission tier for a tool based on its annotations.
+// This is used by the JS runtime to enforce auth context permissions during code_execution.
+func (p *MCPProxyServer) lookupToolPermission(serverName, toolName string) string {
+ // Primary: exact match via StateView (no BM25 fuzzy matching)
+ annotations := p.lookupToolAnnotations(serverName, toolName)
+ if annotations != nil {
+ callWith := contracts.DeriveCallWith(annotations)
+ perm := contracts.ToolVariantToOperationType[callWith]
+ if perm != "" {
+ return perm
+ }
+ }
+
+ // Fallback: search the index with enough candidates to find an exact match
+ if p.index != nil {
+ qualifiedName := serverName + ":" + toolName
+ results, err := p.index.Search(qualifiedName, 20)
+ if err == nil {
+ for _, r := range results {
+ if r.Tool != nil && r.Tool.ServerName == serverName && r.Tool.Name == toolName {
+ callWith := contracts.DeriveCallWith(r.Tool.Annotations)
+ perm := contracts.ToolVariantToOperationType[callWith]
+ if perm != "" {
+ return perm
+ }
+ }
+ }
+ }
+ }
+
+ // Default to read (safest)
+ return contracts.OperationTypeRead
+}
diff --git a/internal/server/mcp_routing.go b/internal/server/mcp_routing.go
new file mode 100644
index 00000000..5451dbc8
--- /dev/null
+++ b/internal/server/mcp_routing.go
@@ -0,0 +1,419 @@
+package server
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/mark3labs/mcp-go/mcp"
+ mcpserver "github.com/mark3labs/mcp-go/server"
+ "go.uber.org/zap"
+
+ "github.com/smart-mcp-proxy/mcpproxy-go/internal/auth"
+ "github.com/smart-mcp-proxy/mcpproxy-go/internal/config"
+ "github.com/smart-mcp-proxy/mcpproxy-go/internal/contracts"
+ "github.com/smart-mcp-proxy/mcpproxy-go/internal/reqcontext"
+)
+
+const (
+ // DirectModeToolSeparator is the separator between server name and tool name in direct mode.
+ // Using double underscore to avoid conflicts with single underscores in tool names.
+ DirectModeToolSeparator = "__"
+)
+
+// ParseDirectToolName parses a direct mode tool name (serverName__toolName) into server and tool components.
+// Splits on the FIRST occurrence of "__" only, so tool names containing "__" are preserved.
+// Returns server name, tool name, and whether the parse was successful.
+func ParseDirectToolName(directName string) (serverName, toolName string, ok bool) {
+ idx := strings.Index(directName, DirectModeToolSeparator)
+ if idx <= 0 || idx+len(DirectModeToolSeparator) >= len(directName) {
+ return "", "", false
+ }
+ return directName[:idx], directName[idx+len(DirectModeToolSeparator):], true
+}
+
+// FormatDirectToolName formats a server name and tool name into a direct mode tool name.
+func FormatDirectToolName(serverName, toolName string) string {
+ return serverName + DirectModeToolSeparator + toolName
+}
+
+// buildDirectModeTools builds MCP tool definitions for direct mode.
+// Each upstream tool is exposed directly with serverName__toolName naming.
+// Only tools from connected, enabled, non-quarantined servers are included.
+func (p *MCPProxyServer) buildDirectModeTools() []mcpserver.ServerTool {
+ ctx := context.Background()
+
+ // Use DiscoverTools which already filters for connected, enabled, non-quarantined servers
+ tools, err := p.upstreamManager.DiscoverTools(ctx)
+ if err != nil {
+ p.logger.Error("failed to discover tools for direct mode", zap.Error(err))
+ return nil
+ }
+
+ serverTools := make([]mcpserver.ServerTool, 0, len(tools))
+ for _, tool := range tools {
+ directName := FormatDirectToolName(tool.ServerName, tool.Name)
+
+ // Build MCP tool options
+ opts := []mcp.ToolOption{
+ mcp.WithDescription(fmt.Sprintf("[%s] %s", tool.ServerName, tool.Description)),
+ }
+
+ // Apply annotations from upstream tool
+ if tool.Annotations != nil {
+ if tool.Annotations.Title != "" {
+ opts = append(opts, mcp.WithTitleAnnotation(tool.Annotations.Title))
+ }
+ if tool.Annotations.ReadOnlyHint != nil {
+ opts = append(opts, mcp.WithReadOnlyHintAnnotation(*tool.Annotations.ReadOnlyHint))
+ }
+ if tool.Annotations.DestructiveHint != nil {
+ opts = append(opts, mcp.WithDestructiveHintAnnotation(*tool.Annotations.DestructiveHint))
+ }
+ if tool.Annotations.IdempotentHint != nil {
+ opts = append(opts, mcp.WithIdempotentHintAnnotation(*tool.Annotations.IdempotentHint))
+ }
+ if tool.Annotations.OpenWorldHint != nil {
+ opts = append(opts, mcp.WithOpenWorldHintAnnotation(*tool.Annotations.OpenWorldHint))
+ }
+ }
+
+ mcpTool := mcp.NewTool(directName, opts...)
+
+ // Apply input schema from upstream tool
+ if tool.ParamsJSON != "" {
+ var schema map[string]interface{}
+ if err := json.Unmarshal([]byte(tool.ParamsJSON), &schema); err == nil {
+ mcpTool.InputSchema = mcp.ToolInputSchema{
+ Type: "object",
+ }
+ if props, ok := schema["properties"].(map[string]interface{}); ok {
+ mcpTool.InputSchema.Properties = props
+ }
+ if req, ok := schema["required"].([]interface{}); ok {
+ reqStrings := make([]string, 0, len(req))
+ for _, r := range req {
+ if s, ok := r.(string); ok {
+ reqStrings = append(reqStrings, s)
+ }
+ }
+ mcpTool.InputSchema.Required = reqStrings
+ }
+ }
+ }
+
+ serverTools = append(serverTools, mcpserver.ServerTool{
+ Tool: mcpTool,
+ Handler: p.makeDirectModeHandler(tool.ServerName, tool.Name, tool.Annotations),
+ })
+ }
+
+ p.logger.Info("built direct mode tools",
+ zap.Int("tool_count", len(serverTools)))
+
+ return serverTools
+}
+
+// makeDirectModeHandler creates a handler function for a direct mode tool.
+// It handles auth checks, permission enforcement, and upstream calls.
+func (p *MCPProxyServer) makeDirectModeHandler(serverName, toolName string, annotations *config.ToolAnnotations) mcpserver.ToolHandlerFunc {
+ return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+ startTime := time.Now()
+
+ // Check auth context for server access and permissions
+ authCtx := auth.AuthContextFromContext(ctx)
+ if authCtx != nil {
+ // Check server access
+ if !authCtx.CanAccessServer(serverName) {
+ return mcp.NewToolResultError(fmt.Sprintf("Access denied: token does not have access to server '%s'", serverName)), nil
+ }
+
+ // Determine required permission from annotations
+ requiredVariant := contracts.DeriveCallWith(annotations)
+ requiredPerm := contracts.ToolVariantToOperationType[requiredVariant]
+ if requiredPerm == "" {
+ requiredPerm = contracts.OperationTypeRead
+ }
+
+ if !authCtx.HasPermission(requiredPerm) {
+ return mcp.NewToolResultError(fmt.Sprintf("Permission denied: token does not have '%s' permission required for tool '%s:%s'", requiredPerm, serverName, toolName)), nil
+ }
+ }
+
+ // Get session ID for activity logging
+ var sessionID string
+ if sess := mcpserver.ClientSessionFromContext(ctx); sess != nil {
+ sessionID = sess.SessionID()
+ }
+
+ // Get request ID from context
+ requestID := reqcontext.GetRequestID(ctx)
+
+ // Get arguments from the request
+ args := request.GetArguments()
+
+ // Emit activity event
+ enrichedArgs := injectAuthMetadata(ctx, args)
+ p.emitActivityToolCallStarted(serverName, toolName, sessionID, requestID, "mcp", enrichedArgs)
+
+ // Call upstream
+ qualifiedName := serverName + ":" + toolName
+ result, err := p.upstreamManager.CallTool(ctx, qualifiedName, args)
+
+ durationMs := time.Since(startTime).Milliseconds()
+
+ if err != nil {
+ // Emit error activity
+ p.emitActivityToolCallCompleted(serverName, toolName, sessionID, requestID, "mcp", "error", err.Error(), durationMs, enrichedArgs, "", false, "", nil)
+ return mcp.NewToolResultError(fmt.Sprintf("Error calling %s:%s: %v", serverName, toolName, err)), nil
+ }
+
+ // Format response
+ var responseText string
+ switch v := result.(type) {
+ case string:
+ responseText = v
+ default:
+ responseBytes, marshalErr := json.Marshal(v)
+ if marshalErr != nil {
+ responseText = fmt.Sprintf("%v", v)
+ } else {
+ responseText = string(responseBytes)
+ }
+ }
+
+ // Determine tool variant for activity logging
+ toolVariant := contracts.DeriveCallWith(annotations)
+
+ // Truncate if needed
+ truncated := false
+ if p.config.ToolResponseLimit > 0 && len(responseText) > p.config.ToolResponseLimit {
+ responseText = responseText[:p.config.ToolResponseLimit]
+ truncated = true
+ }
+
+ // Emit success activity
+ p.emitActivityToolCallCompleted(serverName, toolName, sessionID, requestID, "mcp", "success", "", durationMs, enrichedArgs, responseText, truncated, toolVariant, nil)
+
+ return mcp.NewToolResultText(responseText), nil
+ }
+}
+
+// buildCodeExecModeTools builds the tool set for code_execution routing mode.
+// Includes: code_execution (with enhanced description listing available tools) and retrieve_tools (for discovery).
+// Does NOT include call_tool_read/write/destructive.
+func (p *MCPProxyServer) buildCodeExecModeTools() []mcpserver.ServerTool {
+ tools := make([]mcpserver.ServerTool, 0, 2)
+
+ // Check if code execution is enabled in config
+ if p.config != nil && !p.config.EnableCodeExecution {
+ // Code execution is disabled: register a stub tool that returns a clear error message
+ codeExecutionTool := mcp.NewTool("code_execution",
+ mcp.WithDescription("Code execution is currently disabled. Enable it by setting \"enable_code_execution\": true in your mcpproxy config."),
+ mcp.WithTitleAnnotation("Code Execution (Disabled)"),
+ mcp.WithReadOnlyHintAnnotation(true),
+ mcp.WithString("code",
+ mcp.Required(),
+ mcp.Description("JavaScript source code (ES5.1+) to execute."),
+ ),
+ )
+ tools = append(tools, mcpserver.ServerTool{
+ Tool: codeExecutionTool,
+ Handler: func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+ return mcp.NewToolResultError("Code execution is disabled. Enable it by setting \"enable_code_execution\": true in your mcpproxy configuration file."), nil
+ },
+ })
+ } else {
+ // Build enhanced description with available tools catalog
+ ctx := context.Background()
+ toolCatalog := p.buildToolCatalogDescription(ctx)
+
+ codeExecDescription := fmt.Sprintf(
+ "Execute JavaScript code that orchestrates multiple upstream MCP tools in a single request. "+
+ "Use this when you need to combine results from 2+ tools, implement conditional logic, loops, or data transformations.\n\n"+
+ "**Available in JavaScript**:\n"+
+ "- `input` global: Your input data passed via the 'input' parameter\n"+
+ "- `call_tool(serverName, toolName, args)`: Call upstream tools (returns {ok, result} or {ok, error})\n"+
+ "- Standard ES5.1+ JavaScript (no require(), filesystem, or network access)\n\n"+
+ "**Available tools for orchestration**:\n%s\n\n"+
+ "Use call_tool('serverName', 'toolName', {args}) to invoke tools.",
+ toolCatalog,
+ )
+
+ // code_execution tool with enhanced description
+ codeExecutionTool := mcp.NewTool("code_execution",
+ mcp.WithDescription(codeExecDescription),
+ mcp.WithTitleAnnotation("Code Execution"),
+ mcp.WithDestructiveHintAnnotation(true),
+ mcp.WithString("code",
+ mcp.Required(),
+ mcp.Description("JavaScript source code (ES5.1+) to execute. Use `input` to access input data and `call_tool(serverName, toolName, args)` to invoke upstream tools."),
+ ),
+ mcp.WithObject("input",
+ mcp.Description("Input data accessible as global `input` variable in JavaScript code (default: {})"),
+ ),
+ mcp.WithObject("options",
+ mcp.Description("Execution options: timeout_ms (1-600000, default: 120000), max_tool_calls (>= 0, 0=unlimited), allowed_servers (array of server names, empty=all allowed)"),
+ ),
+ )
+ tools = append(tools, mcpserver.ServerTool{
+ Tool: codeExecutionTool,
+ Handler: p.handleCodeExecution,
+ })
+ }
+
+ // retrieve_tools for discovery
+ retrieveToolsTool := mcp.NewTool("retrieve_tools",
+ mcp.WithDescription("Search and discover available upstream tools using BM25 full-text search. Use this to find tools before orchestrating them with code_execution. Use natural language to describe what you want to accomplish."),
+ mcp.WithTitleAnnotation("Retrieve Tools"),
+ mcp.WithReadOnlyHintAnnotation(true),
+ mcp.WithString("query",
+ mcp.Required(),
+ mcp.Description("Natural language description of what you want to accomplish."),
+ ),
+ mcp.WithNumber("limit",
+ mcp.Description("Maximum number of tools to return (default: configured tools_limit, max: 100)"),
+ ),
+ )
+ tools = append(tools, mcpserver.ServerTool{
+ Tool: retrieveToolsTool,
+ Handler: p.handleRetrieveTools,
+ })
+
+ p.logger.Info("built code execution mode tools",
+ zap.Int("tool_count", len(tools)))
+
+ return tools
+}
+
+// buildToolCatalogDescription builds a human-readable catalog of available tools for the code_execution description.
+func (p *MCPProxyServer) buildToolCatalogDescription(ctx context.Context) string {
+ tools, err := p.upstreamManager.DiscoverTools(ctx)
+ if err != nil {
+ return " (unable to discover tools - use retrieve_tools to search)"
+ }
+
+ if len(tools) == 0 {
+ return " (no upstream tools available)"
+ }
+
+ var sb strings.Builder
+ for _, tool := range tools {
+ // Determine permission tier from annotations
+ callWith := contracts.DeriveCallWith(tool.Annotations)
+ perm := contracts.ToolVariantToOperationType[callWith]
+ if perm == "" {
+ perm = "read"
+ }
+
+ // Truncate description for catalog listing
+ desc := tool.Description
+ if len(desc) > 80 {
+ desc = desc[:77] + "..."
+ }
+
+ sb.WriteString(fmt.Sprintf("- %s:%s (%s) - %s\n", tool.ServerName, tool.Name, perm, desc))
+ }
+
+ return sb.String()
+}
+
+// initRoutingModeServers creates separate MCP server instances for each routing mode.
+// Each server instance has its own set of tools registered appropriate for that mode.
+// The main "server" field remains the retrieve_tools mode server (default).
+func (p *MCPProxyServer) initRoutingModeServers() {
+ // Create direct mode server
+ p.directServer = mcpserver.NewMCPServer(
+ "mcpproxy-go",
+ "1.0.0",
+ mcpserver.WithToolCapabilities(true),
+ mcpserver.WithRecovery(),
+ )
+
+ // Create code execution mode server
+ p.codeExecServer = mcpserver.NewMCPServer(
+ "mcpproxy-go",
+ "1.0.0",
+ mcpserver.WithToolCapabilities(true),
+ mcpserver.WithRecovery(),
+ )
+
+ // Register tools for code execution mode (static tools that don't change)
+ codeExecTools := p.buildCodeExecModeTools()
+ for _, st := range codeExecTools {
+ p.codeExecServer.AddTool(st.Tool, st.Handler)
+ }
+
+ // Note: Direct mode tools are built lazily/on-demand via RefreshDirectModeTools
+ // because upstream servers may not be connected yet during initialization.
+ // The servers.changed event will trigger a refresh.
+
+ p.logger.Info("routing mode servers initialized",
+ zap.String("default_mode", p.config.RoutingMode))
+}
+
+// RefreshDirectModeTools rebuilds the direct mode server's tool set.
+// Should be called when upstream servers change (connect/disconnect/tool updates).
+func (p *MCPProxyServer) RefreshDirectModeTools() {
+ if p.directServer == nil {
+ return
+ }
+
+ directTools := p.buildDirectModeTools()
+
+ // Convert to the format needed by SetTools
+ serverTools := make([]mcpserver.ServerTool, len(directTools))
+ copy(serverTools, directTools)
+
+ // Replace all tools atomically
+ p.directServer.SetTools(serverTools...)
+
+ p.logger.Info("refreshed direct mode tools",
+ zap.Int("tool_count", len(directTools)))
+}
+
+// RefreshCodeExecModeTools rebuilds the code execution mode server's tool catalog description.
+// Should be called when upstream servers change to update the available tools listing.
+func (p *MCPProxyServer) RefreshCodeExecModeTools() {
+ if p.codeExecServer == nil {
+ return
+ }
+
+ codeExecTools := p.buildCodeExecModeTools()
+ serverTools := make([]mcpserver.ServerTool, len(codeExecTools))
+ copy(serverTools, codeExecTools)
+
+ p.codeExecServer.SetTools(serverTools...)
+
+ p.logger.Info("refreshed code execution mode tools",
+ zap.Int("tool_count", len(codeExecTools)))
+}
+
+// GetMCPServerForMode returns the MCP server instance for the given routing mode.
+// Falls back to the default retrieve_tools server for unknown modes.
+func (p *MCPProxyServer) GetMCPServerForMode(mode string) *mcpserver.MCPServer {
+ switch mode {
+ case config.RoutingModeDirect:
+ if p.directServer != nil {
+ return p.directServer
+ }
+ case config.RoutingModeCodeExecution:
+ if p.codeExecServer != nil {
+ return p.codeExecServer
+ }
+ }
+ // Default: retrieve_tools mode (the original server)
+ return p.server
+}
+
+// GetDirectServer returns the direct mode MCP server instance.
+func (p *MCPProxyServer) GetDirectServer() *mcpserver.MCPServer {
+ return p.directServer
+}
+
+// GetCodeExecServer returns the code execution mode MCP server instance.
+func (p *MCPProxyServer) GetCodeExecServer() *mcpserver.MCPServer {
+ return p.codeExecServer
+}
diff --git a/internal/server/mcp_routing_test.go b/internal/server/mcp_routing_test.go
new file mode 100644
index 00000000..5b46d23b
--- /dev/null
+++ b/internal/server/mcp_routing_test.go
@@ -0,0 +1,390 @@
+package server
+
+import (
+ "context"
+ "testing"
+
+ "github.com/mark3labs/mcp-go/mcp"
+ mcpserver "github.com/mark3labs/mcp-go/server"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "go.uber.org/zap"
+
+ "github.com/smart-mcp-proxy/mcpproxy-go/internal/auth"
+ "github.com/smart-mcp-proxy/mcpproxy-go/internal/config"
+)
+
+func TestParseDirectToolName(t *testing.T) {
+ tests := []struct {
+ name string
+ directName string
+ wantServer string
+ wantTool string
+ wantOk bool
+ }{
+ {
+ name: "simple tool name",
+ directName: "github__create_issue",
+ wantServer: "github",
+ wantTool: "create_issue",
+ wantOk: true,
+ },
+ {
+ name: "tool with underscores",
+ directName: "my-server__my_tool_name",
+ wantServer: "my-server",
+ wantTool: "my_tool_name",
+ wantOk: true,
+ },
+ {
+ name: "tool name contains double underscore",
+ directName: "server__tool__with__double",
+ wantServer: "server",
+ wantTool: "tool__with__double",
+ wantOk: true,
+ },
+ {
+ name: "no separator",
+ directName: "noseparator",
+ wantServer: "",
+ wantTool: "",
+ wantOk: false,
+ },
+ {
+ name: "single underscore only",
+ directName: "server_tool",
+ wantServer: "",
+ wantTool: "",
+ wantOk: false,
+ },
+ {
+ name: "empty string",
+ directName: "",
+ wantServer: "",
+ wantTool: "",
+ wantOk: false,
+ },
+ {
+ name: "separator at start",
+ directName: "__toolname",
+ wantServer: "",
+ wantTool: "",
+ wantOk: false,
+ },
+ {
+ name: "separator at end",
+ directName: "server__",
+ wantServer: "",
+ wantTool: "",
+ wantOk: false,
+ },
+ {
+ name: "just separator",
+ directName: "__",
+ wantServer: "",
+ wantTool: "",
+ wantOk: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ server, tool, ok := ParseDirectToolName(tt.directName)
+ assert.Equal(t, tt.wantOk, ok)
+ assert.Equal(t, tt.wantServer, server)
+ assert.Equal(t, tt.wantTool, tool)
+ })
+ }
+}
+
+func TestFormatDirectToolName(t *testing.T) {
+ tests := []struct {
+ name string
+ serverName string
+ toolName string
+ want string
+ }{
+ {
+ name: "simple names",
+ serverName: "github",
+ toolName: "create_issue",
+ want: "github__create_issue",
+ },
+ {
+ name: "server with hyphens",
+ serverName: "my-server",
+ toolName: "get_user",
+ want: "my-server__get_user",
+ },
+ {
+ name: "tool with underscores",
+ serverName: "api",
+ toolName: "list_all_items",
+ want: "api__list_all_items",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := FormatDirectToolName(tt.serverName, tt.toolName)
+ assert.Equal(t, tt.want, got)
+ })
+ }
+}
+
+func TestDirectToolNameRoundTrip(t *testing.T) {
+ // Test that formatting and parsing are inverse operations
+ testCases := []struct {
+ serverName string
+ toolName string
+ }{
+ {"github", "create_issue"},
+ {"my-server", "list_repos"},
+ {"api", "search_files"},
+ {"db-server", "query_users_table"},
+ }
+
+ for _, tc := range testCases {
+ formatted := FormatDirectToolName(tc.serverName, tc.toolName)
+ parsedServer, parsedTool, ok := ParseDirectToolName(formatted)
+ assert.True(t, ok, "should parse successfully for %s/%s", tc.serverName, tc.toolName)
+ assert.Equal(t, tc.serverName, parsedServer)
+ assert.Equal(t, tc.toolName, parsedTool)
+ }
+}
+
+func TestDirectModeToolSeparator(t *testing.T) {
+ assert.Equal(t, "__", DirectModeToolSeparator)
+}
+
+func TestGetMCPServerForMode(t *testing.T) {
+ // Create a minimal MCPProxyServer with mock servers
+ proxy := &MCPProxyServer{}
+
+ // Create distinct server instances so we can verify identity
+ mainServer := mcpserver.NewMCPServer("main", "1.0.0", mcpserver.WithToolCapabilities(true))
+ directServer := mcpserver.NewMCPServer("direct", "1.0.0", mcpserver.WithToolCapabilities(true))
+ codeExecServer := mcpserver.NewMCPServer("code_exec", "1.0.0", mcpserver.WithToolCapabilities(true))
+
+ proxy.server = mainServer
+ proxy.directServer = directServer
+ proxy.codeExecServer = codeExecServer
+
+ tests := []struct {
+ name string
+ mode string
+ expected *mcpserver.MCPServer
+ }{
+ {
+ name: "retrieve_tools returns main server",
+ mode: "retrieve_tools",
+ expected: mainServer,
+ },
+ {
+ name: "direct returns direct server",
+ mode: "direct",
+ expected: directServer,
+ },
+ {
+ name: "code_execution returns code exec server",
+ mode: "code_execution",
+ expected: codeExecServer,
+ },
+ {
+ name: "empty mode returns main server",
+ mode: "",
+ expected: mainServer,
+ },
+ {
+ name: "unknown mode returns main server",
+ mode: "unknown",
+ expected: mainServer,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := proxy.GetMCPServerForMode(tt.mode)
+ assert.Same(t, tt.expected, got)
+ })
+ }
+}
+
+func TestGetMCPServerForMode_NilFallback(t *testing.T) {
+ // When routing mode servers are nil, should fall back to main server
+ mainServer := mcpserver.NewMCPServer("main", "1.0.0", mcpserver.WithToolCapabilities(true))
+ proxy := &MCPProxyServer{
+ server: mainServer,
+ }
+
+ assert.Same(t, mainServer, proxy.GetMCPServerForMode("direct"))
+ assert.Same(t, mainServer, proxy.GetMCPServerForMode("code_execution"))
+ assert.Same(t, mainServer, proxy.GetMCPServerForMode("retrieve_tools"))
+}
+
+func TestDirectModeHandler_PermissionDenied(t *testing.T) {
+ logger, _ := zap.NewDevelopment()
+ proxy := &MCPProxyServer{
+ logger: logger,
+ config: &config.Config{},
+ }
+
+ // Create a handler for a read-only tool
+ readOnlyHint := true
+ annotations := &config.ToolAnnotations{
+ ReadOnlyHint: &readOnlyHint,
+ }
+ handler := proxy.makeDirectModeHandler("github", "list_repos", annotations)
+
+ // Create a context with agent token that only has write permission (no read)
+ agentCtx := auth.WithAuthContext(context.Background(), &auth.AuthContext{
+ Type: auth.AuthTypeAgent,
+ AgentName: "test-agent",
+ AllowedServers: []string{"github"},
+ Permissions: []string{"write"}, // Only write, no read
+ })
+
+ request := mcp.CallToolRequest{
+ Params: mcp.CallToolParams{
+ Name: "github__list_repos",
+ },
+ }
+
+ result, err := handler(agentCtx, request)
+ require.NoError(t, err) // Handler returns errors as tool results, not Go errors
+ require.NotNil(t, result)
+ assert.True(t, result.IsError)
+ assert.Contains(t, result.Content[0].(mcp.TextContent).Text, "Permission denied")
+}
+
+func TestDirectModeHandler_ServerAccessDenied(t *testing.T) {
+ logger, _ := zap.NewDevelopment()
+ proxy := &MCPProxyServer{
+ logger: logger,
+ config: &config.Config{},
+ }
+
+ handler := proxy.makeDirectModeHandler("gitlab", "list_repos", nil)
+
+ // Create a context with agent token that only has access to github
+ agentCtx := auth.WithAuthContext(context.Background(), &auth.AuthContext{
+ Type: auth.AuthTypeAgent,
+ AgentName: "test-agent",
+ AllowedServers: []string{"github"}, // Only github, not gitlab
+ Permissions: []string{"read"},
+ })
+
+ request := mcp.CallToolRequest{
+ Params: mcp.CallToolParams{
+ Name: "gitlab__list_repos",
+ },
+ }
+
+ result, err := handler(agentCtx, request)
+ require.NoError(t, err)
+ require.NotNil(t, result)
+ assert.True(t, result.IsError)
+ assert.Contains(t, result.Content[0].(mcp.TextContent).Text, "Access denied")
+}
+
+func TestDirectModeHandler_AgentWithCorrectPermissions(t *testing.T) {
+ logger, _ := zap.NewDevelopment()
+ proxy := &MCPProxyServer{
+ logger: logger,
+ config: &config.Config{},
+ }
+
+ // A read-only tool requires "read" permission
+ readOnlyHint := true
+ annotations := &config.ToolAnnotations{
+ ReadOnlyHint: &readOnlyHint,
+ }
+ handler := proxy.makeDirectModeHandler("github", "list_repos", annotations)
+
+ // Agent with read permission and github access should pass auth checks
+ agentCtx := auth.WithAuthContext(context.Background(), &auth.AuthContext{
+ Type: auth.AuthTypeAgent,
+ AgentName: "test-agent",
+ AllowedServers: []string{"github"},
+ Permissions: []string{"read"},
+ })
+
+ request := mcp.CallToolRequest{
+ Params: mcp.CallToolParams{
+ Name: "github__list_repos",
+ },
+ }
+
+ // Will panic due to nil upstreamManager, but we use recover to verify
+ // that auth checks passed (if it had failed at auth, result would be returned cleanly)
+ func() {
+ defer func() {
+ r := recover()
+ // If we reach here, the auth check passed and we hit the upstream call
+ // which panics due to nil manager. This is expected behavior.
+ assert.NotNil(t, r, "should panic at upstream call, proving auth checks passed")
+ }()
+ handler(agentCtx, request)
+ }()
+}
+
+func TestDirectModeHandler_DestructiveToolNeedsDestructivePermission(t *testing.T) {
+ logger, _ := zap.NewDevelopment()
+ proxy := &MCPProxyServer{
+ logger: logger,
+ config: &config.Config{},
+ }
+
+ destructiveHint := true
+ annotations := &config.ToolAnnotations{
+ DestructiveHint: &destructiveHint,
+ }
+ handler := proxy.makeDirectModeHandler("github", "delete_repo", annotations)
+
+ // Agent with only read+write but no destructive permission
+ agentCtx := auth.WithAuthContext(context.Background(), &auth.AuthContext{
+ Type: auth.AuthTypeAgent,
+ AgentName: "test-agent",
+ AllowedServers: []string{"github"},
+ Permissions: []string{"read", "write"},
+ })
+
+ request := mcp.CallToolRequest{
+ Params: mcp.CallToolParams{
+ Name: "github__delete_repo",
+ },
+ }
+
+ result, err := handler(agentCtx, request)
+ require.NoError(t, err)
+ require.NotNil(t, result)
+ assert.True(t, result.IsError)
+ assert.Contains(t, result.Content[0].(mcp.TextContent).Text, "Permission denied")
+ assert.Contains(t, result.Content[0].(mcp.TextContent).Text, "destructive")
+}
+
+func TestDirectModeHandler_NoAuthContext(t *testing.T) {
+ logger, _ := zap.NewDevelopment()
+ proxy := &MCPProxyServer{
+ logger: logger,
+ config: &config.Config{},
+ }
+
+ handler := proxy.makeDirectModeHandler("github", "list_repos", nil)
+
+ // No auth context in context - should pass auth checks (backward compatible)
+ request := mcp.CallToolRequest{
+ Params: mcp.CallToolParams{
+ Name: "github__list_repos",
+ },
+ }
+
+ // Will panic due to nil upstreamManager, proving auth checks passed
+ func() {
+ defer func() {
+ r := recover()
+ assert.NotNil(t, r, "should panic at upstream call, proving auth checks passed")
+ }()
+ handler(context.Background(), request)
+ }()
+}
diff --git a/internal/server/server.go b/internal/server/server.go
index 550eb061..5c0360fe 100644
--- a/internal/server/server.go
+++ b/internal/server/server.go
@@ -134,6 +134,7 @@ func NewServerWithConfigPath(cfg *config.Config, configPath string, logger *zap.
server.mcpProxy = mcpProxy
go server.forwardRuntimeStatus()
+ go server.listenForRoutingModeRefresh()
server.runtime.StartBackgroundInitialization()
return server, nil
@@ -356,6 +357,24 @@ func (s *Server) forwardRuntimeStatus() {
}
}
+// listenForRoutingModeRefresh subscribes to server events and refreshes routing mode
+// tool sets when upstream servers change (Spec 031).
+func (s *Server) listenForRoutingModeRefresh() {
+ eventCh := s.runtime.SubscribeEvents()
+ defer s.runtime.UnsubscribeEvents(eventCh)
+
+ for evt := range eventCh {
+ if evt.Type == runtime.EventTypeServersChanged {
+ s.logger.Debug("servers changed, refreshing routing mode tools",
+ zap.String("event_type", string(evt.Type)))
+ if s.mcpProxy != nil {
+ s.mcpProxy.RefreshDirectModeTools()
+ s.mcpProxy.RefreshCodeExecModeTools()
+ }
+ }
+ }
+}
+
// Start starts the MCP proxy server
func (s *Server) Start(ctx context.Context) error {
// Spec 024: Track server start time for lifecycle events
@@ -408,7 +427,12 @@ func (s *Server) Start(ctx context.Context) error {
zap.String("listen", listenAddr))
// Create Streamable HTTP server with custom routing
- streamableServer := server.NewStreamableHTTPServer(s.mcpProxy.GetMCPServer())
+ // Use the MCP server instance that corresponds to the configured routing_mode
+ routingMode := ""
+ if cfg != nil {
+ routingMode = cfg.RoutingMode
+ }
+ streamableServer := server.NewStreamableHTTPServer(s.mcpProxy.GetMCPServerForMode(routingMode))
// Create custom HTTP server for handling multiple routes
if err := s.startCustomHTTPServer(ctx, streamableServer); err != nil {
@@ -1473,6 +1497,30 @@ func (s *Server) startCustomHTTPServer(ctx context.Context, streamableServer *se
mux.Handle("/mcp", mcpHandler)
mux.Handle("/mcp/", mcpHandler) // Handle trailing slash
+ // Routing mode dedicated endpoints (Spec 031)
+ // Each endpoint always serves its specific routing mode regardless of config.
+ // /mcp/all → direct mode (all tools with serverName__toolName naming)
+ directStreamable := server.NewStreamableHTTPServer(s.mcpProxy.GetMCPServerForMode(config.RoutingModeDirect))
+ directHandler := s.mcpAuthMiddleware(loggingHandler(directStreamable))
+ mux.Handle("/mcp/all", directHandler)
+ mux.Handle("/mcp/all/", directHandler)
+
+ // /mcp/code → code_execution mode (JS orchestration)
+ codeExecStreamable := server.NewStreamableHTTPServer(s.mcpProxy.GetMCPServerForMode(config.RoutingModeCodeExecution))
+ codeExecHandler := s.mcpAuthMiddleware(loggingHandler(codeExecStreamable))
+ mux.Handle("/mcp/code", codeExecHandler)
+ mux.Handle("/mcp/code/", codeExecHandler)
+
+ // /mcp/call → retrieve_tools mode (explicit, same as default server)
+ callToolStreamable := server.NewStreamableHTTPServer(s.mcpProxy.GetMCPServer())
+ callToolHandler := s.mcpAuthMiddleware(loggingHandler(callToolStreamable))
+ mux.Handle("/mcp/call", callToolHandler)
+ mux.Handle("/mcp/call/", callToolHandler)
+
+ s.logger.Info("Registered routing mode MCP endpoints",
+ zap.String("default_mode", cfg.RoutingMode),
+ zap.Strings("endpoints", []string{"/mcp/all", "/mcp/code", "/mcp/call"}))
+
// Legacy endpoints for backward compatibility
mux.Handle("/v1/tool_code", mcpHandler)
mux.Handle("/v1/tool-code", mcpHandler) // Alias for python client
@@ -1597,7 +1645,8 @@ func (s *Server) startCustomHTTPServer(ctx context.Context, streamableServer *se
// List all registered endpoints for visibility
allEndpoints := []string{
- "/mcp", "/mcp/", // MCP protocol endpoints
+ "/mcp", "/mcp/", // MCP protocol endpoints (default routing mode)
+ "/mcp/all", "/mcp/code", "/mcp/call", // Routing mode endpoints (Spec 031)
"/v1/tool_code", "/v1/tool-code", // Legacy MCP endpoints
"/api/v1/*", "/events", // REST API and SSE endpoints
"/ui/", "/", // Web UI endpoints
diff --git a/oas/docs.go b/oas/docs.go
index da4b190a..42c453ce 100644
--- a/oas/docs.go
+++ b/oas/docs.go
@@ -6,10 +6,10 @@ import "github.com/swaggo/swag/v2"
const docTemplate = `{
"schemes": {{ marshal .Schemes }},
- "components": {"schemas":{"config.Config":{"properties":{"activity_cleanup_interval_min":{"description":"Background cleanup interval in minutes (default: 60)","type":"integer"},"activity_max_records":{"description":"Max records before pruning (default: 100000)","type":"integer"},"activity_max_response_size":{"description":"Response truncation limit in bytes (default: 65536)","type":"integer"},"activity_retention_days":{"description":"Activity logging settings (RFC-003)","type":"integer"},"allow_server_add":{"type":"boolean"},"allow_server_remove":{"type":"boolean"},"api_key":{"description":"Security settings","type":"string"},"call_tool_timeout":{"type":"string"},"check_server_repo":{"description":"Repository detection settings","type":"boolean"},"code_execution_max_tool_calls":{"description":"Max tool calls per execution (0 = unlimited, default: 0)","type":"integer"},"code_execution_pool_size":{"description":"JavaScript runtime pool size (default: 10)","type":"integer"},"code_execution_timeout_ms":{"description":"Timeout in milliseconds (default: 120000, max: 600000)","type":"integer"},"data_dir":{"type":"string"},"debug_search":{"type":"boolean"},"disable_management":{"type":"boolean"},"docker_isolation":{"$ref":"#/components/schemas/config.DockerIsolationConfig"},"docker_recovery":{"$ref":"#/components/schemas/config.DockerRecoveryConfig"},"enable_code_execution":{"description":"Code execution settings","type":"boolean"},"enable_prompts":{"description":"Prompts settings","type":"boolean"},"enable_socket":{"description":"Enable Unix socket/named pipe for local IPC (default: true)","type":"boolean"},"enable_tray":{"description":"Deprecated: EnableTray is unused and has no runtime effect. Kept for backward compatibility.","type":"boolean"},"environment":{"$ref":"#/components/schemas/secureenv.EnvConfig"},"features":{"$ref":"#/components/schemas/config.FeatureFlags"},"intent_declaration":{"$ref":"#/components/schemas/config.IntentDeclarationConfig"},"listen":{"type":"string"},"logging":{"$ref":"#/components/schemas/config.LogConfig"},"mcpServers":{"items":{"$ref":"#/components/schemas/config.ServerConfig"},"type":"array","uniqueItems":false},"oauth_expiry_warning_hours":{"description":"Health status settings","type":"number"},"quarantine_enabled":{"description":"Tool-level quarantine settings (Spec 032)\nQuarantineEnabled controls whether tool-level quarantine is active.\nWhen nil (default), quarantine is enabled (secure by default).\nSet to explicit false to disable tool-level quarantine.","type":"boolean"},"read_only_mode":{"type":"boolean"},"registries":{"description":"Registries configuration for MCP server discovery","items":{"$ref":"#/components/schemas/config.RegistryEntry"},"type":"array","uniqueItems":false},"require_mcp_auth":{"description":"Require authentication on /mcp endpoint (default: false)","type":"boolean"},"sensitive_data_detection":{"$ref":"#/components/schemas/config.SensitiveDataDetectionConfig"},"tls":{"$ref":"#/components/schemas/config.TLSConfig"},"tokenizer":{"$ref":"#/components/schemas/config.TokenizerConfig"},"tool_response_limit":{"type":"integer"},"tools_limit":{"type":"integer"},"top_k":{"description":"Deprecated: TopK is superseded by ToolsLimit and has no runtime effect. Kept for backward compatibility.","type":"integer"},"tray_endpoint":{"description":"Tray endpoint override (unix:// or npipe://)","type":"string"}},"type":"object"},"config.CustomPattern":{"properties":{"category":{"description":"Category (defaults to \"custom\")","type":"string"},"keywords":{"description":"Keywords to match (mutually exclusive with Regex)","items":{"type":"string"},"type":"array","uniqueItems":false},"name":{"description":"Unique identifier for this pattern","type":"string"},"regex":{"description":"Regex pattern (mutually exclusive with Keywords)","type":"string"},"severity":{"description":"Risk level: critical, high, medium, low","type":"string"}},"type":"object"},"config.DockerIsolationConfig":{"description":"Docker isolation settings","properties":{"cpu_limit":{"description":"CPU limit for containers","type":"string"},"default_images":{"additionalProperties":{"type":"string"},"description":"Map of runtime type to Docker image","type":"object"},"enabled":{"description":"Global enable/disable for Docker isolation","type":"boolean"},"extra_args":{"description":"Additional docker run arguments","items":{"type":"string"},"type":"array","uniqueItems":false},"log_driver":{"description":"Docker log driver (default: json-file)","type":"string"},"log_max_files":{"description":"Maximum number of log files (default: 3)","type":"string"},"log_max_size":{"description":"Maximum size of log files (default: 100m)","type":"string"},"memory_limit":{"description":"Memory limit for containers","type":"string"},"network_mode":{"description":"Docker network mode (default: bridge)","type":"string"},"registry":{"description":"Custom registry (defaults to docker.io)","type":"string"},"timeout":{"description":"Container startup timeout","type":"string"}},"type":"object"},"config.DockerRecoveryConfig":{"description":"Docker recovery settings","properties":{"enabled":{"description":"Enable Docker recovery monitoring (default: true)","type":"boolean"},"max_retries":{"description":"Maximum retry attempts (0 = unlimited)","type":"integer"},"notify_on_failure":{"description":"Show notification on recovery failure (default: true)","type":"boolean"},"notify_on_retry":{"description":"Show notification on each retry (default: false)","type":"boolean"},"notify_on_start":{"description":"Show notification when recovery starts (default: true)","type":"boolean"},"notify_on_success":{"description":"Show notification on successful recovery (default: true)","type":"boolean"},"persistent_state":{"description":"Save recovery state across restarts (default: true)","type":"boolean"}},"type":"object"},"config.FeatureFlags":{"description":"Deprecated: Features flags are unused and have no runtime effect. Kept for backward compatibility.","properties":{"enable_async_storage":{"type":"boolean"},"enable_caching":{"type":"boolean"},"enable_contract_tests":{"type":"boolean"},"enable_debug_logging":{"description":"Development features","type":"boolean"},"enable_docker_isolation":{"type":"boolean"},"enable_event_bus":{"type":"boolean"},"enable_health_checks":{"type":"boolean"},"enable_metrics":{"type":"boolean"},"enable_oauth":{"description":"Security features","type":"boolean"},"enable_observability":{"description":"Observability features","type":"boolean"},"enable_quarantine":{"type":"boolean"},"enable_runtime":{"description":"Runtime features","type":"boolean"},"enable_search":{"description":"Storage features","type":"boolean"},"enable_sse":{"type":"boolean"},"enable_tracing":{"type":"boolean"},"enable_tray":{"type":"boolean"},"enable_web_ui":{"description":"UI features","type":"boolean"}},"type":"object"},"config.IntentDeclarationConfig":{"description":"Intent declaration settings (Spec 018)","properties":{"strict_server_validation":{"description":"StrictServerValidation controls whether server annotation mismatches\ncause rejection (true) or just warnings (false).\nDefault: true (reject mismatches)","type":"boolean"}},"type":"object"},"config.IsolationConfig":{"description":"Per-server isolation settings","properties":{"enabled":{"description":"Enable Docker isolation for this server (nil = inherit global)","type":"boolean"},"extra_args":{"description":"Additional docker run arguments for this server","items":{"type":"string"},"type":"array","uniqueItems":false},"image":{"description":"Custom Docker image (overrides default)","type":"string"},"log_driver":{"description":"Docker log driver override for this server","type":"string"},"log_max_files":{"description":"Maximum number of log files override","type":"string"},"log_max_size":{"description":"Maximum size of log files override","type":"string"},"network_mode":{"description":"Custom network mode for this server","type":"string"},"working_dir":{"description":"Custom working directory in container","type":"string"}},"type":"object"},"config.LogConfig":{"description":"Logging configuration","properties":{"compress":{"type":"boolean"},"enable_console":{"type":"boolean"},"enable_file":{"type":"boolean"},"filename":{"type":"string"},"json_format":{"type":"boolean"},"level":{"type":"string"},"log_dir":{"description":"Custom log directory","type":"string"},"max_age":{"description":"days","type":"integer"},"max_backups":{"description":"number of backup files","type":"integer"},"max_size":{"description":"MB","type":"integer"}},"type":"object"},"config.OAuthConfig":{"description":"OAuth configuration (keep even when empty to signal OAuth requirement)","properties":{"client_id":{"type":"string"},"client_secret":{"type":"string"},"extra_params":{"additionalProperties":{"type":"string"},"description":"Additional OAuth parameters (e.g., RFC 8707 resource)","type":"object"},"pkce_enabled":{"type":"boolean"},"redirect_uri":{"type":"string"},"scopes":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"config.RegistryEntry":{"properties":{"count":{"description":"number or string","type":"string"},"description":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"protocol":{"type":"string"},"servers_url":{"type":"string"},"tags":{"items":{"type":"string"},"type":"array","uniqueItems":false},"url":{"type":"string"}},"type":"object"},"config.SensitiveDataDetectionConfig":{"description":"Sensitive data detection settings (Spec 026)","properties":{"categories":{"additionalProperties":{"type":"boolean"},"description":"Enable/disable specific detection categories","type":"object"},"custom_patterns":{"description":"User-defined detection patterns","items":{"$ref":"#/components/schemas/config.CustomPattern"},"type":"array","uniqueItems":false},"enabled":{"description":"Enable sensitive data detection (default: true)","type":"boolean"},"entropy_threshold":{"description":"Shannon entropy threshold for high-entropy detection (default: 4.5)","type":"number"},"max_payload_size_kb":{"description":"Max size to scan before truncating (default: 1024)","type":"integer"},"scan_requests":{"description":"Scan tool call arguments (default: true)","type":"boolean"},"scan_responses":{"description":"Scan tool responses (default: true)","type":"boolean"},"sensitive_keywords":{"description":"Keywords to flag","items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"config.ServerConfig":{"properties":{"args":{"items":{"type":"string"},"type":"array","uniqueItems":false},"command":{"type":"string"},"created":{"type":"string"},"enabled":{"type":"boolean"},"env":{"additionalProperties":{"type":"string"},"type":"object"},"headers":{"additionalProperties":{"type":"string"},"description":"For HTTP servers","type":"object"},"isolation":{"$ref":"#/components/schemas/config.IsolationConfig"},"name":{"type":"string"},"oauth":{"$ref":"#/components/schemas/config.OAuthConfig"},"protocol":{"description":"stdio, http, sse, streamable-http, auto","type":"string"},"quarantined":{"description":"Security quarantine status","type":"boolean"},"shared":{"description":"Server edition: shared with all users","type":"boolean"},"skip_quarantine":{"description":"Skip tool-level quarantine for this server","type":"boolean"},"updated":{"type":"string"},"url":{"type":"string"},"working_dir":{"description":"Working directory for stdio servers","type":"string"}},"type":"object"},"config.TLSConfig":{"description":"TLS configuration","properties":{"certs_dir":{"description":"Directory for certificates","type":"string"},"enabled":{"description":"Enable HTTPS","type":"boolean"},"hsts":{"description":"Enable HTTP Strict Transport Security","type":"boolean"},"require_client_cert":{"description":"Enable mTLS","type":"boolean"}},"type":"object"},"config.TokenizerConfig":{"description":"Tokenizer configuration for token counting","properties":{"default_model":{"description":"Default model for tokenization (e.g., \"gpt-4\")","type":"string"},"enabled":{"description":"Enable token counting","type":"boolean"},"encoding":{"description":"Default encoding (e.g., \"cl100k_base\")","type":"string"}},"type":"object"},"configimport.FailedServer":{"properties":{"details":{"type":"string"},"error":{"type":"string"},"name":{"type":"string"}},"type":"object"},"configimport.ImportSummary":{"properties":{"failed":{"type":"integer"},"imported":{"type":"integer"},"skipped":{"type":"integer"},"total":{"type":"integer"}},"type":"object"},"configimport.SkippedServer":{"properties":{"name":{"type":"string"},"reason":{"description":"\"already_exists\", \"filtered_out\", \"invalid_name\"","type":"string"}},"type":"object"},"contracts.APIResponse":{"allOf":[{"$ref":"#/components/schemas/data"}],"properties":{"data":{"type":"object"},"error":{"type":"string"},"request_id":{"type":"string"},"success":{"type":"boolean"}},"type":"object"},"contracts.ActivityDetailResponse":{"properties":{"activity":{"$ref":"#/components/schemas/contracts.ActivityRecord"}},"type":"object"},"contracts.ActivityListResponse":{"properties":{"activities":{"items":{"$ref":"#/components/schemas/contracts.ActivityRecord"},"type":"array","uniqueItems":false},"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"}},"type":"object"},"contracts.ActivityRecord":{"properties":{"arguments":{"description":"Tool call arguments","type":"object"},"detection_types":{"description":"List of detection types found","items":{"type":"string"},"type":"array","uniqueItems":false},"duration_ms":{"description":"Execution duration in milliseconds","type":"integer"},"error_message":{"description":"Error details if status is \"error\"","type":"string"},"has_sensitive_data":{"description":"Sensitive data detection fields (Spec 026)","type":"boolean"},"id":{"description":"Unique identifier (ULID format)","type":"string"},"max_severity":{"description":"Highest severity level detected (critical, high, medium, low)","type":"string"},"metadata":{"description":"Additional context-specific data","type":"object"},"request_id":{"description":"HTTP request ID for correlation","type":"string"},"response":{"description":"Tool response (potentially truncated)","type":"string"},"response_truncated":{"description":"True if response was truncated","type":"boolean"},"server_name":{"description":"Name of upstream MCP server","type":"string"},"session_id":{"description":"MCP session ID for correlation","type":"string"},"source":{"$ref":"#/components/schemas/contracts.ActivitySource"},"status":{"description":"Result status: \"success\", \"error\", \"blocked\"","type":"string"},"timestamp":{"description":"When activity occurred","type":"string"},"tool_name":{"description":"Name of tool called","type":"string"},"type":{"$ref":"#/components/schemas/contracts.ActivityType"}},"type":"object"},"contracts.ActivitySource":{"description":"How activity was triggered: \"mcp\", \"cli\", \"api\"","type":"string","x-enum-varnames":["ActivitySourceMCP","ActivitySourceCLI","ActivitySourceAPI"]},"contracts.ActivitySummaryResponse":{"properties":{"blocked_count":{"description":"Count of blocked activities","type":"integer"},"end_time":{"description":"End of the period (RFC3339)","type":"string"},"error_count":{"description":"Count of error activities","type":"integer"},"period":{"description":"Time period (1h, 24h, 7d, 30d)","type":"string"},"start_time":{"description":"Start of the period (RFC3339)","type":"string"},"success_count":{"description":"Count of successful activities","type":"integer"},"top_servers":{"description":"Top servers by activity count","items":{"$ref":"#/components/schemas/contracts.ActivityTopServer"},"type":"array","uniqueItems":false},"top_tools":{"description":"Top tools by activity count","items":{"$ref":"#/components/schemas/contracts.ActivityTopTool"},"type":"array","uniqueItems":false},"total_count":{"description":"Total activity count","type":"integer"}},"type":"object"},"contracts.ActivityTopServer":{"properties":{"count":{"description":"Activity count","type":"integer"},"name":{"description":"Server name","type":"string"}},"type":"object"},"contracts.ActivityTopTool":{"properties":{"count":{"description":"Activity count","type":"integer"},"server":{"description":"Server name","type":"string"},"tool":{"description":"Tool name","type":"string"}},"type":"object"},"contracts.ActivityType":{"description":"Type of activity","type":"string","x-enum-varnames":["ActivityTypeToolCall","ActivityTypePolicyDecision","ActivityTypeQuarantineChange","ActivityTypeServerChange"]},"contracts.ConfigApplyResult":{"properties":{"applied_immediately":{"type":"boolean"},"changed_fields":{"items":{"type":"string"},"type":"array","uniqueItems":false},"requires_restart":{"type":"boolean"},"restart_reason":{"type":"string"},"success":{"type":"boolean"},"validation_errors":{"items":{"$ref":"#/components/schemas/contracts.ValidationError"},"type":"array","uniqueItems":false}},"type":"object"},"contracts.DCRStatus":{"properties":{"attempted":{"type":"boolean"},"error":{"type":"string"},"status_code":{"type":"integer"},"success":{"type":"boolean"}},"type":"object"},"contracts.DeprecatedConfigWarning":{"properties":{"field":{"type":"string"},"message":{"type":"string"},"replacement":{"type":"string"}},"type":"object"},"contracts.Diagnostics":{"properties":{"deprecated_configs":{"description":"Deprecated config fields found","items":{"$ref":"#/components/schemas/contracts.DeprecatedConfigWarning"},"type":"array","uniqueItems":false},"docker_status":{"$ref":"#/components/schemas/contracts.DockerStatus"},"missing_secrets":{"description":"Renamed to avoid conflict","items":{"$ref":"#/components/schemas/contracts.MissingSecretInfo"},"type":"array","uniqueItems":false},"oauth_issues":{"description":"OAuth parameter mismatches","items":{"$ref":"#/components/schemas/contracts.OAuthIssue"},"type":"array","uniqueItems":false},"oauth_required":{"items":{"$ref":"#/components/schemas/contracts.OAuthRequirement"},"type":"array","uniqueItems":false},"runtime_warnings":{"items":{"type":"string"},"type":"array","uniqueItems":false},"timestamp":{"type":"string"},"total_issues":{"type":"integer"},"upstream_errors":{"items":{"$ref":"#/components/schemas/contracts.UpstreamError"},"type":"array","uniqueItems":false}},"type":"object"},"contracts.DockerStatus":{"properties":{"available":{"type":"boolean"},"error":{"type":"string"},"version":{"type":"string"}},"type":"object"},"contracts.ErrorResponse":{"properties":{"error":{"type":"string"},"request_id":{"type":"string"},"success":{"type":"boolean"}},"type":"object"},"contracts.GetConfigResponse":{"properties":{"config":{"description":"The configuration object","type":"object"},"config_path":{"description":"Path to config file","type":"string"}},"type":"object"},"contracts.GetRegistriesResponse":{"properties":{"registries":{"items":{"$ref":"#/components/schemas/contracts.Registry"},"type":"array","uniqueItems":false},"total":{"type":"integer"}},"type":"object"},"contracts.GetServerLogsResponse":{"properties":{"count":{"type":"integer"},"logs":{"items":{"$ref":"#/components/schemas/contracts.LogEntry"},"type":"array","uniqueItems":false},"server_name":{"type":"string"}},"type":"object"},"contracts.GetServerToolCallsResponse":{"properties":{"server_name":{"type":"string"},"tool_calls":{"items":{"$ref":"#/components/schemas/contracts.ToolCallRecord"},"type":"array","uniqueItems":false},"total":{"type":"integer"}},"type":"object"},"contracts.GetServerToolsResponse":{"properties":{"count":{"type":"integer"},"server_name":{"type":"string"},"tools":{"items":{"$ref":"#/components/schemas/contracts.Tool"},"type":"array","uniqueItems":false}},"type":"object"},"contracts.GetServersResponse":{"properties":{"servers":{"items":{"$ref":"#/components/schemas/contracts.Server"},"type":"array","uniqueItems":false},"stats":{"$ref":"#/components/schemas/contracts.ServerStats"}},"type":"object"},"contracts.GetSessionDetailResponse":{"properties":{"session":{"$ref":"#/components/schemas/contracts.MCPSession"}},"type":"object"},"contracts.GetSessionsResponse":{"properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"sessions":{"items":{"$ref":"#/components/schemas/contracts.MCPSession"},"type":"array","uniqueItems":false},"total":{"type":"integer"}},"type":"object"},"contracts.GetToolCallDetailResponse":{"properties":{"tool_call":{"$ref":"#/components/schemas/contracts.ToolCallRecord"}},"type":"object"},"contracts.GetToolCallsResponse":{"properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"tool_calls":{"items":{"$ref":"#/components/schemas/contracts.ToolCallRecord"},"type":"array","uniqueItems":false},"total":{"type":"integer"}},"type":"object"},"contracts.HealthStatus":{"description":"Unified health status calculated by the backend","properties":{"action":{"description":"Action is the suggested fix action: \"login\", \"restart\", \"enable\", \"approve\", \"view_logs\", \"set_secret\", \"configure\", or \"\" (none)","type":"string"},"admin_state":{"description":"AdminState indicates the admin state: \"enabled\", \"disabled\", or \"quarantined\"","type":"string"},"detail":{"description":"Detail is an optional longer explanation of the status","type":"string"},"level":{"description":"Level indicates the health level: \"healthy\", \"degraded\", or \"unhealthy\"","type":"string"},"summary":{"description":"Summary is a human-readable status message (e.g., \"Connected (5 tools)\")","type":"string"}},"type":"object"},"contracts.InfoEndpoints":{"description":"Available API endpoints","properties":{"http":{"description":"HTTP endpoint address (e.g., \"127.0.0.1:8080\")","type":"string"},"socket":{"description":"Unix socket path (empty if disabled)","type":"string"}},"type":"object"},"contracts.InfoResponse":{"properties":{"endpoints":{"$ref":"#/components/schemas/contracts.InfoEndpoints"},"listen_addr":{"description":"Listen address (e.g., \"127.0.0.1:8080\")","type":"string"},"update":{"$ref":"#/components/schemas/contracts.UpdateInfo"},"version":{"description":"Current MCPProxy version","type":"string"},"web_ui_url":{"description":"URL to access the web control panel","type":"string"}},"type":"object"},"contracts.IsolationConfig":{"properties":{"cpu_limit":{"type":"string"},"enabled":{"type":"boolean"},"image":{"type":"string"},"memory_limit":{"type":"string"},"timeout":{"type":"string"},"working_dir":{"type":"string"}},"type":"object"},"contracts.LogEntry":{"properties":{"fields":{"type":"object"},"level":{"type":"string"},"message":{"type":"string"},"server":{"type":"string"},"timestamp":{"type":"string"}},"type":"object"},"contracts.MCPSession":{"properties":{"client_name":{"type":"string"},"client_version":{"type":"string"},"end_time":{"type":"string"},"experimental":{"items":{"type":"string"},"type":"array","uniqueItems":false},"has_roots":{"description":"MCP Client Capabilities","type":"boolean"},"has_sampling":{"type":"boolean"},"id":{"type":"string"},"last_activity":{"type":"string"},"start_time":{"type":"string"},"status":{"type":"string"},"tool_call_count":{"type":"integer"},"total_tokens":{"type":"integer"}},"type":"object"},"contracts.MetadataStatus":{"properties":{"authorization_servers":{"items":{"type":"string"},"type":"array","uniqueItems":false},"error":{"type":"string"},"found":{"type":"boolean"},"url_checked":{"type":"string"}},"type":"object"},"contracts.MissingSecretInfo":{"properties":{"secret_name":{"type":"string"},"used_by":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"contracts.NPMPackageInfo":{"properties":{"exists":{"type":"boolean"},"install_cmd":{"type":"string"}},"type":"object"},"contracts.OAuthConfig":{"properties":{"auth_url":{"type":"string"},"client_id":{"type":"string"},"extra_params":{"additionalProperties":{"type":"string"},"type":"object"},"pkce_enabled":{"type":"boolean"},"redirect_port":{"type":"integer"},"scopes":{"items":{"type":"string"},"type":"array","uniqueItems":false},"token_expires_at":{"description":"When the OAuth token expires","type":"string"},"token_url":{"type":"string"},"token_valid":{"description":"Whether token is currently valid","type":"boolean"}},"type":"object"},"contracts.OAuthErrorDetails":{"description":"Structured discovery/failure details","properties":{"authorization_server_metadata":{"$ref":"#/components/schemas/contracts.MetadataStatus"},"dcr_status":{"$ref":"#/components/schemas/contracts.DCRStatus"},"protected_resource_metadata":{"$ref":"#/components/schemas/contracts.MetadataStatus"},"server_url":{"type":"string"}},"type":"object"},"contracts.OAuthFlowError":{"properties":{"correlation_id":{"description":"Flow tracking ID for log correlation","type":"string"},"debug_hint":{"description":"CLI command for log lookup","type":"string"},"details":{"$ref":"#/components/schemas/contracts.OAuthErrorDetails"},"error_code":{"description":"Machine-readable error code (e.g., OAUTH_NO_METADATA)","type":"string"},"error_type":{"description":"Category of OAuth runtime failure","type":"string"},"message":{"description":"Human-readable error description","type":"string"},"request_id":{"description":"HTTP request ID (from PR #237)","type":"string"},"server_name":{"description":"Server that failed OAuth","type":"string"},"success":{"description":"Always false","type":"boolean"},"suggestion":{"description":"Actionable remediation hint","type":"string"}},"type":"object"},"contracts.OAuthIssue":{"properties":{"documentation_url":{"type":"string"},"error":{"type":"string"},"issue":{"type":"string"},"missing_params":{"items":{"type":"string"},"type":"array","uniqueItems":false},"resolution":{"type":"string"},"server_name":{"type":"string"}},"type":"object"},"contracts.OAuthRequirement":{"properties":{"expires_at":{"type":"string"},"message":{"type":"string"},"server_name":{"type":"string"},"state":{"type":"string"}},"type":"object"},"contracts.OAuthStartResponse":{"properties":{"auth_url":{"description":"Authorization URL (always included for manual use)","type":"string"},"browser_error":{"description":"Error message if browser launch failed","type":"string"},"browser_opened":{"description":"Whether browser launch succeeded","type":"boolean"},"correlation_id":{"description":"UUID for tracking this flow","type":"string"},"message":{"description":"Human-readable status message","type":"string"},"server_name":{"description":"Name of the server being authenticated","type":"string"},"success":{"description":"Always true for successful start","type":"boolean"}},"type":"object"},"contracts.Registry":{"properties":{"count":{"description":"number or string","type":"string"},"description":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"protocol":{"type":"string"},"servers_url":{"type":"string"},"tags":{"items":{"type":"string"},"type":"array","uniqueItems":false},"url":{"type":"string"}},"type":"object"},"contracts.ReplayToolCallRequest":{"properties":{"arguments":{"description":"Modified arguments for replay","type":"object"}},"type":"object"},"contracts.ReplayToolCallResponse":{"properties":{"error":{"description":"Error if replay failed","type":"string"},"new_call_id":{"description":"ID of the newly created call","type":"string"},"new_tool_call":{"$ref":"#/components/schemas/contracts.ToolCallRecord"},"replayed_from":{"description":"Original call ID","type":"string"},"success":{"type":"boolean"}},"type":"object"},"contracts.RepositoryInfo":{"description":"Detected package info","properties":{"npm":{"$ref":"#/components/schemas/contracts.NPMPackageInfo"}},"type":"object"},"contracts.RepositoryServer":{"properties":{"connect_url":{"description":"Alternative connection URL","type":"string"},"created_at":{"type":"string"},"description":{"type":"string"},"id":{"type":"string"},"install_cmd":{"description":"Installation command","type":"string"},"name":{"type":"string"},"registry":{"description":"Which registry this came from","type":"string"},"repository_info":{"$ref":"#/components/schemas/contracts.RepositoryInfo"},"source_code_url":{"description":"Source repository URL","type":"string"},"updated_at":{"type":"string"},"url":{"description":"MCP endpoint for remote servers only","type":"string"}},"type":"object"},"contracts.SearchRegistryServersResponse":{"properties":{"query":{"type":"string"},"registry_id":{"type":"string"},"servers":{"items":{"$ref":"#/components/schemas/contracts.RepositoryServer"},"type":"array","uniqueItems":false},"tag":{"type":"string"},"total":{"type":"integer"}},"type":"object"},"contracts.SearchResult":{"properties":{"matches":{"type":"integer"},"score":{"type":"number"},"snippet":{"type":"string"},"tool":{"$ref":"#/components/schemas/contracts.Tool"}},"type":"object"},"contracts.SearchToolsResponse":{"properties":{"query":{"type":"string"},"results":{"items":{"$ref":"#/components/schemas/contracts.SearchResult"},"type":"array","uniqueItems":false},"took":{"type":"string"},"total":{"type":"integer"}},"type":"object"},"contracts.Server":{"properties":{"args":{"items":{"type":"string"},"type":"array","uniqueItems":false},"authenticated":{"description":"OAuth authentication status","type":"boolean"},"command":{"type":"string"},"connected":{"type":"boolean"},"connected_at":{"type":"string"},"connecting":{"type":"boolean"},"created":{"type":"string"},"enabled":{"type":"boolean"},"env":{"additionalProperties":{"type":"string"},"type":"object"},"headers":{"additionalProperties":{"type":"string"},"type":"object"},"health":{"$ref":"#/components/schemas/contracts.HealthStatus"},"id":{"type":"string"},"isolation":{"$ref":"#/components/schemas/contracts.IsolationConfig"},"last_error":{"type":"string"},"last_reconnect_at":{"type":"string"},"last_retry_time":{"type":"string"},"name":{"type":"string"},"oauth":{"$ref":"#/components/schemas/contracts.OAuthConfig"},"oauth_status":{"description":"OAuth status: \"authenticated\", \"expired\", \"error\", \"none\"","type":"string"},"protocol":{"type":"string"},"quarantined":{"type":"boolean"},"reconnect_count":{"type":"integer"},"retry_count":{"type":"integer"},"should_retry":{"type":"boolean"},"status":{"type":"string"},"token_expires_at":{"description":"When the OAuth token expires (ISO 8601)","type":"string"},"tool_count":{"type":"integer"},"tool_list_token_size":{"description":"Token size for this server's tools","type":"integer"},"updated":{"type":"string"},"url":{"type":"string"},"user_logged_out":{"description":"True if user explicitly logged out (prevents auto-reconnection)","type":"boolean"},"working_dir":{"type":"string"}},"type":"object"},"contracts.ServerActionResponse":{"properties":{"action":{"type":"string"},"async":{"type":"boolean"},"server":{"type":"string"},"success":{"type":"boolean"}},"type":"object"},"contracts.ServerStats":{"properties":{"connected_servers":{"type":"integer"},"docker_containers":{"type":"integer"},"quarantined_servers":{"type":"integer"},"token_metrics":{"$ref":"#/components/schemas/contracts.ServerTokenMetrics"},"total_servers":{"type":"integer"},"total_tools":{"type":"integer"}},"type":"object"},"contracts.ServerTokenMetrics":{"properties":{"average_query_result_size":{"description":"Typical retrieve_tools output (tokens)","type":"integer"},"per_server_tool_list_sizes":{"additionalProperties":{"type":"integer"},"description":"Token size per server","type":"object"},"saved_tokens":{"description":"Difference","type":"integer"},"saved_tokens_percentage":{"description":"Percentage saved","type":"number"},"total_server_tool_list_size":{"description":"All upstream tools combined (tokens)","type":"integer"}},"type":"object"},"contracts.SuccessResponse":{"properties":{"data":{"type":"object"},"success":{"type":"boolean"}},"type":"object"},"contracts.TokenMetrics":{"description":"Token usage metrics (nil for older records)","properties":{"encoding":{"description":"Encoding used (e.g., cl100k_base)","type":"string"},"estimated_cost":{"description":"Optional cost estimate","type":"number"},"input_tokens":{"description":"Tokens in the request","type":"integer"},"model":{"description":"Model used for tokenization","type":"string"},"output_tokens":{"description":"Tokens in the response","type":"integer"},"total_tokens":{"description":"Total tokens (input + output)","type":"integer"},"truncated_tokens":{"description":"Tokens removed by truncation","type":"integer"},"was_truncated":{"description":"Whether response was truncated","type":"boolean"}},"type":"object"},"contracts.Tool":{"properties":{"annotations":{"$ref":"#/components/schemas/contracts.ToolAnnotation"},"description":{"type":"string"},"last_used":{"type":"string"},"name":{"type":"string"},"schema":{"type":"object"},"server_name":{"type":"string"},"usage":{"type":"integer"}},"type":"object"},"contracts.ToolAnnotation":{"description":"Tool behavior hints snapshot","properties":{"destructiveHint":{"type":"boolean"},"idempotentHint":{"type":"boolean"},"openWorldHint":{"type":"boolean"},"readOnlyHint":{"type":"boolean"},"title":{"type":"string"}},"type":"object"},"contracts.ToolCallRecord":{"description":"The new tool call record","properties":{"annotations":{"$ref":"#/components/schemas/contracts.ToolAnnotation"},"arguments":{"description":"Tool arguments","type":"object"},"config_path":{"description":"Active config file path","type":"string"},"duration":{"description":"Duration in nanoseconds","type":"integer"},"error":{"description":"Error message (failure only)","type":"string"},"execution_type":{"description":"\"direct\" or \"code_execution\"","type":"string"},"id":{"description":"Unique identifier","type":"string"},"mcp_client_name":{"description":"MCP client name from InitializeRequest","type":"string"},"mcp_client_version":{"description":"MCP client version","type":"string"},"mcp_session_id":{"description":"MCP session identifier","type":"string"},"metrics":{"$ref":"#/components/schemas/contracts.TokenMetrics"},"parent_call_id":{"description":"Links nested calls to parent code_execution","type":"string"},"request_id":{"description":"Request correlation ID","type":"string"},"response":{"description":"Tool response (success only)","type":"object"},"server_id":{"description":"Server identity hash","type":"string"},"server_name":{"description":"Human-readable server name","type":"string"},"timestamp":{"description":"When the call was made","type":"string"},"tool_name":{"description":"Tool name (without server prefix)","type":"string"}},"type":"object"},"contracts.UpdateInfo":{"description":"Update information (if available)","properties":{"available":{"description":"Whether an update is available","type":"boolean"},"check_error":{"description":"Error message if update check failed","type":"string"},"checked_at":{"description":"When the update check was performed","type":"string"},"is_prerelease":{"description":"Whether the latest version is a prerelease","type":"boolean"},"latest_version":{"description":"Latest version available (e.g., \"v1.2.3\")","type":"string"},"release_url":{"description":"URL to the release page","type":"string"}},"type":"object"},"contracts.UpstreamError":{"properties":{"error_message":{"type":"string"},"server_name":{"type":"string"},"timestamp":{"type":"string"}},"type":"object"},"contracts.ValidateConfigResponse":{"properties":{"errors":{"items":{"$ref":"#/components/schemas/contracts.ValidationError"},"type":"array","uniqueItems":false},"valid":{"type":"boolean"}},"type":"object"},"contracts.ValidationError":{"properties":{"field":{"type":"string"},"message":{"type":"string"}},"type":"object"},"data":{"properties":{"data":{"$ref":"#/components/schemas/contracts.InfoResponse"}},"type":"object"},"httpapi.AddServerRequest":{"properties":{"args":{"items":{"type":"string"},"type":"array","uniqueItems":false},"command":{"type":"string"},"enabled":{"type":"boolean"},"env":{"additionalProperties":{"type":"string"},"type":"object"},"headers":{"additionalProperties":{"type":"string"},"type":"object"},"name":{"type":"string"},"protocol":{"type":"string"},"quarantined":{"type":"boolean"},"url":{"type":"string"},"working_dir":{"type":"string"}},"type":"object"},"httpapi.CanonicalConfigPath":{"properties":{"description":{"description":"Brief description","type":"string"},"exists":{"description":"Whether the file exists","type":"boolean"},"format":{"description":"Format identifier (e.g., \"claude_desktop\")","type":"string"},"name":{"description":"Display name (e.g., \"Claude Desktop\")","type":"string"},"os":{"description":"Operating system (darwin, windows, linux)","type":"string"},"path":{"description":"Full path to the config file","type":"string"}},"type":"object"},"httpapi.CanonicalConfigPathsResponse":{"properties":{"os":{"description":"Current operating system","type":"string"},"paths":{"description":"List of canonical config paths","items":{"$ref":"#/components/schemas/httpapi.CanonicalConfigPath"},"type":"array","uniqueItems":false}},"type":"object"},"httpapi.ImportFromPathRequest":{"properties":{"format":{"description":"Optional format hint","type":"string"},"path":{"description":"File path to import from","type":"string"},"server_names":{"description":"Optional: import only these servers","items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"httpapi.ImportRequest":{"properties":{"content":{"description":"Raw JSON or TOML content","type":"string"},"format":{"description":"Optional format hint","type":"string"},"server_names":{"description":"Optional: import only these servers","items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"httpapi.ImportResponse":{"properties":{"failed":{"items":{"$ref":"#/components/schemas/configimport.FailedServer"},"type":"array","uniqueItems":false},"format":{"type":"string"},"format_name":{"type":"string"},"imported":{"items":{"$ref":"#/components/schemas/httpapi.ImportedServerResponse"},"type":"array","uniqueItems":false},"skipped":{"items":{"$ref":"#/components/schemas/configimport.SkippedServer"},"type":"array","uniqueItems":false},"summary":{"$ref":"#/components/schemas/configimport.ImportSummary"},"warnings":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"httpapi.ImportedServerResponse":{"properties":{"args":{"items":{"type":"string"},"type":"array","uniqueItems":false},"command":{"type":"string"},"fields_skipped":{"items":{"type":"string"},"type":"array","uniqueItems":false},"name":{"type":"string"},"original_name":{"type":"string"},"protocol":{"type":"string"},"source_format":{"type":"string"},"url":{"type":"string"},"warnings":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"management.BulkOperationResult":{"properties":{"errors":{"additionalProperties":{"type":"string"},"description":"Map of server name to error message","type":"object"},"failed":{"description":"Number of failed operations","type":"integer"},"successful":{"description":"Number of successful operations","type":"integer"},"total":{"description":"Total servers processed","type":"integer"}},"type":"object"},"observability.HealthResponse":{"properties":{"components":{"items":{"$ref":"#/components/schemas/observability.HealthStatus"},"type":"array","uniqueItems":false},"status":{"description":"\"healthy\" or \"unhealthy\"","type":"string"},"timestamp":{"type":"string"}},"type":"object"},"observability.HealthStatus":{"properties":{"error":{"type":"string"},"latency":{"type":"string"},"name":{"type":"string"},"status":{"description":"\"healthy\" or \"unhealthy\"","type":"string"}},"type":"object"},"observability.ReadinessResponse":{"properties":{"components":{"items":{"$ref":"#/components/schemas/observability.HealthStatus"},"type":"array","uniqueItems":false},"status":{"description":"\"ready\" or \"not_ready\"","type":"string"},"timestamp":{"type":"string"}},"type":"object"},"secureenv.EnvConfig":{"description":"Environment configuration for secure variable filtering","properties":{"allowed_system_vars":{"items":{"type":"string"},"type":"array","uniqueItems":false},"custom_vars":{"additionalProperties":{"type":"string"},"type":"object"},"enhance_path":{"description":"Enable PATH enhancement for Launchd scenarios","type":"boolean"},"inherit_system_safe":{"type":"boolean"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"description":"API key authentication via query parameter. Use ?apikey=your-key","in":"query","name":"apikey","type":"apiKey"}}},
+ "components": {"schemas":{"config.Config":{"properties":{"activity_cleanup_interval_min":{"description":"Background cleanup interval in minutes (default: 60)","type":"integer"},"activity_max_records":{"description":"Max records before pruning (default: 100000)","type":"integer"},"activity_max_response_size":{"description":"Response truncation limit in bytes (default: 65536)","type":"integer"},"activity_retention_days":{"description":"Activity logging settings (RFC-003)","type":"integer"},"allow_server_add":{"type":"boolean"},"allow_server_remove":{"type":"boolean"},"api_key":{"description":"Security settings","type":"string"},"call_tool_timeout":{"type":"string"},"check_server_repo":{"description":"Repository detection settings","type":"boolean"},"code_execution_max_tool_calls":{"description":"Max tool calls per execution (0 = unlimited, default: 0)","type":"integer"},"code_execution_pool_size":{"description":"JavaScript runtime pool size (default: 10)","type":"integer"},"code_execution_timeout_ms":{"description":"Timeout in milliseconds (default: 120000, max: 600000)","type":"integer"},"data_dir":{"type":"string"},"debug_search":{"type":"boolean"},"disable_management":{"type":"boolean"},"docker_isolation":{"$ref":"#/components/schemas/config.DockerIsolationConfig"},"docker_recovery":{"$ref":"#/components/schemas/config.DockerRecoveryConfig"},"enable_code_execution":{"description":"Code execution settings","type":"boolean"},"enable_prompts":{"description":"Prompts settings","type":"boolean"},"enable_socket":{"description":"Enable Unix socket/named pipe for local IPC (default: true)","type":"boolean"},"enable_tray":{"description":"Deprecated: EnableTray is unused and has no runtime effect. Kept for backward compatibility.","type":"boolean"},"environment":{"$ref":"#/components/schemas/secureenv.EnvConfig"},"features":{"$ref":"#/components/schemas/config.FeatureFlags"},"intent_declaration":{"$ref":"#/components/schemas/config.IntentDeclarationConfig"},"listen":{"type":"string"},"logging":{"$ref":"#/components/schemas/config.LogConfig"},"mcpServers":{"items":{"$ref":"#/components/schemas/config.ServerConfig"},"type":"array","uniqueItems":false},"oauth_expiry_warning_hours":{"description":"Health status settings","type":"number"},"quarantine_enabled":{"description":"Tool-level quarantine settings (Spec 032)\nQuarantineEnabled controls whether tool-level quarantine is active.\nWhen nil (default), quarantine is enabled (secure by default).\nSet to explicit false to disable tool-level quarantine.","type":"boolean"},"read_only_mode":{"type":"boolean"},"registries":{"description":"Registries configuration for MCP server discovery","items":{"$ref":"#/components/schemas/config.RegistryEntry"},"type":"array","uniqueItems":false},"require_mcp_auth":{"description":"Require authentication on /mcp endpoint (default: false)","type":"boolean"},"routing_mode":{"description":"Routing mode (Spec 031): how MCP tools are exposed to clients\nValid values: \"retrieve_tools\" (default), \"direct\", \"code_execution\"","type":"string"},"sensitive_data_detection":{"$ref":"#/components/schemas/config.SensitiveDataDetectionConfig"},"tls":{"$ref":"#/components/schemas/config.TLSConfig"},"tokenizer":{"$ref":"#/components/schemas/config.TokenizerConfig"},"tool_response_limit":{"type":"integer"},"tools_limit":{"type":"integer"},"top_k":{"description":"Deprecated: TopK is superseded by ToolsLimit and has no runtime effect. Kept for backward compatibility.","type":"integer"},"tray_endpoint":{"description":"Tray endpoint override (unix:// or npipe://)","type":"string"}},"type":"object"},"config.CustomPattern":{"properties":{"category":{"description":"Category (defaults to \"custom\")","type":"string"},"keywords":{"description":"Keywords to match (mutually exclusive with Regex)","items":{"type":"string"},"type":"array","uniqueItems":false},"name":{"description":"Unique identifier for this pattern","type":"string"},"regex":{"description":"Regex pattern (mutually exclusive with Keywords)","type":"string"},"severity":{"description":"Risk level: critical, high, medium, low","type":"string"}},"type":"object"},"config.DockerIsolationConfig":{"description":"Docker isolation settings","properties":{"cpu_limit":{"description":"CPU limit for containers","type":"string"},"default_images":{"additionalProperties":{"type":"string"},"description":"Map of runtime type to Docker image","type":"object"},"enabled":{"description":"Global enable/disable for Docker isolation","type":"boolean"},"extra_args":{"description":"Additional docker run arguments","items":{"type":"string"},"type":"array","uniqueItems":false},"log_driver":{"description":"Docker log driver (default: json-file)","type":"string"},"log_max_files":{"description":"Maximum number of log files (default: 3)","type":"string"},"log_max_size":{"description":"Maximum size of log files (default: 100m)","type":"string"},"memory_limit":{"description":"Memory limit for containers","type":"string"},"network_mode":{"description":"Docker network mode (default: bridge)","type":"string"},"registry":{"description":"Custom registry (defaults to docker.io)","type":"string"},"timeout":{"description":"Container startup timeout","type":"string"}},"type":"object"},"config.DockerRecoveryConfig":{"description":"Docker recovery settings","properties":{"enabled":{"description":"Enable Docker recovery monitoring (default: true)","type":"boolean"},"max_retries":{"description":"Maximum retry attempts (0 = unlimited)","type":"integer"},"notify_on_failure":{"description":"Show notification on recovery failure (default: true)","type":"boolean"},"notify_on_retry":{"description":"Show notification on each retry (default: false)","type":"boolean"},"notify_on_start":{"description":"Show notification when recovery starts (default: true)","type":"boolean"},"notify_on_success":{"description":"Show notification on successful recovery (default: true)","type":"boolean"},"persistent_state":{"description":"Save recovery state across restarts (default: true)","type":"boolean"}},"type":"object"},"config.FeatureFlags":{"description":"Deprecated: Features flags are unused and have no runtime effect. Kept for backward compatibility.","properties":{"enable_async_storage":{"type":"boolean"},"enable_caching":{"type":"boolean"},"enable_contract_tests":{"type":"boolean"},"enable_debug_logging":{"description":"Development features","type":"boolean"},"enable_docker_isolation":{"type":"boolean"},"enable_event_bus":{"type":"boolean"},"enable_health_checks":{"type":"boolean"},"enable_metrics":{"type":"boolean"},"enable_oauth":{"description":"Security features","type":"boolean"},"enable_observability":{"description":"Observability features","type":"boolean"},"enable_quarantine":{"type":"boolean"},"enable_runtime":{"description":"Runtime features","type":"boolean"},"enable_search":{"description":"Storage features","type":"boolean"},"enable_sse":{"type":"boolean"},"enable_tracing":{"type":"boolean"},"enable_tray":{"type":"boolean"},"enable_web_ui":{"description":"UI features","type":"boolean"}},"type":"object"},"config.IntentDeclarationConfig":{"description":"Intent declaration settings (Spec 018)","properties":{"strict_server_validation":{"description":"StrictServerValidation controls whether server annotation mismatches\ncause rejection (true) or just warnings (false).\nDefault: true (reject mismatches)","type":"boolean"}},"type":"object"},"config.IsolationConfig":{"description":"Per-server isolation settings","properties":{"enabled":{"description":"Enable Docker isolation for this server (nil = inherit global)","type":"boolean"},"extra_args":{"description":"Additional docker run arguments for this server","items":{"type":"string"},"type":"array","uniqueItems":false},"image":{"description":"Custom Docker image (overrides default)","type":"string"},"log_driver":{"description":"Docker log driver override for this server","type":"string"},"log_max_files":{"description":"Maximum number of log files override","type":"string"},"log_max_size":{"description":"Maximum size of log files override","type":"string"},"network_mode":{"description":"Custom network mode for this server","type":"string"},"working_dir":{"description":"Custom working directory in container","type":"string"}},"type":"object"},"config.LogConfig":{"description":"Logging configuration","properties":{"compress":{"type":"boolean"},"enable_console":{"type":"boolean"},"enable_file":{"type":"boolean"},"filename":{"type":"string"},"json_format":{"type":"boolean"},"level":{"type":"string"},"log_dir":{"description":"Custom log directory","type":"string"},"max_age":{"description":"days","type":"integer"},"max_backups":{"description":"number of backup files","type":"integer"},"max_size":{"description":"MB","type":"integer"}},"type":"object"},"config.OAuthConfig":{"description":"OAuth configuration (keep even when empty to signal OAuth requirement)","properties":{"client_id":{"type":"string"},"client_secret":{"type":"string"},"extra_params":{"additionalProperties":{"type":"string"},"description":"Additional OAuth parameters (e.g., RFC 8707 resource)","type":"object"},"pkce_enabled":{"type":"boolean"},"redirect_uri":{"type":"string"},"scopes":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"config.RegistryEntry":{"properties":{"count":{"description":"number or string","type":"string"},"description":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"protocol":{"type":"string"},"servers_url":{"type":"string"},"tags":{"items":{"type":"string"},"type":"array","uniqueItems":false},"url":{"type":"string"}},"type":"object"},"config.SensitiveDataDetectionConfig":{"description":"Sensitive data detection settings (Spec 026)","properties":{"categories":{"additionalProperties":{"type":"boolean"},"description":"Enable/disable specific detection categories","type":"object"},"custom_patterns":{"description":"User-defined detection patterns","items":{"$ref":"#/components/schemas/config.CustomPattern"},"type":"array","uniqueItems":false},"enabled":{"description":"Enable sensitive data detection (default: true)","type":"boolean"},"entropy_threshold":{"description":"Shannon entropy threshold for high-entropy detection (default: 4.5)","type":"number"},"max_payload_size_kb":{"description":"Max size to scan before truncating (default: 1024)","type":"integer"},"scan_requests":{"description":"Scan tool call arguments (default: true)","type":"boolean"},"scan_responses":{"description":"Scan tool responses (default: true)","type":"boolean"},"sensitive_keywords":{"description":"Keywords to flag","items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"config.ServerConfig":{"properties":{"args":{"items":{"type":"string"},"type":"array","uniqueItems":false},"command":{"type":"string"},"created":{"type":"string"},"enabled":{"type":"boolean"},"env":{"additionalProperties":{"type":"string"},"type":"object"},"headers":{"additionalProperties":{"type":"string"},"description":"For HTTP servers","type":"object"},"isolation":{"$ref":"#/components/schemas/config.IsolationConfig"},"name":{"type":"string"},"oauth":{"$ref":"#/components/schemas/config.OAuthConfig"},"protocol":{"description":"stdio, http, sse, streamable-http, auto","type":"string"},"quarantined":{"description":"Security quarantine status","type":"boolean"},"shared":{"description":"Server edition: shared with all users","type":"boolean"},"skip_quarantine":{"description":"Skip tool-level quarantine for this server","type":"boolean"},"updated":{"type":"string"},"url":{"type":"string"},"working_dir":{"description":"Working directory for stdio servers","type":"string"}},"type":"object"},"config.TLSConfig":{"description":"TLS configuration","properties":{"certs_dir":{"description":"Directory for certificates","type":"string"},"enabled":{"description":"Enable HTTPS","type":"boolean"},"hsts":{"description":"Enable HTTP Strict Transport Security","type":"boolean"},"require_client_cert":{"description":"Enable mTLS","type":"boolean"}},"type":"object"},"config.TokenizerConfig":{"description":"Tokenizer configuration for token counting","properties":{"default_model":{"description":"Default model for tokenization (e.g., \"gpt-4\")","type":"string"},"enabled":{"description":"Enable token counting","type":"boolean"},"encoding":{"description":"Default encoding (e.g., \"cl100k_base\")","type":"string"}},"type":"object"},"configimport.FailedServer":{"properties":{"details":{"type":"string"},"error":{"type":"string"},"name":{"type":"string"}},"type":"object"},"configimport.ImportSummary":{"properties":{"failed":{"type":"integer"},"imported":{"type":"integer"},"skipped":{"type":"integer"},"total":{"type":"integer"}},"type":"object"},"configimport.SkippedServer":{"properties":{"name":{"type":"string"},"reason":{"description":"\"already_exists\", \"filtered_out\", \"invalid_name\"","type":"string"}},"type":"object"},"contracts.APIResponse":{"allOf":[{"$ref":"#/components/schemas/data"}],"properties":{"data":{"type":"object"},"error":{"type":"string"},"request_id":{"type":"string"},"success":{"type":"boolean"}},"type":"object"},"contracts.ActivityDetailResponse":{"properties":{"activity":{"$ref":"#/components/schemas/contracts.ActivityRecord"}},"type":"object"},"contracts.ActivityListResponse":{"properties":{"activities":{"items":{"$ref":"#/components/schemas/contracts.ActivityRecord"},"type":"array","uniqueItems":false},"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"}},"type":"object"},"contracts.ActivityRecord":{"properties":{"arguments":{"description":"Tool call arguments","type":"object"},"detection_types":{"description":"List of detection types found","items":{"type":"string"},"type":"array","uniqueItems":false},"duration_ms":{"description":"Execution duration in milliseconds","type":"integer"},"error_message":{"description":"Error details if status is \"error\"","type":"string"},"has_sensitive_data":{"description":"Sensitive data detection fields (Spec 026)","type":"boolean"},"id":{"description":"Unique identifier (ULID format)","type":"string"},"max_severity":{"description":"Highest severity level detected (critical, high, medium, low)","type":"string"},"metadata":{"description":"Additional context-specific data","type":"object"},"request_id":{"description":"HTTP request ID for correlation","type":"string"},"response":{"description":"Tool response (potentially truncated)","type":"string"},"response_truncated":{"description":"True if response was truncated","type":"boolean"},"server_name":{"description":"Name of upstream MCP server","type":"string"},"session_id":{"description":"MCP session ID for correlation","type":"string"},"source":{"$ref":"#/components/schemas/contracts.ActivitySource"},"status":{"description":"Result status: \"success\", \"error\", \"blocked\"","type":"string"},"timestamp":{"description":"When activity occurred","type":"string"},"tool_name":{"description":"Name of tool called","type":"string"},"type":{"$ref":"#/components/schemas/contracts.ActivityType"}},"type":"object"},"contracts.ActivitySource":{"description":"How activity was triggered: \"mcp\", \"cli\", \"api\"","type":"string","x-enum-varnames":["ActivitySourceMCP","ActivitySourceCLI","ActivitySourceAPI"]},"contracts.ActivitySummaryResponse":{"properties":{"blocked_count":{"description":"Count of blocked activities","type":"integer"},"end_time":{"description":"End of the period (RFC3339)","type":"string"},"error_count":{"description":"Count of error activities","type":"integer"},"period":{"description":"Time period (1h, 24h, 7d, 30d)","type":"string"},"start_time":{"description":"Start of the period (RFC3339)","type":"string"},"success_count":{"description":"Count of successful activities","type":"integer"},"top_servers":{"description":"Top servers by activity count","items":{"$ref":"#/components/schemas/contracts.ActivityTopServer"},"type":"array","uniqueItems":false},"top_tools":{"description":"Top tools by activity count","items":{"$ref":"#/components/schemas/contracts.ActivityTopTool"},"type":"array","uniqueItems":false},"total_count":{"description":"Total activity count","type":"integer"}},"type":"object"},"contracts.ActivityTopServer":{"properties":{"count":{"description":"Activity count","type":"integer"},"name":{"description":"Server name","type":"string"}},"type":"object"},"contracts.ActivityTopTool":{"properties":{"count":{"description":"Activity count","type":"integer"},"server":{"description":"Server name","type":"string"},"tool":{"description":"Tool name","type":"string"}},"type":"object"},"contracts.ActivityType":{"description":"Type of activity","type":"string","x-enum-varnames":["ActivityTypeToolCall","ActivityTypePolicyDecision","ActivityTypeQuarantineChange","ActivityTypeServerChange"]},"contracts.ConfigApplyResult":{"properties":{"applied_immediately":{"type":"boolean"},"changed_fields":{"items":{"type":"string"},"type":"array","uniqueItems":false},"requires_restart":{"type":"boolean"},"restart_reason":{"type":"string"},"success":{"type":"boolean"},"validation_errors":{"items":{"$ref":"#/components/schemas/contracts.ValidationError"},"type":"array","uniqueItems":false}},"type":"object"},"contracts.DCRStatus":{"properties":{"attempted":{"type":"boolean"},"error":{"type":"string"},"status_code":{"type":"integer"},"success":{"type":"boolean"}},"type":"object"},"contracts.DeprecatedConfigWarning":{"properties":{"field":{"type":"string"},"message":{"type":"string"},"replacement":{"type":"string"}},"type":"object"},"contracts.Diagnostics":{"properties":{"deprecated_configs":{"description":"Deprecated config fields found","items":{"$ref":"#/components/schemas/contracts.DeprecatedConfigWarning"},"type":"array","uniqueItems":false},"docker_status":{"$ref":"#/components/schemas/contracts.DockerStatus"},"missing_secrets":{"description":"Renamed to avoid conflict","items":{"$ref":"#/components/schemas/contracts.MissingSecretInfo"},"type":"array","uniqueItems":false},"oauth_issues":{"description":"OAuth parameter mismatches","items":{"$ref":"#/components/schemas/contracts.OAuthIssue"},"type":"array","uniqueItems":false},"oauth_required":{"items":{"$ref":"#/components/schemas/contracts.OAuthRequirement"},"type":"array","uniqueItems":false},"runtime_warnings":{"items":{"type":"string"},"type":"array","uniqueItems":false},"timestamp":{"type":"string"},"total_issues":{"type":"integer"},"upstream_errors":{"items":{"$ref":"#/components/schemas/contracts.UpstreamError"},"type":"array","uniqueItems":false}},"type":"object"},"contracts.DockerStatus":{"properties":{"available":{"type":"boolean"},"error":{"type":"string"},"version":{"type":"string"}},"type":"object"},"contracts.ErrorResponse":{"properties":{"error":{"type":"string"},"request_id":{"type":"string"},"success":{"type":"boolean"}},"type":"object"},"contracts.GetConfigResponse":{"properties":{"config":{"description":"The configuration object","type":"object"},"config_path":{"description":"Path to config file","type":"string"}},"type":"object"},"contracts.GetRegistriesResponse":{"properties":{"registries":{"items":{"$ref":"#/components/schemas/contracts.Registry"},"type":"array","uniqueItems":false},"total":{"type":"integer"}},"type":"object"},"contracts.GetServerLogsResponse":{"properties":{"count":{"type":"integer"},"logs":{"items":{"$ref":"#/components/schemas/contracts.LogEntry"},"type":"array","uniqueItems":false},"server_name":{"type":"string"}},"type":"object"},"contracts.GetServerToolCallsResponse":{"properties":{"server_name":{"type":"string"},"tool_calls":{"items":{"$ref":"#/components/schemas/contracts.ToolCallRecord"},"type":"array","uniqueItems":false},"total":{"type":"integer"}},"type":"object"},"contracts.GetServerToolsResponse":{"properties":{"count":{"type":"integer"},"server_name":{"type":"string"},"tools":{"items":{"$ref":"#/components/schemas/contracts.Tool"},"type":"array","uniqueItems":false}},"type":"object"},"contracts.GetServersResponse":{"properties":{"servers":{"items":{"$ref":"#/components/schemas/contracts.Server"},"type":"array","uniqueItems":false},"stats":{"$ref":"#/components/schemas/contracts.ServerStats"}},"type":"object"},"contracts.GetSessionDetailResponse":{"properties":{"session":{"$ref":"#/components/schemas/contracts.MCPSession"}},"type":"object"},"contracts.GetSessionsResponse":{"properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"sessions":{"items":{"$ref":"#/components/schemas/contracts.MCPSession"},"type":"array","uniqueItems":false},"total":{"type":"integer"}},"type":"object"},"contracts.GetToolCallDetailResponse":{"properties":{"tool_call":{"$ref":"#/components/schemas/contracts.ToolCallRecord"}},"type":"object"},"contracts.GetToolCallsResponse":{"properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"tool_calls":{"items":{"$ref":"#/components/schemas/contracts.ToolCallRecord"},"type":"array","uniqueItems":false},"total":{"type":"integer"}},"type":"object"},"contracts.HealthStatus":{"description":"Unified health status calculated by the backend","properties":{"action":{"description":"Action is the suggested fix action: \"login\", \"restart\", \"enable\", \"approve\", \"view_logs\", \"set_secret\", \"configure\", or \"\" (none)","type":"string"},"admin_state":{"description":"AdminState indicates the admin state: \"enabled\", \"disabled\", or \"quarantined\"","type":"string"},"detail":{"description":"Detail is an optional longer explanation of the status","type":"string"},"level":{"description":"Level indicates the health level: \"healthy\", \"degraded\", or \"unhealthy\"","type":"string"},"summary":{"description":"Summary is a human-readable status message (e.g., \"Connected (5 tools)\")","type":"string"}},"type":"object"},"contracts.InfoEndpoints":{"description":"Available API endpoints","properties":{"http":{"description":"HTTP endpoint address (e.g., \"127.0.0.1:8080\")","type":"string"},"socket":{"description":"Unix socket path (empty if disabled)","type":"string"}},"type":"object"},"contracts.InfoResponse":{"properties":{"endpoints":{"$ref":"#/components/schemas/contracts.InfoEndpoints"},"listen_addr":{"description":"Listen address (e.g., \"127.0.0.1:8080\")","type":"string"},"update":{"$ref":"#/components/schemas/contracts.UpdateInfo"},"version":{"description":"Current MCPProxy version","type":"string"},"web_ui_url":{"description":"URL to access the web control panel","type":"string"}},"type":"object"},"contracts.IsolationConfig":{"properties":{"cpu_limit":{"type":"string"},"enabled":{"type":"boolean"},"image":{"type":"string"},"memory_limit":{"type":"string"},"timeout":{"type":"string"},"working_dir":{"type":"string"}},"type":"object"},"contracts.LogEntry":{"properties":{"fields":{"type":"object"},"level":{"type":"string"},"message":{"type":"string"},"server":{"type":"string"},"timestamp":{"type":"string"}},"type":"object"},"contracts.MCPSession":{"properties":{"client_name":{"type":"string"},"client_version":{"type":"string"},"end_time":{"type":"string"},"experimental":{"items":{"type":"string"},"type":"array","uniqueItems":false},"has_roots":{"description":"MCP Client Capabilities","type":"boolean"},"has_sampling":{"type":"boolean"},"id":{"type":"string"},"last_activity":{"type":"string"},"start_time":{"type":"string"},"status":{"type":"string"},"tool_call_count":{"type":"integer"},"total_tokens":{"type":"integer"}},"type":"object"},"contracts.MetadataStatus":{"properties":{"authorization_servers":{"items":{"type":"string"},"type":"array","uniqueItems":false},"error":{"type":"string"},"found":{"type":"boolean"},"url_checked":{"type":"string"}},"type":"object"},"contracts.MissingSecretInfo":{"properties":{"secret_name":{"type":"string"},"used_by":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"contracts.NPMPackageInfo":{"properties":{"exists":{"type":"boolean"},"install_cmd":{"type":"string"}},"type":"object"},"contracts.OAuthConfig":{"properties":{"auth_url":{"type":"string"},"client_id":{"type":"string"},"extra_params":{"additionalProperties":{"type":"string"},"type":"object"},"pkce_enabled":{"type":"boolean"},"redirect_port":{"type":"integer"},"scopes":{"items":{"type":"string"},"type":"array","uniqueItems":false},"token_expires_at":{"description":"When the OAuth token expires","type":"string"},"token_url":{"type":"string"},"token_valid":{"description":"Whether token is currently valid","type":"boolean"}},"type":"object"},"contracts.OAuthErrorDetails":{"description":"Structured discovery/failure details","properties":{"authorization_server_metadata":{"$ref":"#/components/schemas/contracts.MetadataStatus"},"dcr_status":{"$ref":"#/components/schemas/contracts.DCRStatus"},"protected_resource_metadata":{"$ref":"#/components/schemas/contracts.MetadataStatus"},"server_url":{"type":"string"}},"type":"object"},"contracts.OAuthFlowError":{"properties":{"correlation_id":{"description":"Flow tracking ID for log correlation","type":"string"},"debug_hint":{"description":"CLI command for log lookup","type":"string"},"details":{"$ref":"#/components/schemas/contracts.OAuthErrorDetails"},"error_code":{"description":"Machine-readable error code (e.g., OAUTH_NO_METADATA)","type":"string"},"error_type":{"description":"Category of OAuth runtime failure","type":"string"},"message":{"description":"Human-readable error description","type":"string"},"request_id":{"description":"HTTP request ID (from PR #237)","type":"string"},"server_name":{"description":"Server that failed OAuth","type":"string"},"success":{"description":"Always false","type":"boolean"},"suggestion":{"description":"Actionable remediation hint","type":"string"}},"type":"object"},"contracts.OAuthIssue":{"properties":{"documentation_url":{"type":"string"},"error":{"type":"string"},"issue":{"type":"string"},"missing_params":{"items":{"type":"string"},"type":"array","uniqueItems":false},"resolution":{"type":"string"},"server_name":{"type":"string"}},"type":"object"},"contracts.OAuthRequirement":{"properties":{"expires_at":{"type":"string"},"message":{"type":"string"},"server_name":{"type":"string"},"state":{"type":"string"}},"type":"object"},"contracts.OAuthStartResponse":{"properties":{"auth_url":{"description":"Authorization URL (always included for manual use)","type":"string"},"browser_error":{"description":"Error message if browser launch failed","type":"string"},"browser_opened":{"description":"Whether browser launch succeeded","type":"boolean"},"correlation_id":{"description":"UUID for tracking this flow","type":"string"},"message":{"description":"Human-readable status message","type":"string"},"server_name":{"description":"Name of the server being authenticated","type":"string"},"success":{"description":"Always true for successful start","type":"boolean"}},"type":"object"},"contracts.Registry":{"properties":{"count":{"description":"number or string","type":"string"},"description":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"protocol":{"type":"string"},"servers_url":{"type":"string"},"tags":{"items":{"type":"string"},"type":"array","uniqueItems":false},"url":{"type":"string"}},"type":"object"},"contracts.ReplayToolCallRequest":{"properties":{"arguments":{"description":"Modified arguments for replay","type":"object"}},"type":"object"},"contracts.ReplayToolCallResponse":{"properties":{"error":{"description":"Error if replay failed","type":"string"},"new_call_id":{"description":"ID of the newly created call","type":"string"},"new_tool_call":{"$ref":"#/components/schemas/contracts.ToolCallRecord"},"replayed_from":{"description":"Original call ID","type":"string"},"success":{"type":"boolean"}},"type":"object"},"contracts.RepositoryInfo":{"description":"Detected package info","properties":{"npm":{"$ref":"#/components/schemas/contracts.NPMPackageInfo"}},"type":"object"},"contracts.RepositoryServer":{"properties":{"connect_url":{"description":"Alternative connection URL","type":"string"},"created_at":{"type":"string"},"description":{"type":"string"},"id":{"type":"string"},"install_cmd":{"description":"Installation command","type":"string"},"name":{"type":"string"},"registry":{"description":"Which registry this came from","type":"string"},"repository_info":{"$ref":"#/components/schemas/contracts.RepositoryInfo"},"source_code_url":{"description":"Source repository URL","type":"string"},"updated_at":{"type":"string"},"url":{"description":"MCP endpoint for remote servers only","type":"string"}},"type":"object"},"contracts.SearchRegistryServersResponse":{"properties":{"query":{"type":"string"},"registry_id":{"type":"string"},"servers":{"items":{"$ref":"#/components/schemas/contracts.RepositoryServer"},"type":"array","uniqueItems":false},"tag":{"type":"string"},"total":{"type":"integer"}},"type":"object"},"contracts.SearchResult":{"properties":{"matches":{"type":"integer"},"score":{"type":"number"},"snippet":{"type":"string"},"tool":{"$ref":"#/components/schemas/contracts.Tool"}},"type":"object"},"contracts.SearchToolsResponse":{"properties":{"query":{"type":"string"},"results":{"items":{"$ref":"#/components/schemas/contracts.SearchResult"},"type":"array","uniqueItems":false},"took":{"type":"string"},"total":{"type":"integer"}},"type":"object"},"contracts.Server":{"properties":{"args":{"items":{"type":"string"},"type":"array","uniqueItems":false},"authenticated":{"description":"OAuth authentication status","type":"boolean"},"command":{"type":"string"},"connected":{"type":"boolean"},"connected_at":{"type":"string"},"connecting":{"type":"boolean"},"created":{"type":"string"},"enabled":{"type":"boolean"},"env":{"additionalProperties":{"type":"string"},"type":"object"},"headers":{"additionalProperties":{"type":"string"},"type":"object"},"health":{"$ref":"#/components/schemas/contracts.HealthStatus"},"id":{"type":"string"},"isolation":{"$ref":"#/components/schemas/contracts.IsolationConfig"},"last_error":{"type":"string"},"last_reconnect_at":{"type":"string"},"last_retry_time":{"type":"string"},"name":{"type":"string"},"oauth":{"$ref":"#/components/schemas/contracts.OAuthConfig"},"oauth_status":{"description":"OAuth status: \"authenticated\", \"expired\", \"error\", \"none\"","type":"string"},"protocol":{"type":"string"},"quarantined":{"type":"boolean"},"reconnect_count":{"type":"integer"},"retry_count":{"type":"integer"},"should_retry":{"type":"boolean"},"status":{"type":"string"},"token_expires_at":{"description":"When the OAuth token expires (ISO 8601)","type":"string"},"tool_count":{"type":"integer"},"tool_list_token_size":{"description":"Token size for this server's tools","type":"integer"},"updated":{"type":"string"},"url":{"type":"string"},"user_logged_out":{"description":"True if user explicitly logged out (prevents auto-reconnection)","type":"boolean"},"working_dir":{"type":"string"}},"type":"object"},"contracts.ServerActionResponse":{"properties":{"action":{"type":"string"},"async":{"type":"boolean"},"server":{"type":"string"},"success":{"type":"boolean"}},"type":"object"},"contracts.ServerStats":{"properties":{"connected_servers":{"type":"integer"},"docker_containers":{"type":"integer"},"quarantined_servers":{"type":"integer"},"token_metrics":{"$ref":"#/components/schemas/contracts.ServerTokenMetrics"},"total_servers":{"type":"integer"},"total_tools":{"type":"integer"}},"type":"object"},"contracts.ServerTokenMetrics":{"properties":{"average_query_result_size":{"description":"Typical retrieve_tools output (tokens)","type":"integer"},"per_server_tool_list_sizes":{"additionalProperties":{"type":"integer"},"description":"Token size per server","type":"object"},"saved_tokens":{"description":"Difference","type":"integer"},"saved_tokens_percentage":{"description":"Percentage saved","type":"number"},"total_server_tool_list_size":{"description":"All upstream tools combined (tokens)","type":"integer"}},"type":"object"},"contracts.SuccessResponse":{"properties":{"data":{"type":"object"},"success":{"type":"boolean"}},"type":"object"},"contracts.TokenMetrics":{"description":"Token usage metrics (nil for older records)","properties":{"encoding":{"description":"Encoding used (e.g., cl100k_base)","type":"string"},"estimated_cost":{"description":"Optional cost estimate","type":"number"},"input_tokens":{"description":"Tokens in the request","type":"integer"},"model":{"description":"Model used for tokenization","type":"string"},"output_tokens":{"description":"Tokens in the response","type":"integer"},"total_tokens":{"description":"Total tokens (input + output)","type":"integer"},"truncated_tokens":{"description":"Tokens removed by truncation","type":"integer"},"was_truncated":{"description":"Whether response was truncated","type":"boolean"}},"type":"object"},"contracts.Tool":{"properties":{"annotations":{"$ref":"#/components/schemas/contracts.ToolAnnotation"},"description":{"type":"string"},"last_used":{"type":"string"},"name":{"type":"string"},"schema":{"type":"object"},"server_name":{"type":"string"},"usage":{"type":"integer"}},"type":"object"},"contracts.ToolAnnotation":{"description":"Tool behavior hints snapshot","properties":{"destructiveHint":{"type":"boolean"},"idempotentHint":{"type":"boolean"},"openWorldHint":{"type":"boolean"},"readOnlyHint":{"type":"boolean"},"title":{"type":"string"}},"type":"object"},"contracts.ToolCallRecord":{"description":"The new tool call record","properties":{"annotations":{"$ref":"#/components/schemas/contracts.ToolAnnotation"},"arguments":{"description":"Tool arguments","type":"object"},"config_path":{"description":"Active config file path","type":"string"},"duration":{"description":"Duration in nanoseconds","type":"integer"},"error":{"description":"Error message (failure only)","type":"string"},"execution_type":{"description":"\"direct\" or \"code_execution\"","type":"string"},"id":{"description":"Unique identifier","type":"string"},"mcp_client_name":{"description":"MCP client name from InitializeRequest","type":"string"},"mcp_client_version":{"description":"MCP client version","type":"string"},"mcp_session_id":{"description":"MCP session identifier","type":"string"},"metrics":{"$ref":"#/components/schemas/contracts.TokenMetrics"},"parent_call_id":{"description":"Links nested calls to parent code_execution","type":"string"},"request_id":{"description":"Request correlation ID","type":"string"},"response":{"description":"Tool response (success only)","type":"object"},"server_id":{"description":"Server identity hash","type":"string"},"server_name":{"description":"Human-readable server name","type":"string"},"timestamp":{"description":"When the call was made","type":"string"},"tool_name":{"description":"Tool name (without server prefix)","type":"string"}},"type":"object"},"contracts.UpdateInfo":{"description":"Update information (if available)","properties":{"available":{"description":"Whether an update is available","type":"boolean"},"check_error":{"description":"Error message if update check failed","type":"string"},"checked_at":{"description":"When the update check was performed","type":"string"},"is_prerelease":{"description":"Whether the latest version is a prerelease","type":"boolean"},"latest_version":{"description":"Latest version available (e.g., \"v1.2.3\")","type":"string"},"release_url":{"description":"URL to the release page","type":"string"}},"type":"object"},"contracts.UpstreamError":{"properties":{"error_message":{"type":"string"},"server_name":{"type":"string"},"timestamp":{"type":"string"}},"type":"object"},"contracts.ValidateConfigResponse":{"properties":{"errors":{"items":{"$ref":"#/components/schemas/contracts.ValidationError"},"type":"array","uniqueItems":false},"valid":{"type":"boolean"}},"type":"object"},"contracts.ValidationError":{"properties":{"field":{"type":"string"},"message":{"type":"string"}},"type":"object"},"data":{"properties":{"data":{"$ref":"#/components/schemas/contracts.InfoResponse"}},"type":"object"},"httpapi.AddServerRequest":{"properties":{"args":{"items":{"type":"string"},"type":"array","uniqueItems":false},"command":{"type":"string"},"enabled":{"type":"boolean"},"env":{"additionalProperties":{"type":"string"},"type":"object"},"headers":{"additionalProperties":{"type":"string"},"type":"object"},"name":{"type":"string"},"protocol":{"type":"string"},"quarantined":{"type":"boolean"},"url":{"type":"string"},"working_dir":{"type":"string"}},"type":"object"},"httpapi.CanonicalConfigPath":{"properties":{"description":{"description":"Brief description","type":"string"},"exists":{"description":"Whether the file exists","type":"boolean"},"format":{"description":"Format identifier (e.g., \"claude_desktop\")","type":"string"},"name":{"description":"Display name (e.g., \"Claude Desktop\")","type":"string"},"os":{"description":"Operating system (darwin, windows, linux)","type":"string"},"path":{"description":"Full path to the config file","type":"string"}},"type":"object"},"httpapi.CanonicalConfigPathsResponse":{"properties":{"os":{"description":"Current operating system","type":"string"},"paths":{"description":"List of canonical config paths","items":{"$ref":"#/components/schemas/httpapi.CanonicalConfigPath"},"type":"array","uniqueItems":false}},"type":"object"},"httpapi.ImportFromPathRequest":{"properties":{"format":{"description":"Optional format hint","type":"string"},"path":{"description":"File path to import from","type":"string"},"server_names":{"description":"Optional: import only these servers","items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"httpapi.ImportRequest":{"properties":{"content":{"description":"Raw JSON or TOML content","type":"string"},"format":{"description":"Optional format hint","type":"string"},"server_names":{"description":"Optional: import only these servers","items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"httpapi.ImportResponse":{"properties":{"failed":{"items":{"$ref":"#/components/schemas/configimport.FailedServer"},"type":"array","uniqueItems":false},"format":{"type":"string"},"format_name":{"type":"string"},"imported":{"items":{"$ref":"#/components/schemas/httpapi.ImportedServerResponse"},"type":"array","uniqueItems":false},"skipped":{"items":{"$ref":"#/components/schemas/configimport.SkippedServer"},"type":"array","uniqueItems":false},"summary":{"$ref":"#/components/schemas/configimport.ImportSummary"},"warnings":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"httpapi.ImportedServerResponse":{"properties":{"args":{"items":{"type":"string"},"type":"array","uniqueItems":false},"command":{"type":"string"},"fields_skipped":{"items":{"type":"string"},"type":"array","uniqueItems":false},"name":{"type":"string"},"original_name":{"type":"string"},"protocol":{"type":"string"},"source_format":{"type":"string"},"url":{"type":"string"},"warnings":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"management.BulkOperationResult":{"properties":{"errors":{"additionalProperties":{"type":"string"},"description":"Map of server name to error message","type":"object"},"failed":{"description":"Number of failed operations","type":"integer"},"successful":{"description":"Number of successful operations","type":"integer"},"total":{"description":"Total servers processed","type":"integer"}},"type":"object"},"observability.HealthResponse":{"properties":{"components":{"items":{"$ref":"#/components/schemas/observability.HealthStatus"},"type":"array","uniqueItems":false},"status":{"description":"\"healthy\" or \"unhealthy\"","type":"string"},"timestamp":{"type":"string"}},"type":"object"},"observability.HealthStatus":{"properties":{"error":{"type":"string"},"latency":{"type":"string"},"name":{"type":"string"},"status":{"description":"\"healthy\" or \"unhealthy\"","type":"string"}},"type":"object"},"observability.ReadinessResponse":{"properties":{"components":{"items":{"$ref":"#/components/schemas/observability.HealthStatus"},"type":"array","uniqueItems":false},"status":{"description":"\"ready\" or \"not_ready\"","type":"string"},"timestamp":{"type":"string"}},"type":"object"},"secureenv.EnvConfig":{"description":"Environment configuration for secure variable filtering","properties":{"allowed_system_vars":{"items":{"type":"string"},"type":"array","uniqueItems":false},"custom_vars":{"additionalProperties":{"type":"string"},"type":"object"},"enhance_path":{"description":"Enable PATH enhancement for Launchd scenarios","type":"boolean"},"inherit_system_safe":{"type":"boolean"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"description":"API key authentication via query parameter. Use ?apikey=your-key","in":"query","name":"apikey","type":"apiKey"}}},
"info": {"contact":{"name":"MCPProxy Support","url":"https://github.com/smart-mcp-proxy/mcpproxy-go"},"description":"{{escape .Description}}","license":{"name":"MIT","url":"https://opensource.org/licenses/MIT"},"title":"{{.Title}}","version":"{{.Version}}"},
"externalDocs": {"description":"","url":""},
- "paths": {"/api/v1/activity":{"get":{"description":"Returns paginated list of activity records with optional filtering","parameters":[{"description":"Filter by activity type(s), comma-separated for multiple (Spec 024)","in":"query","name":"type","schema":{"enum":["tool_call","policy_decision","quarantine_change","server_change","system_start","system_stop","internal_tool_call","config_change"],"type":"string"}},{"description":"Filter by server name","in":"query","name":"server","schema":{"type":"string"}},{"description":"Filter by tool name","in":"query","name":"tool","schema":{"type":"string"}},{"description":"Filter by MCP session ID","in":"query","name":"session_id","schema":{"type":"string"}},{"description":"Filter by status","in":"query","name":"status","schema":{"enum":["success","error","blocked"],"type":"string"}},{"description":"Filter by intent operation type (Spec 018)","in":"query","name":"intent_type","schema":{"enum":["read","write","destructive"],"type":"string"}},{"description":"Filter by HTTP request ID for log correlation (Spec 021)","in":"query","name":"request_id","schema":{"type":"string"}},{"description":"Include successful call_tool_* internal tool calls (default: false, excluded to avoid duplicates)","in":"query","name":"include_call_tool","schema":{"type":"boolean"}},{"description":"Filter by sensitive data detection (true=has detections, false=no detections)","in":"query","name":"sensitive_data","schema":{"type":"boolean"}},{"description":"Filter by specific detection type (e.g., 'aws_access_key', 'credit_card')","in":"query","name":"detection_type","schema":{"type":"string"}},{"description":"Filter by severity level","in":"query","name":"severity","schema":{"enum":["critical","high","medium","low"],"type":"string"}},{"description":"Filter by agent token name (Spec 028)","in":"query","name":"agent","schema":{"type":"string"}},{"description":"Filter by auth type (Spec 028)","in":"query","name":"auth_type","schema":{"enum":["admin","agent"],"type":"string"}},{"description":"Filter activities after this time (RFC3339)","in":"query","name":"start_time","schema":{"type":"string"}},{"description":"Filter activities before this time (RFC3339)","in":"query","name":"end_time","schema":{"type":"string"}},{"description":"Maximum records to return (1-100, default 50)","in":"query","name":"limit","schema":{"type":"integer"}},{"description":"Pagination offset (default 0)","in":"query","name":"offset","schema":{"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/data"}],"properties":{"data":{"type":"object"},"error":{"type":"string"},"request_id":{"type":"string"},"success":{"type":"boolean"}},"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Bad Request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Internal Server Error"}},"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]}],"summary":"List activity records","tags":["Activity"]}},"/api/v1/activity/export":{"get":{"description":"Exports activity records in JSON Lines or CSV format for compliance","parameters":[{"description":"Export format: json (default) or csv","in":"query","name":"format","schema":{"type":"string"}},{"description":"Filter by activity type","in":"query","name":"type","schema":{"type":"string"}},{"description":"Filter by server name","in":"query","name":"server","schema":{"type":"string"}},{"description":"Filter by tool name","in":"query","name":"tool","schema":{"type":"string"}},{"description":"Filter by MCP session ID","in":"query","name":"session_id","schema":{"type":"string"}},{"description":"Filter by status","in":"query","name":"status","schema":{"type":"string"}},{"description":"Filter activities after this time (RFC3339)","in":"query","name":"start_time","schema":{"type":"string"}},{"description":"Filter activities before this time (RFC3339)","in":"query","name":"end_time","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"string"}},"application/x-ndjson":{"schema":{"type":"string"}},"text/csv":{"schema":{"type":"string"}}},"description":"Streamed activity records"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Internal Server Error"}},"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]}],"summary":"Export activity records","tags":["Activity"]}},"/api/v1/activity/summary":{"get":{"description":"Returns aggregated activity statistics for a time period","parameters":[{"description":"Time period: 1h, 24h (default), 7d, 30d","in":"query","name":"period","schema":{"type":"string"}},{"description":"Group by: server, tool (optional)","in":"query","name":"group_by","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/data"}],"properties":{"data":{"type":"object"},"error":{"type":"string"},"request_id":{"type":"string"},"success":{"type":"boolean"}},"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Bad Request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Internal Server Error"}},"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]}],"summary":"Get activity summary statistics","tags":["Activity"]}},"/api/v1/activity/{id}":{"get":{"description":"Returns full details for a single activity record","parameters":[{"description":"Activity record ID (ULID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/data"}],"properties":{"data":{"type":"object"},"error":{"type":"string"},"request_id":{"type":"string"},"success":{"type":"boolean"}},"type":"object"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Internal Server Error"}},"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]}],"summary":"Get activity record details","tags":["Activity"]}},"/api/v1/config":{"get":{"description":"Retrieves the current MCPProxy configuration including all server definitions, global settings, and runtime parameters","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.GetConfigResponse"}}},"description":"Configuration retrieved successfully"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Failed to get configuration"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get current configuration","tags":["config"]}},"/api/v1/config/apply":{"post":{"description":"Applies a new MCPProxy configuration. Validates and persists the configuration to disk. Some changes apply immediately, while others may require a restart. Returns detailed information about applied changes and restart requirements.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/config.Config"}}},"description":"Configuration to apply","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ConfigApplyResult"}}},"description":"Configuration applied successfully with change details"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Invalid JSON payload"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Failed to apply configuration"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Apply configuration","tags":["config"]}},"/api/v1/config/validate":{"post":{"description":"Validates a provided MCPProxy configuration without applying it. Checks for syntax errors, invalid server definitions, conflicting settings, and other configuration issues.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/config.Config"}}},"description":"Configuration to validate","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ValidateConfigResponse"}}},"description":"Configuration validation result"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Invalid JSON payload"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Validation failed"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Validate configuration","tags":["config"]}},"/api/v1/diagnostics":{"get":{"description":"Get comprehensive health diagnostics including upstream errors, OAuth requirements, missing secrets, and Docker status","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.Diagnostics"}}},"description":"Health diagnostics"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get health diagnostics","tags":["diagnostics"]}},"/api/v1/docker/status":{"get":{"description":"Retrieve current Docker availability and recovery status","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.SuccessResponse"}}},"description":"Docker status information"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get Docker status","tags":["docker"]}},"/api/v1/doctor":{"get":{"description":"Get comprehensive health diagnostics including upstream errors, OAuth requirements, missing secrets, and Docker status","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.Diagnostics"}}},"description":"Health diagnostics"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get health diagnostics","tags":["diagnostics"]}},"/api/v1/index/search":{"get":{"description":"Search across all upstream MCP server tools using BM25 keyword search","parameters":[{"description":"Search query","in":"query","name":"q","required":true,"schema":{"type":"string"}},{"description":"Maximum number of results","in":"query","name":"limit","schema":{"default":10,"maximum":100,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.SearchToolsResponse"}}},"description":"Search results"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request (missing query parameter)"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Search for tools","tags":["tools"]}},"/api/v1/info":{"get":{"description":"Get essential server metadata including version, web UI URL, endpoint addresses, and update availability\nThis endpoint is designed for tray-core communication and version checking\nUse refresh=true query parameter to force an immediate update check against GitHub","parameters":[{"description":"Force immediate update check against GitHub","in":"query","name":"refresh","schema":{"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/data"}],"properties":{"data":{"type":"object"},"error":{"type":"string"},"request_id":{"type":"string"},"success":{"type":"boolean"}},"type":"object"}}},"description":"Server information with optional update info"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get server information","tags":["status"]}},"/api/v1/registries":{"get":{"description":"Retrieves list of all MCP server registries that can be browsed for discovering and installing new upstream servers. Includes registry metadata, server counts, and API endpoints.","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.GetRegistriesResponse"}}},"description":"Registries retrieved successfully"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Failed to list registries"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"List available MCP server registries","tags":["registries"]}},"/api/v1/registries/{id}/servers":{"get":{"description":"Searches for MCP servers within a specific registry by keyword or tag. Returns server metadata including installation commands, source code URLs, and npm package information for easy discovery and installation.","parameters":[{"description":"Registry ID","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Search query keyword","in":"query","name":"q","schema":{"type":"string"}},{"description":"Filter by tag","in":"query","name":"tag","schema":{"type":"string"}},{"description":"Maximum number of results (default 10)","in":"query","name":"limit","schema":{"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.SearchRegistryServersResponse"}}},"description":"Servers retrieved successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Registry ID required"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Failed to search servers"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Search MCP servers in a registry","tags":["registries"]}},"/api/v1/secrets":{"post":{"description":"Stores a secret value in the operating system's secure keyring. The secret can then be referenced in configuration using ${keyring:secret-name} syntax. Automatically notifies runtime to restart affected servers.","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":{},"type":"object"}}},"description":"Secret stored successfully with reference syntax"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Invalid JSON payload, missing name/value, or unsupported type"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"405":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Method not allowed"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Secret resolver not available or failed to store secret"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Store a secret in OS keyring","tags":["secrets"]}},"/api/v1/secrets/{name}":{"delete":{"description":"Deletes a secret from the operating system's secure keyring. Automatically notifies runtime to restart affected servers. Only keyring type is supported for security.","parameters":[{"description":"Name of the secret to delete","in":"path","name":"name","required":true,"schema":{"type":"string"}},{"description":"Secret type (only 'keyring' supported, defaults to 'keyring')","in":"query","name":"type","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":{},"type":"object"}}},"description":"Secret deleted successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Missing secret name or unsupported type"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"405":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Method not allowed"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Secret resolver not available or failed to delete secret"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Delete a secret from OS keyring","tags":["secrets"]}},"/api/v1/servers":{"get":{"description":"Get a list of all configured upstream MCP servers with their connection status and statistics","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.GetServersResponse"}}},"description":"Server list with statistics"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"List all upstream MCP servers","tags":["servers"]},"post":{"description":"Add a new MCP upstream server to the configuration. New servers are quarantined by default for security.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/httpapi.AddServerRequest"}}},"description":"Server configuration","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ServerActionResponse"}}},"description":"Server added successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request - invalid configuration"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Conflict - server with this name already exists"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Add a new upstream server","tags":["servers"]}},"/api/v1/servers/disable_all":{"post":{"description":"Disable all configured upstream MCP servers with partial failure handling","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/management.BulkOperationResult"}}},"description":"Bulk disable results with success/failure counts"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Forbidden (management disabled)"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Disable all servers","tags":["servers"]}},"/api/v1/servers/enable_all":{"post":{"description":"Enable all configured upstream MCP servers with partial failure handling","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/management.BulkOperationResult"}}},"description":"Bulk enable results with success/failure counts"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Forbidden (management disabled)"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Enable all servers","tags":["servers"]}},"/api/v1/servers/import":{"post":{"description":"Import MCP server configurations from a Claude Desktop, Claude Code, Cursor IDE, Codex CLI, or Gemini CLI configuration file","parameters":[{"description":"If true, return preview without importing","in":"query","name":"preview","schema":{"type":"boolean"}},{"description":"Force format (claude-desktop, claude-code, cursor, codex, gemini)","in":"query","name":"format","schema":{"type":"string"}},{"description":"Comma-separated list of server names to import","in":"query","name":"server_names","schema":{"type":"string"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"file"}}},"description":"Configuration file to import","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/httpapi.ImportResponse"}}},"description":"Import result"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request - invalid file or format"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Import servers from uploaded configuration file","tags":["servers"]}},"/api/v1/servers/import/json":{"post":{"description":"Import MCP server configurations from raw JSON or TOML content (useful for pasting configurations)","parameters":[{"description":"If true, return preview without importing","in":"query","name":"preview","schema":{"type":"boolean"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/httpapi.ImportRequest"}}},"description":"Import request with content","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/httpapi.ImportResponse"}}},"description":"Import result"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request - invalid content or format"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Import servers from JSON/TOML content","tags":["servers"]}},"/api/v1/servers/import/path":{"post":{"description":"Import MCP server configurations by reading a file from the server's filesystem","parameters":[{"description":"If true, return preview without importing","in":"query","name":"preview","schema":{"type":"boolean"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/httpapi.ImportFromPathRequest"}}},"description":"Import request with file path","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/httpapi.ImportResponse"}}},"description":"Import result"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request - invalid path or format"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"File not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Import servers from a file path","tags":["servers"]}},"/api/v1/servers/import/paths":{"get":{"description":"Returns well-known configuration file paths for supported formats with existence check","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/httpapi.CanonicalConfigPathsResponse"}}},"description":"Canonical config paths"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get canonical config file paths","tags":["servers"]}},"/api/v1/servers/reconnect":{"post":{"description":"Force reconnection to all upstream MCP servers","parameters":[{"description":"Reason for reconnection","in":"query","name":"reason","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ServerActionResponse"}}},"description":"All servers reconnected successfully"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Reconnect all servers","tags":["servers"]}},"/api/v1/servers/restart_all":{"post":{"description":"Restart all configured upstream MCP servers sequentially with partial failure handling","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/management.BulkOperationResult"}}},"description":"Bulk restart results with success/failure counts"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Forbidden (management disabled)"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Restart all servers","tags":["servers"]}},"/api/v1/servers/{id}":{"delete":{"description":"Remove an MCP upstream server from the configuration. This stops the server if running and removes it from config.","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ServerActionResponse"}}},"description":"Server removed successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Remove an upstream server","tags":["servers"]}},"/api/v1/servers/{id}/disable":{"post":{"description":"Disable a specific upstream MCP server","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ServerActionResponse"}}},"description":"Server disabled successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Disable an upstream server","tags":["servers"]}},"/api/v1/servers/{id}/discover-tools":{"post":{"description":"Manually trigger tool discovery and indexing for a specific upstream MCP server. This forces an immediate refresh of the server's tool cache.","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ServerActionResponse"}}},"description":"Tool discovery triggered successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request (missing server ID)"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Failed to discover tools"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Discover tools for a specific server","tags":["servers"]}},"/api/v1/servers/{id}/enable":{"post":{"description":"Enable a specific upstream MCP server","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ServerActionResponse"}}},"description":"Server enabled successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Enable an upstream server","tags":["servers"]}},"/api/v1/servers/{id}/login":{"post":{"description":"Initiate OAuth authentication flow for a specific upstream MCP server. Returns structured OAuth start response with correlation ID for tracking.","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.OAuthStartResponse"}}},"description":"OAuth login initiated successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.OAuthFlowError"}}},"description":"OAuth error (client_id required, DCR failed, etc.)"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Trigger OAuth login for server","tags":["servers"]}},"/api/v1/servers/{id}/logout":{"post":{"description":"Clear OAuth authentication token and disconnect a specific upstream MCP server. The server will need to re-authenticate before tools can be used again.","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ServerActionResponse"}}},"description":"OAuth logout completed successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request (missing server ID)"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Forbidden (management disabled or read-only mode)"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Clear OAuth token and disconnect server","tags":["servers"]}},"/api/v1/servers/{id}/logs":{"get":{"description":"Retrieve log entries for a specific upstream MCP server","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Number of log lines to retrieve","in":"query","name":"tail","schema":{"default":100,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.GetServerLogsResponse"}}},"description":"Server logs retrieved successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request (missing server ID)"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get server logs","tags":["servers"]}},"/api/v1/servers/{id}/quarantine":{"post":{"description":"Place a specific upstream MCP server in quarantine to prevent tool execution","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ServerActionResponse"}}},"description":"Server quarantined successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request (missing server ID)"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Quarantine a server","tags":["servers"]}},"/api/v1/servers/{id}/restart":{"post":{"description":"Restart the connection to a specific upstream MCP server","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ServerActionResponse"}}},"description":"Server restarted successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Restart an upstream server","tags":["servers"]}},"/api/v1/servers/{id}/tool-calls":{"get":{"description":"Retrieves tool call history filtered by upstream server ID. Returns recent tool executions for the specified server including timestamps, arguments, results, and errors. Useful for server-specific debugging and monitoring.","parameters":[{"description":"Upstream server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Maximum number of records to return (1-100, default 50)","in":"query","name":"limit","schema":{"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.GetServerToolCallsResponse"}}},"description":"Server tool calls retrieved successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server ID required"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"405":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Method not allowed"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Failed to get server tool calls"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get tool call history for specific server","tags":["tool-calls"]}},"/api/v1/servers/{id}/tools":{"get":{"description":"Retrieve all available tools for a specific upstream MCP server","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.GetServerToolsResponse"}}},"description":"Server tools retrieved successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request (missing server ID)"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get tools for a server","tags":["servers"]}},"/api/v1/servers/{id}/unquarantine":{"post":{"description":"Remove a specific upstream MCP server from quarantine to allow tool execution","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ServerActionResponse"}}},"description":"Server unquarantined successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request (missing server ID)"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Unquarantine a server","tags":["servers"]}},"/api/v1/sessions":{"get":{"description":"Retrieves paginated list of active and recent MCP client sessions. Each session represents a connection from an MCP client to MCPProxy, tracking initialization time, tool calls, and connection status.","parameters":[{"description":"Maximum number of sessions to return (1-100, default 10)","in":"query","name":"limit","schema":{"type":"integer"}},{"description":"Number of sessions to skip for pagination (default 0)","in":"query","name":"offset","schema":{"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.GetSessionsResponse"}}},"description":"Sessions retrieved successfully"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"405":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Method not allowed"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Failed to get sessions"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get active MCP sessions","tags":["sessions"]}},"/api/v1/sessions/{id}":{"get":{"description":"Retrieves detailed information about a specific MCP client session including initialization parameters, connection status, tool call count, and activity timestamps.","parameters":[{"description":"Session ID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.GetSessionDetailResponse"}}},"description":"Session details retrieved successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Session ID required"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Session not found"},"405":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Method not allowed"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get MCP session details by ID","tags":["sessions"]}},"/api/v1/stats/tokens":{"get":{"description":"Retrieve token savings statistics across all servers and sessions","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.SuccessResponse"}}},"description":"Token statistics"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get token savings statistics","tags":["stats"]}},"/api/v1/status":{"get":{"description":"Get comprehensive server status including running state, listen address, upstream statistics, and timestamp","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.SuccessResponse"}}},"description":"Server status information"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get server status","tags":["status"]}},"/api/v1/tool-calls":{"get":{"description":"Retrieves paginated tool call history across all upstream servers or filtered by session ID. Includes execution timestamps, arguments, results, and error information for debugging and auditing.","parameters":[{"description":"Maximum number of records to return (1-100, default 50)","in":"query","name":"limit","schema":{"type":"integer"}},{"description":"Number of records to skip for pagination (default 0)","in":"query","name":"offset","schema":{"type":"integer"}},{"description":"Filter tool calls by MCP session ID","in":"query","name":"session_id","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.GetToolCallsResponse"}}},"description":"Tool calls retrieved successfully"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"405":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Method not allowed"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Failed to get tool calls"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get tool call history","tags":["tool-calls"]}},"/api/v1/tool-calls/{id}":{"get":{"description":"Retrieves detailed information about a specific tool call execution including full request arguments, response data, execution time, and any errors encountered.","parameters":[{"description":"Tool call ID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.GetToolCallDetailResponse"}}},"description":"Tool call details retrieved successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Tool call ID required"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Tool call not found"},"405":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Method not allowed"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get tool call details by ID","tags":["tool-calls"]}},"/api/v1/tool-calls/{id}/replay":{"post":{"description":"Re-executes a previous tool call with optional modified arguments. Useful for debugging and testing tool behavior with different inputs. Creates a new tool call record linked to the original.","parameters":[{"description":"Original tool call ID to replay","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ReplayToolCallRequest"}}},"description":"Optional modified arguments for replay"},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ReplayToolCallResponse"}}},"description":"Tool call replayed successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Tool call ID required or invalid JSON payload"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"405":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Method not allowed"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Failed to replay tool call"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Replay a tool call","tags":["tool-calls"]}},"/api/v1/tools/call":{"post":{"description":"Execute a tool on an upstream MCP server (wrapper around MCP tool calls)","requestBody":{"content":{"application/json":{"schema":{"properties":{"arguments":{"type":"object"},"tool_name":{"type":"string"}},"type":"object"}}},"description":"Tool call request with tool name and arguments","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.SuccessResponse"}}},"description":"Tool call result"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request (invalid payload or missing tool name)"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error or tool execution failure"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Call a tool","tags":["tools"]}},"/healthz":{"get":{"description":"Get comprehensive health status including all component health (Kubernetes-compatible liveness probe)","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/observability.HealthResponse"}}},"description":"Service is healthy"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/observability.HealthResponse"}}},"description":"Service is unhealthy"}},"summary":"Get health status","tags":["health"]}},"/readyz":{"get":{"description":"Get readiness status including all component readiness checks (Kubernetes-compatible readiness probe)","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/observability.ReadinessResponse"}}},"description":"Service is ready"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/observability.ReadinessResponse"}}},"description":"Service is not ready"}},"summary":"Get readiness status","tags":["health"]}}},
+ "paths": {"/api/v1/activity":{"get":{"description":"Returns paginated list of activity records with optional filtering","parameters":[{"description":"Filter by activity type(s), comma-separated for multiple (Spec 024)","in":"query","name":"type","schema":{"enum":["tool_call","policy_decision","quarantine_change","server_change","system_start","system_stop","internal_tool_call","config_change"],"type":"string"}},{"description":"Filter by server name","in":"query","name":"server","schema":{"type":"string"}},{"description":"Filter by tool name","in":"query","name":"tool","schema":{"type":"string"}},{"description":"Filter by MCP session ID","in":"query","name":"session_id","schema":{"type":"string"}},{"description":"Filter by status","in":"query","name":"status","schema":{"enum":["success","error","blocked"],"type":"string"}},{"description":"Filter by intent operation type (Spec 018)","in":"query","name":"intent_type","schema":{"enum":["read","write","destructive"],"type":"string"}},{"description":"Filter by HTTP request ID for log correlation (Spec 021)","in":"query","name":"request_id","schema":{"type":"string"}},{"description":"Include successful call_tool_* internal tool calls (default: false, excluded to avoid duplicates)","in":"query","name":"include_call_tool","schema":{"type":"boolean"}},{"description":"Filter by sensitive data detection (true=has detections, false=no detections)","in":"query","name":"sensitive_data","schema":{"type":"boolean"}},{"description":"Filter by specific detection type (e.g., 'aws_access_key', 'credit_card')","in":"query","name":"detection_type","schema":{"type":"string"}},{"description":"Filter by severity level","in":"query","name":"severity","schema":{"enum":["critical","high","medium","low"],"type":"string"}},{"description":"Filter by agent token name (Spec 028)","in":"query","name":"agent","schema":{"type":"string"}},{"description":"Filter by auth type (Spec 028)","in":"query","name":"auth_type","schema":{"enum":["admin","agent"],"type":"string"}},{"description":"Filter activities after this time (RFC3339)","in":"query","name":"start_time","schema":{"type":"string"}},{"description":"Filter activities before this time (RFC3339)","in":"query","name":"end_time","schema":{"type":"string"}},{"description":"Maximum records to return (1-100, default 50)","in":"query","name":"limit","schema":{"type":"integer"}},{"description":"Pagination offset (default 0)","in":"query","name":"offset","schema":{"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/data"}],"properties":{"data":{"type":"object"},"error":{"type":"string"},"request_id":{"type":"string"},"success":{"type":"boolean"}},"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Bad Request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Internal Server Error"}},"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]}],"summary":"List activity records","tags":["Activity"]}},"/api/v1/activity/export":{"get":{"description":"Exports activity records in JSON Lines or CSV format for compliance","parameters":[{"description":"Export format: json (default) or csv","in":"query","name":"format","schema":{"type":"string"}},{"description":"Filter by activity type","in":"query","name":"type","schema":{"type":"string"}},{"description":"Filter by server name","in":"query","name":"server","schema":{"type":"string"}},{"description":"Filter by tool name","in":"query","name":"tool","schema":{"type":"string"}},{"description":"Filter by MCP session ID","in":"query","name":"session_id","schema":{"type":"string"}},{"description":"Filter by status","in":"query","name":"status","schema":{"type":"string"}},{"description":"Filter activities after this time (RFC3339)","in":"query","name":"start_time","schema":{"type":"string"}},{"description":"Filter activities before this time (RFC3339)","in":"query","name":"end_time","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"string"}},"application/x-ndjson":{"schema":{"type":"string"}},"text/csv":{"schema":{"type":"string"}}},"description":"Streamed activity records"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Internal Server Error"}},"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]}],"summary":"Export activity records","tags":["Activity"]}},"/api/v1/activity/summary":{"get":{"description":"Returns aggregated activity statistics for a time period","parameters":[{"description":"Time period: 1h, 24h (default), 7d, 30d","in":"query","name":"period","schema":{"type":"string"}},{"description":"Group by: server, tool (optional)","in":"query","name":"group_by","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/data"}],"properties":{"data":{"type":"object"},"error":{"type":"string"},"request_id":{"type":"string"},"success":{"type":"boolean"}},"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Bad Request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Internal Server Error"}},"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]}],"summary":"Get activity summary statistics","tags":["Activity"]}},"/api/v1/activity/{id}":{"get":{"description":"Returns full details for a single activity record","parameters":[{"description":"Activity record ID (ULID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/data"}],"properties":{"data":{"type":"object"},"error":{"type":"string"},"request_id":{"type":"string"},"success":{"type":"boolean"}},"type":"object"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Internal Server Error"}},"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]}],"summary":"Get activity record details","tags":["Activity"]}},"/api/v1/config":{"get":{"description":"Retrieves the current MCPProxy configuration including all server definitions, global settings, and runtime parameters","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.GetConfigResponse"}}},"description":"Configuration retrieved successfully"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Failed to get configuration"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get current configuration","tags":["config"]}},"/api/v1/config/apply":{"post":{"description":"Applies a new MCPProxy configuration. Validates and persists the configuration to disk. Some changes apply immediately, while others may require a restart. Returns detailed information about applied changes and restart requirements.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/config.Config"}}},"description":"Configuration to apply","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ConfigApplyResult"}}},"description":"Configuration applied successfully with change details"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Invalid JSON payload"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Failed to apply configuration"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Apply configuration","tags":["config"]}},"/api/v1/config/validate":{"post":{"description":"Validates a provided MCPProxy configuration without applying it. Checks for syntax errors, invalid server definitions, conflicting settings, and other configuration issues.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/config.Config"}}},"description":"Configuration to validate","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ValidateConfigResponse"}}},"description":"Configuration validation result"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Invalid JSON payload"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Validation failed"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Validate configuration","tags":["config"]}},"/api/v1/diagnostics":{"get":{"description":"Get comprehensive health diagnostics including upstream errors, OAuth requirements, missing secrets, and Docker status","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.Diagnostics"}}},"description":"Health diagnostics"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get health diagnostics","tags":["diagnostics"]}},"/api/v1/docker/status":{"get":{"description":"Retrieve current Docker availability and recovery status","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.SuccessResponse"}}},"description":"Docker status information"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get Docker status","tags":["docker"]}},"/api/v1/doctor":{"get":{"description":"Get comprehensive health diagnostics including upstream errors, OAuth requirements, missing secrets, and Docker status","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.Diagnostics"}}},"description":"Health diagnostics"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get health diagnostics","tags":["diagnostics"]}},"/api/v1/index/search":{"get":{"description":"Search across all upstream MCP server tools using BM25 keyword search","parameters":[{"description":"Search query","in":"query","name":"q","required":true,"schema":{"type":"string"}},{"description":"Maximum number of results","in":"query","name":"limit","schema":{"default":10,"maximum":100,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.SearchToolsResponse"}}},"description":"Search results"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request (missing query parameter)"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Search for tools","tags":["tools"]}},"/api/v1/info":{"get":{"description":"Get essential server metadata including version, web UI URL, endpoint addresses, and update availability\nThis endpoint is designed for tray-core communication and version checking\nUse refresh=true query parameter to force an immediate update check against GitHub","parameters":[{"description":"Force immediate update check against GitHub","in":"query","name":"refresh","schema":{"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/data"}],"properties":{"data":{"type":"object"},"error":{"type":"string"},"request_id":{"type":"string"},"success":{"type":"boolean"}},"type":"object"}}},"description":"Server information with optional update info"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get server information","tags":["status"]}},"/api/v1/registries":{"get":{"description":"Retrieves list of all MCP server registries that can be browsed for discovering and installing new upstream servers. Includes registry metadata, server counts, and API endpoints.","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.GetRegistriesResponse"}}},"description":"Registries retrieved successfully"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Failed to list registries"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"List available MCP server registries","tags":["registries"]}},"/api/v1/registries/{id}/servers":{"get":{"description":"Searches for MCP servers within a specific registry by keyword or tag. Returns server metadata including installation commands, source code URLs, and npm package information for easy discovery and installation.","parameters":[{"description":"Registry ID","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Search query keyword","in":"query","name":"q","schema":{"type":"string"}},{"description":"Filter by tag","in":"query","name":"tag","schema":{"type":"string"}},{"description":"Maximum number of results (default 10)","in":"query","name":"limit","schema":{"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.SearchRegistryServersResponse"}}},"description":"Servers retrieved successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Registry ID required"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Failed to search servers"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Search MCP servers in a registry","tags":["registries"]}},"/api/v1/routing":{"get":{"description":"Get the current routing mode and available MCP endpoints","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.SuccessResponse"}}},"description":"Routing mode information"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get routing mode information","tags":["status"]}},"/api/v1/secrets":{"post":{"description":"Stores a secret value in the operating system's secure keyring. The secret can then be referenced in configuration using ${keyring:secret-name} syntax. Automatically notifies runtime to restart affected servers.","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":{},"type":"object"}}},"description":"Secret stored successfully with reference syntax"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Invalid JSON payload, missing name/value, or unsupported type"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"405":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Method not allowed"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Secret resolver not available or failed to store secret"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Store a secret in OS keyring","tags":["secrets"]}},"/api/v1/secrets/{name}":{"delete":{"description":"Deletes a secret from the operating system's secure keyring. Automatically notifies runtime to restart affected servers. Only keyring type is supported for security.","parameters":[{"description":"Name of the secret to delete","in":"path","name":"name","required":true,"schema":{"type":"string"}},{"description":"Secret type (only 'keyring' supported, defaults to 'keyring')","in":"query","name":"type","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":{},"type":"object"}}},"description":"Secret deleted successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Missing secret name or unsupported type"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"405":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Method not allowed"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Secret resolver not available or failed to delete secret"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Delete a secret from OS keyring","tags":["secrets"]}},"/api/v1/servers":{"get":{"description":"Get a list of all configured upstream MCP servers with their connection status and statistics","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.GetServersResponse"}}},"description":"Server list with statistics"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"List all upstream MCP servers","tags":["servers"]},"post":{"description":"Add a new MCP upstream server to the configuration. New servers are quarantined by default for security.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/httpapi.AddServerRequest"}}},"description":"Server configuration","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ServerActionResponse"}}},"description":"Server added successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request - invalid configuration"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Conflict - server with this name already exists"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Add a new upstream server","tags":["servers"]}},"/api/v1/servers/disable_all":{"post":{"description":"Disable all configured upstream MCP servers with partial failure handling","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/management.BulkOperationResult"}}},"description":"Bulk disable results with success/failure counts"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Forbidden (management disabled)"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Disable all servers","tags":["servers"]}},"/api/v1/servers/enable_all":{"post":{"description":"Enable all configured upstream MCP servers with partial failure handling","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/management.BulkOperationResult"}}},"description":"Bulk enable results with success/failure counts"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Forbidden (management disabled)"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Enable all servers","tags":["servers"]}},"/api/v1/servers/import":{"post":{"description":"Import MCP server configurations from a Claude Desktop, Claude Code, Cursor IDE, Codex CLI, or Gemini CLI configuration file","parameters":[{"description":"If true, return preview without importing","in":"query","name":"preview","schema":{"type":"boolean"}},{"description":"Force format (claude-desktop, claude-code, cursor, codex, gemini)","in":"query","name":"format","schema":{"type":"string"}},{"description":"Comma-separated list of server names to import","in":"query","name":"server_names","schema":{"type":"string"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"file"}}},"description":"Configuration file to import","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/httpapi.ImportResponse"}}},"description":"Import result"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request - invalid file or format"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Import servers from uploaded configuration file","tags":["servers"]}},"/api/v1/servers/import/json":{"post":{"description":"Import MCP server configurations from raw JSON or TOML content (useful for pasting configurations)","parameters":[{"description":"If true, return preview without importing","in":"query","name":"preview","schema":{"type":"boolean"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/httpapi.ImportRequest"}}},"description":"Import request with content","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/httpapi.ImportResponse"}}},"description":"Import result"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request - invalid content or format"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Import servers from JSON/TOML content","tags":["servers"]}},"/api/v1/servers/import/path":{"post":{"description":"Import MCP server configurations by reading a file from the server's filesystem","parameters":[{"description":"If true, return preview without importing","in":"query","name":"preview","schema":{"type":"boolean"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/httpapi.ImportFromPathRequest"}}},"description":"Import request with file path","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/httpapi.ImportResponse"}}},"description":"Import result"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request - invalid path or format"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"File not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Import servers from a file path","tags":["servers"]}},"/api/v1/servers/import/paths":{"get":{"description":"Returns well-known configuration file paths for supported formats with existence check","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/httpapi.CanonicalConfigPathsResponse"}}},"description":"Canonical config paths"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get canonical config file paths","tags":["servers"]}},"/api/v1/servers/reconnect":{"post":{"description":"Force reconnection to all upstream MCP servers","parameters":[{"description":"Reason for reconnection","in":"query","name":"reason","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ServerActionResponse"}}},"description":"All servers reconnected successfully"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Reconnect all servers","tags":["servers"]}},"/api/v1/servers/restart_all":{"post":{"description":"Restart all configured upstream MCP servers sequentially with partial failure handling","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/management.BulkOperationResult"}}},"description":"Bulk restart results with success/failure counts"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Forbidden (management disabled)"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Restart all servers","tags":["servers"]}},"/api/v1/servers/{id}":{"delete":{"description":"Remove an MCP upstream server from the configuration. This stops the server if running and removes it from config.","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ServerActionResponse"}}},"description":"Server removed successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Remove an upstream server","tags":["servers"]}},"/api/v1/servers/{id}/disable":{"post":{"description":"Disable a specific upstream MCP server","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ServerActionResponse"}}},"description":"Server disabled successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Disable an upstream server","tags":["servers"]}},"/api/v1/servers/{id}/discover-tools":{"post":{"description":"Manually trigger tool discovery and indexing for a specific upstream MCP server. This forces an immediate refresh of the server's tool cache.","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ServerActionResponse"}}},"description":"Tool discovery triggered successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request (missing server ID)"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Failed to discover tools"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Discover tools for a specific server","tags":["servers"]}},"/api/v1/servers/{id}/enable":{"post":{"description":"Enable a specific upstream MCP server","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ServerActionResponse"}}},"description":"Server enabled successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Enable an upstream server","tags":["servers"]}},"/api/v1/servers/{id}/login":{"post":{"description":"Initiate OAuth authentication flow for a specific upstream MCP server. Returns structured OAuth start response with correlation ID for tracking.","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.OAuthStartResponse"}}},"description":"OAuth login initiated successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.OAuthFlowError"}}},"description":"OAuth error (client_id required, DCR failed, etc.)"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Trigger OAuth login for server","tags":["servers"]}},"/api/v1/servers/{id}/logout":{"post":{"description":"Clear OAuth authentication token and disconnect a specific upstream MCP server. The server will need to re-authenticate before tools can be used again.","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ServerActionResponse"}}},"description":"OAuth logout completed successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request (missing server ID)"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Forbidden (management disabled or read-only mode)"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Clear OAuth token and disconnect server","tags":["servers"]}},"/api/v1/servers/{id}/logs":{"get":{"description":"Retrieve log entries for a specific upstream MCP server","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Number of log lines to retrieve","in":"query","name":"tail","schema":{"default":100,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.GetServerLogsResponse"}}},"description":"Server logs retrieved successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request (missing server ID)"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get server logs","tags":["servers"]}},"/api/v1/servers/{id}/quarantine":{"post":{"description":"Place a specific upstream MCP server in quarantine to prevent tool execution","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ServerActionResponse"}}},"description":"Server quarantined successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request (missing server ID)"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Quarantine a server","tags":["servers"]}},"/api/v1/servers/{id}/restart":{"post":{"description":"Restart the connection to a specific upstream MCP server","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ServerActionResponse"}}},"description":"Server restarted successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Restart an upstream server","tags":["servers"]}},"/api/v1/servers/{id}/tool-calls":{"get":{"description":"Retrieves tool call history filtered by upstream server ID. Returns recent tool executions for the specified server including timestamps, arguments, results, and errors. Useful for server-specific debugging and monitoring.","parameters":[{"description":"Upstream server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Maximum number of records to return (1-100, default 50)","in":"query","name":"limit","schema":{"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.GetServerToolCallsResponse"}}},"description":"Server tool calls retrieved successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server ID required"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"405":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Method not allowed"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Failed to get server tool calls"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get tool call history for specific server","tags":["tool-calls"]}},"/api/v1/servers/{id}/tools":{"get":{"description":"Retrieve all available tools for a specific upstream MCP server","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.GetServerToolsResponse"}}},"description":"Server tools retrieved successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request (missing server ID)"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get tools for a server","tags":["servers"]}},"/api/v1/servers/{id}/unquarantine":{"post":{"description":"Remove a specific upstream MCP server from quarantine to allow tool execution","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ServerActionResponse"}}},"description":"Server unquarantined successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request (missing server ID)"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Unquarantine a server","tags":["servers"]}},"/api/v1/sessions":{"get":{"description":"Retrieves paginated list of active and recent MCP client sessions. Each session represents a connection from an MCP client to MCPProxy, tracking initialization time, tool calls, and connection status.","parameters":[{"description":"Maximum number of sessions to return (1-100, default 10)","in":"query","name":"limit","schema":{"type":"integer"}},{"description":"Number of sessions to skip for pagination (default 0)","in":"query","name":"offset","schema":{"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.GetSessionsResponse"}}},"description":"Sessions retrieved successfully"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"405":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Method not allowed"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Failed to get sessions"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get active MCP sessions","tags":["sessions"]}},"/api/v1/sessions/{id}":{"get":{"description":"Retrieves detailed information about a specific MCP client session including initialization parameters, connection status, tool call count, and activity timestamps.","parameters":[{"description":"Session ID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.GetSessionDetailResponse"}}},"description":"Session details retrieved successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Session ID required"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Session not found"},"405":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Method not allowed"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get MCP session details by ID","tags":["sessions"]}},"/api/v1/stats/tokens":{"get":{"description":"Retrieve token savings statistics across all servers and sessions","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.SuccessResponse"}}},"description":"Token statistics"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get token savings statistics","tags":["stats"]}},"/api/v1/status":{"get":{"description":"Get comprehensive server status including running state, listen address, upstream statistics, and timestamp","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.SuccessResponse"}}},"description":"Server status information"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get server status","tags":["status"]}},"/api/v1/tool-calls":{"get":{"description":"Retrieves paginated tool call history across all upstream servers or filtered by session ID. Includes execution timestamps, arguments, results, and error information for debugging and auditing.","parameters":[{"description":"Maximum number of records to return (1-100, default 50)","in":"query","name":"limit","schema":{"type":"integer"}},{"description":"Number of records to skip for pagination (default 0)","in":"query","name":"offset","schema":{"type":"integer"}},{"description":"Filter tool calls by MCP session ID","in":"query","name":"session_id","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.GetToolCallsResponse"}}},"description":"Tool calls retrieved successfully"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"405":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Method not allowed"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Failed to get tool calls"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get tool call history","tags":["tool-calls"]}},"/api/v1/tool-calls/{id}":{"get":{"description":"Retrieves detailed information about a specific tool call execution including full request arguments, response data, execution time, and any errors encountered.","parameters":[{"description":"Tool call ID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.GetToolCallDetailResponse"}}},"description":"Tool call details retrieved successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Tool call ID required"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Tool call not found"},"405":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Method not allowed"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get tool call details by ID","tags":["tool-calls"]}},"/api/v1/tool-calls/{id}/replay":{"post":{"description":"Re-executes a previous tool call with optional modified arguments. Useful for debugging and testing tool behavior with different inputs. Creates a new tool call record linked to the original.","parameters":[{"description":"Original tool call ID to replay","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ReplayToolCallRequest"}}},"description":"Optional modified arguments for replay"},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ReplayToolCallResponse"}}},"description":"Tool call replayed successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Tool call ID required or invalid JSON payload"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"405":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Method not allowed"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Failed to replay tool call"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Replay a tool call","tags":["tool-calls"]}},"/api/v1/tools/call":{"post":{"description":"Execute a tool on an upstream MCP server (wrapper around MCP tool calls)","requestBody":{"content":{"application/json":{"schema":{"properties":{"arguments":{"type":"object"},"tool_name":{"type":"string"}},"type":"object"}}},"description":"Tool call request with tool name and arguments","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.SuccessResponse"}}},"description":"Tool call result"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request (invalid payload or missing tool name)"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error or tool execution failure"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Call a tool","tags":["tools"]}},"/healthz":{"get":{"description":"Get comprehensive health status including all component health (Kubernetes-compatible liveness probe)","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/observability.HealthResponse"}}},"description":"Service is healthy"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/observability.HealthResponse"}}},"description":"Service is unhealthy"}},"summary":"Get health status","tags":["health"]}},"/readyz":{"get":{"description":"Get readiness status including all component readiness checks (Kubernetes-compatible readiness probe)","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/observability.ReadinessResponse"}}},"description":"Service is ready"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/observability.ReadinessResponse"}}},"description":"Service is not ready"}},"summary":"Get readiness status","tags":["health"]}}},
"openapi": "3.1.0"
}`
diff --git a/oas/swagger.yaml b/oas/swagger.yaml
index 9b729e85..74c13dc4 100644
--- a/oas/swagger.yaml
+++ b/oas/swagger.yaml
@@ -94,6 +94,11 @@ components:
require_mcp_auth:
description: 'Require authentication on /mcp endpoint (default: false)'
type: boolean
+ routing_mode:
+ description: |-
+ Routing mode (Spec 031): how MCP tools are exposed to clients
+ Valid values: "retrieve_tools" (default), "direct", "code_execution"
+ type: string
sensitive_data_detection:
$ref: '#/components/schemas/config.SensitiveDataDetectionConfig'
tls:
@@ -2522,6 +2527,22 @@ paths:
summary: Search MCP servers in a registry
tags:
- registries
+ /api/v1/routing:
+ get:
+ description: Get the current routing mode and available MCP endpoints
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/contracts.SuccessResponse'
+ description: Routing mode information
+ security:
+ - ApiKeyAuth: []
+ - ApiKeyQuery: []
+ summary: Get routing mode information
+ tags:
+ - status
/api/v1/secrets:
post:
description: Stores a secret value in the operating system's secure keyring.
diff --git a/web/frontend/dist/assets/Activity-3jsAMS7R.js b/web/frontend/dist/assets/Activity-3jsAMS7R.js
new file mode 100644
index 00000000..38cf46a2
--- /dev/null
+++ b/web/frontend/dist/assets/Activity-3jsAMS7R.js
@@ -0,0 +1 @@
+import{d as Be,e as Ve,r as v,f as C,s as ie,c as o,o as l,a as t,t as n,n as g,j as y,E as Ke,_ as Qe,x as Ge,A as We,I as Xe,g as r,m as x,M as je,l as ae,L as le,F as B,p as V,y as N,B as Ye,v as Le,i as Y,w as Ee,k as Ze,z as oe,h as et}from"./index-DMP0eryo.js";const tt={class:"json-viewer-container"},st={class:"flex justify-between items-start mb-2"},at={class:"text-xs text-base-content/60"},lt=["title"],ot={key:0,class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},nt={key:1,class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},it=["innerHTML"],rt=Be({__name:"JsonViewer",props:{data:{},maxHeight:{default:"24rem"}},setup(re){const H=re,z=Ve(),b=v(!1);let _=null;const M=C(()=>{try{return JSON.stringify(H.data,null,2)}catch{return String(H.data)}}),F=C(()=>new Blob([M.value]).size.toLocaleString()),i=C(()=>{let m=M.value;return m=m.replace(/&/g,"&").replace(//g,">"),m=m.replace(/("(?:[^"\\]|\\.)*")\s*:/g,'$1:').replace(/:\s*("(?:[^"\\]|\\.)*")/g,': $1').replace(/:\s*(-?\d+\.?\d*)/g,': $1').replace(/:\s*(true|false)/g,': $1').replace(/:\s*(null)/g,': $1'),m}),I=async()=>{try{await navigator.clipboard.writeText(M.value),b.value=!0,z.addToast({type:"success",title:"Copied!",message:"JSON copied to clipboard"}),_&&clearTimeout(_),_=setTimeout(()=>{b.value=!1},2e3)}catch{z.addToast({type:"error",title:"Copy Failed",message:"Failed to copy to clipboard"})}};return ie(()=>H.data,()=>{b.value=!1,_&&clearTimeout(_)}),(m,c)=>(l(),o("div",tt,[t("div",st,[t("div",at,n(F.value)+" bytes ",1),t("button",{onClick:I,class:g(["btn btn-xs btn-ghost gap-1",{"btn-success":b.value}]),title:b.value?"Copied!":"Copy to clipboard"},[b.value?(l(),o("svg",nt,[...c[1]||(c[1]=[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M5 13l4 4L19 7"},null,-1)])])):(l(),o("svg",ot,[...c[0]||(c[0]=[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"},null,-1)])])),y(" "+n(b.value?"Copied!":"Copy"),1)],10,lt)]),t("pre",{class:"json-viewer bg-base-300 p-3 rounded text-xs overflow-auto w-full",style:Ke({maxHeight:m.maxHeight}),innerHTML:i.value},null,12,it)]))}}),ne=Qe(rt,[["__scopeId","data-v-919a3d3e"]]),dt={class:"space-y-6"},ut={class:"flex flex-wrap justify-between items-start gap-4"},ct={class:"flex items-center gap-4"},vt={class:"form-control"},pt={class:"label cursor-pointer gap-2"},mt={class:"flex items-center gap-2"},bt=["disabled"],gt={key:0,class:"stats shadow bg-base-100 w-full"},_t={class:"stat"},xt={class:"stat-value text-2xl"},yt={class:"stat"},ft={class:"stat-value text-2xl text-success"},ht={class:"stat"},kt={class:"stat-value text-2xl text-error"},wt={class:"stat"},Ct={class:"stat-value text-2xl text-warning"},St={class:"card bg-base-100 shadow-md"},At={class:"card-body py-4"},Tt={class:"flex flex-wrap gap-4 items-end"},Dt={class:"form-control min-w-[180px]"},Mt={class:"dropdown dropdown-bottom"},$t={tabindex:"0",role:"button",class:"select select-bordered select-sm w-full text-left flex items-center justify-between"},jt={key:0},Lt={key:1},Et={key:2,class:"truncate"},Bt={tabindex:"0",class:"dropdown-content z-[10] menu p-2 shadow-lg bg-base-200 rounded-box w-56"},Vt={class:"menu-title flex flex-row justify-between items-center"},Nt={class:"label cursor-pointer justify-start gap-2 py-1"},Ft=["checked","onChange"],It={class:"text-lg"},Ut={class:"form-control min-w-[150px]"},zt=["value"],Jt={class:"form-control min-w-[120px]"},Ot={class:"form-control min-w-[120px]"},Rt={key:0,class:"form-control min-w-[150px]"},Ht=["value"],Pt={class:"form-control min-w-[140px]"},qt={key:1,class:"form-control min-w-[120px]"},Kt={class:"form-control min-w-[180px]"},Qt=["value"],Gt={class:"form-control min-w-[160px]"},Wt={class:"form-control min-w-[160px]"},Xt={class:"dropdown dropdown-end"},Yt={tabindex:"0",class:"dropdown-content z-[1] menu p-2 shadow-lg bg-base-200 rounded-box w-40"},Zt={key:0,class:"flex flex-wrap gap-2 mt-2 pt-2 border-t border-base-300"},es=["onClick"],ts={key:0,class:"badge badge-sm badge-outline"},ss={key:1,class:"badge badge-sm badge-outline"},as={key:2,class:"badge badge-sm badge-outline"},ls={key:3,class:"badge badge-sm badge-outline"},os={key:4,class:"badge badge-sm badge-outline"},ns={key:5,class:"badge badge-sm badge-outline"},is={key:6,class:"badge badge-sm badge-outline"},rs={key:7,class:"badge badge-sm badge-outline"},ds={key:8,class:"badge badge-sm badge-outline"},us={class:"card bg-base-100 shadow-md"},cs={class:"card-body"},vs={key:0,class:"flex justify-center py-12"},ps={key:1,class:"alert alert-error"},ms={key:2,class:"text-center py-12 text-base-content/60"},bs={class:"text-lg"},gs={class:"text-sm mt-1"},_s={key:3,class:"overflow-x-auto"},xs={class:"table table-sm"},ys=["onClick"],fs={class:"text-sm"},hs={class:"text-xs text-base-content/60"},ks={class:"flex items-center gap-2"},ws={class:"text-lg"},Cs={class:"text-sm"},Ss={key:1,class:"text-base-content/40"},As={class:"max-w-xs truncate"},Ts={key:0,class:"text-sm bg-base-200 px-2 py-1 rounded"},Ds={key:1,class:"text-sm"},Ms={key:2,class:"text-base-content/40"},$s=["data-tip"],js={key:1,class:"text-base-content/40"},Ls=["data-tip"],Es={key:1,class:"text-base-content/40"},Bs={key:0,class:"text-sm"},Vs={key:1,class:"text-base-content/40"},Ns=["onClick"],Fs={key:0,class:"flex justify-between items-center mt-4 pt-4 border-t border-base-300"},Is={class:"text-sm text-base-content/60"},Us={class:"join"},zs=["disabled"],Js=["disabled"],Os={class:"join-item btn btn-sm"},Rs=["disabled"],Hs=["disabled"],Ps={class:"form-control"},qs={class:"drawer drawer-end"},Ks={class:"drawer-side z-50"},Qs={class:"bg-base-100 w-[500px] min-h-full p-6"},Gs={key:0,class:"space-y-4"},Ws={class:"flex justify-between items-start"},Xs={class:"text-lg font-bold flex items-center gap-2"},Ys={class:"text-2xl"},Zs={class:"text-sm text-base-content/60"},ea={class:"flex items-center gap-2"},ta={class:"space-y-3"},sa={key:0,class:"flex gap-2"},aa={class:"text-xs bg-base-200 px-2 py-1 rounded break-all"},la={key:1,class:"flex gap-2"},oa={key:2,class:"flex gap-2"},na={class:"text-sm bg-base-200 px-2 py-1 rounded"},ia={key:3,class:"flex gap-2"},ra={class:"text-sm"},da={key:4,class:"flex gap-2"},ua={class:"text-xs bg-base-200 px-2 py-1 rounded"},ca={key:5,class:"flex gap-2"},va={class:"badge badge-sm badge-outline"},pa={key:0},ma={class:"font-semibold mb-2 text-warning flex items-center gap-2"},ba={class:"flex flex-col gap-2 w-full text-inherit"},ga={class:"flex items-center gap-2"},_a={key:0,class:"flex flex-col gap-1"},xa={class:"flex flex-wrap gap-1"},ya={key:1,class:"flex flex-col gap-1"},fa={class:"text-sm space-y-1"},ha={class:"font-mono text-xs text-inherit"},ka={class:"text-inherit/70 text-xs"},wa={key:0,class:"badge badge-xs badge-ghost"},Ca={key:1},Sa={class:"alert alert-warning"},Aa={class:"flex flex-col gap-2 w-full"},Ta={class:"flex items-center gap-2"},Da={class:"badge badge-warning"},Ma={key:0,class:"flex flex-col gap-1"},$a={class:"text-sm"},ja={key:1,class:"flex flex-col gap-1"},La={class:"text-sm"},Ea={key:2,class:"text-sm italic"},Ba={key:2},Va={key:3},Na={class:"font-semibold mb-2 flex items-center gap-2"},Fa={key:0,class:"badge badge-sm badge-warning"},Ia={key:4},Ua={class:"alert alert-error"},za={class:"text-sm break-words"},Ja={key:5},Oa={class:"bg-base-200 rounded p-3 space-y-2"},Ra={key:0,class:"flex gap-2"},Ha={key:1,class:"flex gap-2"},Pa={class:"text-sm"},qa={key:2,class:"flex gap-2"},Ka={class:"text-sm"},Qa={key:6},Wa=Be({__name:"Activity",setup(re){const H=We(),z=Ve(),b=v([]),_=v(null),M=v(!1),F=v(null),i=v(null),I=v(!1),m=v(!0),c=v([]),S=v(""),$=v(""),A=v(""),f=v(""),j=v(""),h=v(""),T=v(""),L=v(""),E=v(""),de=[{value:"tool_call",label:"Tool Call",icon:"🔧"},{value:"system_start",label:"System Start",icon:"🚀"},{value:"system_stop",label:"System Stop",icon:"🛑"},{value:"internal_tool_call",label:"Internal Tool Call",icon:"⚙️"},{value:"config_change",label:"Config Change",icon:"⚡"},{value:"policy_decision",label:"Policy Decision",icon:"🛡️"},{value:"quarantine_change",label:"Quarantine Change",icon:"⚠️"},{value:"server_change",label:"Server Change",icon:"🔄"}],p=v(1),U=v(25),G=v("timestamp"),P=v("desc"),Ne=C(()=>{const s=new Set;return b.value.forEach(e=>{e.server_name&&s.add(e.server_name)}),Array.from(s).sort()}),Fe=C(()=>{const s=new Set;return b.value.forEach(e=>{var u;const d=(u=e.metadata)==null?void 0:u._auth_agent_name;d&&s.add(d)}),Array.from(s).sort()}),ue=C(()=>{const s=new Map;return b.value.forEach(e=>{var d;if(e.session_id&&!s.has(e.session_id)){const u=(d=e.metadata)==null?void 0:d.client_name;s.set(e.session_id,{clientName:u})}}),Array.from(s.entries()).map(([e,d])=>{const u=e.slice(-5),D=d.clientName?`${d.clientName} ...${u}`:`...${u}`;return{id:e,label:D,clientName:d.clientName}}).sort((e,d)=>e.label.localeCompare(d.label))}),Ie=s=>{const e=ue.value.find(d=>d.id===s);return(e==null?void 0:e.label)||`...${s.slice(-5)}`},W=C(()=>c.value.length>0||S.value||$.value||A.value||f.value||j.value||h.value||T.value||L.value||E.value),ce=C(()=>{let s=b.value;if(c.value.length>0&&(s=s.filter(e=>c.value.includes(e.type))),S.value&&(s=s.filter(e=>e.server_name===S.value)),$.value&&(s=s.filter(e=>e.session_id===$.value)),A.value&&(s=s.filter(e=>e.status===A.value)),f.value==="true"?s=s.filter(e=>e.has_sensitive_data===!0):f.value==="false"&&(s=s.filter(e=>!e.has_sensitive_data)),j.value&&f.value==="true"&&(s=s.filter(e=>e.max_severity===j.value)),h.value&&(s=s.filter(e=>{var d;return((d=e.metadata)==null?void 0:d._auth_auth_type)===h.value})),T.value&&(s=s.filter(e=>{var d;return((d=e.metadata)==null?void 0:d._auth_agent_name)===T.value})),L.value){const e=new Date(L.value).getTime();s=s.filter(d=>new Date(d.timestamp).getTime()>=e)}if(E.value){const e=new Date(E.value).getTime();s=s.filter(d=>new Date(d.timestamp).getTime()<=e)}return s}),X=C(()=>{const s=[...ce.value],e=G.value,d=P.value;return s.sort((u,D)=>{let k,w;return e==="timestamp"?(k=new Date(u.timestamp).getTime(),w=new Date(D.timestamp).getTime()):e==="duration_ms"?(k=u.duration_ms??0,w=D.duration_ms??0):(k=u[e]??"",w=D[e]??""),typeof k=="string"&&typeof w=="string"?d==="asc"?k.localeCompare(w):w.localeCompare(k):d==="asc"?k-w:w-k}),s}),J=C(()=>Math.ceil(X.value.length/U.value)),Ue=C(()=>{const s=(p.value-1)*U.value;return X.value.slice(s,s+U.value)}),q=async()=>{M.value=!0,F.value=null;try{const[s,e]=await Promise.all([oe.getActivities({limit:200}),oe.getActivitySummary("24h")]);s.success&&s.data?b.value=s.data.activities||[]:F.value=s.error||"Failed to load activities",e.success&&e.data&&(_.value=e.data)}catch(s){F.value=s instanceof Error?s.message:"Unknown error"}finally{M.value=!1}},ze=()=>{c.value=[],S.value="",$.value="",A.value="",f.value="",j.value="",h.value="",T.value="",L.value="",E.value="",p.value=1},ve=s=>{const e=c.value.indexOf(s);e>=0?c.value.splice(e,1):c.value.push(s)},Je=()=>{c.value=[]},K=s=>{G.value===s?P.value=P.value==="asc"?"desc":"asc":(G.value=s,P.value=s==="timestamp"||s==="duration_ms"?"desc":"asc")},Q=s=>G.value!==s?"":P.value==="asc"?"↑":"↓",pe=s=>{i.value=s,I.value=!0},me=()=>{I.value=!1,i.value=null},be=s=>{const e=oe.getActivityExportUrl({format:s,type:c.value.length>0?c.value.join(","):void 0,server:S.value||void 0,status:A.value||void 0});window.open(e,"_blank")},O=s=>{if(!m.value)return;const e=s.detail;e&&(e.server_name||e.tool_name||e.type||e.internal_tool_name||e.action||e.version||e.reason)&&(console.log("Activity event received, refreshing from API:",e),q())},ge=s=>{if(!m.value)return;const e=s.detail;e&&(e.server_name||e.tool_name||e.status||e.internal_tool_name||e.target_server)&&(console.log("Activity completed event received, refreshing from API:",e),q())},_e=s=>new Date(s).toLocaleString(),Oe=s=>{const e=Date.now(),d=new Date(s).getTime(),u=e-d;return u<1e3?"Just now":u<6e4?`${Math.floor(u/1e3)}s ago`:u<36e5?`${Math.floor(u/6e4)}m ago`:u<864e5?`${Math.floor(u/36e5)}h ago`:`${Math.floor(u/864e5)}d ago`},Z=s=>({tool_call:"Tool Call",system_start:"System Start",system_stop:"System Stop",internal_tool_call:"Internal Tool Call",config_change:"Config Change",policy_decision:"Policy Decision",quarantine_change:"Quarantine Change",server_change:"Server Change"})[s]||s,ee=s=>({tool_call:"🔧",system_start:"🚀",system_stop:"🛑",internal_tool_call:"⚙️",config_change:"⚡",policy_decision:"🛡️",quarantine_change:"⚠️",server_change:"🔄"})[s]||"📋",xe=s=>({success:"Success",error:"Error",blocked:"Blocked"})[s]||s,ye=s=>({success:"badge-success",error:"badge-error",blocked:"badge-warning"})[s]||"badge-ghost",fe=s=>s<1e3?`${Math.round(s)}ms`:`${(s/1e3).toFixed(2)}s`,Re=s=>{if(typeof s=="object")return s;try{return JSON.parse(s)}catch{return s}},te=s=>({critical:"☢️",high:"⚠️",medium:"⚡",low:"ℹ️"})[s||""]||"⚠️",se=s=>({critical:"badge-error",high:"badge-warning",medium:"badge-info",low:"badge-ghost"})[s||""]||"badge-warning",he=s=>({read:"📖",write:"✏️",destructive:"⚠️"})[s]||"❓",ke=s=>({read:"badge-info",write:"badge-warning",destructive:"badge-error"})[s]||"badge-ghost",He=s=>{if(!s.metadata)return!1;const e=["intent","decision","reason","policy_rule"];return Object.keys(s.metadata).filter(u=>!e.includes(u)).length>0},Pe=s=>{if(!s.metadata)return{};const e=["intent","decision","reason","policy_rule"],d={};for(const[u,D]of Object.entries(s.metadata))e.includes(u)||(d[u]=D);return d};ie([c,S,A,f,j,h,T,L,E],()=>{p.value=1},{deep:!0}),ie(h,s=>{s!=="agent"&&(T.value="")});const we=s=>{s.key==="Escape"&&I.value&&me()};return Ge(()=>{const s=H.query.session;s&&($.value=s),q(),window.addEventListener("mcpproxy:activity",O),window.addEventListener("mcpproxy:activity-started",O),window.addEventListener("mcpproxy:activity-completed",ge),window.addEventListener("mcpproxy:activity-policy",O),window.addEventListener("keydown",we)}),Xe(()=>{window.removeEventListener("mcpproxy:activity",O),window.removeEventListener("mcpproxy:activity-started",O),window.removeEventListener("mcpproxy:activity-completed",ge),window.removeEventListener("mcpproxy:activity-policy",O),window.removeEventListener("keydown",we)}),(s,e)=>{var u,D,k,w,Ce;const d=Ze("router-link");return l(),o("div",dt,[t("div",ut,[e[26]||(e[26]=t("div",null,[t("h1",{class:"text-3xl font-bold"},"Activity Log"),t("p",{class:"text-base-content/70 mt-1"},"Monitor and analyze all activity across your MCP servers")],-1)),t("div",ct,[t("div",vt,[t("label",pt,[e[24]||(e[24]=t("span",{class:"label-text text-sm"},"Auto-refresh",-1)),x(t("input",{type:"checkbox","onUpdate:modelValue":e[0]||(e[0]=a=>m.value=a),class:"toggle toggle-sm toggle-primary"},null,512),[[je,m.value]])])]),t("div",mt,[t("div",{class:g(["badge",ae(z).connected?"badge-success":"badge-error"])},[t("span",{class:g(["w-2 h-2 rounded-full mr-1",ae(z).connected?"bg-success animate-pulse":"bg-error"])},null,2),y(" "+n(ae(z).connected?"Live":"Disconnected"),1)],2)]),m.value?r("",!0):(l(),o("button",{key:0,onClick:q,class:"btn btn-sm btn-ghost",disabled:M.value},[(l(),o("svg",{class:g(["w-4 h-4",{"animate-spin":M.value}]),fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[...e[25]||(e[25]=[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"},null,-1)])],2))],8,bt))])]),_.value?(l(),o("div",gt,[t("div",_t,[e[27]||(e[27]=t("div",{class:"stat-title"},"Total (24h)",-1)),t("div",xt,n(_.value.total_count),1)]),t("div",yt,[e[28]||(e[28]=t("div",{class:"stat-title"},"Success",-1)),t("div",ft,n(_.value.success_count),1)]),t("div",ht,[e[29]||(e[29]=t("div",{class:"stat-title"},"Errors",-1)),t("div",kt,n(_.value.error_count),1)]),t("div",wt,[e[30]||(e[30]=t("div",{class:"stat-title"},"Blocked",-1)),t("div",Ct,n(_.value.blocked_count),1)])])):r("",!0),t("div",St,[t("div",At,[t("div",Tt,[t("div",Dt,[e[33]||(e[33]=t("label",{class:"label py-1"},[t("span",{class:"label-text text-xs"},"Type")],-1)),t("div",Mt,[t("div",$t,[c.value.length===0?(l(),o("span",jt,"All Types")):c.value.length===de.length?(l(),o("span",Lt,"All Types")):(l(),o("span",Et,n(c.value.length)+" selected",1)),e[31]||(e[31]=t("svg",{class:"w-4 h-4 shrink-0",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M19 9l-7 7-7-7"})],-1))]),t("ul",Bt,[t("li",Vt,[e[32]||(e[32]=t("span",null,"Event Types",-1)),c.value.length>0?(l(),o("button",{key:0,onClick:le(Je,["stop"]),class:"btn btn-xs btn-ghost"}," Clear ")):r("",!0)]),(l(),o(B,null,V(de,a=>t("li",{key:a.value},[t("label",Nt,[t("input",{type:"checkbox",checked:c.value.includes(a.value),onChange:R=>ve(a.value),class:"checkbox checkbox-sm"},null,40,Ft),t("span",It,n(a.icon),1),t("span",null,n(a.label),1)])])),64))])])]),t("div",Ut,[e[35]||(e[35]=t("label",{class:"label py-1"},[t("span",{class:"label-text text-xs"},"Server")],-1)),x(t("select",{"onUpdate:modelValue":e[1]||(e[1]=a=>S.value=a),class:"select select-bordered select-sm"},[e[34]||(e[34]=t("option",{value:""},"All Servers",-1)),(l(!0),o(B,null,V(Ne.value,a=>(l(),o("option",{key:a,value:a},n(a),9,zt))),128))],512),[[N,S.value]])]),t("div",Jt,[e[37]||(e[37]=t("label",{class:"label py-1"},[t("span",{class:"label-text text-xs"},"Status")],-1)),x(t("select",{"onUpdate:modelValue":e[2]||(e[2]=a=>A.value=a),class:"select select-bordered select-sm"},[...e[36]||(e[36]=[t("option",{value:""},"All",-1),t("option",{value:"success"},"Success",-1),t("option",{value:"error"},"Error",-1),t("option",{value:"blocked"},"Blocked",-1)])],512),[[N,A.value]])]),t("div",Ot,[e[39]||(e[39]=t("label",{class:"label py-1"},[t("span",{class:"label-text text-xs"},"Auth")],-1)),x(t("select",{"onUpdate:modelValue":e[3]||(e[3]=a=>h.value=a),class:"select select-bordered select-sm"},[...e[38]||(e[38]=[t("option",{value:""},"All",-1),t("option",{value:"admin"},"🔑 Admin",-1),t("option",{value:"agent"},"🤖 Agent",-1)])],512),[[N,h.value]])]),h.value==="agent"?(l(),o("div",Rt,[e[41]||(e[41]=t("label",{class:"label py-1"},[t("span",{class:"label-text text-xs"},"Agent")],-1)),x(t("select",{"onUpdate:modelValue":e[4]||(e[4]=a=>T.value=a),class:"select select-bordered select-sm"},[e[40]||(e[40]=t("option",{value:""},"All Agents",-1)),(l(!0),o(B,null,V(Fe.value,a=>(l(),o("option",{key:a,value:a},n(a),9,Ht))),128))],512),[[N,T.value]])])):r("",!0),t("div",Pt,[e[43]||(e[43]=t("label",{class:"label py-1"},[t("span",{class:"label-text text-xs"},"Sensitive Data")],-1)),x(t("select",{"onUpdate:modelValue":e[5]||(e[5]=a=>f.value=a),class:"select select-bordered select-sm"},[...e[42]||(e[42]=[t("option",{value:""},"All",-1),t("option",{value:"true"},"⚠️ Detected",-1),t("option",{value:"false"},"Clean",-1)])],512),[[N,f.value]])]),f.value==="true"?(l(),o("div",qt,[e[45]||(e[45]=t("label",{class:"label py-1"},[t("span",{class:"label-text text-xs"},"Severity")],-1)),x(t("select",{"onUpdate:modelValue":e[6]||(e[6]=a=>j.value=a),class:"select select-bordered select-sm"},[...e[44]||(e[44]=[Ye('',5)])],512),[[N,j.value]])])):r("",!0),t("div",Kt,[e[47]||(e[47]=t("label",{class:"label py-1"},[t("span",{class:"label-text text-xs"},"Session")],-1)),x(t("select",{"onUpdate:modelValue":e[7]||(e[7]=a=>$.value=a),class:"select select-bordered select-sm"},[e[46]||(e[46]=t("option",{value:""},"All Sessions",-1)),(l(!0),o(B,null,V(ue.value,a=>(l(),o("option",{key:a.id,value:a.id},n(a.label),9,Qt))),128))],512),[[N,$.value]])]),t("div",Gt,[e[48]||(e[48]=t("label",{class:"label py-1"},[t("span",{class:"label-text text-xs"},"From")],-1)),x(t("input",{type:"datetime-local","onUpdate:modelValue":e[8]||(e[8]=a=>L.value=a),class:"input input-bordered input-sm"},null,512),[[Le,L.value]])]),t("div",Wt,[e[49]||(e[49]=t("label",{class:"label py-1"},[t("span",{class:"label-text text-xs"},"To")],-1)),x(t("input",{type:"datetime-local","onUpdate:modelValue":e[9]||(e[9]=a=>E.value=a),class:"input input-bordered input-sm"},null,512),[[Le,E.value]])]),W.value?(l(),o("button",{key:2,onClick:ze,class:"btn btn-sm btn-ghost"}," Clear Filters ")):r("",!0),e[51]||(e[51]=t("div",{class:"flex-1"},null,-1)),t("div",Xt,[e[50]||(e[50]=t("div",{tabindex:"0",role:"button",class:"btn btn-sm btn-outline"},[t("svg",{class:"w-4 h-4 mr-1",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"})]),y(" Export ")],-1)),t("ul",Yt,[t("li",null,[t("a",{onClick:e[10]||(e[10]=a=>be("json"))},"Export as JSON")]),t("li",null,[t("a",{onClick:e[11]||(e[11]=a=>be("csv"))},"Export as CSV")])])])]),W.value?(l(),o("div",Zt,[e[53]||(e[53]=t("span",{class:"text-xs text-base-content/60"},"Active filters:",-1)),(l(!0),o(B,null,V(c.value,a=>(l(),o("span",{key:a,class:"badge badge-sm badge-outline gap-1 cursor-pointer hover:badge-error",onClick:R=>ve(a)},[y(n(ee(a))+" "+n(Z(a))+" ",1),e[52]||(e[52]=t("svg",{class:"w-3 h-3",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1))],8,es))),128)),S.value?(l(),o("span",ts,"Server: "+n(S.value),1)):r("",!0),A.value?(l(),o("span",ss,"Status: "+n(A.value),1)):r("",!0),h.value?(l(),o("span",as,"Auth: "+n(h.value==="admin"?"🔑 Admin":"🤖 Agent"),1)):r("",!0),T.value?(l(),o("span",ls,"Agent: "+n(T.value),1)):r("",!0),f.value?(l(),o("span",os," Sensitive: "+n(f.value==="true"?"⚠️ Detected":"Clean"),1)):r("",!0),j.value?(l(),o("span",ns,"Severity: "+n(j.value),1)):r("",!0),$.value?(l(),o("span",is,"Session: "+n(Ie($.value)),1)):r("",!0),L.value?(l(),o("span",rs,"From: "+n(new Date(L.value).toLocaleString()),1)):r("",!0),E.value?(l(),o("span",ds,"To: "+n(new Date(E.value).toLocaleString()),1)):r("",!0)])):r("",!0)])]),t("div",us,[t("div",cs,[M.value&&b.value.length===0?(l(),o("div",vs,[...e[54]||(e[54]=[t("span",{class:"loading loading-spinner loading-lg"},null,-1)])])):F.value?(l(),o("div",ps,[e[55]||(e[55]=t("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})],-1)),t("span",null,n(F.value),1),t("button",{onClick:q,class:"btn btn-sm btn-ghost"},"Retry")])):ce.value.length===0?(l(),o("div",ms,[e[56]||(e[56]=t("svg",{class:"w-16 h-16 mx-auto mb-4 opacity-30",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"})],-1)),t("p",bs,n(W.value?"No matching activities":"No activity records found"),1),t("p",gs,n(W.value?"Try adjusting your filters":"Activity will appear here as tools are called and actions are taken"),1)])):(l(),o("div",_s,[t("table",xs,[t("thead",null,[t("tr",null,[t("th",{class:"cursor-pointer hover:bg-base-200",onClick:e[12]||(e[12]=a=>K("timestamp"))}," Time "+n(Q("timestamp")),1),t("th",{class:"cursor-pointer hover:bg-base-200",onClick:e[13]||(e[13]=a=>K("type"))}," Type "+n(Q("type")),1),t("th",{class:"cursor-pointer hover:bg-base-200",onClick:e[14]||(e[14]=a=>K("server_name"))}," Server "+n(Q("server_name")),1),e[57]||(e[57]=t("th",null,"Details",-1)),e[58]||(e[58]=t("th",null,"Sensitive",-1)),e[59]||(e[59]=t("th",null,"Intent",-1)),t("th",{class:"cursor-pointer hover:bg-base-200",onClick:e[15]||(e[15]=a=>K("status"))}," Status "+n(Q("status")),1),t("th",{class:"cursor-pointer hover:bg-base-200",onClick:e[16]||(e[16]=a=>K("duration_ms"))}," Duration "+n(Q("duration_ms")),1),e[60]||(e[60]=t("th",null,null,-1))])]),t("tbody",null,[(l(!0),o(B,null,V(Ue.value,a=>{var R,Se,Ae,Te,De,Me,$e;return l(),o("tr",{key:a.id,class:g(["hover cursor-pointer",{"bg-base-200":((R=i.value)==null?void 0:R.id)===a.id}]),onClick:qe=>pe(a)},[t("td",null,[t("div",fs,n(_e(a.timestamp)),1),t("div",hs,n(Oe(a.timestamp)),1)]),t("td",null,[t("div",ks,[t("span",ws,n(ee(a.type)),1),t("span",Cs,n(Z(a.type)),1)])]),t("td",null,[a.server_name?(l(),et(d,{key:0,to:`/servers/${a.server_name}`,class:"link link-hover font-medium",onClick:e[17]||(e[17]=le(()=>{},["stop"]))},{default:Ee(()=>[y(n(a.server_name),1)]),_:2},1032,["to"])):(l(),o("span",Ss,"-"))]),t("td",null,[t("div",As,[a.tool_name?(l(),o("code",Ts,n(a.tool_name),1)):(Se=a.metadata)!=null&&Se.action?(l(),o("span",Ds,n(a.metadata.action),1)):(l(),o("span",Ms,"-"))])]),t("td",null,[a.has_sensitive_data?(l(),o("div",{key:0,class:"tooltip tooltip-top","data-tip":(a.detection_types||[]).join(", ")},[t("span",{class:g(["badge badge-sm gap-1",se(a.max_severity)])},n(te(a.max_severity))+" "+n(((Ae=a.detection_types)==null?void 0:Ae.length)||0),3)],8,$s)):(l(),o("span",js,"-"))]),t("td",null,[(De=(Te=a.metadata)==null?void 0:Te.intent)!=null&&De.operation_type?(l(),o("div",{key:0,class:"tooltip tooltip-top","data-tip":(($e=(Me=a.metadata)==null?void 0:Me.intent)==null?void 0:$e.reason)||"No reason provided"},[t("span",{class:g(["badge badge-sm gap-1",ke(a.metadata.intent.operation_type)])},n(he(a.metadata.intent.operation_type))+" "+n(a.metadata.intent.operation_type),3)],8,Ls)):(l(),o("span",Es,"-"))]),t("td",null,[t("div",{class:g(["badge badge-sm",ye(a.status)])},n(xe(a.status)),3)]),t("td",null,[a.duration_ms!==void 0?(l(),o("span",Bs,n(fe(a.duration_ms)),1)):(l(),o("span",Vs,"-"))]),t("td",null,[t("button",{class:"btn btn-xs btn-ghost",onClick:le(qe=>pe(a),["stop"])},[...e[61]||(e[61]=[t("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M9 5l7 7-7 7"})],-1)])],8,Ns)])],10,ys)}),128))])]),J.value>1?(l(),o("div",Fs,[t("div",Is," Showing "+n((p.value-1)*U.value+1)+"-"+n(Math.min(p.value*U.value,X.value.length))+" of "+n(X.value.length),1),t("div",Us,[t("button",{onClick:e[18]||(e[18]=a=>p.value=1),disabled:p.value===1,class:"join-item btn btn-sm"}," « ",8,zs),t("button",{onClick:e[19]||(e[19]=a=>p.value=Math.max(1,p.value-1)),disabled:p.value===1,class:"join-item btn btn-sm"}," ‹ ",8,Js),t("button",Os,n(p.value)+" / "+n(J.value),1),t("button",{onClick:e[20]||(e[20]=a=>p.value=Math.min(J.value,p.value+1)),disabled:p.value===J.value,class:"join-item btn btn-sm"}," › ",8,Rs),t("button",{onClick:e[21]||(e[21]=a=>p.value=J.value),disabled:p.value===J.value,class:"join-item btn btn-sm"}," » ",8,Hs)]),t("div",Ps,[x(t("select",{"onUpdate:modelValue":e[22]||(e[22]=a=>U.value=a),class:"select select-bordered select-sm"},[...e[62]||(e[62]=[t("option",{value:10},"10 / page",-1),t("option",{value:25},"25 / page",-1),t("option",{value:50},"50 / page",-1),t("option",{value:100},"100 / page",-1)])],512),[[N,U.value,void 0,{number:!0}]])])])):r("",!0)]))])]),t("div",qs,[x(t("input",{id:"activity-detail-drawer",type:"checkbox",class:"drawer-toggle","onUpdate:modelValue":e[23]||(e[23]=a=>I.value=a)},null,512),[[je,I.value]]),t("div",Ks,[e[88]||(e[88]=t("label",{for:"activity-detail-drawer","aria-label":"close sidebar",class:"drawer-overlay"},null,-1)),t("div",Qs,[i.value?(l(),o("div",Gs,[t("div",Ws,[t("div",null,[t("h3",Xs,[t("span",Ys,n(ee(i.value.type)),1),y(" "+n(Z(i.value.type)),1)]),t("p",Zs,n(_e(i.value.timestamp)),1)]),t("button",{onClick:me,class:"btn btn-sm btn-circle btn-ghost"},[...e[63]||(e[63]=[t("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1)])])]),t("div",ea,[e[64]||(e[64]=t("span",{class:"text-sm text-base-content/60"},"Status:",-1)),t("div",{class:g(["badge",ye(i.value.status)])},n(xe(i.value.status)),3)]),t("div",ta,[i.value.id?(l(),o("div",sa,[e[65]||(e[65]=t("span",{class:"text-sm text-base-content/60 w-24 shrink-0"},"ID:",-1)),t("code",aa,n(i.value.id),1)])):r("",!0),i.value.server_name?(l(),o("div",la,[e[66]||(e[66]=t("span",{class:"text-sm text-base-content/60 w-24 shrink-0"},"Server:",-1)),Y(d,{to:`/servers/${i.value.server_name}`,class:"link link-primary text-sm"},{default:Ee(()=>[y(n(i.value.server_name),1)]),_:1},8,["to"])])):r("",!0),i.value.tool_name?(l(),o("div",oa,[e[67]||(e[67]=t("span",{class:"text-sm text-base-content/60 w-24 shrink-0"},"Tool:",-1)),t("code",na,n(i.value.tool_name),1)])):r("",!0),i.value.duration_ms!==void 0?(l(),o("div",ia,[e[68]||(e[68]=t("span",{class:"text-sm text-base-content/60 w-24 shrink-0"},"Duration:",-1)),t("span",ra,n(fe(i.value.duration_ms)),1)])):r("",!0),i.value.session_id?(l(),o("div",da,[e[69]||(e[69]=t("span",{class:"text-sm text-base-content/60 w-24 shrink-0"},"Session:",-1)),t("code",ua,n(i.value.session_id),1)])):r("",!0),i.value.source?(l(),o("div",ca,[e[70]||(e[70]=t("span",{class:"text-sm text-base-content/60 w-24 shrink-0"},"Source:",-1)),t("span",va,n(i.value.source),1)])):r("",!0)]),i.value.has_sensitive_data?(l(),o("div",pa,[t("h4",ma,[t("span",null,n(te(i.value.max_severity)),1),e[71]||(e[71]=y(" Sensitive Data Detected ",-1))]),t("div",{class:g(["alert",i.value.max_severity==="critical"?"alert-error":"alert-warning"])},[t("div",ba,[t("div",ga,[e[72]||(e[72]=t("span",{class:"font-semibold"},"Severity:",-1)),t("span",{class:g(["badge",se(i.value.max_severity)])},n(te(i.value.max_severity))+" "+n(i.value.max_severity||"unknown"),3)]),i.value.detection_types&&i.value.detection_types.length>0?(l(),o("div",_a,[e[73]||(e[73]=t("span",{class:"font-semibold"},"Detection Types:",-1)),t("div",xa,[(l(!0),o(B,null,V(i.value.detection_types,a=>(l(),o("span",{key:a,class:"badge badge-sm bg-base-100/20 border-current text-inherit"},n(a),1))),128))])])):r("",!0),(u=i.value.metadata)!=null&&u.sensitive_data_detection?(l(),o("div",ya,[e[74]||(e[74]=t("span",{class:"font-semibold"},"Detections:",-1)),t("div",fa,[(l(!0),o(B,null,V(i.value.metadata.sensitive_data_detection.detections||[],(a,R)=>(l(),o("div",{key:R,class:"flex items-center gap-2 bg-base-100/20 rounded px-2 py-1"},[t("span",{class:g(["badge badge-xs",se(a.severity)])},n(a.severity),3),t("span",ha,n(a.type),1),t("span",ka,"in "+n(a.location),1),a.is_likely_example?(l(),o("span",wa,"example")):r("",!0)]))),128))])])):r("",!0)])],2)])):r("",!0),i.value.type==="policy_decision"||i.value.status==="blocked"?(l(),o("div",Ca,[e[78]||(e[78]=t("h4",{class:"font-semibold mb-2 text-warning flex items-center gap-2"},[t("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"})]),y(" Policy Decision ")],-1)),t("div",Sa,[t("div",Aa,[t("div",Ta,[e[75]||(e[75]=t("span",{class:"font-semibold"},"Decision:",-1)),t("span",Da,n(((D=i.value.metadata)==null?void 0:D.decision)||i.value.status||"Blocked"),1)]),(k=i.value.metadata)!=null&&k.reason?(l(),o("div",Ma,[e[76]||(e[76]=t("span",{class:"font-semibold"},"Reason:",-1)),t("span",$a,n(i.value.metadata.reason),1)])):(w=i.value.metadata)!=null&&w.policy_rule?(l(),o("div",ja,[e[77]||(e[77]=t("span",{class:"font-semibold"},"Policy Rule:",-1)),t("span",La,n(i.value.metadata.policy_rule),1)])):(l(),o("div",Ea," Tool call was blocked by security policy "))])])])):r("",!0),i.value.arguments&&Object.keys(i.value.arguments).length>0?(l(),o("div",Ba,[e[79]||(e[79]=t("h4",{class:"font-semibold mb-2 flex items-center gap-2"},[y(" Request Arguments "),t("span",{class:"badge badge-sm badge-info"},"JSON")],-1)),Y(ne,{data:i.value.arguments,"max-height":"12rem"},null,8,["data"])])):r("",!0),i.value.response?(l(),o("div",Va,[t("h4",Na,[e[80]||(e[80]=y(" Response Body ",-1)),e[81]||(e[81]=t("span",{class:"badge badge-sm badge-info"},"JSON",-1)),i.value.response_truncated?(l(),o("span",Fa,"Truncated")):r("",!0)]),Y(ne,{data:Re(i.value.response),"max-height":"16rem"},null,8,["data"])])):r("",!0),i.value.error_message?(l(),o("div",Ia,[e[82]||(e[82]=t("h4",{class:"font-semibold mb-2 text-error"},"Error Message",-1)),t("div",Ua,[t("span",za,n(i.value.error_message),1)])])):r("",!0),(Ce=i.value.metadata)!=null&&Ce.intent?(l(),o("div",Ja,[e[86]||(e[86]=t("h4",{class:"font-semibold mb-2"},"Intent Declaration",-1)),t("div",Oa,[i.value.metadata.intent.operation_type?(l(),o("div",Ra,[e[83]||(e[83]=t("span",{class:"text-sm text-base-content/60"},"Operation:",-1)),t("span",{class:g(["badge badge-sm",ke(i.value.metadata.intent.operation_type)])},n(he(i.value.metadata.intent.operation_type))+" "+n(i.value.metadata.intent.operation_type),3)])):r("",!0),i.value.metadata.intent.data_sensitivity?(l(),o("div",Ha,[e[84]||(e[84]=t("span",{class:"text-sm text-base-content/60"},"Sensitivity:",-1)),t("span",Pa,n(i.value.metadata.intent.data_sensitivity),1)])):r("",!0),i.value.metadata.intent.reason?(l(),o("div",qa,[e[85]||(e[85]=t("span",{class:"text-sm text-base-content/60"},"Reason:",-1)),t("span",Ka,n(i.value.metadata.intent.reason),1)])):r("",!0)])])):r("",!0),He(i.value)?(l(),o("div",Qa,[e[87]||(e[87]=t("h4",{class:"font-semibold mb-2 flex items-center gap-2"},[y(" Additional Details "),t("span",{class:"badge badge-sm badge-ghost"},"JSON")],-1)),Y(ne,{data:Pe(i.value),"max-height":"12rem"},null,8,["data"])])):r("",!0)])):r("",!0)])])])])}}});export{Wa as default};
diff --git a/web/frontend/dist/assets/Activity-7YqHqUto.css b/web/frontend/dist/assets/Activity-7YqHqUto.css
new file mode 100644
index 00000000..923e101f
--- /dev/null
+++ b/web/frontend/dist/assets/Activity-7YqHqUto.css
@@ -0,0 +1 @@
+.json-viewer-container[data-v-919a3d3e]{width:100%}.json-viewer[data-v-919a3d3e]{white-space:pre-wrap;word-wrap:break-word;overflow-wrap:anywhere;line-height:1.5;font-family:Monaco,Menlo,Ubuntu Mono,Consolas,source-code-pro,monospace}.json-viewer[data-v-919a3d3e]::-webkit-scrollbar{width:8px;height:8px}.json-viewer[data-v-919a3d3e]::-webkit-scrollbar-track{border-radius:.25rem;--tw-bg-opacity: 1;background-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity, 1)))}.json-viewer[data-v-919a3d3e]::-webkit-scrollbar-thumb{border-radius:.25rem;background-color:var(--fallback-bc,oklch(var(--bc)/.2))}.json-viewer[data-v-919a3d3e]::-webkit-scrollbar-thumb:hover{background-color:var(--fallback-bc,oklch(var(--bc)/.3))}
diff --git a/web/frontend/dist/assets/AdminDashboard-_SN62PSb.js b/web/frontend/dist/assets/AdminDashboard-_SN62PSb.js
new file mode 100644
index 00000000..7938effb
--- /dev/null
+++ b/web/frontend/dist/assets/AdminDashboard-_SN62PSb.js
@@ -0,0 +1 @@
+import{d as C,r as _,K as j,f as S,x as U,I as A,c as l,o as n,a as s,g as m,j as b,n as f,t as a,i as k,w,k as B,F as y,p as M}from"./index-DMP0eryo.js";const T={class:"space-y-6 max-w-6xl mx-auto"},V={class:"flex justify-between items-center"},N=["disabled"],R={class:"stats shadow bg-base-100 w-full"},D={class:"stat"},H={class:"stat-value text-primary"},$={class:"stat-desc"},z={class:"stat"},I={class:"stat-value text-secondary"},E={class:"stat"},F={class:"stat-value text-accent"},L={class:"stat-desc"},J={class:"stat"},K={class:"stat-value text-info"},P={class:"stat-desc"},q={key:0,class:"flex justify-center py-12"},G={key:1,class:"alert alert-error"},O={key:2,class:"grid grid-cols-1 lg:grid-cols-2 gap-6"},Q={class:"card bg-base-100 shadow-sm"},W={class:"card-body"},X={class:"flex items-center justify-between mb-3"},Y={key:0,class:"text-center py-4 text-base-content/60 text-sm"},Z={key:1,class:"space-y-2"},ss={class:"font-medium text-sm"},ts={class:"text-xs text-base-content/50"},es={class:"flex items-center gap-2"},as={class:"text-xs text-base-content/50"},os={class:"card bg-base-100 shadow-sm"},ls={class:"card-body"},ns={class:"flex items-center justify-between mb-3"},rs={key:0,class:"text-center py-4 text-base-content/60 text-sm"},is={key:1,class:"space-y-2"},ds={class:"text-sm"},cs={class:"text-xs"},vs={key:0,class:"text-base-content/50 ml-1"},us={class:"text-xs text-base-content/50"},hs={class:"flex items-center gap-2"},_s={class:"text-xs text-base-content/50"},bs=C({__name:"AdminDashboard",setup(ms){const i=_(!1),d=_(""),c=_([]),v=_([]);let u=null;const o=j({totalUsers:0,activeUsers:0,activeSessions:0,totalServers:0,healthyServers:0,toolCalls24h:0,errorRate24h:0}),g=S(()=>o.totalUsers>0||c.value.length>0||v.value.length>0);function p(r){const t=Date.now(),h=new Date(r).getTime(),e=t-h;return e<1e3?"Just now":e<6e4?`${Math.floor(e/1e3)}s ago`:e<36e5?`${Math.floor(e/6e4)}m ago`:e<864e5?`${Math.floor(e/36e5)}h ago`:`${Math.floor(e/864e5)}d ago`}async function x(){i.value=!0,d.value="";try{const r=await fetch("/api/v1/admin/dashboard",{credentials:"include"});if(!r.ok)throw new Error(`HTTP ${r.status}: ${r.statusText}`);const t=await r.json();o.totalUsers=t.total_users||0,o.activeUsers=t.active_users||0,o.activeSessions=t.active_sessions||0,o.totalServers=t.total_servers||0,o.healthyServers=t.healthy_servers||0,o.toolCalls24h=t.tool_calls_24h||0,o.errorRate24h=t.error_rate_24h||0,c.value=t.recent_users||[],v.value=t.recent_activity||[]}catch(r){d.value=r instanceof Error?r.message:"Failed to load dashboard data"}finally{i.value=!1}}return U(()=>{x(),u=setInterval(x,3e4)}),A(()=>{u&&(clearInterval(u),u=null)}),(r,t)=>{const h=B("router-link");return n(),l("div",T,[s("div",V,[t[2]||(t[2]=s("div",null,[s("h1",{class:"text-2xl font-bold"},"Admin Dashboard"),s("p",{class:"text-base-content/70 mt-1"},"Server overview and system health")],-1)),s("button",{onClick:x,class:"btn btn-sm btn-ghost",disabled:i.value},[(n(),l("svg",{class:f(["w-4 h-4",{"animate-spin":i.value}]),fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[...t[0]||(t[0]=[s("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"},null,-1)])],2)),t[1]||(t[1]=b(" Refresh ",-1))],8,N)]),s("div",R,[s("div",D,[t[3]||(t[3]=s("div",{class:"stat-figure text-primary"},[s("svg",{class:"w-8 h-8",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[s("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"})])],-1)),t[4]||(t[4]=s("div",{class:"stat-title"},"Total Users",-1)),s("div",H,a(o.totalUsers),1),s("div",$,a(o.activeUsers)+" active",1)]),s("div",z,[t[5]||(t[5]=s("div",{class:"stat-figure text-secondary"},[s("svg",{class:"w-8 h-8",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[s("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M13 10V3L4 14h7v7l9-11h-7z"})])],-1)),t[6]||(t[6]=s("div",{class:"stat-title"},"Active Sessions",-1)),s("div",I,a(o.activeSessions),1),t[7]||(t[7]=s("div",{class:"stat-desc"},"Current connections",-1))]),s("div",E,[t[8]||(t[8]=s("div",{class:"stat-figure text-accent"},[s("svg",{class:"w-8 h-8",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[s("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2"})])],-1)),t[9]||(t[9]=s("div",{class:"stat-title"},"Total Servers",-1)),s("div",F,a(o.totalServers),1),s("div",L,a(o.healthyServers)+" healthy",1)]),s("div",J,[t[10]||(t[10]=s("div",{class:"stat-figure text-info"},[s("svg",{class:"w-8 h-8",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[s("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"})])],-1)),t[11]||(t[11]=s("div",{class:"stat-title"},"Tool Calls (24h)",-1)),s("div",K,a(o.toolCalls24h),1),s("div",P,a(o.errorRate24h)+"% error rate",1)])]),i.value&&!g.value?(n(),l("div",q,[...t[12]||(t[12]=[s("span",{class:"loading loading-spinner loading-lg"},null,-1)])])):m("",!0),d.value?(n(),l("div",G,[t[13]||(t[13]=s("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[s("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})],-1)),s("span",null,a(d.value),1)])):m("",!0),g.value?(n(),l("div",O,[s("div",Q,[s("div",W,[s("div",X,[t[15]||(t[15]=s("h2",{class:"card-title text-lg"},"Recent Users",-1)),k(h,{to:"/admin/users",class:"btn btn-xs btn-ghost"},{default:w(()=>[...t[14]||(t[14]=[b("View All",-1)])]),_:1})]),c.value.length===0?(n(),l("div",Y," No users yet ")):(n(),l("div",Z,[(n(!0),l(y,null,M(c.value,e=>(n(),l("div",{key:e.id,class:"flex items-center justify-between py-2 border-b border-base-200 last:border-0"},[s("div",null,[s("div",ss,a(e.display_name||e.email),1),s("div",ts,a(e.email),1)]),s("div",es,[s("span",{class:f(["badge badge-xs",e.role==="admin"?"badge-primary":"badge-ghost"])},a(e.role),3),s("span",as,a(e.last_login_at?p(e.last_login_at):"Never"),1)])]))),128))]))])]),s("div",os,[s("div",ls,[s("div",ns,[t[17]||(t[17]=s("h2",{class:"card-title text-lg"},"Recent Activity",-1)),k(h,{to:"/activity",class:"btn btn-xs btn-ghost"},{default:w(()=>[...t[16]||(t[16]=[b("View All",-1)])]),_:1})]),v.value.length===0?(n(),l("div",rs," No recent activity ")):(n(),l("div",is,[(n(!0),l(y,null,M(v.value,e=>(n(),l("div",{key:e.id,class:"flex items-center justify-between py-2 border-b border-base-200 last:border-0"},[s("div",null,[s("div",ds,[s("code",cs,a(e.tool_name||e.type),1),e.server_name?(n(),l("span",vs,"on "+a(e.server_name),1)):m("",!0)]),s("div",us,a(e.user_email||"system"),1)]),s("div",hs,[s("span",{class:f(["badge badge-xs",e.status==="success"?"badge-success":e.status==="error"?"badge-error":"badge-ghost"])},a(e.status),3),s("span",_s,a(p(e.timestamp)),1)])]))),128))]))])])])):m("",!0)])}}});export{bs as default};
diff --git a/web/frontend/dist/assets/AdminServers-BS1vVmqX.js b/web/frontend/dist/assets/AdminServers-BS1vVmqX.js
new file mode 100644
index 00000000..e5db215a
--- /dev/null
+++ b/web/frontend/dist/assets/AdminServers-BS1vVmqX.js
@@ -0,0 +1 @@
+import{d as L,r as c,f as m,x as U,c as o,o as r,a as t,g as w,t as n,F as y,m as x,v as R,y as _,p as q,n as k,L as B,N}from"./index-DMP0eryo.js";const V={class:"p-4 max-w-7xl mx-auto"},H={class:"grid grid-cols-4 gap-3 mb-6"},I={class:"stat bg-base-100 rounded-lg shadow-sm p-3"},O={class:"stat-value text-lg"},z={class:"stat bg-base-100 rounded-lg shadow-sm p-3"},J={class:"stat-value text-lg text-success"},Q={class:"stat bg-base-100 rounded-lg shadow-sm p-3"},G={class:"stat-value text-lg text-info"},K={class:"stat bg-base-100 rounded-lg shadow-sm p-3"},W={class:"stat-value text-lg text-base-content/40"},X={key:0,class:"flex justify-center py-8"},Y={class:"flex gap-2 mb-4"},Z={key:0,class:"text-base-content/50 py-8 text-center"},ee={key:1,class:"overflow-x-auto"},te={class:"table table-sm w-full"},se=["onClick"],ae={class:"font-medium"},ne={class:"badge badge-ghost badge-xs"},le={class:"text-xs text-base-content/50 truncate max-w-xs"},oe={key:0,class:"badge badge-info badge-xs"},re={key:1,class:"badge badge-ghost badge-xs"},de={class:"dropdown dropdown-end"},ie={tabindex:"0",class:"dropdown-content z-[1] menu p-1 shadow-lg bg-base-100 rounded-lg w-48 border border-base-300"},ue=["onClick"],ce=["onClick"],ve={class:"border-t border-base-200 mt-1 pt-1"},be=["onClick"],he={key:2,class:"alert alert-error mt-4"},ge={key:3,class:"toast toast-end toast-bottom"},me={class:"alert alert-success"},xe=L({__name:"AdminServers",setup(pe){const C=N(),p=c(!0),l=c(""),d=c(""),i=c([]),v=c(""),b=c(""),h=c(""),S=m(()=>i.value.filter(s=>s.enabled&&s.connected).length),$=m(()=>i.value.filter(s=>s.shared).length),T=m(()=>i.value.filter(s=>!s.enabled).length),E=m(()=>{let s=i.value;if(v.value){const e=v.value.toLowerCase();s=s.filter(a=>a.name.toLowerCase().includes(e)||a.url&&a.url.toLowerCase().includes(e)||a.command&&a.command.toLowerCase().includes(e)||a.protocol.toLowerCase().includes(e))}return b.value==="enabled"?s=s.filter(e=>e.enabled):b.value==="disabled"&&(s=s.filter(e=>!e.enabled)),h.value==="shared"?s=s.filter(e=>e.shared):h.value==="private"&&(s=s.filter(e=>!e.shared)),s});function M(s){if(s.quarantined)return"badge-error";if(!s.enabled)return"badge-ghost";if(s.health)switch(s.health.level){case"healthy":return"badge-success";case"degraded":return"badge-warning";case"unhealthy":return"badge-error"}return s.connected?"badge-success":"badge-warning"}function P(s){return s.quarantined?"quarantined":s.enabled?s.health?s.health.level:s.connected?"connected":"disconnected":"disabled"}function A(s){C.push("/servers/"+encodeURIComponent(s.name))}async function g(){p.value=!0,l.value="";try{const s=await fetch("/api/v1/admin/servers",{credentials:"include"});if(!s.ok)throw new Error(`HTTP ${s.status}: ${s.statusText}`);const e=await s.json();Array.isArray(e)?i.value=e:e&&Array.isArray(e.servers)?i.value=e.servers:i.value=[]}catch(s){l.value=s instanceof Error?s.message:"Failed to load servers"}finally{p.value=!1}}async function F(s){l.value="",d.value="";try{const e=s.enabled?"disable":"enable",a=await fetch(`/api/v1/admin/servers/${encodeURIComponent(s.name)}/${e}`,{method:"POST",credentials:"include"});if(!a.ok){const u=await a.json().catch(()=>({}));throw new Error(u.message||u.error||`HTTP ${a.status}`)}d.value=`Server "${s.name}" ${s.enabled?"disabled":"enabled"}.`,await g(),f()}catch(e){l.value=e instanceof Error?e.message:"Failed to update server"}}async function j(s){if(s.enabled){l.value="",d.value="";try{const e=await fetch(`/api/v1/admin/servers/${encodeURIComponent(s.name)}/restart`,{method:"POST",credentials:"include"});if(!e.ok){const a=await e.json().catch(()=>({}));throw new Error(a.message||a.error||`HTTP ${e.status}`)}d.value=`Server "${s.name}" restarted.`,await g(),f()}catch(e){l.value=e instanceof Error?e.message:"Failed to restart server"}}}async function D(s){l.value="",d.value="";try{const e=!s.shared,a=await fetch(`/api/v1/admin/servers/${encodeURIComponent(s.name)}/shared`,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({shared:e})});if(!a.ok){const u=await a.json().catch(()=>({}));throw new Error(u.message||u.error||`HTTP ${a.status}`)}d.value=`Server "${s.name}" is now ${e?"shared with all users":"private"}.`,await g(),f()}catch(e){l.value=e instanceof Error?e.message:"Failed to update server"}}function f(){setTimeout(()=>{d.value=""},3e3)}return U(()=>{g()}),(s,e)=>(r(),o("div",V,[e[14]||(e[14]=t("div",{class:"flex justify-between items-center mb-6"},[t("div",null,[t("h1",{class:"text-2xl font-bold"},"Server Management"),t("p",{class:"text-sm text-base-content/60 mt-1"},"Manage upstream MCP servers. Shared servers are available to all users.")])],-1)),t("div",H,[t("div",I,[e[5]||(e[5]=t("div",{class:"stat-title text-xs"},"Total",-1)),t("div",O,n(i.value.length),1)]),t("div",z,[e[6]||(e[6]=t("div",{class:"stat-title text-xs"},"Connected",-1)),t("div",J,n(S.value),1)]),t("div",Q,[e[7]||(e[7]=t("div",{class:"stat-title text-xs"},"Shared",-1)),t("div",G,n($.value),1)]),t("div",K,[e[8]||(e[8]=t("div",{class:"stat-title text-xs"},"Disabled",-1)),t("div",W,n(T.value),1)])]),p.value?(r(),o("div",X,[...e[9]||(e[9]=[t("span",{class:"loading loading-spinner loading-lg"},null,-1)])])):(r(),o(y,{key:1},[t("div",Y,[x(t("input",{"onUpdate:modelValue":e[0]||(e[0]=a=>v.value=a),type:"text",placeholder:"Filter servers...",class:"input input-bordered input-sm flex-1"},null,512),[[R,v.value]]),x(t("select",{"onUpdate:modelValue":e[1]||(e[1]=a=>b.value=a),class:"select select-bordered select-sm"},[...e[10]||(e[10]=[t("option",{value:""},"All Status",-1),t("option",{value:"enabled"},"Enabled",-1),t("option",{value:"disabled"},"Disabled",-1)])],512),[[_,b.value]]),x(t("select",{"onUpdate:modelValue":e[2]||(e[2]=a=>h.value=a),class:"select select-bordered select-sm"},[...e[11]||(e[11]=[t("option",{value:""},"All",-1),t("option",{value:"shared"},"Shared",-1),t("option",{value:"private"},"Private",-1)])],512),[[_,h.value]])]),i.value.length===0?(r(),o("div",Z," No servers configured. Add servers in the configuration file. ")):(r(),o("div",ee,[t("table",te,[e[13]||(e[13]=t("thead",null,[t("tr",{class:"text-xs uppercase text-base-content/50"},[t("th",null,"Server"),t("th",null,"Protocol"),t("th",null,"Endpoint"),t("th",null,"Status"),t("th",null,"Sharing"),t("th",{class:"text-right"},"Actions")])],-1)),t("tbody",null,[(r(!0),o(y,null,q(E.value,a=>(r(),o("tr",{key:a.name,class:"hover:bg-base-200/50 cursor-pointer transition-colors",onClick:u=>A(a)},[t("td",ae,n(a.name),1),t("td",null,[t("span",ne,n(a.protocol),1)]),t("td",le,n(a.url||a.command||"—"),1),t("td",null,[t("span",{class:k(["badge badge-xs",M(a)])},n(P(a)),3)]),t("td",null,[a.shared?(r(),o("span",oe,"shared")):(r(),o("span",re,"private"))]),t("td",{class:"text-right",onClick:e[3]||(e[3]=B(()=>{},["stop"]))},[t("div",de,[e[12]||(e[12]=t("label",{tabindex:"0",class:"btn btn-ghost btn-xs btn-square"},[t("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 5v.01M12 12v.01M12 19v.01"})])],-1)),t("ul",ie,[t("li",null,[t("a",{onClick:u=>F(a)},n(a.enabled?"Disable":"Enable"),9,ue)]),t("li",null,[t("a",{onClick:u=>j(a),class:k({"opacity-50":!a.enabled})}," Restart ",10,ce)]),t("li",ve,[t("a",{onClick:u=>D(a)},n(a.shared?"Make Private":"Share with Users"),9,be)])])])])],8,se))),128))])])]))],64)),l.value?(r(),o("div",he,[t("span",null,n(l.value),1),t("button",{class:"btn btn-ghost btn-xs",onClick:e[4]||(e[4]=a=>l.value="")},"Dismiss")])):w("",!0),d.value?(r(),o("div",ge,[t("div",me,[t("span",null,n(d.value),1)])])):w("",!0)]))}});export{xe as default};
diff --git a/web/frontend/dist/assets/AdminUsers-LpYNmcL8.js b/web/frontend/dist/assets/AdminUsers-LpYNmcL8.js
new file mode 100644
index 00000000..37486feb
--- /dev/null
+++ b/web/frontend/dist/assets/AdminUsers-LpYNmcL8.js
@@ -0,0 +1 @@
+import{d as T,r as u,f as g,x as $,c as l,o as n,a as t,g as f,j as p,n as x,t as o,m as M,v as U,F as A,p as D,i as E,w as j,k as N}from"./index-DMP0eryo.js";const L={class:"space-y-6 max-w-6xl mx-auto"},V={class:"flex justify-between items-center"},B=["disabled"],S={class:"stats shadow bg-base-100 w-full"},F={class:"stat"},H={class:"stat-value"},P={class:"stat"},R={class:"stat-value text-success"},q={class:"stat"},z={class:"stat-value text-base-content/40"},I={key:0,class:"flex justify-center py-12"},J={key:1,class:"alert alert-error"},O={key:2,class:"text-center py-12 text-base-content/60"},Q={key:3,class:"card bg-base-100 shadow-sm"},G={class:"p-4 border-b border-base-300"},K={class:"overflow-x-auto"},W={class:"table"},X={class:"font-medium"},Y={class:"text-sm text-base-content/60"},Z={class:"badge badge-sm badge-outline"},tt=["title"],et={key:1,class:"text-sm text-base-content/40"},st={class:"flex gap-2"},at=["onClick","disabled","title"],lt={key:0,class:"loading loading-spinner loading-xs"},nt={key:0,class:"p-8 text-center text-base-content/60"},ot={key:4,class:"alert alert-error"},ut=T({__name:"AdminUsers",setup(it){const c=u(!1),b=u(""),v=u(""),i=u([]),r=u(""),m=u(""),y=g(()=>i.value.filter(a=>!a.disabled).length),w=g(()=>i.value.filter(a=>a.disabled).length),_=g(()=>{if(!r.value)return i.value;const a=r.value.toLowerCase();return i.value.filter(e=>e.email.toLowerCase().includes(a)||e.display_name&&e.display_name.toLowerCase().includes(a))});function k(a){const e=Date.now(),d=new Date(a).getTime(),s=e-d;return s<1e3?"Just now":s<6e4?`${Math.floor(s/1e3)}s ago`:s<36e5?`${Math.floor(s/6e4)}m ago`:s<864e5?`${Math.floor(s/36e5)}h ago`:`${Math.floor(s/864e5)}d ago`}async function h(){c.value=!0,b.value="";try{const a=await fetch("/api/v1/admin/users",{credentials:"include"});if(!a.ok)throw new Error(`HTTP ${a.status}: ${a.statusText}`);const e=await a.json();i.value=Array.isArray(e)?e:[]}catch(a){b.value=a instanceof Error?a.message:"Failed to load users"}finally{c.value=!1}}async function C(a){m.value=a.id,v.value="";try{const e=a.disabled?"enable":"disable",d=await fetch(`/api/v1/admin/users/${encodeURIComponent(a.id)}/${e}`,{method:"POST",credentials:"include"});if(!d.ok){const s=await d.json().catch(()=>({}));throw new Error(s.error||`HTTP ${d.status}`)}await h()}catch(e){v.value=e instanceof Error?e.message:"Failed to update user"}finally{m.value=""}}return $(()=>{h()}),(a,e)=>{const d=N("router-link");return n(),l("div",L,[t("div",V,[e[4]||(e[4]=t("div",null,[t("h1",{class:"text-2xl font-bold"},"Users"),t("p",{class:"text-base-content/70 mt-1"},"Manage users and their access")],-1)),t("button",{onClick:h,class:"btn btn-sm btn-ghost",disabled:c.value},[(n(),l("svg",{class:x(["w-4 h-4",{"animate-spin":c.value}]),fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[...e[2]||(e[2]=[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"},null,-1)])],2)),e[3]||(e[3]=p(" Refresh ",-1))],8,B)]),t("div",S,[t("div",F,[e[5]||(e[5]=t("div",{class:"stat-title"},"Total Users",-1)),t("div",H,o(i.value.length),1)]),t("div",P,[e[6]||(e[6]=t("div",{class:"stat-title"},"Active",-1)),t("div",R,o(y.value),1)]),t("div",q,[e[7]||(e[7]=t("div",{class:"stat-title"},"Disabled",-1)),t("div",z,o(w.value),1)])]),c.value&&i.value.length===0?(n(),l("div",I,[...e[8]||(e[8]=[t("span",{class:"loading loading-spinner loading-lg"},null,-1)])])):b.value?(n(),l("div",J,[e[9]||(e[9]=t("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})],-1)),t("span",null,o(b.value),1),t("button",{class:"btn btn-sm",onClick:h},"Try Again")])):i.value.length===0?(n(),l("div",O,[...e[10]||(e[10]=[t("p",{class:"text-lg font-medium"},"No users found",-1)])])):(n(),l("div",Q,[t("div",G,[M(t("input",{"onUpdate:modelValue":e[0]||(e[0]=s=>r.value=s),type:"text",placeholder:"Search by email or name...",class:"input input-bordered input-sm w-full max-w-xs"},null,512),[[U,r.value]])]),t("div",K,[t("table",W,[e[12]||(e[12]=t("thead",null,[t("tr",null,[t("th",null,"User"),t("th",null,"Provider"),t("th",null,"Last Login"),t("th",null,"Status"),t("th",null,"Actions")])],-1)),t("tbody",null,[(n(!0),l(A,null,D(_.value,s=>(n(),l("tr",{key:s.id,class:"hover"},[t("td",null,[t("div",null,[t("div",X,o(s.display_name||"-"),1),t("div",Y,o(s.email),1)])]),t("td",null,[t("span",Z,o(s.provider),1)]),t("td",null,[s.last_login_at?(n(),l("span",{key:0,class:"text-sm",title:s.last_login_at},o(k(s.last_login_at)),9,tt)):(n(),l("span",et,"Never"))]),t("td",null,[t("span",{class:x(["badge badge-sm",s.disabled?"badge-error":"badge-success"])},o(s.disabled?"Disabled":"Active"),3)]),t("td",null,[t("div",st,[t("button",{class:"btn btn-ghost btn-xs",onClick:dt=>C(s),disabled:m.value===s.id,title:s.disabled?"Enable user":"Disable user"},[m.value===s.id?(n(),l("span",lt)):f("",!0),p(" "+o(s.disabled?"Enable":"Disable"),1)],8,at),E(d,{to:{path:"/activity",query:{user_id:s.id}},class:"btn btn-ghost btn-xs",title:"View user's activity"},{default:j(()=>[...e[11]||(e[11]=[p(" Activity ",-1)])]),_:1},8,["to"])])])]))),128))])])]),_.value.length===0&&r.value?(n(),l("div",nt,' No users match "'+o(r.value)+'" ',1)):f("",!0)])),v.value?(n(),l("div",ot,[t("span",null,o(v.value),1),t("button",{class:"btn btn-ghost btn-xs",onClick:e[1]||(e[1]=s=>v.value="")},"Dismiss")])):f("",!0)])}}});export{ut as default};
diff --git a/web/frontend/dist/assets/AgentTokens-Cr2jVCc5.js b/web/frontend/dist/assets/AgentTokens-Cr2jVCc5.js
new file mode 100644
index 00000000..8ee7d7d9
--- /dev/null
+++ b/web/frontend/dist/assets/AgentTokens-Cr2jVCc5.js
@@ -0,0 +1 @@
+import{d as Y,e as Z,b as G,r as v,f as M,x as J,c as a,o,a as e,g as k,l as C,j as g,t as r,F as T,p as S,n as A,m as y,v as K,M as B,y as Q,z as F}from"./index-DMP0eryo.js";const X={class:"space-y-6"},ee={class:"flex justify-between items-center"},se={class:"flex gap-2"},te=["disabled"],le={key:0,class:"loading loading-spinner loading-sm"},ae={class:"stats shadow bg-base-100 w-full"},oe={class:"stat"},ne={class:"stat-value"},re={class:"stat"},ie={class:"stat-value text-success"},de={class:"stat"},ue={class:"stat-value text-warning"},ce={key:0,class:"text-center py-12"},ve={key:1,class:"alert alert-error"},me={class:"text-sm"},pe={key:2,class:"text-center py-12"},be={key:3,class:"overflow-x-auto"},ke={class:"table table-zebra w-full"},ge={class:"font-medium"},fe={class:"text-sm bg-base-200 px-2 py-1 rounded"},xe={class:"flex flex-wrap gap-1"},he={class:"flex flex-wrap gap-1"},ye={key:0,class:"text-sm"},we={key:1,class:"text-base-content/40 text-sm"},_e={key:0,class:"badge badge-error badge-sm"},Ce={key:1,class:"badge badge-warning badge-sm"},Te={key:2,class:"badge badge-success badge-sm"},Se={class:"flex gap-1"},Ae=["onClick","disabled"],Fe=["onClick","disabled"],De={key:4,class:"alert alert-warning shadow-lg"},Re={class:"flex-1"},Me={class:"flex items-center gap-2"},Be={class:"text-sm bg-neutral text-neutral-content px-3 py-2 rounded font-mono break-all"},je={class:"modal-box"},Le={class:"space-y-4"},Ee={class:"form-control"},He={key:0,class:"label"},Ie={class:"label-text-alt text-error"},Ne={key:1,class:"label"},Ve={class:"form-control"},$e={class:"flex items-center gap-2 cursor-pointer mb-2 px-1"},ze=["checked"],Ue={key:0,class:"border border-base-300 rounded-lg p-3 max-h-48 overflow-y-auto space-y-1"},Pe={key:0,class:"text-sm text-base-content/50 py-2 text-center"},We=["value"],Oe={class:"text-sm"},qe={key:0,class:"badge badge-success badge-xs ml-auto"},Ye={key:1,class:"badge badge-ghost badge-xs ml-auto"},Ze={key:1,class:"label"},Ge={class:"label-text-alt text-error"},Je={class:"form-control"},Ke={class:"flex flex-col gap-2"},Qe={class:"flex items-center gap-2 cursor-pointer"},Xe={class:"flex items-center gap-2 cursor-pointer"},es={class:"form-control"},ss={class:"modal-action"},ts=["disabled"],ls={key:0,class:"loading loading-spinner loading-sm"},ns=Y({__name:"AgentTokens",setup(as){const u=Z(),D=G(),p=v(!0),f=v(null),b=v([]),x=v(!1),m=v(null),c=v(!1),R=v(null),n=v({name:"",allServers:!0,selectedServers:[],permWrite:!1,permDestructive:!1,expiresIn:"720h"}),d=v({}),j=M(()=>D.servers.map(t=>({name:t.name,connected:t.enabled&&t.tool_count>0})).sort((t,s)=>t.name.localeCompare(s.name)));function I(t){const s=t.target.checked;n.value.allServers=s,s&&(n.value.selectedServers=[])}const N=M(()=>b.value.filter(t=>!t.revoked&&!h(t)).length),V=M(()=>b.value.filter(t=>t.revoked||h(t)).length);function h(t){return new Date(t.expires_at){c.value=!1},2e3)}catch{const t=document.createElement("textarea");t.value=m.value,document.body.appendChild(t),t.select(),document.execCommand("copy"),document.body.removeChild(t),c.value=!0,setTimeout(()=>{c.value=!1},2e3)}}function q(){m.value=null,c.value=!1}return J(async()=>{await new Promise(t=>setTimeout(t,100)),w()}),(t,s)=>(o(),a("div",X,[e("div",ee,[s[9]||(s[9]=e("div",null,[e("h1",{class:"text-3xl font-bold"},"Agent Tokens"),e("p",{class:"text-base-content/70 mt-1"},"Create and manage scoped API tokens for AI agents and automation")],-1)),e("div",se,[e("button",{onClick:s[0]||(s[0]=(...l)=>C(_)&&C(_)(...l)),disabled:p.value,class:"btn btn-outline"},[s[7]||(s[7]=e("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"})],-1)),p.value?(o(),a("span",le)):k("",!0),g(" "+r(p.value?"Refreshing...":"Refresh"),1)],8,te),e("button",{onClick:E,class:"btn btn-primary"},[...s[8]||(s[8]=[e("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 4v16m8-8H4"})],-1),g(" Create Token ",-1)])])])]),e("div",ae,[e("div",oe,[s[10]||(s[10]=e("div",{class:"stat-title"},"Total Tokens",-1)),e("div",ne,r(b.value.length),1),s[11]||(s[11]=e("div",{class:"stat-desc"},"All agent tokens",-1))]),e("div",re,[s[12]||(s[12]=e("div",{class:"stat-title"},"Active",-1)),e("div",ie,r(N.value),1),s[13]||(s[13]=e("div",{class:"stat-desc"},"Currently valid",-1))]),e("div",de,[s[14]||(s[14]=e("div",{class:"stat-title"},"Expired / Revoked",-1)),e("div",ue,r(V.value),1),s[15]||(s[15]=e("div",{class:"stat-desc"},"No longer usable",-1))])]),p.value?(o(),a("div",ce,[...s[16]||(s[16]=[e("span",{class:"loading loading-spinner loading-lg"},null,-1),e("p",{class:"mt-4"},"Loading tokens...",-1)])])):f.value?(o(),a("div",ve,[s[18]||(s[18]=e("svg",{class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})],-1)),e("div",null,[s[17]||(s[17]=e("h3",{class:"font-bold"},"Failed to load tokens",-1)),e("div",me,r(f.value),1)]),e("button",{onClick:s[1]||(s[1]=(...l)=>C(_)&&C(_)(...l)),class:"btn btn-sm"}," Try Again ")])):b.value.length===0?(o(),a("div",pe,[s[20]||(s[20]=e("svg",{class:"w-24 h-24 mx-auto mb-4 opacity-50",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"})],-1)),s[21]||(s[21]=e("h3",{class:"text-xl font-semibold mb-2"},"No agent tokens yet",-1)),s[22]||(s[22]=e("p",{class:"text-base-content/70 mb-4"}," Create scoped tokens for your AI agents and automated workflows. ",-1)),e("button",{onClick:E,class:"btn btn-primary"},[...s[19]||(s[19]=[e("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 4v16m8-8H4"})],-1),g(" Create Your First Token ",-1)])])])):(o(),a("div",be,[e("table",ke,[s[25]||(s[25]=e("thead",null,[e("tr",null,[e("th",null,"Name"),e("th",null,"Prefix"),e("th",null,"Servers"),e("th",null,"Permissions"),e("th",null,"Expires"),e("th",null,"Last Used"),e("th",null,"Status"),e("th",null,"Actions")])],-1)),e("tbody",null,[(o(!0),a(T,null,S(b.value,l=>(o(),a("tr",{key:l.name},[e("td",ge,r(l.name),1),e("td",null,[e("code",fe,r(l.token_prefix),1)]),e("td",null,[e("div",xe,[(o(!0),a(T,null,S(l.allowed_servers,i=>(o(),a("span",{key:i,class:"badge badge-outline badge-sm"},r(i),1))),128))])]),e("td",null,[e("div",he,[(o(!0),a(T,null,S(l.permissions,i=>(o(),a("span",{key:i,class:A(["badge badge-sm",z(i)])},r(i),3))),128))])]),e("td",null,[e("span",{class:A({"text-warning":$(l),"text-error":h(l)})},r(L(l.expires_at)),3)]),e("td",null,[l.last_used_at?(o(),a("span",ye,r(L(l.last_used_at)),1)):(o(),a("span",we,"Never"))]),e("td",null,[l.revoked?(o(),a("span",_e,"Revoked")):h(l)?(o(),a("span",Ce,"Expired")):(o(),a("span",Te,"Active"))]),e("td",null,[e("div",Se,[e("button",{onClick:i=>P(l.name),disabled:l.revoked,class:"btn btn-xs btn-outline",title:"Regenerate token secret"},[...s[23]||(s[23]=[e("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"})],-1),g(" Regenerate ",-1)])],8,Ae),e("button",{onClick:i=>W(l.name),disabled:l.revoked,class:"btn btn-xs btn-error btn-outline",title:"Revoke token"},[...s[24]||(s[24]=[e("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"})],-1),g(" Revoke ",-1)])],8,Fe)])])]))),128))])])])),m.value?(o(),a("div",De,[s[28]||(s[28]=e("svg",{class:"w-6 h-6 shrink-0",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"})],-1)),e("div",Re,[s[26]||(s[26]=e("h3",{class:"font-bold"},"Save this token now!",-1)),s[27]||(s[27]=e("p",{class:"text-sm mb-2"},"This token cannot be retrieved again after you dismiss this message.",-1)),e("div",Me,[e("code",Be,r(m.value),1),e("button",{onClick:O,class:A(["btn btn-sm btn-neutral shrink-0",{"btn-success":c.value}])},r(c.value?"Copied!":"Copy"),3)])]),e("button",{onClick:q,class:"btn btn-sm btn-ghost shrink-0"},"Dismiss")])):k("",!0),e("dialog",{ref_key:"createDialog",ref:R,class:"modal"},[e("div",je,[s[40]||(s[40]=e("h3",{class:"font-bold text-lg mb-4"},"Create Agent Token",-1)),e("div",Le,[e("div",Ee,[s[30]||(s[30]=e("label",{class:"label"},[e("span",{class:"label-text font-medium"},"Token Name")],-1)),y(e("input",{"onUpdate:modelValue":s[2]||(s[2]=l=>n.value.name=l),type:"text",placeholder:"e.g., ci-pipeline, dev-agent",class:A(["input input-bordered w-full",{"input-error":d.value.name}])},null,2),[[K,n.value.name]]),d.value.name?(o(),a("label",He,[e("span",Ie,r(d.value.name),1)])):(o(),a("label",Ne,[...s[29]||(s[29]=[e("span",{class:"label-text-alt"},"Alphanumeric, hyphens, and underscores only",-1)])]))]),e("div",Ve,[s[33]||(s[33]=e("label",{class:"label"},[e("span",{class:"label-text font-medium"},"Allowed Servers")],-1)),e("label",$e,[e("input",{type:"checkbox",checked:n.value.allServers,onChange:I,class:"checkbox checkbox-sm checkbox-primary"},null,40,ze),s[31]||(s[31]=e("span",{class:"text-sm font-medium"},"All servers",-1)),s[32]||(s[32]=e("span",{class:"badge badge-ghost badge-xs"},"wildcard",-1))]),n.value.allServers?k("",!0):(o(),a("div",Ue,[j.value.length===0?(o(),a("div",Pe," No servers configured ")):k("",!0),(o(!0),a(T,null,S(j.value,l=>(o(),a("label",{key:l.name,class:"flex items-center gap-2 cursor-pointer hover:bg-base-200 rounded px-2 py-1"},[y(e("input",{type:"checkbox",value:l.name,"onUpdate:modelValue":s[3]||(s[3]=i=>n.value.selectedServers=i),class:"checkbox checkbox-sm"},null,8,We),[[B,n.value.selectedServers]]),e("span",Oe,r(l.name),1),l.connected?(o(),a("span",qe,"connected")):(o(),a("span",Ye,"offline"))]))),128))])),!n.value.allServers&&d.value.servers?(o(),a("label",Ze,[e("span",Ge,r(d.value.servers),1)])):k("",!0)]),e("div",Je,[s[37]||(s[37]=e("label",{class:"label"},[e("span",{class:"label-text font-medium"},"Permissions")],-1)),e("div",Ke,[s[36]||(s[36]=e("label",{class:"flex items-center gap-2 cursor-not-allowed"},[e("input",{type:"checkbox",checked:"",disabled:"",class:"checkbox checkbox-sm checkbox-info"}),e("span",{class:"text-sm"},"read"),e("span",{class:"badge badge-info badge-xs"},"always included")],-1)),e("label",Qe,[y(e("input",{"onUpdate:modelValue":s[4]||(s[4]=l=>n.value.permWrite=l),type:"checkbox",class:"checkbox checkbox-sm checkbox-warning"},null,512),[[B,n.value.permWrite]]),s[34]||(s[34]=e("span",{class:"text-sm"},"write",-1))]),e("label",Xe,[y(e("input",{"onUpdate:modelValue":s[5]||(s[5]=l=>n.value.permDestructive=l),type:"checkbox",class:"checkbox checkbox-sm checkbox-error"},null,512),[[B,n.value.permDestructive]]),s[35]||(s[35]=e("span",{class:"text-sm"},"destructive",-1))])])]),e("div",es,[s[39]||(s[39]=e("label",{class:"label"},[e("span",{class:"label-text font-medium"},"Expires In")],-1)),y(e("select",{"onUpdate:modelValue":s[6]||(s[6]=l=>n.value.expiresIn=l),class:"select select-bordered w-full"},[...s[38]||(s[38]=[e("option",{value:"168h"},"7 days",-1),e("option",{value:"720h"},"30 days",-1),e("option",{value:"2160h"},"90 days",-1),e("option",{value:"8760h"},"365 days",-1)])],512),[[Q,n.value.expiresIn]])])]),e("div",ss,[e("button",{onClick:H,class:"btn"},"Cancel"),e("button",{onClick:U,disabled:x.value,class:"btn btn-primary"},[x.value?(o(),a("span",ls)):k("",!0),g(" "+r(x.value?"Creating...":"Create Token"),1)],8,ts)])]),s[41]||(s[41]=e("form",{method:"dialog",class:"modal-backdrop"},[e("button",null,"close")],-1))],512)]))}});export{ns as default};
diff --git a/web/frontend/dist/assets/Login-BO9OPmov.js b/web/frontend/dist/assets/Login-BO9OPmov.js
new file mode 100644
index 00000000..f3aef96e
--- /dev/null
+++ b/web/frontend/dist/assets/Login-BO9OPmov.js
@@ -0,0 +1 @@
+import{d as o,u as r,c as a,o as i,a as e,t as l}from"./index-DMP0eryo.js";const d={class:"min-h-screen flex items-center justify-center bg-base-200"},c="your organization",p=o({__name:"Login",setup(u){const s=r();function n(){s.login()}return(m,t)=>(i(),a("div",d,[e("div",{class:"card w-96 bg-base-100 shadow-xl"},[e("div",{class:"card-body items-center text-center"},[t[0]||(t[0]=e("h1",{class:"card-title text-2xl font-bold"},"MCPProxy Server",-1)),t[1]||(t[1]=e("p",{class:"text-base-content/70 mb-4"},"Sign in to access your MCP tools",-1)),t[2]||(t[2]=e("div",{class:"divider"},null,-1)),e("button",{class:"btn btn-primary w-full",onClick:n}," Sign in with "+l(c)),t[3]||(t[3]=e("p",{class:"text-sm text-base-content/50 mt-4"}," Powered by MCPProxy ",-1))])])]))}});export{p as default};
diff --git a/web/frontend/dist/assets/NotFound-alLFYf5-.js b/web/frontend/dist/assets/NotFound-alLFYf5-.js
new file mode 100644
index 00000000..2fa3e067
--- /dev/null
+++ b/web/frontend/dist/assets/NotFound-alLFYf5-.js
@@ -0,0 +1 @@
+import{d as s,c as n,o as a,a as e,i as r,w as l,k as d,j as m}from"./index-DMP0eryo.js";const i={class:"text-center py-20"},u=s({__name:"NotFound",setup(p){return(x,t)=>{const o=d("router-link");return a(),n("div",i,[t[1]||(t[1]=e("h1",{class:"text-6xl font-bold text-base-content/50 mb-4"},"404",-1)),t[2]||(t[2]=e("h2",{class:"text-2xl font-semibold mb-4"},"Page Not Found",-1)),t[3]||(t[3]=e("p",{class:"text-base-content/70 mb-8"}," The page you're looking for doesn't exist or has been moved. ",-1)),r(o,{to:"/",class:"btn btn-primary"},{default:l(()=>[...t[0]||(t[0]=[m(" Go Home ",-1)])]),_:1})])}}});export{u as default};
diff --git a/web/frontend/dist/assets/Repositories-Ob_RpJWO.js b/web/frontend/dist/assets/Repositories-Ob_RpJWO.js
new file mode 100644
index 00000000..00c3be52
--- /dev/null
+++ b/web/frontend/dist/assets/Repositories-Ob_RpJWO.js
@@ -0,0 +1 @@
+import{d as $,r,f as T,x as U,c as a,o as l,a as e,g as v,i as B,m as P,y as q,F as j,p as V,v as E,t as d,w as G,T as Q,B as z,C as J,z as x,j as w}from"./index-DMP0eryo.js";const K={class:"space-y-6"},O={class:"card bg-base-100 shadow-md"},W={class:"card-body"},X={class:"flex flex-col sm:flex-row gap-4"},Y={class:"form-control flex-1"},Z=["disabled"],ee=["value"],se={class:"form-control flex-1"},te=["disabled"],oe={class:"form-control sm:self-end"},ae=["disabled"],le={key:0,class:"loading loading-spinner loading-sm"},ne={key:1},ie={key:0,class:"alert alert-info mt-4"},re={class:"font-semibold"},de={class:"text-sm"},ce={key:0,class:"card bg-base-100 shadow-md"},ue={key:1,class:"alert alert-error"},ve={key:2,class:"space-y-4"},pe={class:"flex justify-between items-center"},he={class:"text-sm text-base-content/70"},me={class:"card-body"},fe={class:"flex justify-between items-start"},be={class:"card-title text-lg"},ge={class:"badge badge-outline badge-sm"},ye={class:"text-sm text-base-content/70 line-clamp-3"},_e={class:"flex flex-wrap gap-2 mt-2"},xe={key:0,class:"badge badge-success badge-sm"},we={key:1,class:"badge badge-info badge-sm"},ke={key:0,class:"mt-3"},Ce={class:"flex items-center justify-between bg-base-200 rounded px-2 py-1"},Me={class:"text-xs flex-1 overflow-x-auto"},Se=["onClick"],Re={class:"card-actions justify-end mt-4"},Te=["onClick"],Be=["onClick","disabled"],Pe={key:0,class:"loading loading-spinner loading-xs"},je={key:1},Ve={key:3,class:"card bg-base-100 shadow-md"},ze={key:4,class:"card bg-base-100 shadow-md"},He={key:5,class:"toast toast-end"},Fe={class:"alert alert-success"},Le=$({__name:"Repositories",setup(Ie){const m=r([]),i=r(""),p=r(""),c=r([]),f=r(!1),u=r(!1),n=r(null),h=r(null),b=r(!1),k=r("");let g=null;const y=T(()=>m.value.find(t=>t.id===i.value)),H=T(()=>[{icon:"📦",title:"Discover MCP Servers",description:"Browse official and community MCP servers from multiple registries",sections:[{title:"How to use",list:["Select a registry from the dropdown menu","Search for servers by name or description",'Click "Add to MCP" to install a server',"View source code and installation commands for each server"]}]},{icon:"🤖",title:"LLM Agent Integration",description:"Let AI agents help you discover and install MCP servers",sections:[{title:"Example prompts",list:["Find and add MCP servers for working with GitHub","Install the best MCP server for file system operations","Search for database-related MCP servers and add them","Discover Slack integration servers and configure them"]}]},{icon:"💡",title:"Installation Tips",description:"Servers can be installed via npm, pip, or connected remotely",sections:[{title:"Server types",list:["NPM packages: Installed with npx command","Python packages: Installed with uvx or pipx","Remote servers: Connected via HTTP endpoints","Docker containers: Run in isolated environments"]}]}]);async function F(){f.value=!0,n.value=null;try{const t=await x.listRegistries();t.success&&t.data?m.value=t.data.registries:n.value=t.error||"Failed to load registries"}catch(t){n.value="Failed to load registries: "+t.message}finally{f.value=!1}}async function _(){if(i.value){u.value=!0,n.value=null;try{const t=await x.searchRegistryServers(i.value,{query:p.value,limit:20});t.success&&t.data?c.value=t.data.servers:(n.value=t.error||"Failed to search servers",c.value=[])}catch(t){n.value="Failed to search servers: "+t.message,c.value=[]}finally{u.value=!1}}}function I(){p.value="",c.value=[],n.value=null,i.value&&_()}function N(){g&&clearTimeout(g),g=setTimeout(()=>{i.value&&_()},500)}async function L(t){h.value=t.id,n.value=null;try{const s=await x.addServerFromRepository(t);s.success?C(`Server "${t.name}" added successfully!`):n.value=s.error||"Failed to add server"}catch(s){n.value="Failed to add server: "+s.message}finally{h.value=null}}function D(t){navigator.clipboard.writeText(t),C("Installation command copied to clipboard!")}function A(t){window.open(t,"_blank")}function C(t){k.value=t,b.value=!0,setTimeout(()=>{b.value=!1},3e3)}return U(()=>{F()}),(t,s)=>(l(),a("div",K,[s[15]||(s[15]=e("div",{class:"flex justify-between items-center"},[e("div",null,[e("h1",{class:"text-3xl font-bold"},"Repositories"),e("p",{class:"text-base-content/70 mt-1"},"Browse and discover MCP server repositories")])],-1)),e("div",O,[e("div",W,[e("div",X,[e("div",Y,[s[3]||(s[3]=e("label",{class:"label"},[e("span",{class:"label-text font-semibold"},"Select Registry")],-1)),P(e("select",{"onUpdate:modelValue":s[0]||(s[0]=o=>i.value=o),class:"select select-bordered w-full",onChange:I,disabled:f.value},[s[2]||(s[2]=e("option",{disabled:"",value:""},"Choose a registry...",-1)),(l(!0),a(j,null,V(m.value,o=>(l(),a("option",{key:o.id,value:o.id},d(o.name),9,ee))),128))],40,Z),[[q,i.value]])]),e("div",se,[s[4]||(s[4]=e("label",{class:"label"},[e("span",{class:"label-text font-semibold"},"Search Servers")],-1)),P(e("input",{"onUpdate:modelValue":s[1]||(s[1]=o=>p.value=o),type:"text",placeholder:"Search by name or description...",class:"input input-bordered w-full",onInput:N,disabled:!i.value||u.value},null,40,te),[[E,p.value]])]),e("div",oe,[e("button",{onClick:_,class:"btn btn-primary",disabled:!i.value||u.value},[u.value?(l(),a("span",le)):(l(),a("span",ne,"Search"))],8,ae)])]),y.value?(l(),a("div",ie,[s[5]||(s[5]=e("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})],-1)),e("div",null,[e("p",re,d(y.value.name),1),e("p",de,d(y.value.description),1)])])):v("",!0)])]),u.value?(l(),a("div",ce,[...s[6]||(s[6]=[e("div",{class:"card-body"},[e("div",{class:"flex flex-col items-center justify-center py-12"},[e("div",{class:"loading loading-spinner loading-lg mb-4"}),e("p",{class:"text-base-content/70"},"Searching servers...")])],-1)])])):n.value?(l(),a("div",ue,[s[7]||(s[7]=e("svg",{class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})],-1)),e("span",null,d(n.value),1)])):c.value.length>0?(l(),a("div",ve,[e("div",pe,[e("p",he,"Found "+d(c.value.length)+" server(s)",1)]),B(Q,{name:"repo-card",tag:"div",class:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"},{default:G(()=>[(l(!0),a(j,null,V(c.value,o=>{var M,S;return l(),a("div",{key:o.id,class:"card bg-base-100 shadow-md hover:shadow-lg transition-shadow"},[e("div",me,[e("div",fe,[e("h3",be,d(o.name),1),e("div",ge,d(o.registry),1)]),e("p",ye,d(o.description),1),e("div",_e,[(S=(M=o.repository_info)==null?void 0:M.npm)!=null&&S.exists?(l(),a("div",xe,[...s[8]||(s[8]=[e("svg",{class:"w-3 h-3 mr-1",fill:"currentColor",viewBox:"0 0 24 24"},[e("path",{d:"M0 0h24v24H0z",fill:"none"}),e("path",{d:"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"})],-1),w(" NPM ",-1)])])):v("",!0),o.url?(l(),a("div",we,[...s[9]||(s[9]=[e("svg",{class:"w-3 h-3 mr-1",fill:"currentColor",viewBox:"0 0 24 24"},[e("path",{d:"M0 0h24v24H0z",fill:"none"}),e("path",{d:"M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"})],-1),w(" Remote ",-1)])])):v("",!0)]),o.installCmd?(l(),a("div",ke,[e("div",Ce,[e("code",Me,d(o.installCmd),1),e("button",{onClick:R=>D(o.installCmd),class:"btn btn-ghost btn-xs ml-2",title:"Copy install command"},[...s[10]||(s[10]=[e("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"})],-1)])],8,Se)])])):v("",!0),e("div",Re,[o.source_code_url?(l(),a("button",{key:0,onClick:R=>A(o.source_code_url),class:"btn btn-ghost btn-sm"},[...s[11]||(s[11]=[e("svg",{class:"w-4 h-4 mr-1",fill:"currentColor",viewBox:"0 0 24 24"},[e("path",{d:"M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"})],-1),w(" Source ",-1)])],8,Te)):v("",!0),e("button",{onClick:R=>L(o),class:"btn btn-primary btn-sm",disabled:h.value===o.id},[h.value===o.id?(l(),a("span",Pe)):(l(),a("span",je,"Add to MCP"))],8,Be)])])])}),128))]),_:1})])):i.value?(l(),a("div",ze,[...s[13]||(s[13]=[z('No Servers Found
Try adjusting your search query or select a different registry.
',1)])])):(l(),a("div",Ve,[...s[12]||(s[12]=[z('Select a Registry
Choose a registry from the dropdown to start browsing MCP servers.
',1)])])),b.value?(l(),a("div",He,[e("div",Fe,[s[14]||(s[14]=e("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"})],-1)),e("span",null,d(k.value),1)])])):v("",!0),B(J,{hints:H.value},null,8,["hints"])]))}});export{Le as default};
diff --git a/web/frontend/dist/assets/Search-DItjCgfV.js b/web/frontend/dist/assets/Search-DItjCgfV.js
new file mode 100644
index 00000000..f58cf436
--- /dev/null
+++ b/web/frontend/dist/assets/Search-DItjCgfV.js
@@ -0,0 +1,7 @@
+import{d as P,r as d,f as B,x as D,A as H,c as o,o as l,a as e,g as u,i as f,m as S,v as z,D as U,y as L,j as v,t as a,w as C,k as $,F as A,p as F,B as I,E,C as K,z as Q}from"./index-DMP0eryo.js";const G={class:"space-y-6"},J={class:"card bg-base-100 shadow-lg max-w-4xl mx-auto"},O={class:"card-body"},W={class:"flex flex-col space-y-4"},X={class:"relative"},Y={class:"flex flex-wrap gap-4 items-center"},Z={class:"form-control"},ee={class:"form-control"},se=["disabled"],te={key:0,class:"loading loading-spinner loading-sm"},oe={key:0},le={class:"flex justify-between items-center"},ae={class:"text-base-content/70"},ne={class:"font-medium"},ie={key:0},re={key:0,class:"flex items-center space-x-2"},de={key:0,class:"text-center py-12"},ce={key:1,class:"alert alert-error"},ue={key:2,class:"text-center py-12"},ve={class:"space-x-2"},me={key:3,class:"space-y-3"},pe={class:"card-body py-4"},be={class:"flex items-start justify-between gap-4"},he={class:"flex-1 min-w-0"},fe={class:"flex items-center gap-2 mb-2 flex-wrap"},xe={class:"text-lg font-bold text-base-content"},ge={class:"badge badge-secondary badge-sm"},ye={class:"badge badge-ghost badge-sm"},ke={class:"text-sm text-base-content/70 line-clamp-2 mb-2"},_e={class:"flex items-center gap-3 text-xs text-base-content/60"},we={key:0,class:"flex items-center gap-1"},Se={class:"flex flex-col gap-2 flex-shrink-0"},Ce=["onClick"],Me={key:1,class:"text-center py-16"},Be={key:2,class:"modal modal-open"},Le={class:"modal-box max-w-4xl"},Te={class:"font-bold text-lg mb-4"},Ve={class:"space-y-4"},qe={class:"grid grid-cols-2 gap-4"},Ne={class:"badge badge-secondary"},Re={class:"flex items-center space-x-2"},je={class:"font-mono"},Pe={class:"w-20 bg-base-300 rounded-full h-2"},De={class:"text-sm"},He={key:0},ze={class:"mockup-code"},Ue={class:"modal-action"},Ie=P({__name:"Search",setup($e){const T=H(),i=d(""),x=d(""),c=d([]),p=d(!1),m=d(null),g=d(!1),b=d(null),n=d(null),y=d(10),k=d(0),V=B(()=>c.value.filter(r=>r.score>=k.value));let _=null;const q=()=>{_&&clearTimeout(_),_=setTimeout(()=>{i.value.trim()&&h()},500)};async function h(){if(!i.value.trim())return;p.value=!0,m.value=null,b.value=null,x.value=i.value;const r=Date.now();try{const s=await Q.searchTools(i.value,y.value);s.success&&s.data?(c.value=s.data.results||[],b.value=Date.now()-r,g.value=!0):(m.value=s.error||"Search failed",c.value=[])}catch(s){m.value=s instanceof Error?s.message:"Search failed",c.value=[]}finally{p.value=!1}}function N(r){n.value=r}function M(){i.value="",x.value="",c.value=[],g.value=!1,m.value=null,b.value=null}D(()=>{const r=T.query.q;r&&typeof r=="string"&&(i.value=r,h())});const R=B(()=>[{icon:"🔍",title:"How to Search Tools",description:"Tips for getting the best search results",sections:[{title:"Search strategies",list:['Use descriptive keywords: "create file", "send email", "random number"',"Search by functionality rather than exact tool names","Use multiple keywords to narrow results","Adjust minimum relevance score to filter results"]},{title:"CLI search",codeBlock:{language:"bash",code:`# Search from command line
+mcpproxy tools search "your query"
+
+# Limit results
+mcpproxy tools search "your query" --limit=20`}}]},{icon:"🤖",title:"Search with LLM Agents",description:"Let AI agents search and discover tools for you",sections:[{title:"Example LLM prompts",list:["Search for all file-related tools across my MCP servers","Find tools that can help me work with GitHub issues","Show me the most relevant tools for sending notifications","What tools are available for data analysis?"]},{title:"LLM can call retrieve_tools",text:"AI agents can use the retrieve_tools built-in tool:",codeBlock:{language:"bash",code:`# LLM agents call this tool internally
+mcpproxy call tool --tool-name=retrieve_tools \\
+ --json_args='{"query":"file operations","limit":10}'`}}]},{icon:"💡",title:"Understanding Search Results",description:"How MCPProxy ranks and displays results",sections:[{title:"BM25 scoring",text:"MCPProxy uses BM25 (Best Matching 25) algorithm for relevance ranking:",list:["Scores range from 0.0 to ~1.0+ (higher is more relevant)","Takes into account keyword frequency and rarity","Considers tool name and description","Server-qualified names (server:tool) for easy identification"]}]}]);return(r,s)=>{const w=$("router-link");return l(),o("div",G,[s[28]||(s[28]=e("div",{class:"text-center mb-8"},[e("h1",{class:"text-4xl font-bold mb-4"},"Search Tools"),e("p",{class:"text-base-content/70 text-lg"},"Find tools across all MCP servers using intelligent BM25 search")],-1)),e("div",J,[e("div",O,[e("div",W,[e("div",X,[S(e("input",{"onUpdate:modelValue":s[0]||(s[0]=t=>i.value=t),type:"text",placeholder:"Search for tools (e.g. 'echo', 'file operations', 'random number')...",class:"input input-bordered input-lg w-full pl-12 pr-4",onKeyup:U(h,["enter"]),onInput:q},null,544),[[z,i.value]]),s[5]||(s[5]=e("svg",{class:"absolute left-4 top-1/2 transform -translate-y-1/2 w-6 h-6 text-base-content/50",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"})],-1))]),e("div",Y,[e("div",Z,[s[7]||(s[7]=e("label",{class:"label"},[e("span",{class:"label-text"},"Results per page")],-1)),S(e("select",{"onUpdate:modelValue":s[1]||(s[1]=t=>y.value=t),class:"select select-bordered select-sm"},[...s[6]||(s[6]=[e("option",{value:5},"5",-1),e("option",{value:10},"10",-1),e("option",{value:20},"20",-1),e("option",{value:50},"50",-1)])],512),[[L,y.value]])]),e("div",ee,[s[9]||(s[9]=e("label",{class:"label"},[e("span",{class:"label-text"},"Minimum relevance")],-1)),S(e("select",{"onUpdate:modelValue":s[2]||(s[2]=t=>k.value=t),class:"select select-bordered select-sm"},[...s[8]||(s[8]=[e("option",{value:0},"Any",-1),e("option",{value:.1},"Low (0.1+)",-1),e("option",{value:.3},"Medium (0.3+)",-1),e("option",{value:.5},"High (0.5+)",-1),e("option",{value:.8},"Very High (0.8+)",-1)])],512),[[L,k.value]])]),e("button",{class:"btn btn-primary",disabled:!i.value.trim()||p.value,onClick:h},[p.value?(l(),o("span",te)):u("",!0),s[10]||(s[10]=v(" Search ",-1))],8,se),i.value?(l(),o("button",{key:0,class:"btn btn-outline btn-sm",onClick:M}," Clear ")):u("",!0)])])])]),g.value?(l(),o("div",oe,[e("div",le,[e("div",null,[s[12]||(s[12]=e("h2",{class:"text-2xl font-semibold"},"Search Results",-1)),e("p",ae,[v(a(c.value.length)+' results for "',1),e("span",ne,a(x.value),1),s[11]||(s[11]=v('" ',-1)),b.value?(l(),o("span",ie,"("+a(b.value)+"ms)",1)):u("",!0)])]),c.value.length>0?(l(),o("div",re,[...s[13]||(s[13]=[e("div",{class:"badge badge-outline"},"BM25 Ranked",-1)])])):u("",!0)]),p.value?(l(),o("div",de,[...s[14]||(s[14]=[e("span",{class:"loading loading-spinner loading-lg"},null,-1),e("p",{class:"mt-4"},"Searching tools...",-1)])])):m.value?(l(),o("div",ce,[s[15]||(s[15]=e("svg",{class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})],-1)),e("span",null,a(m.value),1),e("button",{class:"btn btn-sm",onClick:h},"Retry")])):c.value.length===0?(l(),o("div",ue,[s[17]||(s[17]=e("svg",{class:"w-16 h-16 mx-auto mb-4 opacity-50",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"})],-1)),s[18]||(s[18]=e("h3",{class:"text-xl font-semibold mb-2"},"No tools found",-1)),s[19]||(s[19]=e("p",{class:"text-base-content/70 mb-4"}," Try different keywords or check if your servers are connected. ",-1)),e("div",ve,[e("button",{class:"btn btn-outline",onClick:M}," New Search "),f(w,{to:"/servers",class:"btn btn-primary"},{default:C(()=>[...s[16]||(s[16]=[v(" Check Servers ",-1)])]),_:1})])])):(l(),o("div",me,[(l(!0),o(A,null,F(V.value,(t,j)=>(l(),o("div",{key:`${t.tool.server_name}:${t.tool.name}`,class:"card bg-base-100 shadow-md hover:shadow-lg transition-shadow"},[e("div",pe,[e("div",be,[e("div",he,[e("div",fe,[e("h3",xe,a(t.tool.name),1),e("div",ge,a(t.tool.server_name),1),e("div",ye," Score: "+a(t.score.toFixed(2)),1)]),e("p",ke,a(t.tool.description||"No description available"),1),e("div",_e,[e("span",null,"#"+a(j+1)+" in results",1),t.tool.input_schema?(l(),o("span",we,[...s[20]||(s[20]=[e("svg",{class:"w-3 h-3",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"})],-1),v(" Schema available ",-1)])])):u("",!0)])]),e("div",Se,[e("button",{class:"btn btn-sm btn-primary",onClick:Ae=>N(t)}," View Details ",8,Ce),f(w,{to:`/servers/${t.tool.server_name}`,class:"btn btn-sm btn-outline"},{default:C(()=>[...s[21]||(s[21]=[v(" Server Info ",-1)])]),_:1},8,["to"])])])])]))),128))]))])):(l(),o("div",Me,[...s[22]||(s[22]=[I('Powerful Tool Search
Use our BM25-powered search to find the perfect tool for your task. Search by name, description, or functionality.
Natural Language
Search using natural descriptions like "send email" or "file operations"
Relevance Scoring
Results ranked by relevance with visual score indicators
Cross-Server
Search across all connected MCP servers simultaneously
',4)])])),n.value?(l(),o("div",Be,[e("div",Le,[e("h3",Te,a(n.value.tool.name),1),e("div",Ve,[e("div",qe,[e("div",null,[s[23]||(s[23]=e("label",{class:"block text-sm font-medium mb-1"},"Server",-1)),e("div",Ne,a(n.value.tool.server_name),1)]),e("div",null,[s[24]||(s[24]=e("label",{class:"block text-sm font-medium mb-1"},"Relevance Score",-1)),e("div",Re,[e("span",je,a(n.value.score.toFixed(3)),1),e("div",Pe,[e("div",{class:"bg-primary h-2 rounded-full",style:E({width:Math.min(100,n.value.score*100)+"%"})},null,4)])])])]),e("div",null,[s[25]||(s[25]=e("label",{class:"block text-sm font-medium mb-1"},"Description",-1)),e("p",De,a(n.value.tool.description||"No description available"),1)]),n.value.tool.input_schema?(l(),o("div",He,[s[26]||(s[26]=e("label",{class:"block text-sm font-medium mb-1"},"Input Schema",-1)),e("div",ze,[e("pre",null,[e("code",null,a(JSON.stringify(n.value.tool.input_schema,null,2)),1)])])])):u("",!0)]),e("div",Ue,[f(w,{to:`/servers/${n.value.tool.server_name}`,class:"btn btn-outline",onClick:s[3]||(s[3]=t=>n.value=null)},{default:C(()=>[...s[27]||(s[27]=[v(" View Server ",-1)])]),_:1},8,["to"]),e("button",{class:"btn",onClick:s[4]||(s[4]=t=>n.value=null)},"Close")])])])):u("",!0),f(K,{hints:R.value},null,8,["hints"])])}}});export{Ie as default};
diff --git a/web/frontend/dist/assets/Secrets-D_Jzkx2b.js b/web/frontend/dist/assets/Secrets-D_Jzkx2b.js
new file mode 100644
index 00000000..7bf7839b
--- /dev/null
+++ b/web/frontend/dist/assets/Secrets-D_Jzkx2b.js
@@ -0,0 +1,23 @@
+import{d as K,e as R,K as ee,r as f,s as se,c as l,o as a,a as e,L as te,g as y,m as N,v as T,n as x,t as i,j as C,z as B,f as w,x as ne,h as oe,i as I,l as j,w as ae,T as le,F as E,p as F,C as ie}from"./index-DMP0eryo.js";const re=["open"],de={class:"modal-box max-w-2xl"},ce={class:"form-control mb-4"},ue=["readonly"],ve={class:"label"},me={key:0,class:"label-text-alt"},ge={key:1,class:"label-text-alt text-info"},fe={class:"form-control mb-4"},be={key:0,class:"alert alert-info mb-4"},pe={key:1,class:"alert alert-error mb-4"},ye={class:"modal-action"},he=["disabled"],_e={key:0,class:"loading loading-spinner loading-sm"},ke=K({__name:"AddSecretModal",props:{show:{type:Boolean},predefinedName:{}},emits:["close","added"],setup(L,{emit:h}){const m=L,b=h,u=R(),d=ee({name:"",value:""}),p=f(!1),r=f("");se(()=>m.predefinedName,v=>{v&&(d.name=v)},{immediate:!0});async function g(){var v;r.value="",p.value=!0;try{const o=await B.setSecret(d.name,d.value);o.success?(u.addToast({type:"success",title:"Secret Added",message:`${d.name} has been added successfully. Use in config: ${(v=o.data)==null?void 0:v.reference}`}),b("added"),_()):r.value=o.error||"Failed to add secret"}catch(o){r.value=o instanceof Error?o.message:"Failed to add secret"}finally{p.value=!1}}function _(){d.name="",d.value="",r.value="",b("close")}return(v,o)=>(a(),l("dialog",{open:v.show,class:"modal"},[e("div",de,[e("form",{onSubmit:te(g,["prevent"])},[o[7]||(o[7]=e("h3",{class:"font-bold text-lg mb-4"},"Add New Secret",-1)),e("div",ce,[o[2]||(o[2]=e("label",{class:"label"},[e("span",{class:"label-text font-semibold"},"Secret Name")],-1)),N(e("input",{type:"text","onUpdate:modelValue":o[0]||(o[0]=S=>d.name=S),placeholder:"e.g., my-api-key",class:x(["input input-bordered",{"input-disabled":!!m.predefinedName}]),readonly:!!m.predefinedName,required:""},null,10,ue),[[T,d.name]]),e("label",ve,[m.predefinedName?(a(),l("span",ge,"Name is predefined from config")):(a(),l("span",me,"Use only letters, numbers, and hyphens"))])]),e("div",fe,[o[3]||(o[3]=e("label",{class:"label"},[e("span",{class:"label-text font-semibold"},"Secret Value")],-1)),N(e("input",{type:"password","onUpdate:modelValue":o[1]||(o[1]=S=>d.value=S),placeholder:"Enter secret value",class:"input input-bordered",required:""},null,512),[[T,d.value]])]),d.name?(a(),l("div",be,[o[5]||(o[5]=e("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})],-1)),e("div",null,[o[4]||(o[4]=e("div",{class:"font-semibold"},"Configuration reference:",-1)),e("code",null,"${keyring:"+i(d.name)+"}",1)])])):y("",!0),r.value?(a(),l("div",pe,[o[6]||(o[6]=e("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})],-1)),e("span",null,i(r.value),1)])):y("",!0),e("div",ye,[e("button",{type:"button",onClick:_,class:"btn btn-ghost"},"Cancel"),e("button",{type:"submit",class:"btn btn-primary",disabled:p.value||!d.name||!d.value},[p.value?(a(),l("span",_e)):y("",!0),C(" "+i(p.value?"Adding...":"Add Secret"),1)],8,he)])],32)]),e("form",{method:"dialog",class:"modal-backdrop",onClick:_},[...o[8]||(o[8]=[e("button",null,"close",-1)])])],8,re))}}),we={class:"space-y-6"},xe={class:"flex justify-between items-center"},Ce=["disabled"],Se={key:0,class:"loading loading-spinner loading-sm"},$e={class:"stats shadow bg-base-100 w-full"},Me={class:"stat"},Ae={class:"stat-value"},je={class:"stat"},Be={class:"stat-value text-info"},Ee={class:"stat"},Fe={class:"stat-value text-warning"},Ne={class:"stat"},Te={class:"stat-value text-error"},Le={class:"flex flex-wrap gap-4 items-center justify-between"},ze={class:"flex flex-wrap gap-2"},Pe={class:"form-control"},Ve={key:0,class:"text-center py-12"},He={key:1,class:"alert alert-error"},Ie={class:"text-sm"},Ke={key:2,class:"text-center py-12"},Re={class:"text-base-content/70 mb-4"},Ye={class:"card-body"},De={class:"flex justify-between items-start"},Ue={class:"flex-1"},qe={class:"card-title text-lg"},We={class:"flex items-center gap-2 mt-2"},Oe={key:0,class:"badge badge-success"},Ge={key:1,class:"badge badge-error"},Qe={class:"text-sm bg-base-200 px-2 py-1 rounded"},Je={class:"flex gap-2"},Xe=["onClick"],Ze=["onClick"],es=["onClick"],ss={class:"card-body"},ts={class:"flex justify-between items-start"},ns={class:"flex-1"},os={class:"card-title text-lg"},as={class:"flex items-center gap-2 mt-2"},ls={key:0,class:"badge badge-success"},is={key:1,class:"badge badge-error"},rs={class:"text-sm bg-base-200 px-2 py-1 rounded"},ds={class:"flex gap-2"},cs=["onClick"],us={key:4,class:"card bg-base-100 shadow"},vs={class:"card-body"},ms={class:"flex justify-between items-center mb-4"},gs=["disabled"],fs={class:"space-y-3"},bs={class:"flex-1"},ps={class:"font-bold"},ys={class:"text-sm opacity-70"},hs={class:"text-sm mt-1"},_s={class:"bg-base-200 px-2 py-1 rounded"},ks={class:"ml-2 opacity-60"},ws=["onClick","disabled"],Ss=K({__name:"Secrets",setup(L){const h=R(),m=f(!0),b=f(null),u=f(null),d=f([]),p=f(!1),r=f("all"),g=f(""),_=f(!1),v=f(void 0),o=w(()=>{var t,s;return((s=(t=u.value)==null?void 0:t.environment_vars)==null?void 0:s.filter(c=>!c.is_set).length)||0}),S=w(()=>{var t,s;return((s=(t=u.value)==null?void 0:t.secrets)==null?void 0:s.filter(c=>!c.is_set).length)||0}),Y=w(()=>o.value+S.value),D=w(()=>{var t,s;return(((t=u.value)==null?void 0:t.total_secrets)||0)+(((s=u.value)==null?void 0:s.total_env_vars)||0)}),z=w(()=>{var s;if(r.value==="envs")return[];let t=((s=u.value)==null?void 0:s.secrets)||[];if(r.value==="missing"&&(t=t.filter(c=>!c.is_set)),g.value){const c=g.value.toLowerCase();t=t.filter(k=>k.secret_ref.name.toLowerCase().includes(c)||k.secret_ref.original.toLowerCase().includes(c))}return t}),P=w(()=>{var s;if(r.value==="secrets")return[];let t=((s=u.value)==null?void 0:s.environment_vars)||[];if(r.value==="missing"&&(t=t.filter(c=>!c.is_set)),g.value){const c=g.value.toLowerCase();t=t.filter(k=>k.secret_ref.name.toLowerCase().includes(c)||k.secret_ref.original.toLowerCase().includes(c))}return t}),U=w(()=>[...z.value,...P.value]),M=async()=>{m.value=!0,b.value=null;try{const t=await B.getConfigSecrets();t.success&&t.data?u.value=t.data:b.value=t.error||"Failed to load config secrets"}catch(t){b.value=t.message||"Failed to load config secrets",console.error("Failed to load config secrets:",t)}finally{m.value=!1}},A=M,q=async()=>{p.value=!0;try{const t=await B.runMigrationAnalysis();t.success&&t.data?(d.value=t.data.analysis.candidates||[],h.addToast({type:"success",title:"Analysis Complete",message:`Found ${d.value.length} migration candidates`})):b.value=t.error||"Failed to run migration analysis"}catch(t){b.value=t.message||"Failed to run migration analysis",console.error("Failed to run migration analysis:",t)}finally{p.value=!1}},W=t=>{v.value=t,_.value=!0},O=async t=>{v.value=t.name,_.value=!0},G=async t=>{if(confirm(`Are you sure you want to delete secret "${t.name}"?`))try{const s=await B.deleteSecret(t.name,t.type);s.success?(h.addToast({type:"success",title:"Secret Deleted",message:`Secret "${t.name}" deleted successfully`}),await M()):h.addToast({type:"error",title:"Delete Failed",message:s.error||"Failed to delete secret"})}catch(s){h.addToast({type:"error",title:"Delete Failed",message:s.message||"Failed to delete secret"})}},Q=async t=>{t.migrating=!0;try{const s=t.suggested.match(/\$\{keyring:([^}]+)\}/);if(!s)throw new Error("Invalid suggested reference format");const c=s[1];h.addToast({type:"info",title:"Migration Instructions",message:`Run: mcpproxy secrets set ${c}
+Then update config to use: ${t.suggested}`})}catch(s){h.addToast({type:"error",title:"Migration Failed",message:s.message})}finally{t.migrating=!1}},J=async t=>{const s=`To set "${t.secret_ref.name}":
+
+macOS/Linux: export ${t.secret_ref.name}="your-value"
+Windows (PS): $env:${t.secret_ref.name}="your-value"
+Windows (CMD): set ${t.secret_ref.name}=your-value`;h.addToast({type:"info",title:"Set Environment Variable",message:s})},X=async()=>{await M()},Z=w(()=>[{icon:"🔐",title:"Config-First Workflow",description:"Add secret references to your config first, then set their values",sections:[{title:"1. Add secret reference to config",text:"First, add the secret reference to your mcp_config.json file:",codeBlock:{language:"json",code:`{
+ "mcpServers": [
+ {
+ "name": "my-server",
+ "env": {
+ "API_KEY": "\${keyring:my-api-key}"
+ }
+ }
+ ]
+}`}},{title:"2. Missing secrets will appear above",text:'After saving the config, the secret will appear in the "Missing" filter with a red border, showing it needs a value.'},{title:"3. Add the secret value",text:'Click the "Add Value" button next to the missing secret, or use the CLI:',codeBlock:{language:"bash",code:"mcpproxy secrets set my-api-key"}}]},{icon:"🌍",title:"Environment Variables",description:"Reference environment variables in your configuration",sections:[{title:"Use environment variables",codeBlock:{language:"json",code:`{
+ "env": {
+ "API_KEY": "\${env:MY_API_KEY}"
+ }
+}`}},{title:"Set environment variables",codeBlock:{language:"bash",code:`# macOS/Linux
+export MY_API_KEY="your-value"
+
+# Windows PowerShell
+$env:MY_API_KEY="your-value"`}}]},{icon:"🔄",title:"Migrate Existing Secrets",description:"Find and migrate hardcoded secrets to secure storage",sections:[{title:"Run migration analysis",text:'MCPProxy can scan your configuration and identify potential secrets that should be moved to secure storage. Click the "Analyze Configuration" button to find migration candidates.'},{title:"Automatic detection",text:"The analyzer looks for patterns like API keys, tokens, passwords, and other sensitive values that might be hardcoded in your configuration."}]}]);return ne(async()=>{await new Promise(t=>setTimeout(t,100)),M()}),(t,s)=>{var c,k,V,H;return a(),l("div",we,[e("div",xe,[s[10]||(s[10]=e("div",null,[e("h1",{class:"text-3xl font-bold"},"Secrets & Environment Variables"),e("p",{class:"text-base-content/70 mt-1"},"Manage secrets stored in your system's secure keyring and environment variables")],-1)),e("button",{onClick:s[0]||(s[0]=(...n)=>j(A)&&j(A)(...n)),disabled:m.value,class:"btn btn-outline"},[s[9]||(s[9]=e("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"})],-1)),m.value?(a(),l("span",Se)):y("",!0),C(" "+i(m.value?"Refreshing...":"Refresh"),1)],8,Ce)]),e("div",$e,[e("div",Me,[s[11]||(s[11]=e("div",{class:"stat-title"},"Keyring Secrets",-1)),e("div",Ae,i(((c=u.value)==null?void 0:c.total_secrets)||0),1),s[12]||(s[12]=e("div",{class:"stat-desc"},"Stored in system keyring",-1))]),e("div",je,[s[13]||(s[13]=e("div",{class:"stat-title"},"Environment Variables",-1)),e("div",Be,i(((k=u.value)==null?void 0:k.total_env_vars)||0),1),s[14]||(s[14]=e("div",{class:"stat-desc"},"Referenced in config",-1))]),e("div",Ee,[s[15]||(s[15]=e("div",{class:"stat-title"},"Missing Env Vars",-1)),e("div",Fe,i(o.value),1),s[16]||(s[16]=e("div",{class:"stat-desc"},"Need to be set",-1))]),e("div",Ne,[s[17]||(s[17]=e("div",{class:"stat-title"},"Migration Candidates",-1)),e("div",Te,i(d.value.length),1),s[18]||(s[18]=e("div",{class:"stat-desc"},"Potential secrets to secure",-1))])]),e("div",Le,[e("div",ze,[e("button",{onClick:s[1]||(s[1]=n=>r.value="all"),class:x(["btn btn-sm",r.value==="all"?"btn-primary":"btn-outline"])}," All ("+i(D.value)+") ",3),e("button",{onClick:s[2]||(s[2]=n=>r.value="secrets"),class:x(["btn btn-sm",r.value==="secrets"?"btn-primary":"btn-outline"])}," Keyring Secrets ("+i(((V=u.value)==null?void 0:V.total_secrets)||0)+") ",3),e("button",{onClick:s[3]||(s[3]=n=>r.value="envs"),class:x(["btn btn-sm",r.value==="envs"?"btn-primary":"btn-outline"])}," Environment Variables ("+i(((H=u.value)==null?void 0:H.total_env_vars)||0)+") ",3),e("button",{onClick:s[4]||(s[4]=n=>r.value="missing"),class:x(["btn btn-sm",r.value==="missing"?"btn-primary":"btn-outline"])}," Missing ("+i(Y.value)+") ",3)]),e("div",Pe,[N(e("input",{"onUpdate:modelValue":s[5]||(s[5]=n=>g.value=n),type:"text",placeholder:"Search secrets...",class:"input input-bordered input-sm w-64"},null,512),[[T,g.value]])])]),m.value?(a(),l("div",Ve,[...s[19]||(s[19]=[e("span",{class:"loading loading-spinner loading-lg"},null,-1),e("p",{class:"mt-4"},"Loading secrets...",-1)])])):b.value?(a(),l("div",He,[s[21]||(s[21]=e("svg",{class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})],-1)),e("div",null,[s[20]||(s[20]=e("h3",{class:"font-bold"},"Failed to load secrets",-1)),e("div",Ie,i(b.value),1)]),e("button",{onClick:s[6]||(s[6]=(...n)=>j(A)&&j(A)(...n)),class:"btn btn-sm"}," Try Again ")])):U.value.length===0?(a(),l("div",Ke,[s[22]||(s[22]=e("svg",{class:"w-24 h-24 mx-auto mb-4 opacity-50",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"})],-1)),s[23]||(s[23]=e("h3",{class:"text-xl font-semibold mb-2"},"No secrets found",-1)),e("p",Re,i(g.value?"No secrets match your search criteria":`No ${r.value==="all"?"":r.value} secrets available`.replace(/\s+/g," ").trim()),1),g.value?(a(),l("button",{key:0,onClick:s[7]||(s[7]=n=>g.value=""),class:"btn btn-outline"}," Clear Search ")):y("",!0)])):(a(),oe(le,{key:3,name:"secret-list",tag:"div",class:"space-y-4"},{default:ae(()=>[(a(!0),l(E,null,F(z.value,n=>(a(),l("div",{key:`secret-${n.secret_ref.name}`,class:x(["card bg-base-100 shadow",{"border-l-4 border-error":!n.is_set}])},[e("div",Ye,[e("div",De,[e("div",Ue,[e("h3",qe,i(n.secret_ref.name),1),e("div",We,[s[24]||(s[24]=e("span",{class:"badge badge-primary"},"Keyring",-1)),n.is_set?(a(),l("span",Oe,"✓ Set")):(a(),l("span",Ge,"✗ Missing")),e("code",Qe,i(n.secret_ref.original),1)])]),e("div",Je,[n.is_set?y("",!0):(a(),l("button",{key:0,onClick:$=>W(n.secret_ref.name),class:"btn btn-sm btn-primary"},[...s[25]||(s[25]=[e("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 4v16m8-8H4"})],-1),C(" Add Value ",-1)])],8,Xe)),n.is_set?(a(),l("button",{key:1,onClick:$=>O(n.secret_ref),class:"btn btn-sm btn-outline"},[...s[26]||(s[26]=[e("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"})],-1),C(" Update ",-1)])],8,Ze)):y("",!0),n.is_set?(a(),l("button",{key:2,onClick:$=>G(n.secret_ref),class:"btn btn-sm btn-error btn-outline"},[...s[27]||(s[27]=[e("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"})],-1),C(" Remove ",-1)])],8,es)):y("",!0)])])])],2))),128)),(a(!0),l(E,null,F(P.value,n=>(a(),l("div",{key:`env-${n.secret_ref.name}`,class:x(["card bg-base-100 shadow",{"border-l-4 border-error":!n.is_set}])},[e("div",ss,[e("div",ts,[e("div",ns,[e("h3",os,i(n.secret_ref.name),1),e("div",as,[s[28]||(s[28]=e("span",{class:"badge badge-info"},"Environment Variable",-1)),n.is_set?(a(),l("span",ls,"✓ Set")):(a(),l("span",is,"✗ Missing")),e("code",rs,i(n.secret_ref.original),1)])]),e("div",ds,[n.is_set?y("",!0):(a(),l("button",{key:0,onClick:$=>J(n),class:"btn btn-sm btn-primary"},[...s[29]||(s[29]=[e("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})],-1),C(" How to Set ",-1)])],8,cs))])])])],2))),128))]),_:1})),d.value.length>0?(a(),l("div",us,[e("div",vs,[e("div",ms,[s[31]||(s[31]=e("h2",{class:"card-title"},"Migration Candidates",-1)),e("button",{onClick:q,class:"btn btn-sm btn-outline",disabled:p.value},[s[30]||(s[30]=e("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"})],-1)),C(" "+i(p.value?"Analyzing...":"Re-analyze"),1)],8,gs)]),s[34]||(s[34]=e("p",{class:"text-sm text-base-content/70 mb-4"}," These configuration values appear to be secrets that could be migrated to secure storage. ",-1)),e("div",fs,[(a(!0),l(E,null,F(d.value,(n,$)=>(a(),l("div",{key:$,class:x(["alert",{"alert-success":n.confidence>=.8,"alert-warning":n.confidence>=.6&&n.confidence<.8,"alert-error":n.confidence<.6}])},[s[33]||(s[33]=e("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"})],-1)),e("div",bs,[e("div",ps,i(n.field),1),e("div",ys,i(n.value),1),e("div",hs,[s[32]||(s[32]=C(" Suggested: ",-1)),e("code",_s,i(n.suggested),1),e("span",ks,"("+i(Math.round(n.confidence*100))+"% confidence)",1)])]),e("button",{onClick:xs=>Q(n),class:"btn btn-sm btn-primary",disabled:n.migrating},i(n.migrating?"Migrating...":"Store in Keychain"),9,ws)],2))),128))])])])):y("",!0),I(ie,{hints:Z.value},null,8,["hints"]),I(ke,{show:_.value,predefinedName:v.value,onClose:s[8]||(s[8]=n=>{_.value=!1,v.value=void 0}),onAdded:X},null,8,["show","predefinedName"])])}}});export{Ss as default};
diff --git a/web/frontend/dist/assets/ServerDetail-BASjhAMZ.js b/web/frontend/dist/assets/ServerDetail-BASjhAMZ.js
new file mode 100644
index 00000000..87fac2fb
--- /dev/null
+++ b/web/frontend/dist/assets/ServerDetail-BASjhAMZ.js
@@ -0,0 +1,12 @@
+import{d as H,f as T,c as n,g as i,o as a,n as h,t as r,b as ee,e as se,r as p,s as te,x as oe,i as E,a as e,w as R,k as ae,j as f,m as U,v as ne,F as z,p as V,y as le,C as re,z as q,h as ie,A as de}from"./index-DMP0eryo.js";const ue=["title"],ce={key:0},ve={key:1},me=["title"],pe={key:0},ge={key:1},be=["title"],fe={key:0},he={key:1},ye=["title"],ke={key:0},_e={key:1},we=H({__name:"AnnotationBadges",props:{annotations:{},compact:{type:Boolean,default:!1}},setup(N){const u=N,v=T(()=>u.annotations?u.annotations.title||u.annotations.readOnlyHint||u.annotations.destructiveHint||u.annotations.idempotentHint||u.annotations.openWorldHint:!1),c=d=>{const g=u.compact?"badge badge-sm cursor-help":"badge badge-sm";switch(d){case"info":return`${g} badge-info`;case"error":return`${g} badge-error`;case"neutral":return`${g} badge-neutral`;case"secondary":return`${g} badge-secondary`;default:return g}};return(d,g)=>{var t,b,l,y,k;return v.value?(a(),n("div",{key:0,class:h(["flex flex-wrap gap-1 items-center",d.compact?"gap-0.5":"gap-1"])},[(t=d.annotations)!=null&&t.title?(a(),n("div",{key:0,class:h(["text-sm font-medium text-base-content/80",d.compact?"text-xs":""])},r(d.annotations.title),3)):i("",!0),(b=d.annotations)!=null&&b.readOnlyHint?(a(),n("div",{key:1,class:h(c("info")),title:d.compact?"Read-only: Does not modify data":""},[d.compact?(a(),n("span",ve,"📖")):(a(),n("span",ce,"📖 Read-only"))],10,ue)):i("",!0),(l=d.annotations)!=null&&l.destructiveHint?(a(),n("div",{key:2,class:h(c("error")),title:d.compact?"Destructive: May delete or modify data":""},[d.compact?(a(),n("span",ge,"⚠️")):(a(),n("span",pe,"⚠️ Destructive"))],10,me)):i("",!0),(y=d.annotations)!=null&&y.idempotentHint?(a(),n("div",{key:3,class:h(c("neutral")),title:d.compact?"Idempotent: Safe to retry":""},[d.compact?(a(),n("span",he,"🔄")):(a(),n("span",fe,"🔄 Idempotent"))],10,be)):i("",!0),(k=d.annotations)!=null&&k.openWorldHint?(a(),n("div",{key:4,class:h(c("secondary")),title:d.compact?"Open World: May access external resources":""},[d.compact?(a(),n("span",_e,"🌐")):(a(),n("span",ke,"🌐 Open World"))],10,ye)):i("",!0)],2)):i("",!0)}}}),xe={class:"space-y-6"},Ce={key:0,class:"text-center py-12"},Se={key:1,class:"alert alert-error"},Te={class:"text-sm"},$e={key:2,class:"text-center py-12"},Le={class:"text-base-content/70 mb-4"},Me={key:3},Be={class:"flex flex-col lg:flex-row lg:justify-between lg:items-start gap-4"},Ne={class:"breadcrumbs text-sm mb-2"},je={class:"text-3xl font-bold"},Ee={class:"text-base-content/70 mt-1"},qe={class:"flex items-center space-x-2"},De={class:"dropdown dropdown-end"},Ae={tabindex:"0",class:"dropdown-content menu bg-base-100 rounded-box z-[1] w-52 p-2 shadow"},Oe=["disabled"],Fe={key:0,class:"loading loading-spinner loading-xs"},Re={key:0},Ue=["disabled"],ze={key:0,class:"loading loading-spinner loading-xs"},Ve={key:1},He=["disabled"],Pe={key:0,class:"loading loading-spinner loading-xs"},Qe={key:2},We=["disabled"],Ie={key:0,class:"loading loading-spinner loading-xs"},Je=["disabled"],Ge={key:0,class:"loading loading-spinner loading-xs"},Ke=["disabled"],Xe={key:0,class:"loading loading-spinner loading-xs"},Ye={class:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"},Ze={class:"stats shadow bg-base-100"},es={class:"stat"},ss={class:"stat-value"},ts={class:"stats shadow bg-base-100"},os={class:"stat"},as={class:"stat-value text-sm"},ns={class:"stat-desc"},ls={class:"stats shadow bg-base-100"},rs={class:"stat"},is={class:"stat-value text-sm"},ds={class:"stats shadow bg-base-100"},us={class:"stat"},cs={class:"stat-value text-sm"},vs={class:"space-y-4"},ms={key:0,class:"alert alert-error"},ps={class:"text-sm"},gs={key:1,class:"alert alert-warning"},bs=["disabled"],fs={key:0,class:"loading loading-spinner loading-xs"},hs={class:"tabs tabs-bordered"},ys={class:"mt-6"},ks={key:0},_s={key:0,class:"text-center py-8"},ws={key:1,class:"alert alert-error"},xs={key:2,class:"text-center py-8"},Cs={class:"text-base-content/70"},Ss={key:3,class:"space-y-4"},Ts={class:"flex justify-between items-center"},$s={class:"text-base-content/70"},Ls={class:"form-control"},Ms={class:"grid grid-cols-1 lg:grid-cols-2 gap-4"},Bs={class:"card-body"},Ns={class:"card-title text-lg"},js={class:"text-sm text-base-content/70"},Es={key:1,class:"card-actions justify-end mt-4"},qs=["onClick"],Ds={key:1},As={class:"flex justify-between items-center mb-4"},Os={class:"text-base-content/70"},Fs={class:"flex items-center space-x-2"},Rs=["disabled"],Us={key:0,class:"loading loading-spinner loading-xs"},zs={key:0,class:"text-center py-8"},Vs={key:1,class:"alert alert-error"},Hs={key:2,class:"text-center py-8"},Ps={key:3,class:"mockup-code max-h-96 overflow-y-auto"},Qs={key:2},Ws={class:"space-y-6"},Is={class:"grid grid-cols-1 md:grid-cols-2 gap-6"},Js={class:"space-y-4"},Gs=["value"],Ks=["value"],Xs={key:0},Ys=["value"],Zs={key:1},et=["value"],st={class:"space-y-4"},tt={class:"form-control"},ot=["checked","disabled"],at={class:"form-control"},nt=["checked"],lt=["value"],rt={key:4,class:"modal modal-open"},it={class:"modal-box max-w-4xl"},dt={class:"font-bold text-lg mb-4"},ut={class:"mockup-code"},ct={class:"modal-action"},mt=H({__name:"ServerDetail",props:{serverName:{}},setup(N){const u=N;de();const v=ee(),c=se(),d=p(!0),g=p(null),t=p(null),b=p("tools"),l=p(!1),y=p([]),k=p(!1),_=p(null),$=p(""),w=p(null),j=p([]),x=p(!1),C=p(null),L=p(100),P=T(()=>{var o,s;return((o=t.value)==null?void 0:o.protocol)==="http"||((s=t.value)==null?void 0:s.protocol)==="streamable-http"}),Q=T(()=>{var o,s;return((s=(o=t.value)==null?void 0:o.health)==null?void 0:s.action)||""}),W=T(()=>{if(!$.value)return y.value;const o=$.value.toLowerCase();return y.value.filter(s=>{var S;return s.name.toLowerCase().includes(o)||((S=s.description)==null?void 0:S.toLowerCase().includes(o))})});async function M(){d.value=!0,g.value=null;try{if(await v.fetchServers(),t.value=v.servers.find(o=>o.name===u.serverName)||null,!t.value){g.value=`Server "${u.serverName}" not found`;return}await Promise.all([D(),B()])}catch(o){g.value=o instanceof Error?o.message:"Failed to load server details"}finally{d.value=!1}}async function D(){if(t.value){k.value=!0,_.value=null;try{const o=await q.getServerTools(t.value.name);o.success&&o.data?y.value=o.data.tools||[]:_.value=o.error||"Failed to load tools"}catch(o){_.value=o instanceof Error?o.message:"Failed to load tools"}finally{k.value=!1}}}async function B(){if(t.value){x.value=!0,C.value=null;try{const o=await q.getServerLogs(t.value.name,L.value);o.success&&o.data?j.value=o.data.logs||[]:C.value=o.error||"Failed to load logs"}catch(o){C.value=o instanceof Error?o.message:"Failed to load logs"}finally{x.value=!1}}}async function A(){if(t.value){l.value=!0;try{t.value.enabled?(await v.disableServer(t.value.name),c.addToast({type:"success",title:"Server Disabled",message:`${t.value.name} has been disabled`})):(await v.enableServer(t.value.name),c.addToast({type:"success",title:"Server Enabled",message:`${t.value.name} has been enabled`})),await v.fetchServers(),t.value=v.servers.find(o=>o.name===u.serverName)||null}catch(o){c.addToast({type:"error",title:"Operation Failed",message:o instanceof Error?o.message:"Unknown error"})}finally{l.value=!1}}}async function I(){if(t.value){l.value=!0;try{await v.restartServer(t.value.name),c.addToast({type:"success",title:"Server Restarted",message:`${t.value.name} is restarting`}),setTimeout(async()=>{await v.fetchServers(),t.value=v.servers.find(o=>o.name===u.serverName)||null},2e3)}catch(o){c.addToast({type:"error",title:"Restart Failed",message:o instanceof Error?o.message:"Unknown error"})}finally{l.value=!1}}}async function J(){if(t.value){l.value=!0;try{await v.triggerOAuthLogin(t.value.name),c.addToast({type:"success",title:"OAuth Login Triggered",message:`Check your browser for ${t.value.name} login`})}catch(o){c.addToast({type:"error",title:"OAuth Failed",message:o instanceof Error?o.message:"Unknown error"})}finally{l.value=!1}}}async function G(){if(t.value){l.value=!0;try{await v.quarantineServer(t.value.name),c.addToast({type:"success",title:"Server Quarantined",message:`${t.value.name} has been quarantined`}),await v.fetchServers(),t.value=v.servers.find(o=>o.name===u.serverName)||null}catch(o){c.addToast({type:"error",title:"Quarantine Failed",message:o instanceof Error?o.message:"Unknown error"})}finally{l.value=!1}}}async function O(){if(t.value){l.value=!0;try{await v.unquarantineServer(t.value.name),c.addToast({type:"success",title:"Server Unquarantined",message:`${t.value.name} has been removed from quarantine`}),await v.fetchServers(),t.value=v.servers.find(o=>o.name===u.serverName)||null}catch(o){c.addToast({type:"error",title:"Unquarantine Failed",message:o instanceof Error?o.message:"Unknown error"})}finally{l.value=!1}}}async function K(){await M()}async function X(){if(t.value){l.value=!0;try{const o=await q.discoverServerTools(t.value.name);if(!o.success)throw new Error(o.error||"Failed to discover tools");c.addToast({type:"success",title:"Tool Discovery Started",message:`Discovering tools for ${t.value.name}...`}),setTimeout(async()=>{var s;await M(),c.addToast({type:"info",title:"Tools Updated",message:`Tool cache refreshed for ${(s=t.value)==null?void 0:s.name}`})},2e3)}catch(o){c.addToast({type:"error",title:"Tool Discovery Failed",message:o instanceof Error?o.message:"Unknown error"})}finally{l.value=!1}}}function Y(o){w.value=o}const Z=T(()=>[{icon:"🔧",title:"Server Management",description:"Control and monitor this MCP server",sections:[{title:"Enable/Disable server",codeBlock:{language:"bash",code:`# Disable server
+mcpproxy call tool --tool-name=upstream_servers \\
+ --json_args='{"operation":"update","name":"${u.serverName}","enabled":false}'
+
+# Enable server
+mcpproxy call tool --tool-name=upstream_servers \\
+ --json_args='{"operation":"update","name":"${u.serverName}","enabled":true}'`}},{title:"View server logs",codeBlock:{language:"bash",code:`# Real-time logs for this server
+tail -f ~/.mcpproxy/logs/server-${u.serverName}.log`}}]},{icon:"🛠️",title:"Working with Tools",description:"Use tools provided by this server",sections:[{title:"List all tools",codeBlock:{language:"bash",code:`# List tools from this server
+mcpproxy tools list --server=${u.serverName}`}},{title:"Call a tool",text:"Tools are prefixed with server name:",codeBlock:{language:"bash",code:`# Call tool from this server
+mcpproxy call tool --tool-name=${u.serverName}:tool-name \\
+ --json_args='{"arg1":"value1"}'`}}]},{icon:"💡",title:"Troubleshooting",description:"Common issues and solutions",sections:[{title:"Connection issues",list:["Check if server is enabled in configuration","Review server logs for error messages","Verify network connectivity for remote servers","Check authentication credentials for OAuth servers"]},{title:"OAuth authentication",text:"If server requires OAuth login:",codeBlock:{language:"bash",code:`# Trigger OAuth login
+mcpproxy auth login --server=${u.serverName}`}}]}]);return te(L,()=>{B()}),oe(()=>{M()}),(o,s)=>{const S=ae("router-link");return a(),n("div",xe,[d.value?(a(),n("div",Ce,[...s[7]||(s[7]=[e("span",{class:"loading loading-spinner loading-lg"},null,-1),e("p",{class:"mt-4"},"Loading server details...",-1)])])):g.value?(a(),n("div",Se,[s[9]||(s[9]=e("svg",{class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})],-1)),e("div",null,[s[8]||(s[8]=e("h3",{class:"font-bold"},"Failed to load server details",-1)),e("div",Te,r(g.value),1)]),e("button",{onClick:M,class:"btn btn-sm"}," Try Again ")])):t.value?(a(),n("div",Me,[e("div",Be,[e("div",null,[e("div",Ne,[e("ul",null,[e("li",null,[E(S,{to:"/servers"},{default:R(()=>[...s[13]||(s[13]=[f("Servers",-1)])]),_:1})]),e("li",null,r(t.value.name),1)])]),e("h1",je,r(t.value.name),1),e("p",Ee,r(t.value.protocol)+" • "+r(t.value.url||t.value.command||"No endpoint"),1)]),e("div",qe,[e("div",{class:h(["badge badge-lg",t.value.connected?"badge-success":t.value.connecting?"badge-warning":"badge-error"])},r(t.value.connected?"Connected":t.value.connecting?"Connecting":"Disconnected"),3),e("div",De,[s[17]||(s[17]=e("div",{tabindex:"0",role:"button",class:"btn btn-outline"},[f(" Actions "),e("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M19 9l-7 7-7-7"})])],-1)),e("ul",Ae,[e("li",null,[e("button",{onClick:A,disabled:l.value},[l.value?(a(),n("span",Fe)):i("",!0),f(" "+r(t.value.enabled?"Disable":"Enable"),1)],8,Oe)]),t.value.enabled?(a(),n("li",Re,[e("button",{onClick:I,disabled:l.value},[l.value?(a(),n("span",ze)):i("",!0),f(" "+r(P.value?"Reconnect":"Restart"),1)],8,Ue)])):i("",!0),Q.value==="login"?(a(),n("li",Ve,[e("button",{onClick:J,disabled:l.value},[l.value?(a(),n("span",Pe)):i("",!0),s[14]||(s[14]=f(" Login ",-1))],8,He)])):i("",!0),t.value.enabled&&t.value.connected?(a(),n("li",Qe,[e("button",{onClick:X,disabled:l.value},[l.value?(a(),n("span",Ie)):i("",!0),s[15]||(s[15]=f(" Discover Tools ",-1))],8,We)])):i("",!0),e("li",null,[e("button",{onClick:s[0]||(s[0]=m=>t.value.quarantined?O():G()),disabled:l.value},[l.value?(a(),n("span",Ge)):i("",!0),f(" "+r(t.value.quarantined?"Unquarantine":"Quarantine"),1)],8,Je)]),e("li",null,[e("button",{onClick:K,disabled:l.value},[l.value?(a(),n("span",Xe)):i("",!0),s[16]||(s[16]=f(" Refresh ",-1))],8,Ke)])])])])]),e("div",Ye,[e("div",Ze,[e("div",es,[s[18]||(s[18]=e("div",{class:"stat-figure text-primary"},[e("svg",{class:"w-8 h-8",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"})])],-1)),s[19]||(s[19]=e("div",{class:"stat-title"},"Tools",-1)),e("div",ss,r(y.value.length),1),s[20]||(s[20]=e("div",{class:"stat-desc"},"available tools",-1))])]),e("div",ts,[e("div",os,[s[21]||(s[21]=e("div",{class:"stat-figure text-secondary"},[e("svg",{class:"w-8 h-8",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"})])],-1)),s[22]||(s[22]=e("div",{class:"stat-title"},"Status",-1)),e("div",as,r(t.value.enabled?"Enabled":"Disabled"),1),e("div",ns,r(t.value.quarantined?"Quarantined":"Active"),1)])]),e("div",ls,[e("div",rs,[s[23]||(s[23]=e("div",{class:"stat-figure text-info"},[e("svg",{class:"w-8 h-8",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M13 10V3L4 14h7v7l9-11h-7z"})])],-1)),s[24]||(s[24]=e("div",{class:"stat-title"},"Protocol",-1)),e("div",is,r(t.value.protocol),1),s[25]||(s[25]=e("div",{class:"stat-desc"},"communication type",-1))])]),e("div",ds,[e("div",us,[s[26]||(s[26]=e("div",{class:"stat-figure text-warning"},[e("svg",{class:"w-8 h-8",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"})])],-1)),s[27]||(s[27]=e("div",{class:"stat-title"},"Connection",-1)),e("div",cs,r(t.value.connected?"Online":t.value.connecting?"Connecting":"Offline"),1),s[28]||(s[28]=e("div",{class:"stat-desc"},"current state",-1))])])]),e("div",vs,[t.value.last_error?(a(),n("div",ms,[s[30]||(s[30]=e("svg",{class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})],-1)),e("div",null,[s[29]||(s[29]=e("h3",{class:"font-bold"},"Server Error",-1)),e("div",ps,r(t.value.last_error),1)])])):i("",!0),t.value.quarantined?(a(),n("div",gs,[s[32]||(s[32]=e("svg",{class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.5 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"})],-1)),s[33]||(s[33]=e("div",null,[e("h3",{class:"font-bold"},"Security Quarantine"),e("div",{class:"text-sm"},"This server is quarantined and requires manual approval before tools can be executed.")],-1)),e("button",{onClick:O,disabled:l.value,class:"btn btn-sm btn-warning"},[l.value?(a(),n("span",fs)):i("",!0),s[31]||(s[31]=f(" Unquarantine ",-1))],8,bs)])):i("",!0)]),e("div",hs,[e("button",{class:h(["tab tab-lg",b.value==="tools"?"tab-active":""]),onClick:s[1]||(s[1]=m=>b.value="tools")}," Tools ("+r(y.value.length)+") ",3),e("button",{class:h(["tab tab-lg",b.value==="logs"?"tab-active":""]),onClick:s[2]||(s[2]=m=>b.value="logs")}," Logs ",2),e("button",{class:h(["tab tab-lg",b.value==="config"?"tab-active":""]),onClick:s[3]||(s[3]=m=>b.value="config")}," Configuration ",2)]),e("div",ys,[b.value==="tools"?(a(),n("div",ks,[k.value?(a(),n("div",_s,[...s[34]||(s[34]=[e("span",{class:"loading loading-spinner loading-lg"},null,-1),e("p",{class:"mt-2"},"Loading tools...",-1)])])):_.value?(a(),n("div",ws,[s[35]||(s[35]=e("svg",{class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})],-1)),e("span",null,r(_.value),1),e("button",{onClick:D,class:"btn btn-sm"},"Retry")])):y.value.length===0?(a(),n("div",xs,[s[36]||(s[36]=e("svg",{class:"w-16 h-16 mx-auto mb-4 opacity-50",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"})],-1)),s[37]||(s[37]=e("h3",{class:"text-xl font-semibold mb-2"},"No tools available",-1)),e("p",Cs,r(t.value.connected?"This server has no tools available.":"Server must be connected to view tools."),1)])):(a(),n("div",Ss,[e("div",Ts,[e("div",null,[s[38]||(s[38]=e("h3",{class:"text-lg font-semibold"},"Available Tools",-1)),e("p",$s,"Tools provided by "+r(t.value.name),1)]),e("div",Ls,[U(e("input",{"onUpdate:modelValue":s[4]||(s[4]=m=>$.value=m),type:"text",placeholder:"Search tools...",class:"input input-bordered input-sm w-64"},null,512),[[ne,$.value]])])]),e("div",Ms,[(a(!0),n(z,null,V(W.value,m=>(a(),n("div",{key:m.name,class:"card bg-base-100 shadow-md"},[e("div",Bs,[e("h4",Ns,r(m.name),1),e("p",js,r(m.description||"No description available"),1),m.annotations?(a(),ie(we,{key:0,annotations:m.annotations,class:"mt-2"},null,8,["annotations"])):i("",!0),m.input_schema?(a(),n("div",Es,[e("button",{class:"btn btn-sm btn-outline",onClick:F=>Y(m)}," View Schema ",8,qs)])):i("",!0)])]))),128))])]))])):i("",!0),b.value==="logs"?(a(),n("div",Ds,[e("div",As,[e("div",null,[s[39]||(s[39]=e("h3",{class:"text-lg font-semibold"},"Server Logs",-1)),e("p",Os,"Recent log entries for "+r(t.value.name),1)]),e("div",Fs,[U(e("select",{"onUpdate:modelValue":s[5]||(s[5]=m=>L.value=m),class:"select select-bordered select-sm"},[...s[40]||(s[40]=[e("option",{value:50},"Last 50 lines",-1),e("option",{value:100},"Last 100 lines",-1),e("option",{value:200},"Last 200 lines",-1),e("option",{value:500},"Last 500 lines",-1)])],512),[[le,L.value]]),e("button",{onClick:B,class:"btn btn-sm btn-outline",disabled:x.value},[x.value?(a(),n("span",Us)):i("",!0),s[41]||(s[41]=f(" Refresh ",-1))],8,Rs)])]),x.value?(a(),n("div",zs,[...s[42]||(s[42]=[e("span",{class:"loading loading-spinner loading-lg"},null,-1),e("p",{class:"mt-2"},"Loading logs...",-1)])])):C.value?(a(),n("div",Vs,[s[43]||(s[43]=e("svg",{class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})],-1)),e("span",null,r(C.value),1),e("button",{onClick:B,class:"btn btn-sm"},"Retry")])):j.value.length===0?(a(),n("div",Hs,[...s[44]||(s[44]=[e("svg",{class:"w-16 h-16 mx-auto mb-4 opacity-50",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"})],-1),e("h3",{class:"text-xl font-semibold mb-2"},"No logs available",-1),e("p",{class:"text-base-content/70"},"No log entries found for this server.",-1)])])):(a(),n("div",Ps,[(a(!0),n(z,null,V(j.value,(m,F)=>(a(),n("pre",{key:F,class:"text-xs"},[e("code",null,r(m),1)]))),128))]))])):i("",!0),b.value==="config"?(a(),n("div",Qs,[e("div",Ws,[e("div",null,[s[52]||(s[52]=e("h3",{class:"text-lg font-semibold mb-4"},"Server Configuration",-1)),e("div",Is,[e("div",Js,[e("div",null,[s[45]||(s[45]=e("label",{class:"label"},[e("span",{class:"label-text font-medium"},"Name")],-1)),e("input",{value:t.value.name,readonly:"",class:"input input-bordered w-full"},null,8,Gs)]),e("div",null,[s[46]||(s[46]=e("label",{class:"label"},[e("span",{class:"label-text font-medium"},"Protocol")],-1)),e("input",{value:t.value.protocol,readonly:"",class:"input input-bordered w-full"},null,8,Ks)]),t.value.url?(a(),n("div",Xs,[s[47]||(s[47]=e("label",{class:"label"},[e("span",{class:"label-text font-medium"},"URL")],-1)),e("input",{value:t.value.url,readonly:"",class:"input input-bordered w-full"},null,8,Ys)])):i("",!0),t.value.command?(a(),n("div",Zs,[s[48]||(s[48]=e("label",{class:"label"},[e("span",{class:"label-text font-medium"},"Command")],-1)),e("input",{value:t.value.command,readonly:"",class:"input input-bordered w-full"},null,8,et)])):i("",!0)]),e("div",st,[e("div",tt,[s[49]||(s[49]=e("label",{class:"label"},[e("span",{class:"label-text font-medium"},"Enabled")],-1)),e("input",{type:"checkbox",checked:t.value.enabled,onChange:A,class:"toggle",disabled:l.value},null,40,ot)]),e("div",at,[s[50]||(s[50]=e("label",{class:"label"},[e("span",{class:"label-text font-medium"},"Quarantined")],-1)),e("input",{type:"checkbox",checked:t.value.quarantined,readonly:"",class:"toggle",disabled:""},null,8,nt)]),e("div",null,[s[51]||(s[51]=e("label",{class:"label"},[e("span",{class:"label-text font-medium"},"Tools Count")],-1)),e("input",{value:t.value.tool_count,readonly:"",class:"input input-bordered w-full"},null,8,lt)])])])])])])):i("",!0)])])):(a(),n("div",$e,[s[11]||(s[11]=e("svg",{class:"w-16 h-16 mx-auto mb-4 opacity-50",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2"})],-1)),s[12]||(s[12]=e("h3",{class:"text-xl font-semibold mb-2"},"Server not found",-1)),e("p",Le,' The server "'+r(o.serverName)+'" was not found. ',1),E(S,{to:"/servers",class:"btn btn-primary"},{default:R(()=>[...s[10]||(s[10]=[f(" Back to Servers ",-1)])]),_:1})])),w.value?(a(),n("div",rt,[e("div",it,[e("h3",dt,r(w.value.name)+" - Input Schema",1),e("div",ut,[e("pre",null,[e("code",null,r(JSON.stringify(w.value.input_schema,null,2)),1)])]),e("div",ct,[e("button",{class:"btn",onClick:s[6]||(s[6]=m=>w.value=null)},"Close")])])])):i("",!0),E(re,{hints:Z.value},null,8,["hints"])])}}});export{mt as default};
diff --git a/web/frontend/dist/assets/Servers-DInf8P5a.js b/web/frontend/dist/assets/Servers-DInf8P5a.js
new file mode 100644
index 00000000..c5d00b92
--- /dev/null
+++ b/web/frontend/dist/assets/Servers-DInf8P5a.js
@@ -0,0 +1,16 @@
+import{d as L,b as q,e as N,r as T,f,c as d,o,a as e,g as v,t as u,n as _,h as x,i as E,j as b,k as O,w,l as m,m as H,v as R,T as V,C as z,F as Q,p as G,q as I}from"./index-DMP0eryo.js";const J={class:"card bg-base-100 shadow-md hover:shadow-lg transition-shadow"},K={class:"card-body"},W={class:"flex justify-between items-start mb-4"},X={class:"flex-1 min-w-0 mr-2"},Y={class:"card-title text-lg truncate"},Z={class:"text-sm text-base-content/70 truncate"},ee=["data-tip"],se={class:"grid grid-cols-2 gap-4 mb-4"},te={class:"stat bg-base-200 rounded-lg p-3"},re={class:"stat-value text-lg"},ae={key:0,class:"stat-desc text-xs"},ne={class:"stat bg-base-200 rounded-lg p-3"},oe={class:"stat-value text-lg"},le={class:"flex items-center space-x-1"},ie=["checked","disabled"],de={class:"text-sm"},ue={key:0,class:"alert alert-error alert-sm mb-4"},ce={class:"text-xs"},ve={key:1,class:"alert alert-warning alert-sm mb-4"},me={class:"card-actions justify-end space-x-2"},be=["disabled"],ge={key:0,class:"loading loading-spinner loading-xs"},pe=["disabled"],fe={key:0,class:"loading loading-spinner loading-xs"},he=["disabled"],ye={key:0,class:"loading loading-spinner loading-xs"},ke=["disabled"],_e={key:0,class:"loading loading-spinner loading-xs"},we=["disabled"],xe={key:0,class:"loading loading-spinner loading-xs"},Ce=["disabled"],Se={key:0,class:"modal modal-open"},$e={class:"modal-box"},Te={class:"mb-4"},Me={class:"modal-action"},Ae=["disabled"],Le=["disabled"],qe={key:0,class:"loading loading-spinner loading-xs"},Ee=L({__name:"ServerCard",props:{server:{}},setup(A){const s=A,c=q(),i=N(),n=T(!1),h=T(!1),M=f(()=>s.server.protocol==="http"||s.server.protocol==="streamable-http"),p=f(()=>{const t=s.server.health;if(t)switch(t.admin_state){case"disabled":return"badge-neutral";case"quarantined":return"badge-secondary";default:switch(t.level){case"healthy":return"badge-success";case"degraded":return"badge-warning";case"unhealthy":return"badge-error";default:return"badge-ghost"}}return s.server.connected?"badge-success":s.server.connecting?"badge-warning":"badge-error"}),r=f(()=>{const t=s.server.health;return t?t.summary||t.level:s.server.connected?"Connected":s.server.connecting?"Connecting":"Disconnected"}),l=f(()=>{const t=s.server.health;return t!=null&&t.detail?t.detail:""}),g=f(()=>{var t;return((t=s.server.health)==null?void 0:t.action)||""}),k=f(()=>!(!s.server.last_error||["login","set_secret","configure"].includes(g.value))),y=f(()=>!s.server.enabled||s.server.user_logged_out||!M.value||!(s.server.authenticated===!0)||s.server.connecting?!1:s.server.connected?!0:s.server.last_error?!(s.server.oauth_status==="expired"||s.server.last_error.includes("OAuth authentication required")||s.server.last_error.includes("authorization")||s.server.last_error.includes("401")||s.server.last_error.includes("invalid_token")):s.server.oauth_status==="authenticated");async function C(){n.value=!0;try{s.server.enabled?(await c.disableServer(s.server.name),i.addToast({type:"success",title:"Server Disabled",message:`${s.server.name} has been disabled`})):(await c.enableServer(s.server.name),i.addToast({type:"success",title:"Server Enabled",message:`${s.server.name} has been enabled`}))}catch(t){i.addToast({type:"error",title:"Operation Failed",message:t instanceof Error?t.message:"Unknown error"})}finally{n.value=!1}}async function S(){n.value=!0;try{await c.enableServer(s.server.name),i.addToast({type:"success",title:"Server Enabled",message:`${s.server.name} has been enabled`})}catch(t){i.addToast({type:"error",title:"Enable Failed",message:t instanceof Error?t.message:"Unknown error"})}finally{n.value=!1}}async function j(){n.value=!0;try{await c.restartServer(s.server.name),i.addToast({type:"success",title:"Server Restarted",message:`${s.server.name} is restarting`})}catch(t){i.addToast({type:"error",title:"Restart Failed",message:t instanceof Error?t.message:"Unknown error"})}finally{n.value=!1}}async function B(){n.value=!0;try{await c.triggerOAuthLogin(s.server.name),i.addToast({type:"success",title:"OAuth Login Triggered",message:`Check your browser for ${s.server.name} login`})}catch(t){i.addToast({type:"error",title:"OAuth Failed",message:t instanceof Error?t.message:"Unknown error"})}finally{n.value=!1}}async function D(){n.value=!0;try{await c.triggerOAuthLogout(s.server.name),i.addToast({type:"success",title:"OAuth Logout Successful",message:`${s.server.name} has been logged out`})}catch(t){i.addToast({type:"error",title:"Logout Failed",message:t instanceof Error?t.message:"Unknown error"})}finally{n.value=!1}}async function P(){n.value=!0;try{await c.unquarantineServer(s.server.name),i.addToast({type:"success",title:"Server Unquarantined",message:`${s.server.name} has been removed from quarantine`})}catch(t){i.addToast({type:"error",title:"Unquarantine Failed",message:t instanceof Error?t.message:"Unknown error"})}finally{n.value=!1}}async function F(){n.value=!0;try{await c.deleteServer(s.server.name),i.addToast({type:"success",title:"Server Deleted",message:`${s.server.name} has been deleted successfully`}),h.value=!1}catch(t){i.addToast({type:"error",title:"Delete Failed",message:t instanceof Error?t.message:"Unknown error"})}finally{n.value=!1}}return(t,a)=>{const $=O("router-link");return o(),d("div",J,[e("div",K,[e("div",W,[e("div",X,[e("h3",Y,u(t.server.name),1),e("p",Z,u(t.server.protocol)+" • "+u(t.server.url||t.server.command||"No endpoint"),1)]),e("div",{class:_(["badge badge-sm flex-shrink-0",p.value,l.value?"tooltip tooltip-left":""]),"data-tip":l.value},u(r.value),11,ee)]),e("div",se,[e("div",te,[a[2]||(a[2]=e("div",{class:"stat-title text-xs"},"Tools",-1)),e("div",re,u(t.server.tool_count),1),t.server.tool_list_token_size?(o(),d("div",ae,u(t.server.tool_list_token_size.toLocaleString())+" tokens ",1)):v("",!0)]),e("div",ne,[a[3]||(a[3]=e("div",{class:"stat-title text-xs"},"Status",-1)),e("div",oe,[e("div",le,[e("input",{type:"checkbox",checked:t.server.enabled,onChange:C,class:"toggle toggle-sm",disabled:n.value},null,40,ie),e("span",de,u(t.server.enabled?"Enabled":"Disabled"),1)])])])]),k.value?(o(),d("div",ue,[a[4]||(a[4]=e("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})],-1)),e("span",ce,u(t.server.last_error),1)])):v("",!0),t.server.quarantined?(o(),d("div",ve,[...a[5]||(a[5]=[e("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.5 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"})],-1),e("span",{class:"text-xs"},"Server is quarantined",-1)])])):v("",!0),e("div",me,[g.value==="approve"?(o(),d("button",{key:0,onClick:P,disabled:n.value,class:"btn btn-sm btn-warning"},[n.value?(o(),d("span",ge)):v("",!0),a[6]||(a[6]=b(" Approve ",-1))],8,be)):v("",!0),g.value==="enable"?(o(),d("button",{key:1,onClick:S,disabled:n.value,class:"btn btn-sm btn-primary"},[n.value?(o(),d("span",fe)):v("",!0),a[7]||(a[7]=b(" Enable ",-1))],8,pe)):v("",!0),g.value==="login"?(o(),d("button",{key:2,onClick:B,disabled:n.value,class:"btn btn-sm btn-primary"},[n.value?(o(),d("span",ye)):v("",!0),a[8]||(a[8]=b(" Login ",-1))],8,he)):v("",!0),g.value==="restart"?(o(),d("button",{key:3,onClick:j,disabled:n.value,class:"btn btn-sm btn-primary"},[n.value?(o(),d("span",_e)):v("",!0),a[9]||(a[9]=b(" Restart ",-1))],8,ke)):v("",!0),g.value==="view_logs"?(o(),x($,{key:4,to:`/servers/${t.server.name}?tab=logs`,class:"btn btn-sm btn-primary"},{default:w(()=>[...a[10]||(a[10]=[b(" View Logs ",-1)])]),_:1},8,["to"])):v("",!0),g.value==="set_secret"?(o(),x($,{key:5,to:"/secrets",class:"btn btn-sm btn-primary"},{default:w(()=>[...a[11]||(a[11]=[b(" Set Secret ",-1)])]),_:1})):v("",!0),g.value==="configure"?(o(),x($,{key:6,to:`/servers/${t.server.name}?tab=config`,class:"btn btn-sm btn-primary"},{default:w(()=>[...a[12]||(a[12]=[b(" Configure ",-1)])]),_:1},8,["to"])):v("",!0),y.value?(o(),d("button",{key:7,onClick:D,disabled:n.value,class:"btn btn-sm btn-outline btn-warning"},[n.value?(o(),d("span",xe)):v("",!0),a[13]||(a[13]=b(" Logout ",-1))],8,we)):v("",!0),E($,{to:`/servers/${t.server.name}`,class:"btn btn-sm btn-outline"},{default:w(()=>[...a[14]||(a[14]=[b(" Details ",-1)])]),_:1},8,["to"]),e("button",{onClick:a[0]||(a[0]=U=>h.value=!0),disabled:n.value,class:"btn btn-sm btn-error"}," Delete ",8,Ce)])]),h.value?(o(),d("div",Se,[e("div",$e,[a[18]||(a[18]=e("h3",{class:"font-bold text-lg mb-4"},"Delete Server",-1)),e("p",Te,[a[15]||(a[15]=b(" Are you sure you want to delete the server ",-1)),e("strong",null,u(t.server.name),1),a[16]||(a[16]=b("? ",-1))]),a[19]||(a[19]=e("p",{class:"text-sm text-base-content/70 mb-6"}," This action cannot be undone. The server will be removed from your configuration. ",-1)),e("div",Me,[e("button",{onClick:a[1]||(a[1]=U=>h.value=!1),disabled:n.value,class:"btn btn-outline"}," Cancel ",8,Ae),e("button",{onClick:F,disabled:n.value,class:"btn btn-error"},[n.value?(o(),d("span",qe)):v("",!0),a[17]||(a[17]=b(" Delete Server ",-1))],8,Le)])])])):v("",!0)])}}}),je={class:"space-y-6"},Be={class:"flex justify-between items-center"},De={class:"flex items-center space-x-2"},Pe=["disabled"],Fe={key:0,class:"loading loading-spinner loading-sm"},Ue={class:"stats shadow bg-base-100 w-full"},Ne={class:"stat"},Oe={class:"stat-value"},He={class:"stat-desc"},Re={class:"stat"},Ve={class:"stat-value text-success"},ze={class:"stat-desc"},Qe={class:"stat"},Ge={class:"stat-value text-warning"},Ie={class:"stat"},Je={class:"stat-value text-info"},Ke={class:"flex flex-wrap gap-4 items-center justify-between"},We={class:"flex flex-wrap gap-2"},Xe={class:"form-control"},Ye={key:0,class:"text-center py-12"},Ze={key:1,class:"alert alert-error"},es={class:"text-sm"},ss={key:2,class:"text-center py-12"},ts={class:"text-base-content/70 mb-4"},as=L({__name:"Servers",setup(A){const s=q(),c=T("all"),i=T(""),n=f(()=>{let p=s.servers;switch(c.value){case"connected":p=s.connectedServers;break;case"enabled":p=s.enabledServers;break;case"quarantined":p=s.quarantinedServers;break}if(i.value){const r=i.value.toLowerCase();p=p.filter(l=>{var g,k;return l.name.toLowerCase().includes(r)||((g=l.url)==null?void 0:g.toLowerCase().includes(r))||((k=l.command)==null?void 0:k.toLowerCase().includes(r))})}return p});async function h(){await s.fetchServers()}const M=f(()=>[{icon:"➕",title:"Add New MCP Servers",description:"Multiple ways to add servers to MCPProxy",sections:[{title:"Add HTTP/HTTPS server",codeBlock:{language:"bash",code:`# Add a remote MCP server
+mcpproxy call tool --tool-name=upstream_servers \\
+ --json_args='{"operation":"add","name":"my-server","url":"https://api.example.com/mcp","protocol":"http","enabled":true}'`}},{title:"Add stdio server (npx)",codeBlock:{language:"bash",code:`# Add an npm-based MCP server
+mcpproxy call tool --tool-name=upstream_servers \\
+ --json_args='{"operation":"add","name":"filesystem","command":"npx","args_json":"[\\"@modelcontextprotocol/server-filesystem\\"]","protocol":"stdio","enabled":true}'`}},{title:"Add stdio server (uvx)",codeBlock:{language:"bash",code:`# Add a Python-based MCP server
+mcpproxy call tool --tool-name=upstream_servers \\
+ --json_args='{"operation":"add","name":"python-server","command":"uvx","args_json":"[\\"mcp-server-package\\"]","protocol":"stdio","enabled":true}'`}}]},{icon:"🔧",title:"Manage Servers via CLI",description:"Common server management operations",sections:[{title:"List all servers",codeBlock:{language:"bash",code:`# View all upstream servers
+mcpproxy upstream list`}},{title:"Enable/disable server",codeBlock:{language:"bash",code:`# Disable a server
+mcpproxy call tool --tool-name=upstream_servers \\
+ --json_args='{"operation":"update","name":"server-name","enabled":false}'
+
+# Enable a server
+mcpproxy call tool --tool-name=upstream_servers \\
+ --json_args='{"operation":"update","name":"server-name","enabled":true}'`}},{title:"Remove server",codeBlock:{language:"bash",code:`# Delete a server
+mcpproxy call tool --tool-name=upstream_servers \\
+ --json_args='{"operation":"delete","name":"server-name"}'`}}]},{icon:"🤖",title:"Use LLM Agents to Manage Servers",description:"Let AI agents help you configure MCPProxy",sections:[{title:"Example LLM prompts",list:["Add the GitHub MCP server from @modelcontextprotocol/server-github to my configuration","Show me all quarantined servers and help me review them","Disable all servers that haven't been used in the last 24 hours","Find and add MCP servers for working with Slack"]}]}]);return(p,r)=>(o(),d("div",je,[e("div",Be,[r[9]||(r[9]=e("div",null,[e("h1",{class:"text-3xl font-bold"},"Servers"),e("p",{class:"text-base-content/70 mt-1"},"Manage upstream MCP servers")],-1)),e("div",De,[e("button",{onClick:h,disabled:m(s).loading.loading,class:"btn btn-outline"},[r[8]||(r[8]=e("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"})],-1)),m(s).loading.loading?(o(),d("span",Fe)):v("",!0),b(" "+u(m(s).loading.loading?"Refreshing...":"Refresh"),1)],8,Pe)])]),e("div",Ue,[e("div",Ne,[r[10]||(r[10]=e("div",{class:"stat-title"},"Total Servers",-1)),e("div",Oe,u(m(s).serverCount.total),1),e("div",He,u(m(s).serverCount.enabled)+" enabled",1)]),e("div",Re,[r[11]||(r[11]=e("div",{class:"stat-title"},"Connected",-1)),e("div",Ve,u(m(s).serverCount.connected),1),e("div",ze,u(Math.round(m(s).serverCount.connected/m(s).serverCount.total*100)||0)+"% online",1)]),e("div",Qe,[r[12]||(r[12]=e("div",{class:"stat-title"},"Quarantined",-1)),e("div",Ge,u(m(s).serverCount.quarantined),1),r[13]||(r[13]=e("div",{class:"stat-desc"},"Need security review",-1))]),e("div",Ie,[r[14]||(r[14]=e("div",{class:"stat-title"},"Total Tools",-1)),e("div",Je,u(m(s).totalTools),1),r[15]||(r[15]=e("div",{class:"stat-desc"},"Available across all servers",-1))])]),e("div",Ke,[e("div",We,[e("button",{onClick:r[0]||(r[0]=l=>c.value="all"),class:_(["btn btn-sm",c.value==="all"?"btn-primary":"btn-outline"])}," All ("+u(m(s).servers.length)+") ",3),e("button",{onClick:r[1]||(r[1]=l=>c.value="connected"),class:_(["btn btn-sm",c.value==="connected"?"btn-primary":"btn-outline"])}," Connected ("+u(m(s).connectedServers.length)+") ",3),e("button",{onClick:r[2]||(r[2]=l=>c.value="enabled"),class:_(["btn btn-sm",c.value==="enabled"?"btn-primary":"btn-outline"])}," Enabled ("+u(m(s).enabledServers.length)+") ",3),e("button",{onClick:r[3]||(r[3]=l=>c.value="quarantined"),class:_(["btn btn-sm",c.value==="quarantined"?"btn-primary":"btn-outline"])}," Quarantined ("+u(m(s).quarantinedServers.length)+") ",3)]),e("div",Xe,[H(e("input",{"onUpdate:modelValue":r[4]||(r[4]=l=>i.value=l),type:"text",placeholder:"Search servers...",class:"input input-bordered input-sm w-64"},null,512),[[R,i.value]])])]),m(s).loading.loading?(o(),d("div",Ye,[...r[16]||(r[16]=[e("span",{class:"loading loading-spinner loading-lg"},null,-1),e("p",{class:"mt-4"},"Loading servers...",-1)])])):m(s).loading.error?(o(),d("div",Ze,[r[18]||(r[18]=e("svg",{class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})],-1)),e("div",null,[r[17]||(r[17]=e("h3",{class:"font-bold"},"Failed to load servers",-1)),e("div",es,u(m(s).loading.error),1)]),e("button",{onClick:h,class:"btn btn-sm"}," Try Again ")])):n.value.length===0?(o(),d("div",ss,[r[19]||(r[19]=e("svg",{class:"w-24 h-24 mx-auto mb-4 opacity-50",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2"})],-1)),r[20]||(r[20]=e("h3",{class:"text-xl font-semibold mb-2"},"No servers found",-1)),e("p",ts,u(i.value?"No servers match your search criteria":`No ${c.value==="all"?"":c.value} servers available`.replace(/\s+/g," ").trim()),1),i.value?(o(),d("button",{key:0,onClick:r[5]||(r[5]=l=>i.value=""),class:"btn btn-outline"}," Clear Search ")):v("",!0)])):(o(),x(V,{key:3,name:"server-list",tag:"div",class:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"},{default:w(()=>[(o(!0),d(Q,null,G(n.value,(l,g,k,y)=>{const C=[l.connected,l.connecting,l.enabled,l.quarantined,l.tool_count,l.last_error,l.authenticated];if(y&&y.key===l.name&&I(y,C))return y;const S=(o(),x(Ee,{key:l.name,server:l},null,8,["server"]));return S.memo=C,S},r,6),128))]),_:1})),E(z,{hints:M.value},null,8,["hints"])]))}});export{as as default};
diff --git a/web/frontend/dist/assets/Sessions-B3Q2H5AK.js b/web/frontend/dist/assets/Sessions-B3Q2H5AK.js
new file mode 100644
index 00000000..4aacaf43
--- /dev/null
+++ b/web/frontend/dist/assets/Sessions-B3Q2H5AK.js
@@ -0,0 +1 @@
+import{d as b,r as v,x as f,I as k,c as n,o,a as t,B as y,j as x,n as _,g as i,t as l,F as w,p as C,z as M,i as S,w as T,k as A}from"./index-DMP0eryo.js";const N={class:"space-y-6"},j={class:"flex justify-between items-center"},B=["disabled"],P={class:"card bg-base-100 shadow-md"},V={class:"card-body"},D={key:0,class:"flex justify-center py-12"},I={key:1,class:"alert alert-error"},E={key:2,class:"text-center py-12 text-base-content/60"},$={key:3,class:"overflow-x-auto"},z={class:"table"},L=["title"],U={class:"font-medium"},F={key:0,class:"text-xs text-base-content/60"},R={class:"flex flex-wrap gap-1"},H={key:0,class:"badge badge-sm badge-info",title:"Client supports roots capability"},q={key:1,class:"badge badge-sm badge-info",title:"Client supports sampling capability"},J=["title"],G={key:3,class:"text-xs text-base-content/40"},K={class:"font-mono"},O={class:"font-mono text-sm",title:"Total tokens used in this session"},Q={class:"text-sm"},W={class:"text-xs text-base-content/60"},X={class:"text-sm"},Y={class:"text-xs text-base-content/60"},Z={key:4,class:"text-sm text-base-content/60 mt-4 text-center"},st=b({__name:"Sessions",setup(tt){const r=v([]),d=v(!1),c=v(null);let u=null;const m=async()=>{d.value=!0,c.value=null;try{const a=await M.getSessions(10);a.success&&a.data?r.value=a.data.sessions||[]:c.value=a.error||"Failed to load sessions"}catch(a){c.value=a instanceof Error?a.message:"Unknown error"}finally{d.value=!1}},h=a=>new Date(a).toLocaleString(),g=a=>{const s=Date.now(),p=new Date(a).getTime(),e=s-p;return e<1e3?"Just now":e<6e4?`${Math.floor(e/1e3)}s ago`:e<36e5?`${Math.floor(e/6e4)}m ago`:e<864e5?`${Math.floor(e/36e5)}h ago`:`${Math.floor(e/864e5)}d ago`};return f(()=>{m(),u=setInterval(m,3e4)}),k(()=>{u&&clearInterval(u)}),(a,s)=>{const p=A("router-link");return o(),n("div",N,[t("div",j,[s[2]||(s[2]=t("div",null,[t("h1",{class:"text-3xl font-bold"},"MCP Sessions"),t("p",{class:"text-base-content/70 mt-1"},"Monitor active and recent MCP client sessions")],-1)),t("button",{onClick:m,class:"btn btn-sm btn-ghost",disabled:d.value},[(o(),n("svg",{class:_(["w-4 h-4",{"animate-spin":d.value}]),fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[...s[0]||(s[0]=[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"},null,-1)])],2)),s[1]||(s[1]=x(" Refresh ",-1))],8,B)]),t("div",P,[t("div",V,[d.value?(o(),n("div",D,[...s[3]||(s[3]=[t("span",{class:"loading loading-spinner loading-lg"},null,-1)])])):c.value?(o(),n("div",I,[s[4]||(s[4]=t("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})],-1)),t("span",null,l(c.value),1)])):r.value.length===0?(o(),n("div",E,[...s[5]||(s[5]=[t("svg",{class:"w-16 h-16 mx-auto mb-4 opacity-30",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"})],-1),t("p",{class:"text-lg"},"No sessions found",-1),t("p",{class:"text-sm mt-1"},"Sessions will appear here when MCP clients connect",-1)])])):(o(),n("div",$,[t("table",z,[s[7]||(s[7]=t("thead",null,[t("tr",null,[t("th",null,"Session ID"),t("th",null,"Client"),t("th",null,"Status"),t("th",null,"Capabilities"),t("th",null,"Tool Calls"),t("th",null,"Tokens"),t("th",null,"Started"),t("th",null,"Last Active"),t("th",null,"Actions")])],-1)),t("tbody",null,[(o(!0),n(w,null,C(r.value,e=>(o(),n("tr",{key:e.id},[t("td",null,[t("code",{class:"text-xs bg-base-200 px-2 py-1 rounded",title:e.id},l(e.id.substring(0,12))+"... ",9,L)]),t("td",null,[t("div",U,l(e.client_name||"Unknown"),1),e.client_version?(o(),n("div",F," v"+l(e.client_version),1)):i("",!0)]),t("td",null,[t("div",{class:_(["badge",e.status==="active"?"badge-success":"badge-neutral"])},l(e.status==="active"?"Active":"Closed"),3)]),t("td",null,[t("div",R,[e.has_roots?(o(),n("span",H," Roots ")):i("",!0),e.has_sampling?(o(),n("span",q," Sampling ")):i("",!0),e.experimental&&e.experimental.length>0?(o(),n("span",{key:2,class:"badge badge-sm badge-warning",title:`Experimental features: ${e.experimental.join(", ")}`}," Experimental ("+l(e.experimental.length)+") ",9,J)):i("",!0),!e.has_roots&&!e.has_sampling&&(!e.experimental||e.experimental.length===0)?(o(),n("span",G," None ")):i("",!0)])]),t("td",null,[t("span",K,l(e.tool_call_count),1)]),t("td",null,[t("span",O,l(e.total_tokens.toLocaleString()),1)]),t("td",null,[t("div",Q,l(h(e.start_time)),1),t("div",W,l(g(e.start_time)),1)]),t("td",null,[t("div",X,l(h(e.last_activity)),1),t("div",Y,l(g(e.last_activity)),1)]),t("td",null,[S(p,{to:{name:"activity",query:{session:e.id}},class:"btn btn-xs btn-primary",title:"View activity for this session"},{default:T(()=>[...s[6]||(s[6]=[x(" View Activity ",-1)])]),_:1},8,["to"])])]))),128))])])])),r.value.length>0?(o(),n("div",Z," Showing "+l(r.value.length)+" most recent sessions ",1)):i("",!0)])]),s[8]||(s[8]=y('About MCP Sessions
MCP sessions represent individual connections from AI clients (like Claude Code) to MCPProxy. Each session tracks:
- Tool Calls: Number of tool invocations made during the session
- Token Usage: Total tokens consumed across all tool calls
- Duration: Time from connection to disconnection
Sessions are retained for the 100 most recent connections.
',1))])}}});export{st as default};
diff --git a/web/frontend/dist/assets/Settings-Bk3uo8hY.js b/web/frontend/dist/assets/Settings-Bk3uo8hY.js
new file mode 100644
index 00000000..cd5ee88f
--- /dev/null
+++ b/web/frontend/dist/assets/Settings-Bk3uo8hY.js
@@ -0,0 +1,22 @@
+import{d as ie,G as A,H as U,f as T,I as oe,s as C,r as y,x as J,J as le,b as pe,c as p,o as m,a as c,B as me,i as R,g as j,n as he,t as I,l as ye,F as be,p as we,j as L,C as Oe,z as F}from"./index-DMP0eryo.js";function Se(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function W(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(a){return Object.getOwnPropertyDescriptor(e,a).enumerable})),n.push.apply(n,r)}return n}function G(e){for(var t=1;t=0)&&(n[a]=e[a]);return n}function _e(e,t){if(e==null)return{};var n=je(e,t),r,a;if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Ce(e,t){return Pe(e)||xe(e,t)||Me(e,t)||Ee()}function Pe(e){if(Array.isArray(e))return e}function xe(e,t){if(!(typeof Symbol>"u"||!(Symbol.iterator in Object(e)))){var n=[],r=!0,a=!1,i=void 0;try{for(var l=e[Symbol.iterator](),o;!(r=(o=l.next()).done)&&(n.push(o.value),!(t&&n.length===t));r=!0);}catch(u){a=!0,i=u}finally{try{!r&&l.return!=null&&l.return()}finally{if(a)throw i}}return n}}function Me(e,t){if(e){if(typeof e=="string")return K(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);if(n==="Object"&&e.constructor&&(n=e.constructor.name),n==="Map"||n==="Set")return Array.from(e);if(n==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return K(e,t)}}function K(e,t){(t==null||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=e.length?e.apply(this,a):function(){for(var l=arguments.length,o=new Array(l),u=0;u1&&arguments[1]!==void 0?arguments[1]:{};k.initial(e),k.handler(t);var n={current:e},r=_(He)(n,t),a=_(ze)(n),i=_(k.changes)(e),l=_(Be)(n);function o(){var v=arguments.length>0&&arguments[0]!==void 0?arguments[0]:function(g){return g};return k.selector(v),v(n.current)}function u(v){ke(r,a,i,l)(v)}return[o,u]}function Be(e,t){return P(t)?t(e.current):t}function ze(e,t){return e.current=Q(Q({},e.current),t),t}function He(e,t,n){return P(t)?t(e.current):Object.keys(n).forEach(function(r){var a;return(a=t[r])===null||a===void 0?void 0:a.call(t,e.current[r])}),n}var Ue={create:qe},Je={paths:{vs:"https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs"}};function Re(e){return function t(){for(var n=this,r=arguments.length,a=new Array(r),i=0;i=e.length?e.apply(this,a):function(){for(var l=arguments.length,o=new Array(l),u=0;ut in e?st(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,B=(e,t)=>{for(var n in t||(t={}))dt.call(t,n)&&te(e,n,t[n]);if(ee)for(var n of ee(t))ft.call(t,n)&&te(e,n,t[n]);return e},vt=(e,t)=>ut(e,ct(t));const z={wrapper:{display:"flex",position:"relative",textAlign:"initial"},fullWidth:{width:"100%"},hide:{display:"none"}};function gt(e,t){const n=T(()=>{const{width:a,height:i}=e;return vt(B({},z.wrapper),{width:a,height:i})}),r=T(()=>B(B({},z.fullWidth),!t.value&&z.hide));return{wrapperStyle:n,containerStyle:r}}function pt(){const e=U(Z.__getMonacoInstance()),t=y(!1);let n;return J(()=>{e.value||(n=Z.init(),n.then(a=>e.value=a).catch(a=>{(a==null?void 0:a.type)!=="cancelation"&&(t.value=!0,console.error("Monaco initialization error:",a))}))}),{monacoRef:e,unload:()=>n==null?void 0:n.cancel(),isLoadFailed:t}}function ne(e){return typeof e=="function"?e():e}function H(e){return e===void 0}function fe(e,t,n,r){return mt(e,r)||ht(e,t,n,r)}function mt(e,t){return e.editor.getModel(ve(e,t))}function ht(e,t,n,r){return e.editor.createModel(t,n,r?ve(e,r):void 0)}function ve(e,t){return e.Uri.parse(t)}var yt=Object.defineProperty,re=Object.getOwnPropertySymbols,bt=Object.prototype.hasOwnProperty,wt=Object.prototype.propertyIsEnumerable,ae=(e,t,n)=>t in e?yt(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,Ot=(e,t)=>{for(var n in t||(t={}))bt.call(t,n)&&ae(e,n,t[n]);if(re)for(var n of re(t))wt.call(t,n)&&ae(e,n,t[n]);return e};const St={display:"flex",height:"100%",width:"100%",justifyContent:"center",alignItems:"center"};var jt=ie({name:"VueMonacoEditor",model:{prop:"value",event:"update:value"},props:{defaultValue:String,defaultPath:String,defaultLanguage:String,value:String,language:String,path:String,theme:{type:String,default:"vs"},line:Number,options:{type:Object,default:()=>({})},overrideServices:{type:Object,default:()=>({})},saveViewState:{type:Boolean,default:!0},width:{type:[Number,String],default:"100%"},height:{type:[Number,String],default:"100%"},className:String},emits:["update:value","beforeMount","mount","change","validate"],setup(e,t){const n=new Map,r=U(null),{monacoRef:a,unload:i,isLoadFailed:l}=pt(),{editorRef:o}=_t(t,e,a,r),{disposeValidator:u}=Ct(t,e,a,o),v=T(()=>!!a.value&&!!o.value),{wrapperStyle:g,containerStyle:M}=gt(e,v);return oe(()=>{var f,h;(f=u.value)==null||f.call(u),o.value?((h=o.value.getModel())==null||h.dispose(),o.value.dispose()):i()}),C([()=>e.path,()=>e.value,()=>e.language,()=>e.line],([f,h,S,w],[E,N,d,s])=>{if(v.value){if(f!==E){const O=fe(a.value,h||e.defaultValue||"",S||e.defaultLanguage||"",f||e.defaultPath||"");e.saveViewState&&n.set(E,o.value.saveViewState()),o.value.setModel(O),e.saveViewState&&o.value.restoreViewState(n.get(f)),H(w)||o.value.revealLine(w);return}o.value.getValue()!==h&&o.value.setValue(h),S!==d&&a.value.editor.setModelLanguage(o.value.getModel(),S),!H(w)&&w!==s&&o.value.revealLine(w)}}),C(()=>e.options,f=>o.value&&o.value.updateOptions(f),{deep:!0}),C(()=>e.theme,f=>a.value&&a.value.editor.setTheme(f)),{containerRef:r,isEditorReady:v,isLoadFailed:l,wrapperStyle:g,containerStyle:M}},render(){const{$slots:e,isEditorReady:t,isLoadFailed:n,wrapperStyle:r,containerStyle:a,className:i}=this;return A("div",{style:r},[!t&&A("div",{style:St},n?e.failure?ne(e.failure):"load failed":e.default?ne(e.default):"loading..."),A("div",{ref:"containerRef",key:"monaco_editor_container",style:a,class:i})])}});function _t({emit:e},t,n,r){const a=U(null);J(()=>{const l=C(n,()=>{r.value&&n.value&&(le(()=>l()),i())},{immediate:!0})});function i(){var l;if(!r.value||!n.value||a.value)return;e("beforeMount",n.value);const o=t.path||t.defaultPath,u=fe(n.value,t.value||t.defaultValue||"",t.language||t.defaultLanguage||"",o||"");a.value=n.value.editor.create(r.value,Ot({model:u,theme:t.theme,automaticLayout:!0,autoIndent:"brackets",formatOnPaste:!0,formatOnType:!0},t.options),t.overrideServices),(l=a.value)==null||l.onDidChangeModelContent(v=>{const g=a.value.getValue();g!==t.value&&(e("update:value",g),e("change",g,v))}),a.value&&!H(t.line)&&a.value.revealLine(t.line),e("mount",a.value,n.value)}return{editorRef:a}}function Ct({emit:e},t,n,r){const a=y(null),i=C([n,r],()=>{if(n.value&&r.value){le(()=>i());const l=n.value.editor.onDidChangeMarkers(o=>{var u,v;const g=(v=(u=r.value)==null?void 0:u.getModel())==null?void 0:v.uri;if(g&&o.find(f=>f.path===g.path)){const f=n.value.editor.getModelMarkers({resource:g});e("validate",f)}});a.value=()=>l==null?void 0:l.dispose()}});return{disposeValidator:a}}const Pt={class:"space-y-6"},xt={class:"card bg-base-100 shadow-md"},Mt={class:"card-body"},Et={class:"flex justify-between items-center mb-4"},It={class:"flex items-center space-x-2"},kt=["disabled"],Tt={key:0,class:"loading loading-spinner loading-xs"},$t={key:1},Vt={class:"border border-base-300 rounded-lg overflow-hidden",style:{height:"600px"}},Nt={key:0,class:"alert alert-error mt-4"},At={class:"list-disc list-inside text-sm"},Lt={class:"font-mono"},Ft={class:"flex justify-between items-center mt-4"},Dt={class:"text-sm text-base-content/70"},qt={key:0,class:"text-warning"},Bt={key:1,class:"text-success"},zt={class:"flex items-center space-x-2"},Ht=["disabled"],Ut={key:0,class:"loading loading-spinner loading-sm"},Jt=["disabled"],Rt={key:0,class:"loading loading-spinner loading-sm"},Gt=ie({__name:"Settings",setup(e){const t=pe(),n=y(""),r=y(!1),a=y(!1),i=y(!1),l=y(null),o=y([]),u=y(null),v=y(null),g={automaticLayout:!0,formatOnType:!0,formatOnPaste:!0,minimap:{enabled:!1},scrollBeyondLastLine:!1,fontSize:14,tabSize:2,wordWrap:"on",lineNumbers:"on",glyphMargin:!0,folding:!0,lineDecorationsWidth:10,lineNumbersMinChars:3};function M(d){v.value=d}function f(){o.value=[],l.value=null,u.value=null;try{JSON.parse(n.value),l.value={valid:!0}}catch{l.value={valid:!1}}}async function h(){r.value=!0,o.value=[],u.value=null;try{const d=await F.getConfig();d.success&&d.data?(n.value=JSON.stringify(d.data.config,null,2),l.value={valid:!0}):o.value=[{field:"general",message:d.error||"Failed to load configuration"}]}catch(d){console.error("Failed to load config:",d),o.value=[{field:"general",message:d.message||"Failed to load configuration"}]}finally{r.value=!1}}async function S(){a.value=!0,o.value=[];try{const d=JSON.parse(n.value),s=await F.validateConfig(d);s.success&&s.data?(o.value=s.data.errors||[],l.value={valid:s.data.valid},s.data.valid&&console.log("Configuration validated successfully")):(o.value=[{field:"general",message:s.error||"Validation failed"}],l.value={valid:!1})}catch(d){o.value=[{field:"json",message:d.message||"Invalid JSON syntax"}],l.value={valid:!1}}finally{a.value=!1}}async function w(){i.value=!0,o.value=[],u.value=null;try{const d=JSON.parse(n.value),s=await F.applyConfig(d);s.success&&s.data?(u.value=s.data,s.data.applied_immediately&&await t.fetchServers(),console.log("Configuration applied successfully:",s.data)):o.value=[{field:"apply",message:s.error||"Failed to apply configuration"}]}catch(d){o.value=[{field:"apply",message:d.message||"Failed to apply configuration"}]}finally{i.value=!1}}const E=T(()=>[{icon:"⚙️",title:"Configuration Management",description:"Edit MCPProxy configuration with JSON editor",sections:[{title:"Hot-Reloadable Settings",text:"These settings are applied immediately without restarting:",list:["Server enable/disable status","Tool limits and search parameters","Log levels and output settings","Cache and timeout settings"]},{title:"Restart Required",text:"These settings require mcpproxy restart to take effect:",list:["Listen address (network binding)","Data directory path","API key authentication","TLS/HTTPS configuration"]}]},{icon:"🔧",title:"CLI Configuration Tools",description:"Manage configuration from the command line",sections:[{title:"View current configuration",codeBlock:{language:"bash",code:`# View configuration location
+mcpproxy config path
+
+# Dump current config
+cat ~/.mcpproxy/mcp_config.json`}},{title:"Backup configuration",codeBlock:{language:"bash",code:`# Create backup
+cp ~/.mcpproxy/mcp_config.json ~/.mcpproxy/mcp_config.backup.json`}}]},{icon:"💡",title:"Configuration Tips",description:"Best practices for managing MCPProxy config",sections:[{title:"Editor features",list:["Use Ctrl+Space for autocomplete suggestions","Use Ctrl+F to search within the configuration","Invalid JSON is highlighted with red squiggles","Format with Ctrl+Shift+F (or Cmd+Shift+F on Mac)"]},{title:"Version control",text:"Consider tracking your configuration in git (excluding secrets):",codeBlock:{language:"bash",code:`# Initialize git repo for configs
+cd ~/.mcpproxy
+git init
+echo "*.db" >> .gitignore
+echo "*.bleve/" >> .gitignore
+git add mcp_config.json
+git commit -m "Initial MCPProxy configuration"`}}]}]);function N(d){console.log("Configuration saved event received, reloading config:",d.detail),h()}return J(()=>{h(),window.addEventListener("mcpproxy:config-saved",N)}),oe(()=>{window.removeEventListener("mcpproxy:config-saved",N)}),(d,s)=>(m(),p("div",Pt,[s[6]||(s[6]=c("div",{class:"flex justify-between items-center"},[c("div",null,[c("h1",{class:"text-3xl font-bold"},"Configuration"),c("p",{class:"text-base-content/70 mt-1"},"Edit your MCPProxy configuration directly. Changes require restart for some settings.")])],-1)),c("div",xt,[c("div",Mt,[c("div",Et,[s[1]||(s[1]=c("div",null,[c("h2",{class:"card-title"},"Configuration Editor"),c("p",{class:"text-sm text-base-content/70 mt-1"}," Edit your MCPProxy configuration directly. Changes require restart for some settings. ")],-1)),c("div",It,[l.value?(m(),p("div",{key:0,class:he(["badge",l.value.valid?"badge-success":"badge-error"])},I(l.value.valid?"✓ Valid":"✗ Invalid"),3)):j("",!0),c("button",{class:"btn btn-sm btn-outline",onClick:h,disabled:r.value},[r.value?(m(),p("span",Tt)):(m(),p("span",$t,"Reload"))],8,kt)])]),c("div",Vt,[R(ye(jt),{value:n.value,"onUpdate:value":s[0]||(s[0]=O=>n.value=O),language:"json",theme:"vs-dark",options:g,onMount:M,onChange:f},null,8,["value"])]),o.value.length>0?(m(),p("div",Nt,[s[3]||(s[3]=c("svg",{xmlns:"http://www.w3.org/2000/svg",class:"stroke-current shrink-0 h-6 w-6",fill:"none",viewBox:"0 0 24 24"},[c("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"})],-1)),c("div",null,[s[2]||(s[2]=c("h3",{class:"font-bold"},"Validation Errors",-1)),c("ul",At,[(m(!0),p(be,null,we(o.value,(O,ge)=>(m(),p("li",{key:ge},[c("span",Lt,I(O.field),1),L(": "+I(O.message),1)]))),128))])])])):j("",!0),c("div",Ft,[c("div",Dt,[u.value&&u.value.requires_restart?(m(),p("span",qt," ⚠️ "+I(u.value.restart_reason),1)):u.value&&u.value.applied_immediately?(m(),p("span",Bt," ✓ Configuration applied successfully ")):j("",!0)]),c("div",zt,[c("button",{class:"btn btn-outline",onClick:S,disabled:a.value||!n.value},[a.value?(m(),p("span",Ut)):j("",!0),s[4]||(s[4]=L(" Validate ",-1))],8,Ht),c("button",{class:"btn btn-primary",onClick:w,disabled:i.value||o.value.length>0||!n.value},[i.value?(m(),p("span",Rt)):j("",!0),s[5]||(s[5]=L(" Apply Configuration ",-1))],8,Jt)])])])]),s[7]||(s[7]=me('Configuration Tips
• Use Ctrl+Space for autocomplete
• Use Ctrl+F to search in the configuration
• Invalid JSON will be highlighted with red squiggles
• Hot-reloadable: server changes, limits, logging
• Requires restart: listen address, data directory, API key, TLS
',1)),R(Oe,{hints:E.value},null,8,["hints"])]))}});export{Gt as default};
diff --git a/web/frontend/dist/assets/UserActivity-D77DruWK.js b/web/frontend/dist/assets/UserActivity-D77DruWK.js
new file mode 100644
index 00000000..eb58d88a
--- /dev/null
+++ b/web/frontend/dist/assets/UserActivity-D77DruWK.js
@@ -0,0 +1 @@
+import{d as F,r as v,K as E,f as S,x as P,c as r,o as i,a as t,j as U,n as g,g as _,m as y,y as k,F as T,p as A,t as n}from"./index-DMP0eryo.js";const H={class:"space-y-6 max-w-6xl mx-auto"},L={class:"flex flex-wrap justify-between items-start gap-4"},z={class:"flex items-center gap-2"},R=["disabled"],J={class:"flex flex-wrap gap-3 items-center"},K={class:"form-control"},q=["value"],G={class:"form-control"},I={class:"form-control"},O={key:0,class:"ml-2"},Q={key:0,class:"flex justify-center py-12"},W={key:1,class:"alert alert-error"},X={key:2,class:"text-center py-12 text-base-content/60"},Y={key:3,class:"card bg-base-100 shadow-sm"},Z={class:"overflow-x-auto"},tt={class:"table table-sm"},st=["onClick"],et=["title"],ot={class:"text-xs"},lt={class:"text-sm"},nt={class:"text-right"},at={class:"text-xs text-base-content/70"},rt={class:"flex justify-between items-center p-4 border-t border-base-300"},it={class:"text-sm text-base-content/60"},ut={class:"join"},dt=["disabled"],ct={class:"join-item btn btn-sm btn-active"},vt=["disabled"],mt={class:"modal-box max-w-2xl"},pt={key:0,class:"space-y-3"},bt={class:"grid grid-cols-2 gap-3 text-sm"},ft={class:"font-medium"},gt={class:"font-medium"},xt={class:"font-medium"},ht={key:0,class:"mt-4"},_t={class:"bg-base-200 p-3 rounded-lg text-xs mt-1 overflow-x-auto"},yt={class:"modal-action"},M=25,Ct=F({__name:"UserActivity",setup(kt){const m=v(!1),b=v(""),d=v([]),x=v(0),c=v(1),a=v(null),w=v([]),l=E({server:"",status:"",type:""}),$=S(()=>!!(l.server||l.status||l.type)),j=S(()=>d.value.lengthh.name),u=(s.shared||[]).map(h=>h.name);w.value=[...e,...u]}}catch{}}function f(){c.value=1,p()}function V(){l.server="",l.status="",l.type="",f()}function C(o){c.value=o,p()}function B(o){a.value=o}return P(()=>{p(),N()}),(o,s)=>(i(),r("div",H,[t("div",L,[s[9]||(s[9]=t("div",null,[t("h1",{class:"text-2xl font-bold"},"My Activity"),t("p",{class:"text-base-content/70 mt-1"},"Tool calls and activity for your sessions")],-1)),t("div",z,[t("button",{onClick:p,class:"btn btn-sm btn-ghost",disabled:m.value},[(i(),r("svg",{class:g(["w-4 h-4",{"animate-spin":m.value}]),fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[...s[7]||(s[7]=[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"},null,-1)])],2)),s[8]||(s[8]=U(" Refresh ",-1))],8,R)])]),t("div",J,[t("div",K,[y(t("select",{"onUpdate:modelValue":s[0]||(s[0]=e=>l.server=e),class:"select select-bordered select-sm",onChange:f},[s[10]||(s[10]=t("option",{value:""},"All Servers",-1)),(i(!0),r(T,null,A(w.value,e=>(i(),r("option",{key:e,value:e},n(e),9,q))),128))],544),[[k,l.server]])]),t("div",G,[y(t("select",{"onUpdate:modelValue":s[1]||(s[1]=e=>l.status=e),class:"select select-bordered select-sm",onChange:f},[...s[11]||(s[11]=[t("option",{value:""},"All Statuses",-1),t("option",{value:"success"},"Success",-1),t("option",{value:"error"},"Error",-1)])],544),[[k,l.status]])]),t("div",I,[y(t("select",{"onUpdate:modelValue":s[2]||(s[2]=e=>l.type=e),class:"select select-bordered select-sm",onChange:f},[...s[12]||(s[12]=[t("option",{value:""},"All Types",-1),t("option",{value:"tool_call"},"Tool Calls",-1),t("option",{value:"connection"},"Connections",-1),t("option",{value:"auth"},"Authentication",-1)])],544),[[k,l.type]])]),$.value?(i(),r("div",O,[t("button",{class:"btn btn-ghost btn-xs",onClick:V},"Clear Filters")])):_("",!0)]),m.value&&d.value.length===0?(i(),r("div",Q,[...s[13]||(s[13]=[t("span",{class:"loading loading-spinner loading-lg"},null,-1)])])):b.value?(i(),r("div",W,[s[14]||(s[14]=t("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})],-1)),t("span",null,n(b.value),1),t("button",{class:"btn btn-sm",onClick:p},"Try Again")])):d.value.length===0?(i(),r("div",X,[...s[15]||(s[15]=[t("svg",{class:"w-16 h-16 mx-auto mb-4 opacity-30",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"})],-1),t("p",{class:"text-lg font-medium"},"No activity yet",-1),t("p",{class:"text-sm mt-1"},"Activity will appear here once you start using tools",-1)])])):(i(),r("div",Y,[t("div",Z,[t("table",tt,[s[16]||(s[16]=t("thead",null,[t("tr",null,[t("th",null,"Time"),t("th",null,"Tool"),t("th",null,"Server"),t("th",null,"Status"),t("th",{class:"text-right"},"Duration")])],-1)),t("tbody",null,[(i(!0),r(T,null,A(d.value,e=>(i(),r("tr",{key:e.id,class:"hover cursor-pointer",onClick:u=>B(e)},[t("td",null,[t("span",{class:"text-xs",title:e.timestamp},n(D(e.timestamp)),9,et)]),t("td",null,[t("code",ot,n(e.tool_name||e.type),1)]),t("td",null,[t("span",lt,n(e.server_name||"-"),1)]),t("td",null,[t("span",{class:g(["badge badge-sm",e.status==="success"?"badge-success":e.status==="error"?"badge-error":"badge-ghost"])},n(e.status),3)]),t("td",nt,[t("span",at,n(e.duration_ms?`${e.duration_ms}ms`:"-"),1)])],8,st))),128))])])]),t("div",rt,[t("div",it," Showing "+n(d.value.length)+" of "+n(x.value)+" activities ",1),t("div",ut,[t("button",{class:"join-item btn btn-sm",disabled:c.value<=1,onClick:s[3]||(s[3]=e=>C(c.value-1))}," Previous ",8,dt),t("button",ct,n(c.value),1),t("button",{class:"join-item btn btn-sm",disabled:!j.value,onClick:s[4]||(s[4]=e=>C(c.value+1))}," Next ",8,vt)])])])),t("dialog",{class:g(["modal",{"modal-open":!!a.value}])},[t("div",mt,[s[24]||(s[24]=t("h3",{class:"font-bold text-lg mb-4"},"Activity Details",-1)),a.value?(i(),r("div",pt,[t("div",bt,[t("div",null,[s[17]||(s[17]=t("span",{class:"text-base-content/50"},"Type",-1)),t("p",ft,n(a.value.type),1)]),t("div",null,[s[18]||(s[18]=t("span",{class:"text-base-content/50"},"Status",-1)),t("p",null,[t("span",{class:g(["badge badge-sm",a.value.status==="success"?"badge-success":"badge-error"])},n(a.value.status),3)])]),t("div",null,[s[19]||(s[19]=t("span",{class:"text-base-content/50"},"Server",-1)),t("p",gt,n(a.value.server_name||"-"),1)]),t("div",null,[s[20]||(s[20]=t("span",{class:"text-base-content/50"},"Tool",-1)),t("p",xt,n(a.value.tool_name||"-"),1)]),t("div",null,[s[21]||(s[21]=t("span",{class:"text-base-content/50"},"Time",-1)),t("p",null,n(new Date(a.value.timestamp).toLocaleString()),1)]),t("div",null,[s[22]||(s[22]=t("span",{class:"text-base-content/50"},"Duration",-1)),t("p",null,n(a.value.duration_ms?`${a.value.duration_ms}ms`:"-"),1)])]),a.value.error?(i(),r("div",ht,[s[23]||(s[23]=t("span",{class:"text-base-content/50 text-sm"},"Error",-1)),t("pre",_t,n(a.value.error),1)])):_("",!0)])):_("",!0),t("div",yt,[t("button",{class:"btn",onClick:s[5]||(s[5]=e=>a.value=null)},"Close")])]),t("form",{method:"dialog",class:"modal-backdrop",onClick:s[6]||(s[6]=e=>a.value=null)})],2)]))}});export{Ct as default};
diff --git a/web/frontend/dist/assets/UserDiagnostics-CYaZlbXF.js b/web/frontend/dist/assets/UserDiagnostics-CYaZlbXF.js
new file mode 100644
index 00000000..01a0eb84
--- /dev/null
+++ b/web/frontend/dist/assets/UserDiagnostics-CYaZlbXF.js
@@ -0,0 +1 @@
+import{d as x,r as _,f as w,x as k,c as n,o,a as e,j as C,n as u,t as l,F as T,p as S,g as h}from"./index-DMP0eryo.js";const j={class:"space-y-6 max-w-6xl mx-auto"},D={class:"flex justify-between items-center"},A=["disabled"],B={class:"stats shadow bg-base-100 w-full"},U={class:"stat"},$={class:"stat-value"},E={class:"stat"},M={class:"stat-value text-success"},N={class:"stat"},P={class:"stat-value text-warning"},R={class:"stat"},H={class:"stat-value text-error"},L={key:0,class:"flex justify-center py-12"},V={key:1,class:"alert alert-error"},z={key:2,class:"text-center py-12 text-base-content/60"},F={key:3,class:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"},I={class:"card-body p-4"},O={class:"flex items-center justify-between mb-2"},Y={class:"font-semibold truncate"},q={class:"flex items-center gap-2 mb-3"},G={key:0,class:"text-sm text-base-content/60 mb-2"},J={class:"flex flex-wrap gap-2 mt-auto"},K={class:"badge badge-outline badge-xs"},Q={key:0,class:"badge badge-outline badge-xs badge-success"},W={key:1,class:"badge badge-outline badge-xs badge-error"},X={key:2,class:"badge badge-outline badge-xs"},Z={key:1,class:"mt-3 text-xs text-base-content/50"},ee={key:2,class:"mt-2"},te=["onClick"],oe=x({__name:"UserDiagnostics",setup(se){const d=_(!1),c=_(""),i=_([]),g=w(()=>{const t={healthy:0,degraded:0,unhealthy:0};for(const s of i.value)s.health_level==="healthy"?t.healthy++:s.health_level==="degraded"?t.degraded++:t.unhealthy++;return t});function m(t){return t?t.charAt(0).toUpperCase()+t.slice(1):""}function b(t){switch(t){case"healthy":return"bg-success";case"degraded":return"bg-warning";case"unhealthy":return"bg-error";default:return"bg-base-content/30"}}function v(t){switch(t){case"healthy":return"text-success";case"degraded":return"text-warning";case"unhealthy":return"text-error";default:return""}}function p(t){return t==="shared"?"badge-info":"badge-primary"}function f(t){switch(t){case"login":return"Login";case"restart":return"Restart";case"enable":return"Enable";case"approve":return"Approve";case"view_logs":return"View Logs";case"set_secret":return"Set Secret";case"configure":return"Configure";default:return t}}async function y(t){try{t.health_action==="login"?await fetch(`/api/v1/user/servers/${encodeURIComponent(t.name)}/login`,{method:"POST",credentials:"include"}):t.health_action==="restart"?await fetch(`/api/v1/user/servers/${encodeURIComponent(t.name)}/restart`,{method:"POST",credentials:"include"}):t.health_action==="enable"&&await fetch(`/api/v1/user/servers/${encodeURIComponent(t.name)}/enable`,{method:"POST",credentials:"include"}),setTimeout(r,1e3)}catch(s){c.value=s instanceof Error?s.message:"Action failed"}}async function r(){d.value=!0,c.value="";try{const t=await fetch("/api/v1/user/diagnostics",{credentials:"include"});if(!t.ok)throw new Error(`HTTP ${t.status}: ${t.statusText}`);const s=await t.json();i.value=(s.servers||[]).map(a=>({...a,owner_type:a.ownership||"shared",health_level:a.connected?"healthy":a.enabled?"unhealthy":"degraded",health_summary:a.connected?"Connected":a.enabled?"Not connected":"Disabled",health_detail:"",health_action:""}))}catch(t){c.value=t instanceof Error?t.message:"Failed to load diagnostics"}finally{d.value=!1}}return k(()=>{r()}),(t,s)=>(o(),n("div",j,[e("div",D,[s[2]||(s[2]=e("div",null,[e("h1",{class:"text-2xl font-bold"},"Diagnostics"),e("p",{class:"text-base-content/70 mt-1"},"Server health for your accessible MCP servers")],-1)),e("button",{onClick:r,class:"btn btn-sm btn-ghost",disabled:d.value},[(o(),n("svg",{class:u(["w-4 h-4",{"animate-spin":d.value}]),fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[...s[0]||(s[0]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"},null,-1)])],2)),s[1]||(s[1]=C(" Refresh ",-1))],8,A)]),e("div",B,[e("div",U,[s[3]||(s[3]=e("div",{class:"stat-title"},"Total Servers",-1)),e("div",$,l(i.value.length),1)]),e("div",E,[s[4]||(s[4]=e("div",{class:"stat-title"},"Healthy",-1)),e("div",M,l(g.value.healthy),1)]),e("div",N,[s[5]||(s[5]=e("div",{class:"stat-title"},"Degraded",-1)),e("div",P,l(g.value.degraded),1)]),e("div",R,[s[6]||(s[6]=e("div",{class:"stat-title"},"Unhealthy",-1)),e("div",H,l(g.value.unhealthy),1)])]),d.value&&i.value.length===0?(o(),n("div",L,[...s[7]||(s[7]=[e("span",{class:"loading loading-spinner loading-lg"},null,-1)])])):c.value?(o(),n("div",V,[s[8]||(s[8]=e("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})],-1)),e("span",null,l(c.value),1),e("button",{class:"btn btn-sm",onClick:r},"Try Again")])):i.value.length===0?(o(),n("div",z,[...s[9]||(s[9]=[e("p",{class:"text-lg font-medium"},"No servers found",-1),e("p",{class:"text-sm mt-1"},"You don't have any accessible servers yet",-1)])])):(o(),n("div",F,[(o(!0),n(T,null,S(i.value,a=>(o(),n("div",{key:a.name,class:"card bg-base-100 shadow-sm"},[e("div",I,[e("div",O,[e("h3",Y,l(a.name),1),e("span",{class:u(["badge badge-sm",p(a.owner_type)])},l(a.owner_type),3)]),e("div",q,[e("div",{class:u(["w-3 h-3 rounded-full",b(a.health_level)])},null,2),e("span",{class:u(["text-sm font-medium",v(a.health_level)])},l(m(a.health_level)),3)]),a.health_summary?(o(),n("p",G,l(a.health_summary),1)):h("",!0),e("div",J,[e("span",K,l(a.protocol),1),a.connected?(o(),n("span",Q,"connected")):(o(),n("span",W,"disconnected")),a.tool_count>0?(o(),n("span",X,l(a.tool_count)+" tools",1)):h("",!0)]),a.health_detail?(o(),n("div",Z,l(a.health_detail),1)):h("",!0),a.health_action?(o(),n("div",ee,[e("button",{class:"btn btn-xs btn-outline btn-primary",onClick:ae=>y(a)},l(f(a.health_action)),9,te)])):h("",!0)])]))),128))]))]))}});export{oe as default};
diff --git a/web/frontend/dist/assets/UserServers-BBrm1aSk.js b/web/frontend/dist/assets/UserServers-BBrm1aSk.js
new file mode 100644
index 00000000..ec2c2323
--- /dev/null
+++ b/web/frontend/dist/assets/UserServers-BBrm1aSk.js
@@ -0,0 +1,3 @@
+import{d as L,r as c,K as O,f as q,x as I,c as n,o as l,a as e,g as b,j as p,F as R,p as U,t as r,L as M,m as f,v as S,y as z,n as y,N as J}from"./index-DMP0eryo.js";const K={class:"p-4 max-w-6xl mx-auto"},Y={class:"flex justify-between items-center mb-6"},G={class:"mb-8"},Q={key:0,class:"flex justify-center py-8"},W={key:1,class:"text-base-content/50 py-8 text-center"},X={key:2,class:"overflow-x-auto"},Z={class:"table table-sm w-full"},ee=["onClick"],te={class:"font-medium"},oe={class:"badge badge-ghost badge-xs"},se={class:"text-xs text-base-content/50 truncate max-w-xs"},ae={class:"text-xs text-base-content/60"},ne={class:"dropdown dropdown-end"},le={tabindex:"0",class:"dropdown-content z-[1] menu p-1 shadow-lg bg-base-100 rounded-lg w-40 border border-base-300"},re=["onClick","disabled"],de={class:"border-t border-base-200 mt-1 pt-1"},ie=["onClick","disabled"],ue={key:0,class:"rounded-lg border border-base-300 bg-base-200/30 py-8 text-center"},ce={key:1,class:"overflow-x-auto"},be={class:"table table-sm w-full"},me=["onClick"],pe={class:"font-medium"},ve={class:"badge badge-ghost badge-xs"},ge={class:"text-xs text-base-content/50 truncate max-w-xs"},he={class:"text-xs text-base-content/60"},xe=["onClick","disabled"],fe={key:0,class:"loading loading-spinner loading-xs"},ye={key:0,class:"alert alert-error mt-4"},we={class:"modal-box"},ke={class:"form-control mb-3"},_e={class:"form-control mb-3"},Se={key:0,class:"form-control mb-3"},Ce={key:1,class:"form-control mb-3"},Te={key:2,class:"form-control mb-3"},Ee={key:3,class:"alert alert-error mb-3 text-sm"},$e={class:"modal-action"},Me=["disabled"],Pe={key:0,class:"loading loading-spinner loading-xs"},je={class:"modal-box"},Ae={class:"py-4"},Re={class:"modal-action"},Ue=["disabled"],Be={key:0,class:"loading loading-spinner loading-xs"},De=L({__name:"UserServers",setup(Ne){const B=J(),C=c(!0),d=c(""),T=c([]),E=c(!1),v=c(!1),g=c(""),m=c(""),h=c(""),u=c(""),a=O({name:"",url:"",protocol:"http",command:"",args:""}),w=q(()=>({personal:T.value.filter(s=>s.owner_type==="personal"),shared:T.value.filter(s=>s.owner_type==="shared")}));function P(s){if(!s.health)return s.enabled?s.connected?"badge-success":"badge-warning":"badge-ghost";switch(s.health.level){case"healthy":return"badge-success";case"degraded":return"badge-warning";case"unhealthy":return"badge-error";default:return"badge-ghost"}}function j(s){return s.health?s.health.level:s.enabled?s.connected?"connected":"disconnected":"disabled"}function A(s){B.push("/servers/"+encodeURIComponent(s.name))}async function x(){C.value=!0,d.value="";try{const s=await fetch("/api/v1/user/servers",{credentials:"include"});if(!s.ok)throw new Error(`HTTP ${s.status}: ${s.statusText}`);const t=await s.json(),o=(t.personal||[]).map(_=>({..._,owner_type:"personal"})),i=(t.shared||[]).map(_=>({..._,owner_type:"shared",user_enabled:_.user_enabled??null}));T.value=[...o,...i]}catch(s){d.value=s instanceof Error?s.message:"Failed to load servers"}finally{C.value=!1}}function k(s){return s.user_enabled===!1}async function N(s){m.value=s.name,d.value="";try{const t=!!k(s),o=await fetch(`/api/v1/user/servers/${encodeURIComponent(s.name)}/enable`,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({enabled:t})});if(!o.ok){const i=await o.json().catch(()=>({}));throw new Error(i.message||`HTTP ${o.status}`)}await x()}catch(t){d.value=t instanceof Error?t.message:"Failed to update server"}finally{m.value=""}}async function V(s){m.value=s.name,d.value="";try{const t=s.enabled?"disable":"enable",o=await fetch(`/api/v1/user/servers/${encodeURIComponent(s.name)}/${t}`,{method:"POST",credentials:"include"});if(!o.ok){const i=await o.json().catch(()=>({}));throw new Error(i.error||`HTTP ${o.status}`)}await x()}catch(t){d.value=t instanceof Error?t.message:"Failed to update server"}finally{m.value=""}}function D(s){u.value=s}async function H(){const s=u.value;if(s){h.value=s,d.value="";try{const t=await fetch(`/api/v1/user/servers/${encodeURIComponent(s)}`,{method:"DELETE",credentials:"include"});if(!t.ok){const o=await t.json().catch(()=>({}));throw new Error(o.error||`HTTP ${t.status}`)}u.value="",await x()}catch(t){d.value=t instanceof Error?t.message:"Failed to remove server"}finally{h.value=""}}}async function F(){v.value=!0,g.value="";try{const s={name:a.name,protocol:a.protocol,enabled:!0};a.protocol==="stdio"?(s.command=a.command,a.args.trim()&&(s.args=a.args.trim().split(`
+`).map(o=>o.trim()).filter(Boolean))):s.url=a.url;const t=await fetch("/api/v1/user/servers",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});if(!t.ok){const o=await t.json().catch(()=>({}));throw new Error(o.error||`HTTP ${t.status}`)}$(),await x()}catch(s){g.value=s instanceof Error?s.message:"Failed to add server"}finally{v.value=!1}}function $(){E.value=!1,g.value="",a.name="",a.url="",a.protocol="http",a.command="",a.args=""}return I(()=>{x()}),(s,t)=>(l(),n("div",K,[e("div",Y,[t[12]||(t[12]=e("h1",{class:"text-2xl font-bold"},"My Servers",-1)),e("button",{class:"btn btn-primary btn-sm",onClick:t[0]||(t[0]=o=>E.value=!0)},[...t[11]||(t[11]=[e("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 4v16m8-8H4"})],-1),p(" Add Personal Server ",-1)])])]),e("div",G,[t[16]||(t[16]=e("h2",{class:"text-lg font-semibold mb-3"},"Personal Servers",-1)),C.value?(l(),n("div",Q,[...t[13]||(t[13]=[e("span",{class:"loading loading-spinner loading-lg"},null,-1)])])):w.value.personal.length===0?(l(),n("div",W,' No personal servers yet. Click "Add Personal Server" to get started. ')):(l(),n("div",X,[e("table",Z,[t[15]||(t[15]=e("thead",null,[e("tr",{class:"text-xs uppercase text-base-content/50"},[e("th",null,"Server"),e("th",null,"Protocol"),e("th",null,"Endpoint"),e("th",null,"Status"),e("th",null,"Tools"),e("th",{class:"text-right"},"Actions")])],-1)),e("tbody",null,[(l(!0),n(R,null,U(w.value.personal,o=>(l(),n("tr",{key:o.name,class:"hover:bg-base-200/50 cursor-pointer transition-colors",onClick:i=>A(o)},[e("td",te,r(o.name),1),e("td",null,[e("span",oe,r(o.protocol),1)]),e("td",se,r(o.url||o.command||"—"),1),e("td",null,[e("span",{class:y(["badge badge-xs",P(o)])},r(j(o)),3)]),e("td",ae,r(o.tool_count!=null?o.tool_count:"—"),1),e("td",{class:"text-right",onClick:t[1]||(t[1]=M(()=>{},["stop"]))},[e("div",ne,[t[14]||(t[14]=e("label",{tabindex:"0",class:"btn btn-ghost btn-xs btn-square"},[e("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 5v.01M12 12v.01M12 19v.01"})])],-1)),e("ul",le,[e("li",null,[e("a",{onClick:i=>V(o),disabled:m.value===o.name},r(o.enabled?"Disable":"Enable"),9,re)]),e("li",de,[e("a",{class:"text-error",onClick:i=>D(o.name),disabled:h.value===o.name}," Remove ",8,ie)])])])])],8,ee))),128))])])]))]),e("div",null,[t[20]||(t[20]=e("h2",{class:"text-lg font-semibold mb-3"},"Shared Servers",-1)),w.value.shared.length===0?(l(),n("div",ue,[...t[17]||(t[17]=[e("svg",{class:"w-10 h-10 mx-auto mb-3 text-base-content/30",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"1.5",d:"M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2"})],-1),e("p",{class:"text-base-content/50 text-sm"},"No shared servers available.",-1),e("p",{class:"text-base-content/40 text-xs mt-1"},"Your administrator can share servers from the Server Management page.",-1)])])):(l(),n("div",ce,[e("table",be,[t[19]||(t[19]=e("thead",null,[e("tr",{class:"text-xs uppercase text-base-content/50"},[e("th",null,"Server"),e("th",null,"Protocol"),e("th",null,"Endpoint"),e("th",null,"Status"),e("th",null,"Tools"),e("th",{class:"text-right"},"Actions")])],-1)),e("tbody",null,[(l(!0),n(R,null,U(w.value.shared,o=>(l(),n("tr",{key:o.name,class:y(["hover:bg-base-200/50 cursor-pointer transition-colors",{"opacity-50":k(o)}]),onClick:i=>A(o)},[e("td",pe,[p(r(o.name)+" ",1),t[18]||(t[18]=e("span",{class:"badge badge-info badge-xs ml-1"},"shared",-1))]),e("td",null,[e("span",ve,r(o.protocol),1)]),e("td",ge,r(o.url||o.command||"—"),1),e("td",null,[e("span",{class:y(["badge badge-xs",P(o)])},r(k(o)?"disabled by you":j(o)),3)]),e("td",he,r(o.tool_count!=null?o.tool_count:"—"),1),e("td",{class:"text-right",onClick:t[2]||(t[2]=M(()=>{},["stop"]))},[e("button",{class:"btn btn-ghost btn-xs",onClick:i=>N(o),disabled:m.value===o.name},[m.value===o.name?(l(),n("span",fe)):b("",!0),p(" "+r(k(o)?"Enable":"Disable"),1)],8,xe)])],10,me))),128))])])]))]),d.value?(l(),n("div",ye,[t[21]||(t[21]=e("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})],-1)),e("span",null,r(d.value),1),e("button",{class:"btn btn-ghost btn-xs",onClick:t[3]||(t[3]=o=>d.value="")},"Dismiss")])):b("",!0),e("dialog",{class:y(["modal",{"modal-open":E.value}])},[e("div",we,[t[28]||(t[28]=e("h3",{class:"font-bold text-lg mb-4"},"Add Personal Server",-1)),e("form",{onSubmit:M(F,["prevent"])},[e("div",ke,[t[22]||(t[22]=e("label",{class:"label"},[e("span",{class:"label-text"},"Name")],-1)),f(e("input",{"onUpdate:modelValue":t[4]||(t[4]=o=>a.name=o),type:"text",class:"input input-bordered",required:"",placeholder:"my-server"},null,512),[[S,a.name]])]),e("div",_e,[t[24]||(t[24]=e("label",{class:"label"},[e("span",{class:"label-text"},"Protocol")],-1)),f(e("select",{"onUpdate:modelValue":t[5]||(t[5]=o=>a.protocol=o),class:"select select-bordered"},[...t[23]||(t[23]=[e("option",{value:"http"},"HTTP",-1),e("option",{value:"sse"},"SSE",-1),e("option",{value:"streamable-http"},"Streamable HTTP",-1),e("option",{value:"stdio"},"stdio",-1)])],512),[[z,a.protocol]])]),a.protocol!=="stdio"?(l(),n("div",Se,[t[25]||(t[25]=e("label",{class:"label"},[e("span",{class:"label-text"},"URL")],-1)),f(e("input",{"onUpdate:modelValue":t[6]||(t[6]=o=>a.url=o),type:"text",class:"input input-bordered",placeholder:"https://...",required:""},null,512),[[S,a.url]])])):b("",!0),a.protocol==="stdio"?(l(),n("div",Ce,[t[26]||(t[26]=e("label",{class:"label"},[e("span",{class:"label-text"},"Command")],-1)),f(e("input",{"onUpdate:modelValue":t[7]||(t[7]=o=>a.command=o),type:"text",class:"input input-bordered",placeholder:"npx",required:""},null,512),[[S,a.command]])])):b("",!0),a.protocol==="stdio"?(l(),n("div",Te,[t[27]||(t[27]=e("label",{class:"label"},[e("span",{class:"label-text"},"Arguments (one per line)")],-1)),f(e("textarea",{"onUpdate:modelValue":t[8]||(t[8]=o=>a.args=o),class:"textarea textarea-bordered",placeholder:`@modelcontextprotocol/server-filesystem
+/path/to/dir`,rows:"3"},null,512),[[S,a.args]])])):b("",!0),g.value?(l(),n("div",Ee,r(g.value),1)):b("",!0),e("div",$e,[e("button",{type:"button",class:"btn",onClick:$},"Cancel"),e("button",{type:"submit",class:"btn btn-primary",disabled:v.value},[v.value?(l(),n("span",Pe)):b("",!0),p(" "+r(v.value?"Adding...":"Add Server"),1)],8,Me)])],32)]),e("form",{method:"dialog",class:"modal-backdrop",onClick:$})],2),e("dialog",{class:y(["modal",{"modal-open":!!u.value}])},[e("div",je,[t[32]||(t[32]=e("h3",{class:"font-bold text-lg"},"Remove Server",-1)),e("p",Ae,[t[29]||(t[29]=p("Are you sure you want to remove ",-1)),e("strong",null,r(u.value),1),t[30]||(t[30]=p("? This action cannot be undone.",-1))]),e("div",Re,[e("button",{class:"btn",onClick:t[9]||(t[9]=o=>u.value="")},"Cancel"),e("button",{class:"btn btn-error",onClick:H,disabled:h.value===u.value},[h.value===u.value?(l(),n("span",Be)):b("",!0),t[31]||(t[31]=p(" Remove ",-1))],8,Ue)])]),e("form",{method:"dialog",class:"modal-backdrop",onClick:t[10]||(t[10]=o=>u.value="")})],2)]))}});export{De as default};
diff --git a/web/frontend/dist/assets/UserTokens-DOuVGX0Y.js b/web/frontend/dist/assets/UserTokens-DOuVGX0Y.js
new file mode 100644
index 00000000..0a1ca47c
--- /dev/null
+++ b/web/frontend/dist/assets/UserTokens-DOuVGX0Y.js
@@ -0,0 +1 @@
+import{d as q,r as d,f as H,x as J,c as o,o as l,a as e,g as h,j as E,t as i,n as p,F as T,p as S,m as y,v as Y,M,y as Z}from"./index-DMP0eryo.js";const G={class:"p-4 max-w-6xl mx-auto"},K={class:"flex justify-between items-center mb-6"},Q={class:"flex gap-2"},X=["disabled"],ee={class:"grid grid-cols-3 gap-3 mb-6"},se={class:"stat bg-base-100 rounded-lg shadow-sm p-3"},te={class:"stat-value text-lg"},ae={class:"stat bg-base-100 rounded-lg shadow-sm p-3"},oe={class:"stat-value text-lg text-success"},le={class:"stat bg-base-100 rounded-lg shadow-sm p-3"},ne={class:"stat-value text-lg text-warning"},re={key:0,class:"alert alert-warning shadow-lg mb-6"},ie={class:"flex-1"},de={class:"flex items-center gap-2"},ce={class:"text-sm bg-neutral text-neutral-content px-3 py-2 rounded font-mono break-all"},ue={key:1,class:"flex justify-center py-8"},ve={key:2,class:"alert alert-error"},me={class:"text-sm"},pe={key:3,class:"rounded-lg border border-base-300 bg-base-200/30 py-12 text-center"},be={key:4,class:"overflow-x-auto"},xe={class:"table table-sm w-full"},he={class:"font-medium"},ge={class:"text-xs bg-base-200 px-1.5 py-0.5 rounded"},ke={class:"flex flex-wrap gap-1"},fe={class:"flex flex-wrap gap-1"},we={key:0,class:"text-xs"},ye={key:1,class:"text-base-content/40 text-xs"},_e={key:0,class:"badge badge-error badge-xs"},Ce={key:1,class:"badge badge-warning badge-xs"},Te={key:2,class:"badge badge-success badge-xs"},Se={class:"text-right"},je={class:"dropdown dropdown-end"},Ae={tabindex:"0",class:"dropdown-content z-[1] menu p-1 shadow-lg bg-base-100 rounded-lg w-40 border border-base-300"},De=["onClick"],Ee={class:"border-t border-base-200 mt-1 pt-1"},Me=["onClick"],$e={class:"modal-box"},Be={class:"space-y-4"},He={class:"form-control"},Re={key:0,class:"label"},Fe={class:"label-text-alt text-error"},Ie={key:1,class:"label"},Le={class:"form-control"},Ne={class:"flex items-center gap-2 cursor-pointer mb-2 px-1"},Pe=["checked"],Ue={key:0,class:"border border-base-300 rounded-lg p-3 max-h-48 overflow-y-auto space-y-1"},Ve={key:0,class:"text-sm text-base-content/50 py-2 text-center"},ze=["value"],Oe={class:"text-sm"},We={key:1,class:"label"},qe={class:"label-text-alt text-error"},Je={class:"form-control"},Ye={class:"flex flex-col gap-2"},Ze={class:"flex items-center gap-2 cursor-pointer"},Ge={class:"flex items-center gap-2 cursor-pointer"},Ke={class:"form-control"},Qe={key:0,class:"alert alert-error mt-4 text-sm"},Xe={class:"modal-action"},es=["disabled"],ss={key:0,class:"loading loading-spinner loading-sm"},os=q({__name:"UserTokens",setup(ts){const _=d(!0),b=d(null),x=d([]),g=d(!1),k=d(""),v=d(null),u=d(!1),j=d(!1),A=d([]),n=d({name:"",allServers:!0,selectedServers:[],permWrite:!1,permDestructive:!1,expiresIn:"720h"}),c=d({}),R=H(()=>x.value.filter(t=>!t.revoked&&!f(t)).length),F=H(()=>x.value.filter(t=>t.revoked||f(t)).length);function f(t){return new Date(t.expires_at)({name:m.name})),r=(s.shared||[]).map(m=>({name:m.name}));A.value=[...a,...r].sort((m,C)=>m.name.localeCompare(C.name))}catch{}}function B(){n.value={name:"",allServers:!0,selectedServers:[],permWrite:!1,permDestructive:!1,expiresIn:"720h"},c.value={},k.value="",P(),j.value=!0}function D(){j.value=!1}async function U(){c.value={},k.value="";const t=n.value.name.trim();if(!t){c.value.name="Token name is required";return}if(!/^[a-zA-Z0-9_-]+$/.test(t)){c.value.name="Only alphanumeric characters, hyphens, and underscores allowed";return}if(!n.value.allServers&&n.value.selectedServers.length===0){c.value.servers='Select at least one server or choose "All servers"';return}g.value=!0;try{const s=n.value.allServers?["*"]:[...n.value.selectedServers],a=["read"];n.value.permWrite&&a.push("write"),n.value.permDestructive&&a.push("destructive");const r=await fetch("/api/v1/user/tokens",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:t,allowed_servers:s,permissions:a,expires_in:n.value.expiresIn})});if(!r.ok){const C=await r.json().catch(()=>({}));throw new Error(C.error||C.message||`HTTP ${r.status}`)}const m=await r.json();v.value=m.token,u.value=!1,D(),await w()}catch(s){k.value=s.message||"Failed to create token"}finally{g.value=!1}}async function V(t){if(confirm(`Regenerate the secret for token "${t}"? The old secret will stop working immediately.`))try{const s=await fetch(`/api/v1/user/tokens/${encodeURIComponent(t)}/regenerate`,{method:"POST",credentials:"include"});if(!s.ok){const r=await s.json().catch(()=>({}));throw new Error(r.error||r.message||`HTTP ${s.status}`)}const a=await s.json();v.value=a.token,u.value=!1}catch(s){b.value=s.message||"Failed to regenerate token"}}async function z(t){if(confirm(`Revoke token "${t}"? This action cannot be undone.`))try{const s=await fetch(`/api/v1/user/tokens/${encodeURIComponent(t)}`,{method:"DELETE",credentials:"include"});if(!s.ok){const a=await s.json().catch(()=>({}));throw new Error(a.error||a.message||`HTTP ${s.status}`)}await w()}catch(s){b.value=s.message||"Failed to revoke token"}}async function O(){if(v.value)try{await navigator.clipboard.writeText(v.value),u.value=!0,setTimeout(()=>{u.value=!1},2e3)}catch{const t=document.createElement("textarea");t.value=v.value,document.body.appendChild(t),t.select(),document.execCommand("copy"),document.body.removeChild(t),u.value=!0,setTimeout(()=>{u.value=!1},2e3)}}function W(){v.value=null,u.value=!1}return J(()=>{w()}),(t,s)=>(l(),o("div",G,[e("div",K,[s[7]||(s[7]=e("div",null,[e("h1",{class:"text-2xl font-bold"},"Agent Tokens"),e("p",{class:"text-sm text-base-content/60 mt-1"},"Create tokens for AI agents to act on your behalf")],-1)),e("div",Q,[e("button",{onClick:w,disabled:_.value,class:"btn btn-ghost btn-sm"},[...s[5]||(s[5]=[e("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"})],-1)])],8,X),e("button",{onClick:B,class:"btn btn-primary btn-sm"},[...s[6]||(s[6]=[e("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 4v16m8-8H4"})],-1),E(" Create Token ",-1)])])])]),e("div",ee,[e("div",se,[s[8]||(s[8]=e("div",{class:"stat-title text-xs"},"Total Tokens",-1)),e("div",te,i(x.value.length),1)]),e("div",ae,[s[9]||(s[9]=e("div",{class:"stat-title text-xs"},"Active",-1)),e("div",oe,i(R.value),1)]),e("div",le,[s[10]||(s[10]=e("div",{class:"stat-title text-xs"},"Expired / Revoked",-1)),e("div",ne,i(F.value),1)])]),v.value?(l(),o("div",re,[s[13]||(s[13]=e("svg",{class:"w-6 h-6 shrink-0",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"})],-1)),e("div",ie,[s[11]||(s[11]=e("h3",{class:"font-bold"},"Save this token now!",-1)),s[12]||(s[12]=e("p",{class:"text-sm mb-2"},"This token cannot be retrieved again after you dismiss this message.",-1)),e("div",de,[e("code",ce,i(v.value),1),e("button",{onClick:O,class:p(["btn btn-sm btn-neutral shrink-0",{"btn-success":u.value}])},i(u.value?"Copied!":"Copy"),3)])]),e("button",{onClick:W,class:"btn btn-sm btn-ghost shrink-0"},"Dismiss")])):h("",!0),_.value?(l(),o("div",ue,[...s[14]||(s[14]=[e("span",{class:"loading loading-spinner loading-lg"},null,-1)])])):b.value?(l(),o("div",ve,[s[16]||(s[16]=e("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})],-1)),e("div",null,[s[15]||(s[15]=e("h3",{class:"font-bold"},"Failed to load tokens",-1)),e("div",me,i(b.value),1)]),e("button",{onClick:w,class:"btn btn-sm"},"Try Again")])):x.value.length===0?(l(),o("div",pe,[s[18]||(s[18]=e("svg",{class:"w-16 h-16 mx-auto mb-4 text-base-content/30",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"1.5",d:"M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"})],-1)),s[19]||(s[19]=e("h3",{class:"text-lg font-semibold mb-2"},"No agent tokens yet",-1)),s[20]||(s[20]=e("p",{class:"text-base-content/50 text-sm mb-4"},"Create scoped tokens for your AI agents and automated workflows.",-1)),e("button",{onClick:B,class:"btn btn-primary btn-sm"},[...s[17]||(s[17]=[e("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 4v16m8-8H4"})],-1),E(" Create Your First Token ",-1)])])])):(l(),o("div",be,[e("table",xe,[s[22]||(s[22]=e("thead",null,[e("tr",{class:"text-xs uppercase text-base-content/50"},[e("th",null,"Name"),e("th",null,"Prefix"),e("th",null,"Servers"),e("th",null,"Permissions"),e("th",null,"Expires"),e("th",null,"Last Used"),e("th",null,"Status"),e("th",{class:"text-right"},"Actions")])],-1)),e("tbody",null,[(l(!0),o(T,null,S(x.value,a=>(l(),o("tr",{key:a.name,class:"hover:bg-base-200/50"},[e("td",he,i(a.name),1),e("td",null,[e("code",ge,i(a.token_prefix),1)]),e("td",null,[e("div",ke,[(l(!0),o(T,null,S(a.allowed_servers,r=>(l(),o("span",{key:r,class:"badge badge-outline badge-xs"},i(r),1))),128))])]),e("td",null,[e("div",fe,[(l(!0),o(T,null,S(a.permissions,r=>(l(),o("span",{key:r,class:p(["badge badge-xs",L(r)])},i(r),3))),128))])]),e("td",null,[e("span",{class:p(["text-xs",{"text-warning":I(a),"text-error":f(a)}])},i($(a.expires_at)),3)]),e("td",null,[a.last_used_at?(l(),o("span",we,i($(a.last_used_at)),1)):(l(),o("span",ye,"Never"))]),e("td",null,[a.revoked?(l(),o("span",_e,"Revoked")):f(a)?(l(),o("span",Ce,"Expired")):(l(),o("span",Te,"Active"))]),e("td",Se,[e("div",je,[s[21]||(s[21]=e("label",{tabindex:"0",class:"btn btn-ghost btn-xs btn-square"},[e("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 5v.01M12 12v.01M12 19v.01"})])],-1)),e("ul",Ae,[e("li",null,[e("a",{onClick:r=>V(a.name),class:p({"opacity-50 pointer-events-none":a.revoked})}," Regenerate ",10,De)]),e("li",Ee,[e("a",{class:p(["text-error",{"opacity-50 pointer-events-none":a.revoked}]),onClick:r=>z(a.name)}," Revoke ",10,Me)])])])])]))),128))])])])),e("dialog",{class:p(["modal",{"modal-open":j.value}])},[e("div",$e,[s[34]||(s[34]=e("h3",{class:"font-bold text-lg mb-4"},"Create Agent Token",-1)),e("div",Be,[e("div",He,[s[24]||(s[24]=e("label",{class:"label"},[e("span",{class:"label-text font-medium"},"Token Name")],-1)),y(e("input",{"onUpdate:modelValue":s[0]||(s[0]=a=>n.value.name=a),type:"text",placeholder:"e.g., ci-pipeline, dev-agent",class:p(["input input-bordered w-full",{"input-error":c.value.name}])},null,2),[[Y,n.value.name]]),c.value.name?(l(),o("label",Re,[e("span",Fe,i(c.value.name),1)])):(l(),o("label",Ie,[...s[23]||(s[23]=[e("span",{class:"label-text-alt"},"Alphanumeric, hyphens, and underscores only",-1)])]))]),e("div",Le,[s[27]||(s[27]=e("label",{class:"label"},[e("span",{class:"label-text font-medium"},"Allowed Servers")],-1)),e("label",Ne,[e("input",{type:"checkbox",checked:n.value.allServers,onChange:N,class:"checkbox checkbox-sm checkbox-primary"},null,40,Pe),s[25]||(s[25]=e("span",{class:"text-sm font-medium"},"All servers",-1)),s[26]||(s[26]=e("span",{class:"badge badge-ghost badge-xs"},"wildcard",-1))]),n.value.allServers?h("",!0):(l(),o("div",Ue,[A.value.length===0?(l(),o("div",Ve," No servers available ")):h("",!0),(l(!0),o(T,null,S(A.value,a=>(l(),o("label",{key:a.name,class:"flex items-center gap-2 cursor-pointer hover:bg-base-200 rounded px-2 py-1"},[y(e("input",{type:"checkbox",value:a.name,"onUpdate:modelValue":s[1]||(s[1]=r=>n.value.selectedServers=r),class:"checkbox checkbox-sm"},null,8,ze),[[M,n.value.selectedServers]]),e("span",Oe,i(a.name),1)]))),128))])),!n.value.allServers&&c.value.servers?(l(),o("label",We,[e("span",qe,i(c.value.servers),1)])):h("",!0)]),e("div",Je,[s[31]||(s[31]=e("label",{class:"label"},[e("span",{class:"label-text font-medium"},"Permissions")],-1)),e("div",Ye,[s[30]||(s[30]=e("label",{class:"flex items-center gap-2 cursor-not-allowed"},[e("input",{type:"checkbox",checked:"",disabled:"",class:"checkbox checkbox-sm checkbox-info"}),e("span",{class:"text-sm"},"read"),e("span",{class:"badge badge-info badge-xs"},"always included")],-1)),e("label",Ze,[y(e("input",{"onUpdate:modelValue":s[2]||(s[2]=a=>n.value.permWrite=a),type:"checkbox",class:"checkbox checkbox-sm checkbox-warning"},null,512),[[M,n.value.permWrite]]),s[28]||(s[28]=e("span",{class:"text-sm"},"write",-1))]),e("label",Ge,[y(e("input",{"onUpdate:modelValue":s[3]||(s[3]=a=>n.value.permDestructive=a),type:"checkbox",class:"checkbox checkbox-sm checkbox-error"},null,512),[[M,n.value.permDestructive]]),s[29]||(s[29]=e("span",{class:"text-sm"},"destructive",-1))])])]),e("div",Ke,[s[33]||(s[33]=e("label",{class:"label"},[e("span",{class:"label-text font-medium"},"Expires In")],-1)),y(e("select",{"onUpdate:modelValue":s[4]||(s[4]=a=>n.value.expiresIn=a),class:"select select-bordered w-full"},[...s[32]||(s[32]=[e("option",{value:"168h"},"7 days",-1),e("option",{value:"720h"},"30 days",-1),e("option",{value:"2160h"},"90 days",-1),e("option",{value:"8760h"},"365 days",-1)])],512),[[Z,n.value.expiresIn]])])]),k.value?(l(),o("div",Qe,i(k.value),1)):h("",!0),e("div",Xe,[e("button",{onClick:D,class:"btn"},"Cancel"),e("button",{onClick:U,disabled:g.value,class:"btn btn-primary"},[g.value?(l(),o("span",ss)):h("",!0),E(" "+i(g.value?"Creating...":"Create Token"),1)],8,es)])]),e("form",{method:"dialog",class:"modal-backdrop",onClick:D})],2)]))}});export{os as default};
diff --git a/web/frontend/dist/assets/index-BR76citP.css b/web/frontend/dist/assets/index-BR76citP.css
new file mode 100644
index 00000000..39823a6e
--- /dev/null
+++ b/web/frontend/dist/assets/index-BR76citP.css
@@ -0,0 +1 @@
+.toast-enter-active[data-v-b6801221],.toast-leave-active[data-v-b6801221]{transition:all .3s ease}.toast-enter-from[data-v-b6801221]{opacity:0;transform:translate(100%)}.toast-leave-to[data-v-b6801221]{opacity:0;transform:translate(100%) scale(.8)}.toast-move[data-v-b6801221]{transition:transform .3s ease}.modal-backdrop[data-v-54026b07]{-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}code[data-v-54026b07]{font-family:Courier New,monospace;font-size:.875rem}.page-enter-active[data-v-b42f08c7],.page-leave-active[data-v-b42f08c7]{transition:all .3s ease}.page-enter-from[data-v-b42f08c7]{opacity:0;transform:translate(10px)}.page-leave-to[data-v-b42f08c7]{opacity:0;transform:translate(-10px)}.hints-panel-wrapper[data-v-22065a6e]{margin-top:2rem;z-index:10}.hints-collapsed[data-v-22065a6e]{display:flex;align-items:center;gap:.75rem;padding:.75rem 1.25rem;background:hsl(var(--b2));border:1px solid hsl(var(--bc) / .15);border-radius:.5rem;cursor:pointer;transition:all .2s ease;box-shadow:0 2px 8px #0000001a}.hints-collapsed[data-v-22065a6e]:hover{background:hsl(var(--b3));border-color:hsl(var(--bc) / .25);box-shadow:0 4px 12px #00000026}.bulb-icon[data-v-22065a6e]{font-size:1.25rem;flex-shrink:0}.hints-title[data-v-22065a6e]{font-weight:600;font-size:.95rem;flex:1;color:hsl(var(--bc) / .85)}.expand-icon[data-v-22065a6e],.collapse-icon[data-v-22065a6e]{width:1.25rem;height:1.25rem;flex-shrink:0;color:hsl(var(--bc) / .6);transition:transform .2s ease}.hints-expanded[data-v-22065a6e]{background:hsl(var(--b2));border:1px solid hsl(var(--bc) / .15);border-radius:.5rem;overflow:hidden;box-shadow:0 4px 16px #00000026;animation:expandHints-22065a6e .3s ease}@keyframes expandHints-22065a6e{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.hints-header[data-v-22065a6e]{display:flex;align-items:center;justify-content:space-between;padding:.75rem 1.25rem;cursor:pointer;border-bottom:1px solid hsl(var(--bc) / .1);background:hsl(var(--b3))}.hints-header[data-v-22065a6e]:hover{background:hsl(var(--b2))}.hints-header-left[data-v-22065a6e]{display:flex;align-items:center;gap:.75rem}.hints-content[data-v-22065a6e]{padding:1.5rem;max-height:70vh;overflow-y:auto}.hint-section[data-v-22065a6e]{margin-bottom:2rem;padding-bottom:2rem;border-bottom:1px solid hsl(var(--bc) / .1)}.hint-section[data-v-22065a6e]:last-child{margin-bottom:0;padding-bottom:0;border-bottom:none}.hint-section-header[data-v-22065a6e]{display:flex;align-items:center;gap:.75rem;margin-bottom:.75rem}.hint-icon[data-v-22065a6e]{font-size:1.5rem;flex-shrink:0}.hint-section-title[data-v-22065a6e]{font-size:1.1rem;font-weight:600;color:hsl(var(--bc));margin:0}.hint-description[data-v-22065a6e]{margin:0 0 1rem;color:hsl(var(--bc) / .7);font-size:.9rem;line-height:1.5}.hint-subsection[data-v-22065a6e]{margin-bottom:1.25rem}.hint-subsection[data-v-22065a6e]:last-child{margin-bottom:0}.subsection-title[data-v-22065a6e]{font-size:.95rem;font-weight:600;color:hsl(var(--bc) / .9);margin:0 0 .5rem}.subsection-text[data-v-22065a6e]{margin:0 0 .75rem;color:hsl(var(--bc) / .7);font-size:.875rem;line-height:1.5}.code-block-wrapper[data-v-22065a6e]{background:hsl(var(--b3));border:1px solid hsl(var(--bc) / .1);border-radius:.5rem;overflow:hidden;margin:.5rem 0}.code-block-header[data-v-22065a6e]{display:flex;align-items:center;justify-content:space-between;padding:.5rem .75rem;background:hsl(var(--b2));border-bottom:1px solid hsl(var(--bc) / .1)}.code-language[data-v-22065a6e]{font-size:.75rem;font-family:Courier New,Courier,monospace;color:hsl(var(--bc) / .6);text-transform:uppercase}.copy-button[data-v-22065a6e]{padding:.25rem .5rem;font-size:.75rem;background:transparent;border:1px solid hsl(var(--bc) / .2);border-radius:.25rem;cursor:pointer;color:hsl(var(--bc) / .7);transition:all .2s ease}.copy-button[data-v-22065a6e]:hover{background:hsl(var(--bc) / .1);border-color:hsl(var(--bc) / .3);color:hsl(var(--bc))}.code-block[data-v-22065a6e]{padding:.75rem;margin:0;overflow-x:auto;font-family:Courier New,Courier,monospace;font-size:.8rem;line-height:1.5;color:hsl(var(--bc));background:hsl(var(--b3))}.code-block code[data-v-22065a6e]{font-family:inherit;white-space:pre}.simple-code[data-v-22065a6e]{background:hsl(var(--b3));border:1px solid hsl(var(--bc) / .1);border-radius:.5rem;padding:.75rem;margin:.5rem 0}.simple-code pre[data-v-22065a6e]{margin:0;font-family:Courier New,Courier,monospace;font-size:.8rem;overflow-x:auto}.hint-list[data-v-22065a6e]{margin:.5rem 0;padding-left:1.5rem;color:hsl(var(--bc) / .8);font-size:.875rem;line-height:1.6}.hint-list li[data-v-22065a6e]{margin-bottom:.5rem}.hint-list li[data-v-22065a6e]:last-child{margin-bottom:0}.hints-content[data-v-22065a6e]::-webkit-scrollbar{width:8px}.hints-content[data-v-22065a6e]::-webkit-scrollbar-track{background:hsl(var(--b3));border-radius:.25rem}.hints-content[data-v-22065a6e]::-webkit-scrollbar-thumb{background:hsl(var(--bc) / .3);border-radius:.25rem}.hints-content[data-v-22065a6e]::-webkit-scrollbar-thumb:hover{background:hsl(var(--bc) / .5)}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root,[data-theme]{background-color:var(--fallback-b1,oklch(var(--b1)/1));color:var(--fallback-bc,oklch(var(--bc)/1))}@supports not (color: oklch(0% 0 0)){:root{color-scheme:light;--fallback-p: #491eff;--fallback-pc: #d4dbff;--fallback-s: #ff41c7;--fallback-sc: #fff9fc;--fallback-a: #00cfbd;--fallback-ac: #00100d;--fallback-n: #2b3440;--fallback-nc: #d7dde4;--fallback-b1: #ffffff;--fallback-b2: #e5e6e6;--fallback-b3: #e5e6e6;--fallback-bc: #1f2937;--fallback-in: #00b3f0;--fallback-inc: #000000;--fallback-su: #00ca92;--fallback-suc: #000000;--fallback-wa: #ffc22d;--fallback-wac: #000000;--fallback-er: #ff6f70;--fallback-erc: #000000}@media (prefers-color-scheme: dark){:root{color-scheme:dark;--fallback-p: #7582ff;--fallback-pc: #050617;--fallback-s: #ff71cf;--fallback-sc: #190211;--fallback-a: #00c7b5;--fallback-ac: #000e0c;--fallback-n: #2a323c;--fallback-nc: #a6adbb;--fallback-b1: #1d232a;--fallback-b2: #191e24;--fallback-b3: #15191e;--fallback-bc: #a6adbb;--fallback-in: #00b3f0;--fallback-inc: #000000;--fallback-su: #00ca92;--fallback-suc: #000000;--fallback-wa: #ffc22d;--fallback-wac: #000000;--fallback-er: #ff6f70;--fallback-erc: #000000}}}html{-webkit-tap-highlight-color:transparent}*{scrollbar-color:color-mix(in oklch,currentColor 35%,transparent) transparent}*:hover{scrollbar-color:color-mix(in oklch,currentColor 60%,transparent) transparent}:root{color-scheme:light;--in: 72.06% .191 231.6;--su: 64.8% .15 160;--wa: 84.71% .199 83.87;--er: 71.76% .221 22.18;--pc: 89.824% .06192 275.75;--ac: 15.352% .0368 183.61;--inc: 0% 0 0;--suc: 0% 0 0;--wac: 0% 0 0;--erc: 0% 0 0;--rounded-box: 1rem;--rounded-btn: .5rem;--rounded-badge: 1.9rem;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--tab-radius: .5rem;--p: 49.12% .3096 275.75;--s: 69.71% .329 342.55;--sc: 98.71% .0106 342.55;--a: 76.76% .184 183.61;--n: 32.1785% .02476 255.701624;--nc: 89.4994% .011585 252.096176;--b1: 100% 0 0;--b2: 96.1151% 0 0;--b3: 92.4169% .00108 197.137559;--bc: 27.8078% .029596 256.847952}@media (prefers-color-scheme: dark){:root{color-scheme:dark;--in: 72.06% .191 231.6;--su: 64.8% .15 160;--wa: 84.71% .199 83.87;--er: 71.76% .221 22.18;--pc: 13.138% .0392 275.75;--sc: 14.96% .052 342.55;--ac: 14.902% .0334 183.61;--inc: 0% 0 0;--suc: 0% 0 0;--wac: 0% 0 0;--erc: 0% 0 0;--rounded-box: 1rem;--rounded-btn: .5rem;--rounded-badge: 1.9rem;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--tab-radius: .5rem;--p: 65.69% .196 275.75;--s: 74.8% .26 342.55;--a: 74.51% .167 183.61;--n: 31.3815% .021108 254.139175;--nc: 74.6477% .0216 264.435964;--b1: 25.3267% .015896 252.417568;--b2: 23.2607% .013807 253.100675;--b3: 21.1484% .01165 254.087939;--bc: 74.6477% .0216 264.435964}}[data-theme=light]{color-scheme:light;--in: 72.06% .191 231.6;--su: 64.8% .15 160;--wa: 84.71% .199 83.87;--er: 71.76% .221 22.18;--pc: 89.824% .06192 275.75;--ac: 15.352% .0368 183.61;--inc: 0% 0 0;--suc: 0% 0 0;--wac: 0% 0 0;--erc: 0% 0 0;--rounded-box: 1rem;--rounded-btn: .5rem;--rounded-badge: 1.9rem;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--tab-radius: .5rem;--p: 49.12% .3096 275.75;--s: 69.71% .329 342.55;--sc: 98.71% .0106 342.55;--a: 76.76% .184 183.61;--n: 32.1785% .02476 255.701624;--nc: 89.4994% .011585 252.096176;--b1: 100% 0 0;--b2: 96.1151% 0 0;--b3: 92.4169% .00108 197.137559;--bc: 27.8078% .029596 256.847952}[data-theme=dark]{color-scheme:dark;--in: 72.06% .191 231.6;--su: 64.8% .15 160;--wa: 84.71% .199 83.87;--er: 71.76% .221 22.18;--pc: 13.138% .0392 275.75;--sc: 14.96% .052 342.55;--ac: 14.902% .0334 183.61;--inc: 0% 0 0;--suc: 0% 0 0;--wac: 0% 0 0;--erc: 0% 0 0;--rounded-box: 1rem;--rounded-btn: .5rem;--rounded-badge: 1.9rem;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--tab-radius: .5rem;--p: 65.69% .196 275.75;--s: 74.8% .26 342.55;--a: 74.51% .167 183.61;--n: 31.3815% .021108 254.139175;--nc: 74.6477% .0216 264.435964;--b1: 25.3267% .015896 252.417568;--b2: 23.2607% .013807 253.100675;--b3: 21.1484% .01165 254.087939;--bc: 74.6477% .0216 264.435964}[data-theme=cupcake]{color-scheme:light;--in: 72.06% .191 231.6;--su: 64.8% .15 160;--wa: 84.71% .199 83.87;--er: 71.76% .221 22.18;--pc: 15.2344% .017892 200.026556;--sc: 15.787% .020249 356.29965;--ac: 15.8762% .029206 78.618794;--nc: 84.7148% .013247 313.189598;--inc: 0% 0 0;--suc: 0% 0 0;--wac: 0% 0 0;--erc: 0% 0 0;--rounded-box: 1rem;--rounded-badge: 1.9rem;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--p: 76.172% .089459 200.026556;--s: 78.9351% .101246 356.29965;--a: 79.3811% .146032 78.618794;--n: 23.5742% .066235 313.189598;--b1: 97.7882% .00418 56.375637;--b2: 93.9822% .007638 61.449292;--b3: 91.5861% .006811 53.440502;--bc: 23.5742% .066235 313.189598;--rounded-btn: 1.9rem;--tab-border: 2px;--tab-radius: .7rem}[data-theme=bumblebee]{color-scheme:light;--b2: 93% 0 0;--b3: 86% 0 0;--in: 72.06% .191 231.6;--su: 64.8% .15 160;--wa: 84.71% .199 83.87;--er: 71.76% .221 22.18;--bc: 20% 0 0;--ac: 16.254% .0314 56.52;--nc: 82.55% .015 281.99;--inc: 0% 0 0;--suc: 0% 0 0;--wac: 0% 0 0;--erc: 0% 0 0;--rounded-box: 1rem;--rounded-btn: .5rem;--rounded-badge: 1.9rem;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--tab-radius: .5rem;--p: 89.51% .2132 96.61;--pc: 38.92% .046 96.61;--s: 80.39% .194 70.76;--sc: 39.38% .068 70.76;--a: 81.27% .157 56.52;--n: 12.75% .075 281.99;--b1: 100% 0 0}[data-theme=emerald]{color-scheme:light;--b2: 93% 0 0;--b3: 86% 0 0;--in: 72.06% .191 231.6;--su: 64.8% .15 160;--wa: 84.71% .199 83.87;--er: 71.76% .221 22.18;--inc: 0% 0 0;--suc: 0% 0 0;--wac: 0% 0 0;--erc: 0% 0 0;--rounded-box: 1rem;--rounded-btn: .5rem;--rounded-badge: 1.9rem;--border-btn: 1px;--tab-border: 1px;--tab-radius: .5rem;--p: 76.6626% .135433 153.450024;--pc: 33.3872% .040618 162.240129;--s: 61.3028% .202368 261.294233;--sc: 100% 0 0;--a: 72.7725% .149783 33.200363;--ac: 0% 0 0;--n: 35.5192% .032071 262.988584;--nc: 98.4625% .001706 247.838921;--b1: 100% 0 0;--bc: 35.5192% .032071 262.988584;--animation-btn: 0;--animation-input: 0;--btn-focus-scale: 1}[data-theme=corporate]{color-scheme:light;--b2: 93% 0 0;--b3: 86% 0 0;--in: 72.06% .191 231.6;--su: 64.8% .15 160;--wa: 84.71% .199 83.87;--er: 71.76% .221 22.18;--pc: 12.078% .0456 269.1;--sc: 13.0739% .010951 256.688055;--ac: 15.3934% .022799 163.57888;--inc: 0% 0 0;--suc: 0% 0 0;--wac: 0% 0 0;--erc: 0% 0 0;--border-btn: 1px;--tab-border: 1px;--p: 60.39% .228 269.1;--s: 65.3694% .054756 256.688055;--a: 76.9669% .113994 163.57888;--n: 22.3899% .031305 278.07229;--nc: 95.8796% .008588 247.915135;--b1: 100% 0 0;--bc: 22.3899% .031305 278.07229;--rounded-box: .25rem;--rounded-btn: .125rem;--rounded-badge: .125rem;--tab-radius: .25rem;--animation-btn: 0;--animation-input: 0;--btn-focus-scale: 1}[data-theme=synthwave]{color-scheme:dark;--b2: 20.2941% .076211 287.835609;--b3: 18.7665% .070475 287.835609;--pc: 14.4421% .031903 342.009383;--sc: 15.6543% .02362 227.382405;--ac: 17.608% .0412 93.72;--rounded-box: 1rem;--rounded-btn: .5rem;--rounded-badge: 1.9rem;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--tab-radius: .5rem;--p: 72.2105% .159514 342.009383;--s: 78.2714% .118101 227.382405;--a: 88.04% .206 93.72;--n: 25.5554% .103537 286.507967;--nc: 97.9365% .00819 301.358346;--b1: 21.8216% .081948 287.835609;--bc: 97.9365% .00819 301.358346;--in: 76.5197% .12273 231.831603;--inc: 23.5017% .096418 290.329844;--su: 86.0572% .115038 178.624677;--suc: 23.5017% .096418 290.329844;--wa: 85.531% .122117 93.722227;--wac: 23.5017% .096418 290.329844;--er: 73.7005% .121339 32.639257;--erc: 23.5017% .096418 290.329844}[data-theme=retro]{color-scheme:light;--inc: 90.923% .043042 262.880917;--suc: 12.541% .033982 149.213788;--wac: 13.3168% .031484 58.31834;--erc: 13.144% .0398 27.33;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--p: 76.8664% .104092 22.664655;--pc: 26.5104% .006243 .522862;--s: 80.7415% .052534 159.094608;--sc: 26.5104% .006243 .522862;--a: 70.3919% .125455 52.953428;--ac: 26.5104% .006243 .522862;--n: 28.4181% .009519 355.534017;--nc: 92.5604% .025113 89.217311;--b1: 91.6374% .034554 90.51575;--b2: 88.2722% .049418 91.774344;--b3: 84.133% .065952 90.856665;--bc: 26.5104% .006243 .522862;--in: 54.615% .215208 262.880917;--su: 62.7052% .169912 149.213788;--wa: 66.584% .157422 58.31834;--er: 65.72% .199 27.33;--rounded-box: .4rem;--rounded-btn: .4rem;--rounded-badge: .4rem;--tab-radius: .4rem}[data-theme=cyberpunk]{color-scheme:light;--b2: 87.8943% .16647 104.32;--b3: 81.2786% .15394 104.32;--in: 72.06% .191 231.6;--su: 64.8% .15 160;--wa: 84.71% .199 83.87;--er: 71.76% .221 22.18;--bc: 18.902% .0358 104.32;--pc: 14.844% .0418 6.35;--sc: 16.666% .0368 204.72;--ac: 14.372% .04352 310.43;--inc: 0% 0 0;--suc: 0% 0 0;--wac: 0% 0 0;--erc: 0% 0 0;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;--p: 74.22% .209 6.35;--s: 83.33% .184 204.72;--a: 71.86% .2176 310.43;--n: 23.04% .065 269.31;--nc: 94.51% .179 104.32;--b1: 94.51% .179 104.32;--rounded-box: 0;--rounded-btn: 0;--rounded-badge: 0;--tab-radius: 0}[data-theme=valentine]{color-scheme:light;--b2: 88.0567% .024834 337.06289;--b3: 81.4288% .022964 337.06289;--pc: 13.7239% .030755 15.066527;--sc: 14.3942% .029258 293.189609;--ac: 14.2537% .014961 197.828857;--inc: 90.923% .043042 262.880917;--suc: 12.541% .033982 149.213788;--wac: 13.3168% .031484 58.31834;--erc: 14.614% .0414 27.33;--rounded-box: 1rem;--rounded-badge: 1.9rem;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--p: 68.6197% .153774 15.066527;--s: 71.971% .14629 293.189609;--a: 71.2685% .074804 197.828857;--n: 54.6053% .143342 358.004839;--nc: 90.2701% .037202 336.955191;--b1: 94.6846% .026703 337.06289;--bc: 37.3085% .081131 4.606426;--in: 54.615% .215208 262.880917;--su: 62.7052% .169912 149.213788;--wa: 66.584% .157422 58.31834;--er: 73.07% .207 27.33;--rounded-btn: 1.9rem;--tab-radius: .7rem}[data-theme=halloween]{color-scheme:dark;--b2: 23.0416% 0 0;--b3: 21.3072% 0 0;--bc: 84.9552% 0 0;--sc: 89.196% .0496 305.03;--nc: 84.8742% .009322 65.681484;--inc: 90.923% .043042 262.880917;--suc: 12.541% .033982 149.213788;--wac: 13.3168% .031484 58.31834;--erc: 13.144% .0398 27.33;--rounded-box: 1rem;--rounded-btn: .5rem;--rounded-badge: 1.9rem;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--tab-radius: .5rem;--p: 77.48% .204 60.62;--pc: 19.6935% .004671 196.779412;--s: 45.98% .248 305.03;--a: 64.8% .223 136.073479;--ac: 0% 0 0;--n: 24.371% .046608 65.681484;--b1: 24.7759% 0 0;--in: 54.615% .215208 262.880917;--su: 62.7052% .169912 149.213788;--wa: 66.584% .157422 58.31834;--er: 65.72% .199 27.33}[data-theme=garden]{color-scheme:light;--b2: 86.4453% .002011 17.197414;--b3: 79.9386% .00186 17.197414;--in: 72.06% .191 231.6;--su: 64.8% .15 160;--wa: 84.71% .199 83.87;--er: 71.76% .221 22.18;--sc: 89.699% .022197 355.095988;--ac: 11.2547% .010859 154.390187;--inc: 0% 0 0;--suc: 0% 0 0;--wac: 0% 0 0;--erc: 0% 0 0;--rounded-box: 1rem;--rounded-btn: .5rem;--rounded-badge: 1.9rem;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--tab-radius: .5rem;--p: 62.45% .278 3.83636;--pc: 100% 0 0;--s: 48.4952% .110985 355.095988;--a: 56.2735% .054297 154.390187;--n: 24.1559% .049362 89.070594;--nc: 92.9519% .002163 17.197414;--b1: 92.9519% .002163 17.197414;--bc: 16.9617% .001664 17.32068}[data-theme=forest]{color-scheme:dark;--b2: 17.522% .007709 17.911578;--b3: 16.2032% .007129 17.911578;--in: 72.06% .191 231.6;--su: 64.8% .15 160;--wa: 84.71% .199 83.87;--er: 71.76% .221 22.18;--bc: 83.7682% .001658 17.911578;--sc: 13.9553% .027077 168.327128;--ac: 14.1257% .02389 185.713193;--nc: 86.1397% .007806 171.364646;--inc: 0% 0 0;--suc: 0% 0 0;--wac: 0% 0 0;--erc: 0% 0 0;--rounded-box: 1rem;--rounded-badge: 1.9rem;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--tab-radius: .5rem;--p: 68.6283% .185567 148.958922;--pc: 0% 0 0;--s: 69.7764% .135385 168.327128;--a: 70.6285% .119451 185.713193;--n: 30.6985% .039032 171.364646;--b1: 18.8409% .00829 17.911578;--rounded-btn: 1.9rem}[data-theme=aqua]{color-scheme:dark;--b2: 45.3464% .118611 261.181672;--b3: 41.9333% .109683 261.181672;--bc: 89.7519% .025508 261.181672;--sc: 12.1365% .02175 309.782946;--ac: 18.6854% .020445 94.555431;--nc: 12.2124% .023402 243.760661;--inc: 90.923% .043042 262.880917;--suc: 12.541% .033982 149.213788;--wac: 13.3168% .031484 58.31834;--erc: 14.79% .038 27.33;--rounded-box: 1rem;--rounded-btn: .5rem;--rounded-badge: 1.9rem;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--tab-radius: .5rem;--p: 85.6617% .14498 198.6458;--pc: 40.1249% .068266 197.603872;--s: 60.6827% .108752 309.782946;--a: 93.4269% .102225 94.555431;--n: 61.0622% .117009 243.760661;--b1: 48.7596% .127539 261.181672;--in: 54.615% .215208 262.880917;--su: 62.7052% .169912 149.213788;--wa: 66.584% .157422 58.31834;--er: 73.95% .19 27.33}[data-theme=lofi]{color-scheme:light;--inc: 15.908% .0206 205.9;--suc: 18.026% .0306 164.14;--wac: 17.674% .027 79.94;--erc: 15.732% .03 28.47;--border-btn: 1px;--tab-border: 1px;--p: 15.9066% 0 0;--pc: 100% 0 0;--s: 21.455% .001566 17.278957;--sc: 100% 0 0;--a: 26.8618% 0 0;--ac: 100% 0 0;--n: 0% 0 0;--nc: 100% 0 0;--b1: 100% 0 0;--b2: 96.1151% 0 0;--b3: 92.268% .001082 17.17934;--bc: 0% 0 0;--in: 79.54% .103 205.9;--su: 90.13% .153 164.14;--wa: 88.37% .135 79.94;--er: 78.66% .15 28.47;--rounded-box: .25rem;--rounded-btn: .125rem;--rounded-badge: .125rem;--tab-radius: .125rem;--animation-btn: 0;--animation-input: 0;--btn-focus-scale: 1}[data-theme=pastel]{color-scheme:light;--in: 72.06% .191 231.6;--su: 64.8% .15 160;--wa: 84.71% .199 83.87;--er: 71.76% .221 22.18;--bc: 20% 0 0;--pc: 16.6166% .006979 316.8737;--sc: 17.6153% .009839 8.688364;--ac: 17.8419% .012056 170.923263;--nc: 14.2681% .014702 228.183906;--inc: 0% 0 0;--suc: 0% 0 0;--wac: 0% 0 0;--erc: 0% 0 0;--rounded-box: 1rem;--rounded-badge: 1.9rem;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--p: 83.0828% .034896 316.8737;--s: 88.0763% .049197 8.688364;--a: 89.2096% .06028 170.923263;--n: 71.3406% .07351 228.183906;--b1: 100% 0 0;--b2: 98.4625% .001706 247.838921;--b3: 87.1681% .009339 258.338227;--rounded-btn: 1.9rem;--tab-radius: .7rem}[data-theme=fantasy]{color-scheme:light;--b2: 93% 0 0;--b3: 86% 0 0;--in: 72.06% .191 231.6;--su: 64.8% .15 160;--wa: 84.71% .199 83.87;--er: 71.76% .221 22.18;--pc: 87.49% .0378 325.02;--sc: 90.784% .0324 241.36;--ac: 15.196% .0408 56.72;--nc: 85.5616% .005919 256.847952;--inc: 0% 0 0;--suc: 0% 0 0;--wac: 0% 0 0;--erc: 0% 0 0;--rounded-box: 1rem;--rounded-btn: .5rem;--rounded-badge: 1.9rem;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--tab-radius: .5rem;--p: 37.45% .189 325.02;--s: 53.92% .162 241.36;--a: 75.98% .204 56.72;--n: 27.8078% .029596 256.847952;--b1: 100% 0 0;--bc: 27.8078% .029596 256.847952}[data-theme=wireframe]{color-scheme:light;--bc: 20% 0 0;--pc: 15.6521% 0 0;--sc: 15.6521% 0 0;--ac: 15.6521% 0 0;--nc: 18.8014% 0 0;--inc: 89.0403% .062643 264.052021;--suc: 90.395% .035372 142.495339;--wac: 14.1626% .019994 108.702381;--erc: 12.5591% .051537 29.233885;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;font-family:Chalkboard,comic sans ms,"sans-serif";--p: 78.2604% 0 0;--s: 78.2604% 0 0;--a: 78.2604% 0 0;--n: 94.007% 0 0;--b1: 100% 0 0;--b2: 94.9119% 0 0;--b3: 89.7547% 0 0;--in: 45.2014% .313214 264.052021;--su: 51.9752% .176858 142.495339;--wa: 70.8131% .099969 108.702381;--er: 62.7955% .257683 29.233885;--rounded-box: .2rem;--rounded-btn: .2rem;--rounded-badge: .2rem;--tab-radius: .2rem}[data-theme=black]{color-scheme:dark;--pc: 86.736% 0 0;--sc: 86.736% 0 0;--ac: 86.736% 0 0;--nc: 86.736% 0 0;--inc: 89.0403% .062643 264.052021;--suc: 90.395% .035372 142.495339;--wac: 19.3597% .042201 109.769232;--erc: 12.5591% .051537 29.233885;--border-btn: 1px;--tab-border: 1px;--p: 33.6799% 0 0;--s: 33.6799% 0 0;--a: 33.6799% 0 0;--b1: 0% 0 0;--b2: 19.1251% 0 0;--b3: 26.8618% 0 0;--bc: 87.6096% 0 0;--n: 33.6799% 0 0;--in: 45.2014% .313214 264.052021;--su: 51.9752% .176858 142.495339;--wa: 96.7983% .211006 109.769232;--er: 62.7955% .257683 29.233885;--rounded-box: 0;--rounded-btn: 0;--rounded-badge: 0;--animation-btn: 0;--animation-input: 0;--btn-focus-scale: 1;--tab-radius: 0}[data-theme=luxury]{color-scheme:dark;--pc: 20% 0 0;--sc: 85.5163% .012821 261.069149;--ac: 87.3349% .010348 338.82597;--inc: 15.8122% .024356 237.133883;--suc: 15.6239% .038579 132.154381;--wac: 17.2255% .027305 102.89115;--erc: 14.3506% .035271 22.568916;--rounded-box: 1rem;--rounded-btn: .5rem;--rounded-badge: 1.9rem;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--tab-radius: .5rem;--p: 100% 0 0;--s: 27.5815% .064106 261.069149;--a: 36.6744% .051741 338.82597;--n: 24.27% .057015 59.825019;--nc: 93.2033% .089631 90.861683;--b1: 14.0765% .004386 285.822869;--b2: 20.2191% .004211 308.22937;--b3: 29.8961% .003818 308.318612;--bc: 75.6879% .123666 76.890484;--in: 79.0612% .121778 237.133883;--su: 78.1197% .192894 132.154381;--wa: 86.1274% .136524 102.89115;--er: 71.7531% .176357 22.568916}[data-theme=dracula]{color-scheme:dark;--b2: 26.8053% .020556 277.508664;--b3: 24.7877% .019009 277.508664;--pc: 15.0922% .036614 346.812432;--sc: 14.8405% .029709 301.883095;--ac: 16.6785% .024826 66.558491;--nc: 87.8891% .006515 275.524078;--inc: 17.6526% .018676 212.846491;--suc: 17.4199% .043903 148.024881;--wac: 19.1068% .026849 112.757109;--erc: 13.6441% .041266 24.430965;--rounded-box: 1rem;--rounded-btn: .5rem;--rounded-badge: 1.9rem;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--tab-radius: .5rem;--p: 75.4611% .18307 346.812432;--s: 74.2023% .148546 301.883095;--a: 83.3927% .124132 66.558491;--n: 39.4456% .032576 275.524078;--b1: 28.8229% .022103 277.508664;--bc: 97.7477% .007913 106.545019;--in: 88.263% .09338 212.846491;--su: 87.0995% .219516 148.024881;--wa: 95.5338% .134246 112.757109;--er: 68.2204% .206328 24.430965}[data-theme=cmyk]{color-scheme:light;--b2: 93% 0 0;--b3: 86% 0 0;--bc: 20% 0 0;--pc: 14.3544% .02666 239.443325;--sc: 12.8953% .040552 359.339283;--ac: 18.8458% .037948 105.306968;--nc: 84.3557% 0 0;--inc: 13.6952% .0189 217.284104;--suc: 89.3898% .032505 321.406278;--wac: 14.2473% .031969 52.023412;--erc: 12.4027% .041677 28.717543;--rounded-box: 1rem;--rounded-btn: .5rem;--rounded-badge: 1.9rem;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--tab-radius: .5rem;--p: 71.7722% .133298 239.443325;--s: 64.4766% .202758 359.339283;--a: 94.2289% .189741 105.306968;--n: 21.7787% 0 0;--b1: 100% 0 0;--in: 68.4759% .094499 217.284104;--su: 46.949% .162524 321.406278;--wa: 71.2364% .159843 52.023412;--er: 62.0133% .208385 28.717543}[data-theme=autumn]{color-scheme:light;--b2: 89.1077% 0 0;--b3: 82.4006% 0 0;--bc: 19.1629% 0 0;--pc: 88.1446% .032232 17.530175;--sc: 12.3353% .033821 23.865865;--ac: 14.6851% .018999 60.729616;--nc: 90.8734% .007475 51.902819;--inc: 13.8449% .019596 207.284192;--suc: 12.199% .016032 174.616213;--wac: 14.0163% .032982 56.844303;--erc: 90.614% .0482 24.16;--rounded-box: 1rem;--rounded-btn: .5rem;--rounded-badge: 1.9rem;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--tab-radius: .5rem;--p: 40.7232% .16116 17.530175;--s: 61.6763% .169105 23.865865;--a: 73.4253% .094994 60.729616;--n: 54.3672% .037374 51.902819;--b1: 95.8147% 0 0;--in: 69.2245% .097979 207.284192;--su: 60.9951% .080159 174.616213;--wa: 70.0817% .164909 56.844303;--er: 53.07% .241 24.16}[data-theme=business]{color-scheme:dark;--b2: 22.6487% 0 0;--b3: 20.944% 0 0;--bc: 84.8707% 0 0;--pc: 88.3407% .019811 251.473931;--sc: 12.8185% .005481 229.389418;--ac: 13.4542% .033545 35.791525;--nc: 85.4882% .00265 253.041249;--inc: 12.5233% .028702 240.033697;--suc: 14.0454% .018919 156.59611;--wac: 15.4965% .023141 81.519177;--erc: 90.3221% .029356 29.674507;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--tab-radius: .5rem;--p: 41.7036% .099057 251.473931;--s: 64.0924% .027405 229.389418;--a: 67.271% .167726 35.791525;--n: 27.441% .01325 253.041249;--b1: 24.3535% 0 0;--in: 62.6163% .143511 240.033697;--su: 70.2268% .094594 156.59611;--wa: 77.4824% .115704 81.519177;--er: 51.6105% .14678 29.674507;--rounded-box: .25rem;--rounded-btn: .125rem;--rounded-badge: .125rem}[data-theme=acid]{color-scheme:light;--b2: 91.6146% 0 0;--b3: 84.7189% 0 0;--bc: 19.7021% 0 0;--pc: 14.38% .0714 330.759573;--sc: 14.674% .0448 48.250878;--ac: 18.556% .0528 122.962951;--nc: 84.262% .0256 278.68;--inc: 12.144% .0454 252.05;--suc: 17.144% .0532 158.53;--wac: 18.202% .0424 100.5;--erc: 12.968% .0586 29.349188;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--p: 71.9% .357 330.759573;--s: 73.37% .224 48.250878;--a: 92.78% .264 122.962951;--n: 21.31% .128 278.68;--b1: 98.5104% 0 0;--in: 60.72% .227 252.05;--su: 85.72% .266 158.53;--wa: 91.01% .212 100.5;--er: 64.84% .293 29.349188;--rounded-box: 1.25rem;--rounded-btn: 1rem;--rounded-badge: 1rem;--tab-radius: .7rem}[data-theme=lemonade]{color-scheme:light;--b2: 91.8003% .0186 123.72;--b3: 84.8906% .0172 123.72;--bc: 19.742% .004 123.72;--pc: 11.784% .0398 134.6;--sc: 15.55% .0392 111.09;--ac: 17.078% .0402 100.73;--nc: 86.196% .015 108.6;--inc: 17.238% .0094 224.14;--suc: 17.238% .0094 157.85;--wac: 17.238% .0094 102.15;--erc: 17.238% .0094 25.85;--rounded-box: 1rem;--rounded-btn: .5rem;--rounded-badge: 1.9rem;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--tab-radius: .5rem;--p: 58.92% .199 134.6;--s: 77.75% .196 111.09;--a: 85.39% .201 100.73;--n: 30.98% .075 108.6;--b1: 98.71% .02 123.72;--in: 86.19% .047 224.14;--su: 86.19% .047 157.85;--wa: 86.19% .047 102.15;--er: 86.19% .047 25.85}[data-theme=night]{color-scheme:dark;--b2: 19.3144% .037037 265.754874;--b3: 17.8606% .034249 265.754874;--bc: 84.1536% .007965 265.754874;--pc: 15.0703% .027798 232.66148;--sc: 13.6023% .031661 276.934902;--ac: 14.4721% .035244 350.048739;--nc: 85.5899% .00737 260.030984;--suc: 15.6904% .026506 181.911977;--wac: 16.6486% .027912 82.95003;--erc: 14.3572% .034051 13.11834;--rounded-box: 1rem;--rounded-btn: .5rem;--rounded-badge: 1.9rem;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--tab-radius: .5rem;--p: 75.3513% .138989 232.66148;--s: 68.0113% .158303 276.934902;--a: 72.3603% .176218 350.048739;--n: 27.9495% .036848 260.030984;--b1: 20.7682% .039824 265.754874;--in: 68.4553% .148062 237.25135;--inc: 0% 0 0;--su: 78.452% .132529 181.911977;--wa: 83.2428% .139558 82.95003;--er: 71.7858% .170255 13.11834}[data-theme=coffee]{color-scheme:dark;--b2: 20.1585% .021457 329.708637;--b3: 18.6412% .019842 329.708637;--pc: 14.3993% .024765 62.756393;--sc: 86.893% .00597 199.19444;--ac: 88.5243% .014881 224.389184;--nc: 83.3022% .003149 326.261446;--inc: 15.898% .012774 184.558367;--suc: 14.9445% .014491 131.116276;--wac: 17.6301% .028162 87.722413;--erc: 15.4637% .025644 31.871922;--rounded-box: 1rem;--rounded-btn: .5rem;--rounded-badge: 1.9rem;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--tab-radius: .5rem;--p: 71.9967% .123825 62.756393;--s: 34.465% .029849 199.19444;--a: 42.6213% .074405 224.389184;--n: 16.5109% .015743 326.261446;--b1: 21.6758% .023072 329.708637;--bc: 72.3547% .092794 79.129387;--in: 79.4902% .063869 184.558367;--su: 74.7224% .072456 131.116276;--wa: 88.1503% .140812 87.722413;--er: 77.3187% .12822 31.871922}[data-theme=winter]{color-scheme:light;--pc: 91.372% .051 257.57;--sc: 88.5103% .03222 282.339433;--ac: 11.988% .038303 335.171434;--nc: 83.9233% .012704 257.651965;--inc: 17.6255% .017178 214.515264;--suc: 16.0988% .015404 197.823719;--wac: 17.8345% .009167 71.47031;--erc: 14.6185% .022037 20.076293;--rounded-box: 1rem;--rounded-btn: .5rem;--rounded-badge: 1.9rem;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--tab-radius: .5rem;--p: 56.86% .255 257.57;--s: 42.5516% .161098 282.339433;--a: 59.9398% .191515 335.171434;--n: 19.6166% .063518 257.651965;--b1: 100% 0 0;--b2: 97.4663% .011947 259.822565;--b3: 93.2686% .016223 262.751375;--bc: 41.8869% .053885 255.824911;--in: 88.1275% .085888 214.515264;--su: 80.4941% .077019 197.823719;--wa: 89.1725% .045833 71.47031;--er: 73.0926% .110185 20.076293}[data-theme=dim]{color-scheme:dark;--pc: 17.2267% .028331 139.549991;--sc: 14.6752% .033181 35.353059;--ac: 14.8459% .026728 311.37924;--inc: 17.2157% .028409 206.182959;--suc: 17.2343% .028437 166.534048;--wac: 17.2327% .028447 94.818679;--erc: 16.4838% .019914 33.756357;--rounded-box: 1rem;--rounded-btn: .5rem;--rounded-badge: 1.9rem;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--tab-radius: .5rem;--p: 86.1335% .141656 139.549991;--s: 73.3759% .165904 35.353059;--a: 74.2296% .133641 311.37924;--n: 24.7311% .020483 264.094728;--nc: 82.9011% .031335 222.959324;--b1: 30.8577% .023243 264.149498;--b2: 28.0368% .01983 264.182074;--b3: 26.3469% .018403 262.177739;--bc: 82.9011% .031335 222.959324;--in: 86.0785% .142046 206.182959;--su: 86.1717% .142187 166.534048;--wa: 86.1634% .142236 94.818679;--er: 82.4189% .09957 33.756357}[data-theme=nord]{color-scheme:light;--pc: 11.8872% .015449 254.027774;--sc: 13.9303% .011822 248.687186;--ac: 15.4929% .01245 217.469017;--inc: 13.8414% .012499 332.664922;--suc: 15.3654% .01498 131.063061;--wac: 17.0972% .017847 84.093335;--erc: 12.122% .024119 15.341883;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--p: 59.4359% .077246 254.027774;--s: 69.6516% .059108 248.687186;--a: 77.4643% .062249 217.469017;--n: 45.229% .035214 264.1312;--nc: 89.9258% .016374 262.749256;--b1: 95.1276% .007445 260.731539;--b2: 93.2996% .010389 261.788485;--b3: 89.9258% .016374 262.749256;--bc: 32.4374% .022945 264.182036;--in: 69.2072% .062496 332.664922;--su: 76.827% .074899 131.063061;--wa: 85.4862% .089234 84.093335;--er: 60.61% .120594 15.341883;--rounded-box: .4rem;--rounded-btn: .2rem;--rounded-badge: .4rem;--tab-radius: .2rem}[data-theme=sunset]{color-scheme:dark;--pc: 14.9408% .031656 39.94703;--sc: 14.5075% .035531 2.72034;--ac: 14.2589% .033336 299.844533;--inc: 17.1119% .017054 206.015183;--suc: 17.1122% .017172 144.77874;--wac: 17.1139% .016961 74.427797;--erc: 17.1023% .015778 16.886379;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--p: 74.7039% .158278 39.94703;--s: 72.5375% .177654 2.72034;--a: 71.2947% .166678 299.844533;--n: 26% .019 237.69;--nc: 70% .019 237.69;--b1: 22% .019 237.69;--b2: 20% .019 237.69;--b3: 18% .019 237.69;--bc: 77.3835% .043586 245.096534;--in: 85.5596% .085271 206.015183;--su: 85.5609% .08586 144.77874;--wa: 85.5695% .084806 74.427797;--er: 85.5116% .07889 16.886379;--rounded-box: 1.2rem;--rounded-btn: .8rem;--rounded-badge: .4rem;--tab-radius: .7rem}.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.prose{color:var(--tw-prose-body);max-width:65ch}.prose :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em;margin-bottom:1.25em}.prose :where([class~=lead]):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-lead);font-size:1.25em;line-height:1.6;margin-top:1.2em;margin-bottom:1.2em}.prose :where(a):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-links);text-decoration:underline;font-weight:500}.prose :where(strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-bold);font-weight:600}.prose :where(a strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(blockquote strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(thead th strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:decimal;margin-top:1.25em;margin-bottom:1.25em;padding-inline-start:1.625em}.prose :where(ol[type=A]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.prose :where(ol[type=a]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.prose :where(ol[type=A s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.prose :where(ol[type=a s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.prose :where(ol[type=I]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.prose :where(ol[type=i]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.prose :where(ol[type=I s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.prose :where(ol[type=i s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.prose :where(ol[type="1"]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:decimal}.prose :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:disc;margin-top:1.25em;margin-bottom:1.25em;padding-inline-start:1.625em}.prose :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{font-weight:400;color:var(--tw-prose-counters)}.prose :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{color:var(--tw-prose-bullets)}.prose :where(dt):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;margin-top:1.25em}.prose :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:var(--tw-prose-hr);border-top-width:1px;margin-top:3em;margin-bottom:3em}.prose :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)){font-weight:500;font-style:italic;color:var(--tw-prose-quotes);border-inline-start-width:.25rem;border-inline-start-color:var(--tw-prose-quote-borders);quotes:"“""”""‘""’";margin-top:1.6em;margin-bottom:1.6em;padding-inline-start:1em}.prose :where(blockquote p:first-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:open-quote}.prose :where(blockquote p:last-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:close-quote}.prose :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:800;font-size:2.25em;margin-top:0;margin-bottom:.8888889em;line-height:1.1111111}.prose :where(h1 strong):not(:where([class~=not-prose],[class~=not-prose] *)){font-weight:900;color:inherit}.prose :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:700;font-size:1.5em;margin-top:2em;margin-bottom:1em;line-height:1.3333333}.prose :where(h2 strong):not(:where([class~=not-prose],[class~=not-prose] *)){font-weight:800;color:inherit}.prose :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;font-size:1.25em;margin-top:1.6em;margin-bottom:.6em;line-height:1.6}.prose :where(h3 strong):not(:where([class~=not-prose],[class~=not-prose] *)){font-weight:700;color:inherit}.prose :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;margin-top:1.5em;margin-bottom:.5em;line-height:1.5}.prose :where(h4 strong):not(:where([class~=not-prose],[class~=not-prose] *)){font-weight:700;color:inherit}.prose :where(img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(picture):not(:where([class~=not-prose],[class~=not-prose] *)){display:block;margin-top:2em;margin-bottom:2em}.prose :where(video):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){font-weight:500;font-family:inherit;color:var(--tw-prose-kbd);box-shadow:0 0 0 1px rgb(var(--tw-prose-kbd-shadows) / 10%),0 3px rgb(var(--tw-prose-kbd-shadows) / 10%);font-size:.875em;border-radius:.3125rem;padding-top:.1875em;padding-inline-end:.375em;padding-bottom:.1875em;padding-inline-start:.375em}.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-code);font-weight:600;font-size:.875em}.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:"`"}.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:"`"}.prose :where(a code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(h1 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(h2 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.875em}.prose :where(h3 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.9em}.prose :where(h4 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(blockquote code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(thead th code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(pre):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-pre-code);background-color:var(--tw-prose-pre-bg);overflow-x:auto;font-weight:400;font-size:.875em;line-height:1.7142857;margin-top:1.7142857em;margin-bottom:1.7142857em;border-radius:.375rem;padding-top:.8571429em;padding-inline-end:1.1428571em;padding-bottom:.8571429em;padding-inline-start:1.1428571em}.prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:transparent;border-width:0;border-radius:0;padding:0;font-weight:inherit;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit}.prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:none}.prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:none}.prose :where(table):not(:where([class~=not-prose],[class~=not-prose] *)){width:100%;table-layout:auto;margin-top:2em;margin-bottom:2em;font-size:.875em;line-height:1.7142857}.prose :where(thead):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:1px;border-bottom-color:var(--tw-prose-th-borders)}.prose :where(thead th):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;vertical-align:bottom;padding-inline-end:.5714286em;padding-bottom:.5714286em;padding-inline-start:.5714286em}.prose :where(tbody tr):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:1px;border-bottom-color:var(--tw-prose-td-borders)}.prose :where(tbody tr:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:0}.prose :where(tbody td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:baseline}.prose :where(tfoot):not(:where([class~=not-prose],[class~=not-prose] *)){border-top-width:1px;border-top-color:var(--tw-prose-th-borders)}.prose :where(tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:top}.prose :where(th,td):not(:where([class~=not-prose],[class~=not-prose] *)){text-align:start}.prose :where(figure>*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0;margin-bottom:0}.prose :where(figcaption):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-captions);font-size:.875em;line-height:1.4285714;margin-top:.8571429em}.prose{--tw-prose-body: #374151;--tw-prose-headings: #111827;--tw-prose-lead: #4b5563;--tw-prose-links: #111827;--tw-prose-bold: #111827;--tw-prose-counters: #6b7280;--tw-prose-bullets: #d1d5db;--tw-prose-hr: #e5e7eb;--tw-prose-quotes: #111827;--tw-prose-quote-borders: #e5e7eb;--tw-prose-captions: #6b7280;--tw-prose-kbd: #111827;--tw-prose-kbd-shadows: 17 24 39;--tw-prose-code: #111827;--tw-prose-pre-code: #e5e7eb;--tw-prose-pre-bg: #1f2937;--tw-prose-th-borders: #d1d5db;--tw-prose-td-borders: #e5e7eb;--tw-prose-invert-body: #d1d5db;--tw-prose-invert-headings: #fff;--tw-prose-invert-lead: #9ca3af;--tw-prose-invert-links: #fff;--tw-prose-invert-bold: #fff;--tw-prose-invert-counters: #9ca3af;--tw-prose-invert-bullets: #4b5563;--tw-prose-invert-hr: #374151;--tw-prose-invert-quotes: #f3f4f6;--tw-prose-invert-quote-borders: #374151;--tw-prose-invert-captions: #9ca3af;--tw-prose-invert-kbd: #fff;--tw-prose-invert-kbd-shadows: 255 255 255;--tw-prose-invert-code: #fff;--tw-prose-invert-pre-code: #d1d5db;--tw-prose-invert-pre-bg: rgb(0 0 0 / 50%);--tw-prose-invert-th-borders: #4b5563;--tw-prose-invert-td-borders: #374151;font-size:1rem;line-height:1.75}.prose :where(picture>img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0;margin-bottom:0}.prose :where(li):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.5em;margin-bottom:.5em}.prose :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.375em}.prose :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.375em}.prose :where(.prose>ul>li p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.75em;margin-bottom:.75em}.prose :where(.prose>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.prose :where(.prose>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.prose :where(.prose>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.prose :where(.prose>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.prose :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.75em;margin-bottom:.75em}.prose :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em;margin-bottom:1.25em}.prose :where(dd):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.5em;padding-inline-start:1.625em}.prose :where(hr+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(h2+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(h3+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(h4+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(thead th:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.prose :where(thead th:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.prose :where(tbody td,tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){padding-top:.5714286em;padding-inline-end:.5714286em;padding-bottom:.5714286em;padding-inline-start:.5714286em}.prose :where(tbody td:first-child,tfoot td:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.prose :where(tbody td:last-child,tfoot td:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.prose :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(.prose>:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(.prose>:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0}.prose-sm{font-size:.875rem;line-height:1.7142857}.prose-sm :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.1428571em;margin-bottom:1.1428571em}.prose-sm :where([class~=lead]):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:1.2857143em;line-height:1.5555556;margin-top:.8888889em;margin-bottom:.8888889em}.prose-sm :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.3333333em;margin-bottom:1.3333333em;padding-inline-start:1.1111111em}.prose-sm :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:2.1428571em;margin-top:0;margin-bottom:.8em;line-height:1.2}.prose-sm :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:1.4285714em;margin-top:1.6em;margin-bottom:.8em;line-height:1.4}.prose-sm :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:1.2857143em;margin-top:1.5555556em;margin-bottom:.4444444em;line-height:1.5555556}.prose-sm :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.4285714em;margin-bottom:.5714286em;line-height:1.4285714}.prose-sm :where(img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.7142857em;margin-bottom:1.7142857em}.prose-sm :where(picture):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.7142857em;margin-bottom:1.7142857em}.prose-sm :where(picture>img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0;margin-bottom:0}.prose-sm :where(video):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.7142857em;margin-bottom:1.7142857em}.prose-sm :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.8571429em;border-radius:.3125rem;padding-top:.1428571em;padding-inline-end:.3571429em;padding-bottom:.1428571em;padding-inline-start:.3571429em}.prose-sm :where(code):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.8571429em}.prose-sm :where(h2 code):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.9em}.prose-sm :where(h3 code):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.8888889em}.prose-sm :where(pre):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.8571429em;line-height:1.6666667;margin-top:1.6666667em;margin-bottom:1.6666667em;border-radius:.25rem;padding-top:.6666667em;padding-inline-end:1em;padding-bottom:.6666667em;padding-inline-start:1em}.prose-sm :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.1428571em;margin-bottom:1.1428571em;padding-inline-start:1.5714286em}.prose-sm :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.1428571em;margin-bottom:1.1428571em;padding-inline-start:1.5714286em}.prose-sm :where(li):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.2857143em;margin-bottom:.2857143em}.prose-sm :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.4285714em}.prose-sm :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.4285714em}.prose-sm :where(.prose-sm>ul>li p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.5714286em;margin-bottom:.5714286em}.prose-sm :where(.prose-sm>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.1428571em}.prose-sm :where(.prose-sm>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.1428571em}.prose-sm :where(.prose-sm>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.1428571em}.prose-sm :where(.prose-sm>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.1428571em}.prose-sm :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.5714286em;margin-bottom:.5714286em}.prose-sm :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.1428571em;margin-bottom:1.1428571em}.prose-sm :where(dt):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.1428571em}.prose-sm :where(dd):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.2857143em;padding-inline-start:1.5714286em}.prose-sm :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2.8571429em;margin-bottom:2.8571429em}.prose-sm :where(hr+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose-sm :where(h2+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose-sm :where(h3+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose-sm :where(h4+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose-sm :where(table):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.8571429em;line-height:1.5}.prose-sm :where(thead th):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:1em;padding-bottom:.6666667em;padding-inline-start:1em}.prose-sm :where(thead th:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.prose-sm :where(thead th:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.prose-sm :where(tbody td,tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){padding-top:.6666667em;padding-inline-end:1em;padding-bottom:.6666667em;padding-inline-start:1em}.prose-sm :where(tbody td:first-child,tfoot td:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.prose-sm :where(tbody td:last-child,tfoot td:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.prose-sm :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.7142857em;margin-bottom:1.7142857em}.prose-sm :where(figure>*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0;margin-bottom:0}.prose-sm :where(figcaption):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.8571429em;line-height:1.3333333;margin-top:.6666667em}.prose-sm :where(.prose-sm>:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose-sm :where(.prose-sm>:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0}.alert{display:grid;width:100%;grid-auto-flow:row;align-content:flex-start;align-items:center;justify-items:center;gap:1rem;text-align:center;border-radius:var(--rounded-box, 1rem);border-width:1px;--tw-border-opacity: 1;border-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));padding:1rem;--tw-text-opacity: 1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));--alert-bg: var(--fallback-b2,oklch(var(--b2)/1));--alert-bg-mix: var(--fallback-b1,oklch(var(--b1)/1));background-color:var(--alert-bg)}@media (min-width: 640px){.alert{grid-auto-flow:column;grid-template-columns:auto minmax(auto,1fr);justify-items:start;text-align:start}}.avatar{position:relative;display:inline-flex}.avatar>div{display:block;aspect-ratio:1 / 1;overflow:hidden}.avatar img{height:100%;width:100%;-o-object-fit:cover;object-fit:cover}.avatar.placeholder>div{display:flex;align-items:center;justify-content:center}.badge{display:inline-flex;align-items:center;justify-content:center;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(0,0,.2,1);transition-duration:.2s;height:1.25rem;font-size:.875rem;line-height:1.25rem;width:-moz-fit-content;width:fit-content;padding-left:.563rem;padding-right:.563rem;border-radius:var(--rounded-badge, 1.9rem);border-width:1px;--tw-border-opacity: 1;border-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));--tw-bg-opacity: 1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)))}.breadcrumbs{max-width:100%;overflow-x:auto;padding-top:.5rem;padding-bottom:.5rem}.breadcrumbs>ul,.breadcrumbs>ol{display:flex;align-items:center;white-space:nowrap;min-height:-moz-min-content;min-height:min-content}.breadcrumbs>ul>li,.breadcrumbs>ol>li{display:flex;align-items:center}.breadcrumbs>ul>li>a,.breadcrumbs>ol>li>a{display:flex;cursor:pointer;align-items:center}@media (hover:hover){.breadcrumbs>ul>li>a:hover,.breadcrumbs>ol>li>a:hover{text-decoration-line:underline}.link-hover:hover{text-decoration-line:underline}.checkbox-primary:hover{--tw-border-opacity: 1;border-color:var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)))}.checkbox-warning:hover{--tw-border-opacity: 1;border-color:var(--fallback-wa,oklch(var(--wa)/var(--tw-border-opacity)))}.checkbox-info:hover{--tw-border-opacity: 1;border-color:var(--fallback-in,oklch(var(--in)/var(--tw-border-opacity)))}.checkbox-error:hover{--tw-border-opacity: 1;border-color:var(--fallback-er,oklch(var(--er)/var(--tw-border-opacity)))}.label a:hover{--tw-text-opacity: 1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)))}.menu li>*:not(ul,.menu-title,details,.btn):active,.menu li>*:not(ul,.menu-title,details,.btn).active,.menu li>details>summary:active{--tw-bg-opacity: 1;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)))}.radio-primary:hover{--tw-border-opacity: 1;border-color:var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)))}.tab:hover{--tw-text-opacity: 1}.tabs-boxed :is(.tab-active,[aria-selected=true]):not(.tab-disabled):not([disabled]):hover,.tabs-boxed :is(input:checked):hover{--tw-text-opacity: 1;color:var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)))}.table tr.hover:hover,.table tr.hover:nth-child(2n):hover{--tw-bg-opacity: 1;background-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)))}.table-zebra tr.hover:hover,.table-zebra tr.hover:nth-child(2n):hover{--tw-bg-opacity: 1;background-color:var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity)))}}.btn{display:inline-flex;height:3rem;min-height:3rem;flex-shrink:0;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-wrap:wrap;align-items:center;justify-content:center;border-radius:var(--rounded-btn, .5rem);border-color:transparent;border-color:oklch(var(--btn-color, var(--b2)) / var(--tw-border-opacity));padding-left:1rem;padding-right:1rem;text-align:center;font-size:.875rem;line-height:1em;gap:.5rem;font-weight:600;text-decoration-line:none;transition-duration:.2s;transition-timing-function:cubic-bezier(0,0,.2,1);border-width:var(--border-btn, 1px);transition-property:color,background-color,border-color,opacity,box-shadow,transform;--tw-text-opacity: 1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow);outline-color:var(--fallback-bc,oklch(var(--bc)/1));background-color:oklch(var(--btn-color, var(--b2)) / var(--tw-bg-opacity));--tw-bg-opacity: 1;--tw-border-opacity: 1}.btn-disabled,.btn[disabled],.btn:disabled{pointer-events:none}.btn-square{height:3rem;width:3rem;padding:0}.btn-circle{height:3rem;width:3rem;border-radius:9999px;padding:0}:where(.btn:is(input[type=checkbox])),:where(.btn:is(input[type=radio])){width:auto;-webkit-appearance:none;-moz-appearance:none;appearance:none}.btn:is(input[type=checkbox]):after,.btn:is(input[type=radio]):after{--tw-content: attr(aria-label);content:var(--tw-content)}.card{position:relative;display:flex;flex-direction:column;border-radius:var(--rounded-box, 1rem)}.card:focus{outline:2px solid transparent;outline-offset:2px}.card-body{display:flex;flex:1 1 auto;flex-direction:column;padding:var(--padding-card, 2rem);gap:.5rem}.card-body :where(p){flex-grow:1}.card-actions{display:flex;flex-wrap:wrap;align-items:flex-start;gap:.5rem}.card figure{display:flex;align-items:center;justify-content:center}.card.image-full{display:grid}.card.image-full:before{position:relative;content:"";z-index:10;border-radius:var(--rounded-box, 1rem);--tw-bg-opacity: 1;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));opacity:.75}.card.image-full:before,.card.image-full>*{grid-column-start:1;grid-row-start:1}.card.image-full>figure img{height:100%;-o-object-fit:cover;object-fit:cover}.card.image-full>.card-body{position:relative;z-index:20;--tw-text-opacity: 1;color:var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)))}.checkbox{flex-shrink:0;--chkbg: var(--fallback-bc,oklch(var(--bc)/1));--chkfg: var(--fallback-b1,oklch(var(--b1)/1));height:1.5rem;width:1.5rem;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:var(--rounded-btn, .5rem);border-width:1px;border-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));--tw-border-opacity: .2}.collapse:not(td):not(tr):not(colgroup){visibility:visible}.collapse{position:relative;display:grid;overflow:hidden;grid-template-rows:max-content 0fr;transition:grid-template-rows .2s;width:100%;border-radius:var(--rounded-box, 1rem)}.collapse-title,.collapse>input[type=checkbox],.collapse>input[type=radio],.collapse-content{grid-column-start:1;grid-row-start:1}.collapse>input[type=checkbox],.collapse>input[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;opacity:0}:where(.collapse>input[type=checkbox]),:where(.collapse>input[type=radio]){height:100%;width:100%;z-index:1}.collapse-content{visibility:hidden;grid-column-start:1;grid-row-start:2;min-height:0px;transition:visibility .2s;transition:padding .2s ease-out,background-color .2s ease-out;padding-left:1rem;padding-right:1rem;cursor:unset}.collapse[open],.collapse-open,.collapse:focus:not(.collapse-close){grid-template-rows:max-content 1fr}.collapse:not(.collapse-close):has(>input[type=checkbox]:checked),.collapse:not(.collapse-close):has(>input[type=radio]:checked){grid-template-rows:max-content 1fr}.collapse[open]>.collapse-content,.collapse-open>.collapse-content,.collapse:focus:not(.collapse-close)>.collapse-content,.collapse:not(.collapse-close)>input[type=checkbox]:checked~.collapse-content,.collapse:not(.collapse-close)>input[type=radio]:checked~.collapse-content{visibility:visible;min-height:-moz-fit-content;min-height:fit-content}.diff{position:relative;display:grid;width:100%;overflow:hidden;direction:ltr;container-type:inline-size;grid-template-columns:auto 1fr}.divider{display:flex;flex-direction:row;align-items:center;align-self:stretch;margin-top:1rem;margin-bottom:1rem;height:1rem;white-space:nowrap}.divider:before,.divider:after{height:.125rem;width:100%;flex-grow:1;--tw-content: "";content:var(--tw-content);background-color:var(--fallback-bc,oklch(var(--bc)/.1))}.drawer{position:relative;display:grid;grid-auto-columns:max-content auto;width:100%}.drawer-content{grid-column-start:2;grid-row-start:1;min-width:0px}.drawer-side{pointer-events:none;position:fixed;inset-inline-start:0px;top:0;grid-column-start:1;grid-row-start:1;display:grid;width:100%;grid-template-columns:repeat(1,minmax(0,1fr));grid-template-rows:repeat(1,minmax(0,1fr));align-items:flex-start;justify-items:start;overflow-x:hidden;overflow-y:hidden;overscroll-behavior:contain;height:100vh;height:100dvh}.drawer-side>.drawer-overlay{position:sticky;top:0;place-self:stretch;cursor:pointer;background-color:transparent;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(0,0,.2,1);transition-duration:.2s}.drawer-side>*{grid-column-start:1;grid-row-start:1}.drawer-side>*:not(.drawer-overlay){transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(0,0,.2,1);transition-duration:.3s;will-change:transform;transform:translate(-100%)}[dir=rtl] .drawer-side>*:not(.drawer-overlay){transform:translate(100%)}.drawer-toggle{position:fixed;height:0px;width:0px;-webkit-appearance:none;-moz-appearance:none;appearance:none;opacity:0}.drawer-toggle:checked~.drawer-side{pointer-events:auto;visibility:visible;overflow-y:auto}.drawer-toggle:checked~.drawer-side>*:not(.drawer-overlay){transform:translate(0)}.drawer-end{grid-auto-columns:auto max-content}.drawer-end>.drawer-toggle~.drawer-content{grid-column-start:1}.drawer-end>.drawer-toggle~.drawer-side{grid-column-start:2;justify-items:end}.drawer-end>.drawer-toggle~.drawer-side>*:not(.drawer-overlay){transform:translate(100%)}[dir=rtl] .drawer-end>.drawer-toggle~.drawer-side>*:not(.drawer-overlay){transform:translate(-100%)}.drawer-end>.drawer-toggle:checked~.drawer-side>*:not(.drawer-overlay){transform:translate(0)}.dropdown{position:relative;display:inline-block}.dropdown>*:not(summary):focus{outline:2px solid transparent;outline-offset:2px}.dropdown .dropdown-content{position:absolute}.dropdown:is(:not(details)) .dropdown-content{visibility:hidden;opacity:0;transform-origin:top;--tw-scale-x: .95;--tw-scale-y: .95;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(0,0,.2,1);transition-duration:.2s}.dropdown-end .dropdown-content{inset-inline-end:0px}.dropdown-left .dropdown-content{bottom:auto;inset-inline-end:100%;top:0;transform-origin:right}.dropdown-right .dropdown-content{bottom:auto;inset-inline-start:100%;top:0;transform-origin:left}.dropdown-bottom .dropdown-content{bottom:auto;top:100%;transform-origin:top}.dropdown-top .dropdown-content{bottom:100%;top:auto;transform-origin:bottom}.dropdown-end.dropdown-right .dropdown-content,.dropdown-end.dropdown-left .dropdown-content{bottom:0;top:auto}.dropdown.dropdown-open .dropdown-content,.dropdown:not(.dropdown-hover):focus .dropdown-content,.dropdown:focus-within .dropdown-content{visibility:visible;opacity:1}@media (hover: hover){.dropdown.dropdown-hover:hover .dropdown-content{visibility:visible;opacity:1}.btm-nav>*.disabled:hover,.btm-nav>*[disabled]:hover{pointer-events:none;--tw-border-opacity: 0;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));--tw-bg-opacity: .1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));--tw-text-opacity: .2}.btn:hover{--tw-border-opacity: 1;border-color:var(--fallback-b3,oklch(var(--b3)/var(--tw-border-opacity)));--tw-bg-opacity: 1;background-color:var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity)))}@supports (color: color-mix(in oklab,black,black)){.btn:hover{background-color:color-mix(in oklab,oklch(var(--btn-color, var(--b2)) / var(--tw-bg-opacity, 1)) 90%,black);border-color:color-mix(in oklab,oklch(var(--btn-color, var(--b2)) / var(--tw-border-opacity, 1)) 90%,black)}}@supports not (color: oklch(0% 0 0)){.btn:hover{background-color:var(--btn-color, var(--fallback-b2));border-color:var(--btn-color, var(--fallback-b2))}}.btn.glass:hover{--glass-opacity: 25%;--glass-border-opacity: 15%}.btn-ghost:hover{border-color:transparent}@supports (color: oklch(0% 0 0)){.btn-ghost:hover{background-color:var(--fallback-bc,oklch(var(--bc)/.2))}}.btn-outline:hover{--tw-border-opacity: 1;border-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));--tw-bg-opacity: 1;background-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-b1,oklch(var(--b1)/var(--tw-text-opacity)))}.btn-outline.btn-primary:hover{--tw-text-opacity: 1;color:var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)))}@supports (color: color-mix(in oklab,black,black)){.btn-outline.btn-primary:hover{background-color:color-mix(in oklab,var(--fallback-p,oklch(var(--p)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-p,oklch(var(--p)/1)) 90%,black)}}.btn-outline.btn-secondary:hover{--tw-text-opacity: 1;color:var(--fallback-sc,oklch(var(--sc)/var(--tw-text-opacity)))}@supports (color: color-mix(in oklab,black,black)){.btn-outline.btn-secondary:hover{background-color:color-mix(in oklab,var(--fallback-s,oklch(var(--s)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-s,oklch(var(--s)/1)) 90%,black)}}.btn-outline.btn-accent:hover{--tw-text-opacity: 1;color:var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity)))}@supports (color: color-mix(in oklab,black,black)){.btn-outline.btn-accent:hover{background-color:color-mix(in oklab,var(--fallback-a,oklch(var(--a)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-a,oklch(var(--a)/1)) 90%,black)}}.btn-outline.btn-success:hover{--tw-text-opacity: 1;color:var(--fallback-suc,oklch(var(--suc)/var(--tw-text-opacity)))}@supports (color: color-mix(in oklab,black,black)){.btn-outline.btn-success:hover{background-color:color-mix(in oklab,var(--fallback-su,oklch(var(--su)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-su,oklch(var(--su)/1)) 90%,black)}}.btn-outline.btn-info:hover{--tw-text-opacity: 1;color:var(--fallback-inc,oklch(var(--inc)/var(--tw-text-opacity)))}@supports (color: color-mix(in oklab,black,black)){.btn-outline.btn-info:hover{background-color:color-mix(in oklab,var(--fallback-in,oklch(var(--in)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-in,oklch(var(--in)/1)) 90%,black)}}.btn-outline.btn-warning:hover{--tw-text-opacity: 1;color:var(--fallback-wac,oklch(var(--wac)/var(--tw-text-opacity)))}@supports (color: color-mix(in oklab,black,black)){.btn-outline.btn-warning:hover{background-color:color-mix(in oklab,var(--fallback-wa,oklch(var(--wa)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-wa,oklch(var(--wa)/1)) 90%,black)}}.btn-outline.btn-error:hover{--tw-text-opacity: 1;color:var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity)))}@supports (color: color-mix(in oklab,black,black)){.btn-outline.btn-error:hover{background-color:color-mix(in oklab,var(--fallback-er,oklch(var(--er)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-er,oklch(var(--er)/1)) 90%,black)}}.btn-disabled:hover,.btn[disabled]:hover,.btn:disabled:hover{--tw-border-opacity: 0;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));--tw-bg-opacity: .2;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));--tw-text-opacity: .2}@supports (color: color-mix(in oklab,black,black)){.btn:is(input[type=checkbox]:checked):hover,.btn:is(input[type=radio]:checked):hover{background-color:color-mix(in oklab,var(--fallback-p,oklch(var(--p)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-p,oklch(var(--p)/1)) 90%,black)}}.dropdown.dropdown-hover:hover .dropdown-content{--tw-scale-x: 1;--tw-scale-y: 1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}:where(.menu li:not(.menu-title,.disabled)>*:not(ul,details,.menu-title)):not(.active,.btn):hover,:where(.menu li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(.active,.btn):hover{cursor:pointer;outline:2px solid transparent;outline-offset:2px}@supports (color: oklch(0% 0 0)){:where(.menu li:not(.menu-title,.disabled)>*:not(ul,details,.menu-title)):not(.active,.btn):hover,:where(.menu li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(.active,.btn):hover{background-color:var(--fallback-bc,oklch(var(--bc)/.1))}}.tab[disabled],.tab[disabled]:hover{cursor:not-allowed;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));--tw-text-opacity: .2}}.dropdown:is(details) summary::-webkit-details-marker{display:none}.file-input{height:3rem;flex-shrink:1;padding-inline-end:1rem;font-size:1rem;line-height:2;line-height:1.5rem;overflow:hidden;border-radius:var(--rounded-btn, .5rem);border-width:1px;border-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));--tw-border-opacity: 0;--tw-bg-opacity: 1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)))}.file-input::file-selector-button{margin-inline-end:1rem;display:inline-flex;height:100%;flex-shrink:0;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-wrap:wrap;align-items:center;justify-content:center;padding-left:1rem;padding-right:1rem;text-align:center;font-size:.875rem;line-height:1.25rem;line-height:1em;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(0,0,.2,1);transition-duration:.2s;border-style:solid;--tw-border-opacity: 1;border-color:var(--fallback-n,oklch(var(--n)/var(--tw-border-opacity)));--tw-bg-opacity: 1;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));font-weight:600;text-transform:uppercase;--tw-text-opacity: 1;color:var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));text-decoration-line:none;border-width:var(--border-btn, 1px);animation:button-pop var(--animation-btn, .25s) ease-out}.footer{display:grid;width:100%;grid-auto-flow:row;place-items:start;-moz-column-gap:1rem;column-gap:1rem;row-gap:2.5rem;font-size:.875rem;line-height:1.25rem}.footer>*{display:grid;place-items:start;gap:.5rem}@media (min-width: 48rem){.footer{grid-auto-flow:column}.footer-center{grid-auto-flow:row dense}}.form-control{display:flex;flex-direction:column}.label{display:flex;-webkit-user-select:none;-moz-user-select:none;user-select:none;align-items:center;justify-content:space-between;padding:.5rem .25rem}.indicator{position:relative;display:inline-flex;width:-moz-max-content;width:max-content}.indicator :where(.indicator-item){z-index:1;position:absolute;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));white-space:nowrap}.input{flex-shrink:1;-webkit-appearance:none;-moz-appearance:none;appearance:none;height:3rem;padding-left:1rem;padding-right:1rem;font-size:1rem;line-height:2;line-height:1.5rem;border-radius:var(--rounded-btn, .5rem);border-width:1px;border-color:transparent;--tw-bg-opacity: 1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)))}.input[type=number]::-webkit-inner-spin-button,.input-md[type=number]::-webkit-inner-spin-button{margin-top:-1rem;margin-bottom:-1rem;margin-inline-end:-1rem}.input-sm[type=number]::-webkit-inner-spin-button{margin-top:0;margin-bottom:0;margin-inline-end:-0px}.input-lg[type=number]::-webkit-inner-spin-button{margin-top:-1.5rem;margin-bottom:-1.5rem;margin-inline-end:-1.5rem}.join{display:inline-flex;align-items:stretch;border-radius:var(--rounded-btn, .5rem)}.join :where(.join-item){border-start-end-radius:0;border-end-end-radius:0;border-end-start-radius:0;border-start-start-radius:0}.join .join-item:not(:first-child):not(:last-child),.join *:not(:first-child):not(:last-child) .join-item{border-start-end-radius:0;border-end-end-radius:0;border-end-start-radius:0;border-start-start-radius:0}.join .join-item:first-child:not(:last-child),.join *:first-child:not(:last-child) .join-item{border-start-end-radius:0;border-end-end-radius:0}.join .dropdown .join-item:first-child:not(:last-child),.join *:first-child:not(:last-child) .dropdown .join-item{border-start-end-radius:inherit;border-end-end-radius:inherit}.join :where(.join-item:first-child:not(:last-child)),.join :where(*:first-child:not(:last-child) .join-item){border-end-start-radius:inherit;border-start-start-radius:inherit}.join .join-item:last-child:not(:first-child),.join *:last-child:not(:first-child) .join-item{border-end-start-radius:0;border-start-start-radius:0}.join :where(.join-item:last-child:not(:first-child)),.join :where(*:last-child:not(:first-child) .join-item){border-start-end-radius:inherit;border-end-end-radius:inherit}@supports not selector(:has(*)){:where(.join *){border-radius:inherit}}@supports selector(:has(*)){:where(.join *:has(.join-item)){border-radius:inherit}}.kbd{display:inline-flex;align-items:center;justify-content:center;border-radius:var(--rounded-btn, .5rem);border-width:1px;border-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));--tw-border-opacity: .2;--tw-bg-opacity: 1;background-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));padding-left:.5rem;padding-right:.5rem;--tw-text-opacity: 1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));border-bottom-width:2px;min-height:2.2em;min-width:2.2em}.link{cursor:pointer;text-decoration-line:underline}.link-hover{text-decoration-line:none}.menu{display:flex;flex-direction:column;flex-wrap:wrap;font-size:.875rem;line-height:1.25rem;padding:.5rem}.menu :where(li ul){position:relative;white-space:nowrap;margin-inline-start:1rem;padding-inline-start:.5rem}.menu :where(li:not(.menu-title)>*:not(ul,details,.menu-title,.btn)),.menu :where(li:not(.menu-title)>details>summary:not(.menu-title)){display:grid;grid-auto-flow:column;align-content:flex-start;align-items:center;gap:.5rem;grid-auto-columns:minmax(auto,max-content) auto max-content;-webkit-user-select:none;-moz-user-select:none;user-select:none}.menu li.disabled{cursor:not-allowed;-webkit-user-select:none;-moz-user-select:none;user-select:none;color:var(--fallback-bc,oklch(var(--bc)/.3))}.menu :where(li>.menu-dropdown:not(.menu-dropdown-show)){display:none}:where(.menu li){position:relative;display:flex;flex-shrink:0;flex-direction:column;flex-wrap:wrap;align-items:stretch}:where(.menu li) .badge{justify-self:end}.mockup-code{position:relative;overflow:hidden;overflow-x:auto;min-width:18rem;border-radius:var(--rounded-box, 1rem);--tw-bg-opacity: 1;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));padding-top:1.25rem;padding-bottom:1.25rem;--tw-text-opacity: 1;color:var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));direction:ltr}.mockup-code pre[data-prefix]:before{content:attr(data-prefix);display:inline-block;text-align:right;width:2rem;opacity:.5}.modal{pointer-events:none;position:fixed;top:0;right:0;bottom:0;left:0;margin:0;display:grid;height:100%;max-height:none;width:100%;max-width:none;justify-items:center;padding:0;opacity:0;overscroll-behavior:contain;z-index:999;background-color:transparent;color:inherit;transition-duration:.2s;transition-timing-function:cubic-bezier(0,0,.2,1);transition-property:transform,opacity,visibility;overflow-y:hidden}:where(.modal){align-items:center}.modal-box{max-height:calc(100vh - 5em);grid-column-start:1;grid-row-start:1;width:91.666667%;max-width:32rem;--tw-scale-x: .9;--tw-scale-y: .9;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-bottom-right-radius:var(--rounded-box, 1rem);border-bottom-left-radius:var(--rounded-box, 1rem);border-top-left-radius:var(--rounded-box, 1rem);border-top-right-radius:var(--rounded-box, 1rem);--tw-bg-opacity: 1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));padding:1.5rem;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(0,0,.2,1);transition-duration:.2s;box-shadow:#00000040 0 25px 50px -12px;overflow-y:auto;overscroll-behavior:contain}.modal-open,.modal:target,.modal-toggle:checked+.modal,.modal[open]{pointer-events:auto;visibility:visible;opacity:1}.modal-action{display:flex;margin-top:1.5rem;justify-content:flex-end}:root:has(:is(.modal-open,.modal:target,.modal-toggle:checked+.modal,.modal[open])){overflow:hidden;scrollbar-gutter:stable}.navbar{display:flex;align-items:center;padding:var(--navbar-padding, .5rem);min-height:4rem;width:100%}:where(.navbar>*:not(script,style)){display:inline-flex;align-items:center}.navbar-start{width:50%;justify-content:flex-start}.navbar-center{flex-shrink:0}.navbar-end{width:50%;justify-content:flex-end}.progress{position:relative;width:100%;-webkit-appearance:none;-moz-appearance:none;appearance:none;overflow:hidden;height:.5rem;border-radius:var(--rounded-box, 1rem);background-color:var(--fallback-bc,oklch(var(--bc)/.2))}.radio{flex-shrink:0;--chkbg: var(--bc);height:1.5rem;width:1.5rem;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:9999px;border-width:1px;border-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));--tw-border-opacity: .2}.range{height:1.5rem;width:100%;cursor:pointer;-moz-appearance:none;appearance:none;-webkit-appearance:none;--range-shdw: var(--fallback-bc,oklch(var(--bc)/1));overflow:hidden;border-radius:var(--rounded-box, 1rem);background-color:transparent}.range:focus{outline:none}.select{display:inline-flex;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;height:3rem;min-height:3rem;padding-inline-start:1rem;padding-inline-end:2.5rem;font-size:.875rem;line-height:1.25rem;line-height:2;border-radius:var(--rounded-btn, .5rem);border-width:1px;border-color:transparent;--tw-bg-opacity: 1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));background-image:linear-gradient(45deg,transparent 50%,currentColor 50%),linear-gradient(135deg,currentColor 50%,transparent 50%);background-position:calc(100% - 20px) calc(1px + 50%),calc(100% - 16.1px) calc(1px + 50%);background-size:4px 4px,4px 4px;background-repeat:no-repeat}.select[multiple]{height:auto}.stats{display:inline-grid;border-radius:var(--rounded-box, 1rem);--tw-bg-opacity: 1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)))}:where(.stats){grid-auto-flow:column;overflow-x:auto}.stat{display:inline-grid;width:100%;grid-template-columns:repeat(1,1fr);-moz-column-gap:1rem;column-gap:1rem;border-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));--tw-border-opacity: .1;padding:1rem 1.5rem}.stat-figure{grid-column-start:2;grid-row:span 3 / span 3;grid-row-start:1;place-self:center;justify-self:end}.stat-title{grid-column-start:1;white-space:nowrap;color:var(--fallback-bc,oklch(var(--bc)/.6))}.stat-value{grid-column-start:1;white-space:nowrap;font-size:2.25rem;line-height:2.5rem;font-weight:800}.stat-desc{grid-column-start:1;white-space:nowrap;font-size:.75rem;line-height:1rem;color:var(--fallback-bc,oklch(var(--bc)/.6))}.tabs{display:grid;align-items:flex-end}.tabs-lifted:has(.tab-content[class^=rounded-]) .tab:first-child:not(:is(.tab-active,[aria-selected=true])),.tabs-lifted:has(.tab-content[class*=" rounded-"]) .tab:first-child:not(:is(.tab-active,[aria-selected=true])){border-bottom-color:transparent}.tab{position:relative;grid-row-start:1;display:inline-flex;height:2rem;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;flex-wrap:wrap;align-items:center;justify-content:center;text-align:center;font-size:.875rem;line-height:1.25rem;line-height:2;--tab-padding: 1rem;--tw-text-opacity: .5;--tab-color: var(--fallback-bc,oklch(var(--bc)/1));--tab-bg: var(--fallback-b1,oklch(var(--b1)/1));--tab-border-color: var(--fallback-b3,oklch(var(--b3)/1));color:var(--tab-color);padding-inline-start:var(--tab-padding, 1rem);padding-inline-end:var(--tab-padding, 1rem)}.tab:is(input[type=radio]){width:auto;border-bottom-right-radius:0;border-bottom-left-radius:0}.tab:is(input[type=radio]):after{--tw-content: attr(aria-label);content:var(--tw-content)}.tab:not(input):empty{cursor:default;grid-column-start:span 9999}:checked+.tab-content:nth-child(2),:is(.tab-active,[aria-selected=true])+.tab-content:nth-child(2){border-start-start-radius:0px}input.tab:checked+.tab-content,:is(.tab-active,[aria-selected=true])+.tab-content{display:block}.table{position:relative;width:100%;border-radius:var(--rounded-box, 1rem);text-align:left;font-size:.875rem;line-height:1.25rem}.table :where(.table-pin-rows thead tr){position:sticky;top:0;z-index:1;--tw-bg-opacity: 1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)))}.table :where(.table-pin-rows tfoot tr){position:sticky;bottom:0;z-index:1;--tw-bg-opacity: 1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)))}.table :where(.table-pin-cols tr th){position:sticky;left:0;right:0;--tw-bg-opacity: 1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)))}.table-zebra tbody tr:nth-child(2n) :where(.table-pin-cols tr th){--tw-bg-opacity: 1;background-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)))}.textarea{min-height:3rem;flex-shrink:1;padding:.5rem 1rem;font-size:.875rem;line-height:1.25rem;line-height:2;border-radius:var(--rounded-btn, .5rem);border-width:1px;border-color:transparent;--tw-bg-opacity: 1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)))}.toast{position:fixed;display:flex;min-width:-moz-fit-content;min-width:fit-content;flex-direction:column;white-space:nowrap;gap:.5rem;padding:1rem}.toggle{flex-shrink:0;--tglbg: var(--fallback-b1,oklch(var(--b1)/1));--handleoffset: 1.5rem;--handleoffsetcalculator: calc(var(--handleoffset) * -1);--togglehandleborder: 0 0;height:1.5rem;width:3rem;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:var(--rounded-badge, 1.9rem);border-width:1px;border-color:currentColor;background-color:currentColor;color:var(--fallback-bc,oklch(var(--bc)/.5));transition:background,box-shadow var(--animation-input, .2s) ease-out;box-shadow:var(--handleoffsetcalculator) 0 0 2px var(--tglbg) inset,0 0 0 2px var(--tglbg) inset,var(--togglehandleborder)}.alert-info{border-color:var(--fallback-in,oklch(var(--in)/.2));--tw-text-opacity: 1;color:var(--fallback-inc,oklch(var(--inc)/var(--tw-text-opacity)));--alert-bg: var(--fallback-in,oklch(var(--in)/1));--alert-bg-mix: var(--fallback-b1,oklch(var(--b1)/1))}.alert-success{border-color:var(--fallback-su,oklch(var(--su)/.2));--tw-text-opacity: 1;color:var(--fallback-suc,oklch(var(--suc)/var(--tw-text-opacity)));--alert-bg: var(--fallback-su,oklch(var(--su)/1));--alert-bg-mix: var(--fallback-b1,oklch(var(--b1)/1))}.alert-warning{border-color:var(--fallback-wa,oklch(var(--wa)/.2));--tw-text-opacity: 1;color:var(--fallback-wac,oklch(var(--wac)/var(--tw-text-opacity)));--alert-bg: var(--fallback-wa,oklch(var(--wa)/1));--alert-bg-mix: var(--fallback-b1,oklch(var(--b1)/1))}.alert-error{border-color:var(--fallback-er,oklch(var(--er)/.2));--tw-text-opacity: 1;color:var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity)));--alert-bg: var(--fallback-er,oklch(var(--er)/1));--alert-bg-mix: var(--fallback-b1,oklch(var(--b1)/1))}.avatar-group :where(.avatar){overflow:hidden;border-radius:9999px;border-width:4px;--tw-border-opacity: 1;border-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-border-opacity)))}.badge-neutral{--tw-border-opacity: 1;border-color:var(--fallback-n,oklch(var(--n)/var(--tw-border-opacity)));--tw-bg-opacity: 1;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)))}.badge-primary{--tw-border-opacity: 1;border-color:var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)));--tw-bg-opacity: 1;background-color:var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)))}.badge-secondary{--tw-border-opacity: 1;border-color:var(--fallback-s,oklch(var(--s)/var(--tw-border-opacity)));--tw-bg-opacity: 1;background-color:var(--fallback-s,oklch(var(--s)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-sc,oklch(var(--sc)/var(--tw-text-opacity)))}.badge-info{border-color:transparent;--tw-bg-opacity: 1;background-color:var(--fallback-in,oklch(var(--in)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-inc,oklch(var(--inc)/var(--tw-text-opacity)))}.badge-success{border-color:transparent;--tw-bg-opacity: 1;background-color:var(--fallback-su,oklch(var(--su)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-suc,oklch(var(--suc)/var(--tw-text-opacity)))}.badge-warning{border-color:transparent;--tw-bg-opacity: 1;background-color:var(--fallback-wa,oklch(var(--wa)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-wac,oklch(var(--wac)/var(--tw-text-opacity)))}.badge-error{border-color:transparent;--tw-bg-opacity: 1;background-color:var(--fallback-er,oklch(var(--er)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity)))}.badge-ghost{--tw-border-opacity: 1;border-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));--tw-bg-opacity: 1;background-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)))}.badge-outline{border-color:currentColor;--tw-border-opacity: .5;background-color:transparent;color:currentColor}.badge-outline.badge-neutral{--tw-text-opacity: 1;color:var(--fallback-n,oklch(var(--n)/var(--tw-text-opacity)))}.badge-outline.badge-primary{--tw-text-opacity: 1;color:var(--fallback-p,oklch(var(--p)/var(--tw-text-opacity)))}.badge-outline.badge-secondary{--tw-text-opacity: 1;color:var(--fallback-s,oklch(var(--s)/var(--tw-text-opacity)))}.badge-outline.badge-accent{--tw-text-opacity: 1;color:var(--fallback-a,oklch(var(--a)/var(--tw-text-opacity)))}.badge-outline.badge-info{--tw-text-opacity: 1;color:var(--fallback-in,oklch(var(--in)/var(--tw-text-opacity)))}.badge-outline.badge-success{--tw-text-opacity: 1;color:var(--fallback-su,oklch(var(--su)/var(--tw-text-opacity)))}.badge-outline.badge-warning{--tw-text-opacity: 1;color:var(--fallback-wa,oklch(var(--wa)/var(--tw-text-opacity)))}.badge-outline.badge-error{--tw-text-opacity: 1;color:var(--fallback-er,oklch(var(--er)/var(--tw-text-opacity)))}.btm-nav>*:where(.active){border-top-width:2px;--tw-bg-opacity: 1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)))}.btm-nav>*.disabled,.btm-nav>*[disabled]{pointer-events:none;--tw-border-opacity: 0;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));--tw-bg-opacity: .1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));--tw-text-opacity: .2}.btm-nav>* .label{font-size:1rem;line-height:1.5rem}.breadcrumbs>ul>li>a:focus,.breadcrumbs>ol>li>a:focus{outline:2px solid transparent;outline-offset:2px}.breadcrumbs>ul>li>a:focus-visible,.breadcrumbs>ol>li>a:focus-visible{outline:2px solid currentColor;outline-offset:2px}.breadcrumbs>ul>li+*:before,.breadcrumbs>ol>li+*:before{content:"";margin-left:.5rem;margin-right:.75rem;display:block;height:.375rem;width:.375rem;--tw-rotate: 45deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));opacity:.4;border-top:1px solid;border-right:1px solid;background-color:transparent}[dir=rtl] .breadcrumbs>ul>li+*:before,[dir=rtl] .breadcrumbs>ol>li+*:before{--tw-rotate: -135deg}@media (prefers-reduced-motion: no-preference){.btn{animation:button-pop var(--animation-btn, .25s) ease-out}}.btn:active:hover,.btn:active:focus{animation:button-pop 0s ease-out;transform:scale(var(--btn-focus-scale, .97))}@supports not (color: oklch(0% 0 0)){.btn{background-color:var(--btn-color, var(--fallback-b2));border-color:var(--btn-color, var(--fallback-b2))}.btn-primary{--btn-color: var(--fallback-p)}.btn-neutral{--btn-color: var(--fallback-n)}.btn-success{--btn-color: var(--fallback-su)}.btn-warning{--btn-color: var(--fallback-wa)}.btn-error{--btn-color: var(--fallback-er)}.prose :where(code):not(:where([class~=not-prose] *,pre *)){background-color:var(--fallback-b3,oklch(var(--b3)/1))}}@supports (color: color-mix(in oklab,black,black)){.btn-active{background-color:color-mix(in oklab,oklch(var(--btn-color, var(--b3)) / var(--tw-bg-opacity, 1)) 90%,black);border-color:color-mix(in oklab,oklch(var(--btn-color, var(--b3)) / var(--tw-border-opacity, 1)) 90%,black)}.btn-outline.btn-primary.btn-active{background-color:color-mix(in oklab,var(--fallback-p,oklch(var(--p)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-p,oklch(var(--p)/1)) 90%,black)}.btn-outline.btn-secondary.btn-active{background-color:color-mix(in oklab,var(--fallback-s,oklch(var(--s)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-s,oklch(var(--s)/1)) 90%,black)}.btn-outline.btn-accent.btn-active{background-color:color-mix(in oklab,var(--fallback-a,oklch(var(--a)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-a,oklch(var(--a)/1)) 90%,black)}.btn-outline.btn-success.btn-active{background-color:color-mix(in oklab,var(--fallback-su,oklch(var(--su)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-su,oklch(var(--su)/1)) 90%,black)}.btn-outline.btn-info.btn-active{background-color:color-mix(in oklab,var(--fallback-in,oklch(var(--in)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-in,oklch(var(--in)/1)) 90%,black)}.btn-outline.btn-warning.btn-active{background-color:color-mix(in oklab,var(--fallback-wa,oklch(var(--wa)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-wa,oklch(var(--wa)/1)) 90%,black)}.btn-outline.btn-error.btn-active{background-color:color-mix(in oklab,var(--fallback-er,oklch(var(--er)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-er,oklch(var(--er)/1)) 90%,black)}}.btn:focus-visible{outline-style:solid;outline-width:2px;outline-offset:2px}.btn-primary{--tw-text-opacity: 1;color:var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));outline-color:var(--fallback-p,oklch(var(--p)/1))}@supports (color: oklch(0% 0 0)){.btn-primary{--btn-color: var(--p)}.btn-neutral{--btn-color: var(--n)}.btn-success{--btn-color: var(--su)}.btn-warning{--btn-color: var(--wa)}.btn-error{--btn-color: var(--er)}}.btn-neutral{--tw-text-opacity: 1;color:var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));outline-color:var(--fallback-n,oklch(var(--n)/1))}.btn-success{--tw-text-opacity: 1;color:var(--fallback-suc,oklch(var(--suc)/var(--tw-text-opacity)));outline-color:var(--fallback-su,oklch(var(--su)/1))}.btn-warning{--tw-text-opacity: 1;color:var(--fallback-wac,oklch(var(--wac)/var(--tw-text-opacity)));outline-color:var(--fallback-wa,oklch(var(--wa)/1))}.btn-error{--tw-text-opacity: 1;color:var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity)));outline-color:var(--fallback-er,oklch(var(--er)/1))}.btn.glass{--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow);outline-color:currentColor}.btn.glass.btn-active{--glass-opacity: 25%;--glass-border-opacity: 15%}.btn-ghost{border-width:1px;border-color:transparent;background-color:transparent;color:currentColor;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow);outline-color:currentColor}.btn-ghost.btn-active{border-color:transparent;background-color:var(--fallback-bc,oklch(var(--bc)/.2))}.btn-link.btn-active{border-color:transparent;background-color:transparent;text-decoration-line:underline}.btn-outline{border-color:currentColor;background-color:transparent;--tw-text-opacity: 1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.btn-outline.btn-active{--tw-border-opacity: 1;border-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));--tw-bg-opacity: 1;background-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-b1,oklch(var(--b1)/var(--tw-text-opacity)))}.btn-outline.btn-primary{--tw-text-opacity: 1;color:var(--fallback-p,oklch(var(--p)/var(--tw-text-opacity)))}.btn-outline.btn-primary.btn-active{--tw-text-opacity: 1;color:var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)))}.btn-outline.btn-secondary{--tw-text-opacity: 1;color:var(--fallback-s,oklch(var(--s)/var(--tw-text-opacity)))}.btn-outline.btn-secondary.btn-active{--tw-text-opacity: 1;color:var(--fallback-sc,oklch(var(--sc)/var(--tw-text-opacity)))}.btn-outline.btn-accent{--tw-text-opacity: 1;color:var(--fallback-a,oklch(var(--a)/var(--tw-text-opacity)))}.btn-outline.btn-accent.btn-active{--tw-text-opacity: 1;color:var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity)))}.btn-outline.btn-success{--tw-text-opacity: 1;color:var(--fallback-su,oklch(var(--su)/var(--tw-text-opacity)))}.btn-outline.btn-success.btn-active{--tw-text-opacity: 1;color:var(--fallback-suc,oklch(var(--suc)/var(--tw-text-opacity)))}.btn-outline.btn-info{--tw-text-opacity: 1;color:var(--fallback-in,oklch(var(--in)/var(--tw-text-opacity)))}.btn-outline.btn-info.btn-active{--tw-text-opacity: 1;color:var(--fallback-inc,oklch(var(--inc)/var(--tw-text-opacity)))}.btn-outline.btn-warning{--tw-text-opacity: 1;color:var(--fallback-wa,oklch(var(--wa)/var(--tw-text-opacity)))}.btn-outline.btn-warning.btn-active{--tw-text-opacity: 1;color:var(--fallback-wac,oklch(var(--wac)/var(--tw-text-opacity)))}.btn-outline.btn-error{--tw-text-opacity: 1;color:var(--fallback-er,oklch(var(--er)/var(--tw-text-opacity)))}.btn-outline.btn-error.btn-active{--tw-text-opacity: 1;color:var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity)))}.btn.btn-disabled,.btn[disabled],.btn:disabled{--tw-border-opacity: 0;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));--tw-bg-opacity: .2;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));--tw-text-opacity: .2}.btn:is(input[type=checkbox]:checked),.btn:is(input[type=radio]:checked){--tw-border-opacity: 1;border-color:var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)));--tw-bg-opacity: 1;background-color:var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)))}.btn:is(input[type=checkbox]:checked):focus-visible,.btn:is(input[type=radio]:checked):focus-visible{outline-color:var(--fallback-p,oklch(var(--p)/1))}@keyframes button-pop{0%{transform:scale(var(--btn-focus-scale, .98))}40%{transform:scale(1.02)}to{transform:scale(1)}}.card :where(figure:first-child){overflow:hidden;border-start-start-radius:inherit;border-start-end-radius:inherit;border-end-start-radius:unset;border-end-end-radius:unset}.card :where(figure:last-child){overflow:hidden;border-start-start-radius:unset;border-start-end-radius:unset;border-end-start-radius:inherit;border-end-end-radius:inherit}.card:focus-visible{outline:2px solid currentColor;outline-offset:2px}.card.bordered{border-width:1px;--tw-border-opacity: 1;border-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)))}.card.compact .card-body{padding:1rem;font-size:.875rem;line-height:1.25rem}.card.\!compact .card-body{padding:1rem!important;font-size:.875rem!important;line-height:1.25rem!important}.card-title{display:flex;align-items:center;gap:.5rem;font-size:1.25rem;line-height:1.75rem;font-weight:600}.card.image-full :where(figure){overflow:hidden;border-radius:inherit}.checkbox:focus{box-shadow:none}.checkbox:focus-visible{outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:var(--fallback-bc,oklch(var(--bc)/1))}.checkbox:disabled{border-width:0px;cursor:not-allowed;border-color:transparent;--tw-bg-opacity: 1;background-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));opacity:.2}.checkbox:checked,.checkbox[aria-checked=true]{background-repeat:no-repeat;animation:checkmark var(--animation-input, .2s) ease-out;background-color:var(--chkbg);background-image:linear-gradient(-45deg,transparent 65%,var(--chkbg) 65.99%),linear-gradient(45deg,transparent 75%,var(--chkbg) 75.99%),linear-gradient(-45deg,var(--chkbg) 40%,transparent 40.99%),linear-gradient(45deg,var(--chkbg) 30%,var(--chkfg) 30.99%,var(--chkfg) 40%,transparent 40.99%),linear-gradient(-45deg,var(--chkfg) 50%,var(--chkbg) 50.99%)}.checkbox:indeterminate{--tw-bg-opacity: 1;background-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));background-repeat:no-repeat;animation:checkmark var(--animation-input, .2s) ease-out;background-image:linear-gradient(90deg,transparent 80%,var(--chkbg) 80%),linear-gradient(-90deg,transparent 80%,var(--chkbg) 80%),linear-gradient(0deg,var(--chkbg) 43%,var(--chkfg) 43%,var(--chkfg) 57%,var(--chkbg) 57%)}.checkbox-primary{--chkbg: var(--fallback-p,oklch(var(--p)/1));--chkfg: var(--fallback-pc,oklch(var(--pc)/1));--tw-border-opacity: 1;border-color:var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)))}.checkbox-primary:focus-visible{outline-color:var(--fallback-p,oklch(var(--p)/1))}.checkbox-primary:checked,.checkbox-primary[aria-checked=true]{--tw-border-opacity: 1;border-color:var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)));--tw-bg-opacity: 1;background-color:var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)))}.checkbox-warning{--chkbg: var(--fallback-wa,oklch(var(--wa)/1));--chkfg: var(--fallback-wac,oklch(var(--wac)/1));--tw-border-opacity: 1;border-color:var(--fallback-wa,oklch(var(--wa)/var(--tw-border-opacity)))}.checkbox-warning:focus-visible{outline-color:var(--fallback-wa,oklch(var(--wa)/1))}.checkbox-warning:checked,.checkbox-warning[aria-checked=true]{--tw-border-opacity: 1;border-color:var(--fallback-wa,oklch(var(--wa)/var(--tw-border-opacity)));--tw-bg-opacity: 1;background-color:var(--fallback-wa,oklch(var(--wa)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-wac,oklch(var(--wac)/var(--tw-text-opacity)))}.checkbox-info{--chkbg: var(--fallback-in,oklch(var(--in)/1));--chkfg: var(--fallback-inc,oklch(var(--inc)/1));--tw-border-opacity: 1;border-color:var(--fallback-in,oklch(var(--in)/var(--tw-border-opacity)))}.checkbox-info:focus-visible{outline-color:var(--fallback-in,oklch(var(--in)/1))}.checkbox-info:checked,.checkbox-info[aria-checked=true]{--tw-border-opacity: 1;border-color:var(--fallback-in,oklch(var(--in)/var(--tw-border-opacity)));--tw-bg-opacity: 1;background-color:var(--fallback-in,oklch(var(--in)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-inc,oklch(var(--inc)/var(--tw-text-opacity)))}.checkbox-error{--chkbg: var(--fallback-er,oklch(var(--er)/1));--chkfg: var(--fallback-erc,oklch(var(--erc)/1));--tw-border-opacity: 1;border-color:var(--fallback-er,oklch(var(--er)/var(--tw-border-opacity)))}.checkbox-error:focus-visible{outline-color:var(--fallback-er,oklch(var(--er)/1))}.checkbox-error:checked,.checkbox-error[aria-checked=true]{--tw-border-opacity: 1;border-color:var(--fallback-er,oklch(var(--er)/var(--tw-border-opacity)));--tw-bg-opacity: 1;background-color:var(--fallback-er,oklch(var(--er)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity)))}@keyframes checkmark{0%{background-position-y:5px}50%{background-position-y:-2px}to{background-position-y:0}}details.collapse{width:100%}details.collapse summary{position:relative;display:block;outline:2px solid transparent;outline-offset:2px}details.collapse summary::-webkit-details-marker{display:none}.collapse:focus-visible{outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:var(--fallback-bc,oklch(var(--bc)/1))}.collapse:has(.collapse-title:focus-visible),.collapse:has(>input[type=checkbox]:focus-visible),.collapse:has(>input[type=radio]:focus-visible){outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:var(--fallback-bc,oklch(var(--bc)/1))}.collapse-arrow>.collapse-title:after{position:absolute;display:block;height:.5rem;width:.5rem;--tw-translate-y: -100%;--tw-rotate: 45deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(0,0,.2,1);transition-duration:.15s;transition-duration:.2s;top:1.9rem;inset-inline-end:1.4rem;content:"";transform-origin:75% 75%;box-shadow:2px 2px;pointer-events:none}.collapse-plus>.collapse-title:after{position:absolute;display:block;height:.5rem;width:.5rem;transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(0,0,.2,1);transition-duration:.3s;top:.9rem;inset-inline-end:1.4rem;content:"+";pointer-events:none}.collapse:not(.collapse-open):not(.collapse-close)>input[type=checkbox],.collapse:not(.collapse-open):not(.collapse-close)>input[type=radio]:not(:checked),.collapse:not(.collapse-open):not(.collapse-close)>.collapse-title{cursor:pointer}.collapse:focus:not(.collapse-open):not(.collapse-close):not(.collapse[open])>.collapse-title{cursor:unset}.collapse-title{position:relative}.collapse-title,:where(.collapse>input[type=checkbox]),:where(.collapse>input[type=radio]){padding:1rem;padding-inline-end:3rem;min-height:3.75rem;transition:background-color .2s ease-out}.collapse[open]>:where(.collapse-content),.collapse-open>:where(.collapse-content),.collapse:focus:not(.collapse-close)>:where(.collapse-content),.collapse:not(.collapse-close)>:where(input[type=checkbox]:checked~.collapse-content),.collapse:not(.collapse-close)>:where(input[type=radio]:checked~.collapse-content){padding-bottom:1rem;transition:padding .2s ease-out,background-color .2s ease-out}.collapse[open].collapse-arrow>.collapse-title:after,.collapse-open.collapse-arrow>.collapse-title:after,.collapse-arrow:focus:not(.collapse-close)>.collapse-title:after,.collapse-arrow:not(.collapse-close)>input[type=checkbox]:checked~.collapse-title:after,.collapse-arrow:not(.collapse-close)>input[type=radio]:checked~.collapse-title:after{--tw-translate-y: -50%;--tw-rotate: 225deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.collapse[open].collapse-plus>.collapse-title:after,.collapse-open.collapse-plus>.collapse-title:after,.collapse-plus:focus:not(.collapse-close)>.collapse-title:after,.collapse-plus:not(.collapse-close)>input[type=checkbox]:checked~.collapse-title:after,.collapse-plus:not(.collapse-close)>input[type=radio]:checked~.collapse-title:after{content:"−"}.divider:not(:empty){gap:1rem}.drawer-toggle:checked~.drawer-side>.drawer-overlay{background-color:#0006}.drawer-toggle:focus-visible~.drawer-content label.drawer-button{outline-style:solid;outline-width:2px;outline-offset:2px}.dropdown.dropdown-open .dropdown-content,.dropdown:focus .dropdown-content,.dropdown:focus-within .dropdown-content{--tw-scale-x: 1;--tw-scale-y: 1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.file-input-bordered{--tw-border-opacity: .2}.file-input:focus{outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:var(--fallback-bc,oklch(var(--bc)/.2))}.file-input-disabled,.file-input[disabled]{cursor:not-allowed;--tw-border-opacity: 1;border-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));--tw-bg-opacity: 1;background-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));--tw-text-opacity: .2}.file-input-disabled::-moz-placeholder,.file-input[disabled]::-moz-placeholder{color:var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));--tw-placeholder-opacity: .2}.file-input-disabled::placeholder,.file-input[disabled]::placeholder{color:var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));--tw-placeholder-opacity: .2}.file-input-disabled::file-selector-button,.file-input[disabled]::file-selector-button{--tw-border-opacity: 0;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));--tw-bg-opacity: .2;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));--tw-text-opacity: .2}.label-text{font-size:.875rem;line-height:1.25rem;--tw-text-opacity: 1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)))}.label-text-alt{font-size:.75rem;line-height:1rem;--tw-text-opacity: 1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)))}.input input{--tw-bg-opacity: 1;background-color:var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)));background-color:transparent}.input input:focus{outline:2px solid transparent;outline-offset:2px}.input[list]::-webkit-calendar-picker-indicator{line-height:1em}.input-bordered{border-color:var(--fallback-bc,oklch(var(--bc)/.2))}.input:focus,.input:focus-within{box-shadow:none;border-color:var(--fallback-bc,oklch(var(--bc)/.2));outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:var(--fallback-bc,oklch(var(--bc)/.2))}.input-error{--tw-border-opacity: 1;border-color:var(--fallback-er,oklch(var(--er)/var(--tw-border-opacity)))}.input-error:focus,.input-error:focus-within{--tw-border-opacity: 1;border-color:var(--fallback-er,oklch(var(--er)/var(--tw-border-opacity)));outline-color:var(--fallback-er,oklch(var(--er)/1))}.input:has(>input[disabled]),.input-disabled,.input:disabled,.input[disabled]{cursor:not-allowed;--tw-border-opacity: 1;border-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));--tw-bg-opacity: 1;background-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));color:var(--fallback-bc,oklch(var(--bc)/.4))}.input:has(>input[disabled])::-moz-placeholder,.input-disabled::-moz-placeholder,.input:disabled::-moz-placeholder,.input[disabled]::-moz-placeholder{color:var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));--tw-placeholder-opacity: .2}.input:has(>input[disabled])::placeholder,.input-disabled::placeholder,.input:disabled::placeholder,.input[disabled]::placeholder{color:var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));--tw-placeholder-opacity: .2}.input:has(>input[disabled])>input[disabled]{cursor:not-allowed}.input::-webkit-date-and-time-value{text-align:inherit}.join>:where(*:not(:first-child)){margin-top:0;margin-bottom:0;margin-inline-start:-1px}.join>:where(*:not(:first-child)):is(.btn){margin-inline-start:calc(var(--border-btn) * -1)}.join-item:focus{isolation:isolate}.link-primary{--tw-text-opacity: 1;color:var(--fallback-p,oklch(var(--p)/var(--tw-text-opacity)))}@supports (color:color-mix(in oklab,black,black)){@media (hover:hover){.link-primary:hover{color:color-mix(in oklab,var(--fallback-p,oklch(var(--p)/1)) 80%,black)}}}.link:focus{outline:2px solid transparent;outline-offset:2px}.link:focus-visible{outline:2px solid currentColor;outline-offset:2px}.loading{pointer-events:none;display:inline-block;aspect-ratio:1 / 1;width:1.5rem;background-color:currentColor;-webkit-mask-size:100%;mask-size:100%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-position:center;mask-position:center;-webkit-mask-image:url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='black' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform-origin='center'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3' stroke-linecap='round'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='2s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dasharray' values='0,150;42,150;42,150' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dashoffset' values='0;-16;-59' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3C/circle%3E%3C/g%3E%3C/svg%3E");mask-image:url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='black' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform-origin='center'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3' stroke-linecap='round'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='2s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dasharray' values='0,150;42,150;42,150' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dashoffset' values='0;-16;-59' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3C/circle%3E%3C/g%3E%3C/svg%3E")}.loading-spinner{-webkit-mask-image:url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='black' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform-origin='center'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3' stroke-linecap='round'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='2s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dasharray' values='0,150;42,150;42,150' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dashoffset' values='0;-16;-59' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3C/circle%3E%3C/g%3E%3C/svg%3E");mask-image:url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='black' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform-origin='center'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3' stroke-linecap='round'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='2s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dasharray' values='0,150;42,150;42,150' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dashoffset' values='0;-16;-59' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3C/circle%3E%3C/g%3E%3C/svg%3E")}.loading-xs{width:1rem}.loading-sm{width:1.25rem}.loading-md{width:1.5rem}.loading-lg{width:2.5rem}:where(.menu li:empty){--tw-bg-opacity: 1;background-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));opacity:.1;margin:.5rem 1rem;height:1px}.menu :where(li ul):before{position:absolute;bottom:.75rem;inset-inline-start:0px;top:.75rem;width:1px;--tw-bg-opacity: 1;background-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));opacity:.1;content:""}.menu :where(li:not(.menu-title)>*:not(ul,details,.menu-title,.btn)),.menu :where(li:not(.menu-title)>details>summary:not(.menu-title)){border-radius:var(--rounded-btn, .5rem);padding:.5rem 1rem;text-align:start;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(0,0,.2,1);transition-duration:.2s;text-wrap:balance}:where(.menu li:not(.menu-title,.disabled)>*:not(ul,details,.menu-title)):not(summary,.active,.btn).focus,:where(.menu li:not(.menu-title,.disabled)>*:not(ul,details,.menu-title)):not(summary,.active,.btn):focus,:where(.menu li:not(.menu-title,.disabled)>*:not(ul,details,.menu-title)):is(summary):not(.active,.btn):focus-visible,:where(.menu li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(summary,.active,.btn).focus,:where(.menu li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(summary,.active,.btn):focus,:where(.menu li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):is(summary):not(.active,.btn):focus-visible{cursor:pointer;background-color:var(--fallback-bc,oklch(var(--bc)/.1));--tw-text-opacity: 1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));outline:2px solid transparent;outline-offset:2px}.menu li>*:not(ul,.menu-title,details,.btn):active,.menu li>*:not(ul,.menu-title,details,.btn).active,.menu li>details>summary:active{--tw-bg-opacity: 1;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)))}.menu :where(li>details>summary)::-webkit-details-marker{display:none}.menu :where(li>details>summary):after,.menu :where(li>.menu-dropdown-toggle):after{justify-self:end;display:block;margin-top:-.5rem;height:.5rem;width:.5rem;transform:rotate(45deg);transition-property:transform,margin-top;transition-duration:.3s;transition-timing-function:cubic-bezier(.4,0,.2,1);content:"";transform-origin:75% 75%;box-shadow:2px 2px;pointer-events:none}.menu :where(li>details[open]>summary):after,.menu :where(li>.menu-dropdown-toggle.menu-dropdown-show):after{transform:rotate(225deg);margin-top:0}.menu-title{padding:.5rem 1rem;font-size:.875rem;line-height:1.25rem;font-weight:700;color:var(--fallback-bc,oklch(var(--bc)/.4))}.mockup-code:before{content:"";margin-bottom:1rem;display:block;height:.75rem;width:.75rem;border-radius:9999px;opacity:.3;box-shadow:1.4em 0,2.8em 0,4.2em 0}.mockup-code pre{padding-right:1.25rem}.mockup-code pre:before{content:"";margin-right:2ch}.mockup-phone .display{overflow:hidden;border-radius:40px;margin-top:-25px}.mockup-browser .mockup-browser-toolbar .input{position:relative;margin-left:auto;margin-right:auto;display:block;height:1.75rem;width:24rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;--tw-bg-opacity: 1;background-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));padding-left:2rem;direction:ltr}.mockup-browser .mockup-browser-toolbar .input:before{content:"";position:absolute;left:.5rem;top:50%;aspect-ratio:1 / 1;height:.75rem;--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-radius:9999px;border-width:2px;border-color:currentColor;opacity:.6}.mockup-browser .mockup-browser-toolbar .input:after{content:"";position:absolute;left:1.25rem;top:50%;height:.5rem;--tw-translate-y: 25%;--tw-rotate: -45deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-radius:9999px;border-width:1px;border-color:currentColor;opacity:.6}.modal:not(dialog:not(.modal-open)),.modal::backdrop{background-color:#0006;animation:modal-pop .2s ease-out}.modal-backdrop{z-index:-1;grid-column-start:1;grid-row-start:1;display:grid;align-self:stretch;justify-self:stretch;color:transparent}.modal-open .modal-box,.modal-toggle:checked+.modal .modal-box,.modal:target .modal-box,.modal[open] .modal-box{--tw-translate-y: 0px;--tw-scale-x: 1;--tw-scale-y: 1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.modal-action>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.modal-action:where([dir=rtl],[dir=rtl] *)>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 1}@keyframes modal-pop{0%{opacity:0}}.progress::-moz-progress-bar{border-radius:var(--rounded-box, 1rem);background-color:currentColor}.progress:indeterminate{--progress-color: var(--fallback-bc,oklch(var(--bc)/1));background-image:repeating-linear-gradient(90deg,var(--progress-color) -1%,var(--progress-color) 10%,transparent 10%,transparent 90%);background-size:200%;background-position-x:15%;animation:progress-loading 5s ease-in-out infinite}.progress::-webkit-progress-bar{border-radius:var(--rounded-box, 1rem);background-color:transparent}.progress::-webkit-progress-value{border-radius:var(--rounded-box, 1rem);background-color:currentColor}.progress:indeterminate::-moz-progress-bar{background-color:transparent;background-image:repeating-linear-gradient(90deg,var(--progress-color) -1%,var(--progress-color) 10%,transparent 10%,transparent 90%);background-size:200%;background-position-x:15%;animation:progress-loading 5s ease-in-out infinite}@keyframes progress-loading{50%{background-position-x:-115%}}.radio:focus{box-shadow:none}.radio:focus-visible{outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:var(--fallback-bc,oklch(var(--bc)/1))}.radio:checked,.radio[aria-checked=true]{--tw-bg-opacity: 1;background-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));background-image:none;animation:radiomark var(--animation-input, .2s) ease-out;box-shadow:0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset,0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset}.radio-primary{--chkbg: var(--p);--tw-border-opacity: 1;border-color:var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)))}.radio-primary:focus-visible{outline-color:var(--fallback-p,oklch(var(--p)/1))}.radio-primary:checked,.radio-primary[aria-checked=true]{--tw-border-opacity: 1;border-color:var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)));--tw-bg-opacity: 1;background-color:var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)))}.radio:disabled{cursor:not-allowed;opacity:.2}@keyframes radiomark{0%{box-shadow:0 0 0 12px var(--fallback-b1,oklch(var(--b1)/1)) inset,0 0 0 12px var(--fallback-b1,oklch(var(--b1)/1)) inset}50%{box-shadow:0 0 0 3px var(--fallback-b1,oklch(var(--b1)/1)) inset,0 0 0 3px var(--fallback-b1,oklch(var(--b1)/1)) inset}to{box-shadow:0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset,0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset}}.range:focus-visible::-webkit-slider-thumb{--focus-shadow: 0 0 0 6px var(--fallback-b1,oklch(var(--b1)/1)) inset, 0 0 0 2rem var(--range-shdw) inset}.range:focus-visible::-moz-range-thumb{--focus-shadow: 0 0 0 6px var(--fallback-b1,oklch(var(--b1)/1)) inset, 0 0 0 2rem var(--range-shdw) inset}.range::-webkit-slider-runnable-track{height:.5rem;width:100%;border-radius:var(--rounded-box, 1rem);background-color:var(--fallback-bc,oklch(var(--bc)/.1))}.range::-moz-range-track{height:.5rem;width:100%;border-radius:var(--rounded-box, 1rem);background-color:var(--fallback-bc,oklch(var(--bc)/.1))}.range::-webkit-slider-thumb{position:relative;height:1.5rem;width:1.5rem;border-radius:var(--rounded-box, 1rem);border-style:none;--tw-bg-opacity: 1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));-moz-appearance:none;appearance:none;-webkit-appearance:none;top:50%;color:var(--range-shdw);transform:translateY(-50%);--filler-size: 100rem;--filler-offset: .6rem;box-shadow:0 0 0 3px var(--range-shdw) inset,var(--focus-shadow, 0 0),calc(var(--filler-size) * -1 - var(--filler-offset)) 0 0 var(--filler-size)}.range::-moz-range-thumb{position:relative;height:1.5rem;width:1.5rem;border-radius:var(--rounded-box, 1rem);border-style:none;--tw-bg-opacity: 1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));top:50%;color:var(--range-shdw);--filler-size: 100rem;--filler-offset: .5rem;box-shadow:0 0 0 3px var(--range-shdw) inset,var(--focus-shadow, 0 0),calc(var(--filler-size) * -1 - var(--filler-offset)) 0 0 var(--filler-size)}@keyframes rating-pop{0%{transform:translateY(-.125em)}40%{transform:translateY(-.125em)}to{transform:translateY(0)}}.select-bordered{border-color:var(--fallback-bc,oklch(var(--bc)/.2))}.select:focus{box-shadow:none;border-color:var(--fallback-bc,oklch(var(--bc)/.2));outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:var(--fallback-bc,oklch(var(--bc)/.2))}.select-disabled,.select:disabled,.select[disabled]{cursor:not-allowed;--tw-border-opacity: 1;border-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));--tw-bg-opacity: 1;background-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));color:var(--fallback-bc,oklch(var(--bc)/.4))}.select-disabled::-moz-placeholder,.select:disabled::-moz-placeholder,.select[disabled]::-moz-placeholder{color:var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));--tw-placeholder-opacity: .2}.select-disabled::placeholder,.select:disabled::placeholder,.select[disabled]::placeholder{color:var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));--tw-placeholder-opacity: .2}.select-multiple,.select[multiple],.select[size].select:not([size="1"]){background-image:none;padding-right:1rem}[dir=rtl] .select{background-position:calc(0% + 12px) calc(1px + 50%),calc(0% + 16px) calc(1px + 50%)}@keyframes skeleton{0%{background-position:150%}to{background-position:-50%}}:where(.stats)>:not([hidden])~:not([hidden]){--tw-divide-x-reverse: 0;border-right-width:calc(1px * var(--tw-divide-x-reverse));border-left-width:calc(1px * calc(1 - var(--tw-divide-x-reverse)));--tw-divide-y-reverse: 0;border-top-width:calc(0px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(0px * var(--tw-divide-y-reverse))}[dir=rtl] .stats>*:not([hidden])~*:not([hidden]){--tw-divide-x-reverse: 1}.tabs-lifted>.tab:focus-visible{border-end-end-radius:0;border-end-start-radius:0}.tab:is(.tab-active,[aria-selected=true]):not(.tab-disabled):not([disabled]),.tab:is(input:checked){border-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));--tw-border-opacity: 1;--tw-text-opacity: 1}.tab:focus{outline:2px solid transparent;outline-offset:2px}.tab:focus-visible{outline:2px solid currentColor;outline-offset:-5px}.tab-disabled,.tab[disabled]{cursor:not-allowed;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));--tw-text-opacity: .2}.tabs-bordered>.tab{border-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));--tw-border-opacity: .2;border-style:solid;border-bottom-width:calc(var(--tab-border, 1px) + 1px)}.tabs-lifted>.tab{border:var(--tab-border, 1px) solid transparent;border-width:0 0 var(--tab-border, 1px) 0;border-start-start-radius:var(--tab-radius, .5rem);border-start-end-radius:var(--tab-radius, .5rem);border-bottom-color:var(--tab-border-color);padding-inline-start:var(--tab-padding, 1rem);padding-inline-end:var(--tab-padding, 1rem);padding-top:var(--tab-border, 1px)}.tabs-lifted>.tab:is(.tab-active,[aria-selected=true]):not(.tab-disabled):not([disabled]),.tabs-lifted>.tab:is(input:checked){background-color:var(--tab-bg);border-width:var(--tab-border, 1px) var(--tab-border, 1px) 0 var(--tab-border, 1px);border-inline-start-color:var(--tab-border-color);border-inline-end-color:var(--tab-border-color);border-top-color:var(--tab-border-color);padding-inline-start:calc(var(--tab-padding, 1rem) - var(--tab-border, 1px));padding-inline-end:calc(var(--tab-padding, 1rem) - var(--tab-border, 1px));padding-bottom:var(--tab-border, 1px);padding-top:0}.tabs-lifted>.tab:is(.tab-active,[aria-selected=true]):not(.tab-disabled):not([disabled]):before,.tabs-lifted>.tab:is(input:checked):before{z-index:1;content:"";display:block;position:absolute;width:calc(100% + var(--tab-radius, .5rem) * 2);height:var(--tab-radius, .5rem);bottom:0;background-size:var(--tab-radius, .5rem);background-position:top left,top right;background-repeat:no-repeat;--tab-grad: calc(69% - var(--tab-border, 1px));--radius-start: radial-gradient( circle at top left, transparent var(--tab-grad), var(--tab-border-color) calc(var(--tab-grad) + .25px), var(--tab-border-color) calc(var(--tab-grad) + var(--tab-border, 1px)), var(--tab-bg) calc(var(--tab-grad) + var(--tab-border, 1px) + .25px) );--radius-end: radial-gradient( circle at top right, transparent var(--tab-grad), var(--tab-border-color) calc(var(--tab-grad) + .25px), var(--tab-border-color) calc(var(--tab-grad) + var(--tab-border, 1px)), var(--tab-bg) calc(var(--tab-grad) + var(--tab-border, 1px) + .25px) );background-image:var(--radius-start),var(--radius-end)}.tabs-lifted>.tab:is(.tab-active,[aria-selected=true]):not(.tab-disabled):not([disabled]):first-child:before,.tabs-lifted>.tab:is(input:checked):first-child:before{background-image:var(--radius-end);background-position:top right}[dir=rtl] .tabs-lifted>.tab:is(.tab-active,[aria-selected=true]):not(.tab-disabled):not([disabled]):first-child:before,[dir=rtl] .tabs-lifted>.tab:is(input:checked):first-child:before{background-image:var(--radius-start);background-position:top left}.tabs-lifted>.tab:is(.tab-active,[aria-selected=true]):not(.tab-disabled):not([disabled]):last-child:before,.tabs-lifted>.tab:is(input:checked):last-child:before{background-image:var(--radius-start);background-position:top left}[dir=rtl] .tabs-lifted>.tab:is(.tab-active,[aria-selected=true]):not(.tab-disabled):not([disabled]):last-child:before,[dir=rtl] .tabs-lifted>.tab:is(input:checked):last-child:before{background-image:var(--radius-end);background-position:top right}.tabs-lifted>:is(.tab-active,[aria-selected=true]):not(.tab-disabled):not([disabled])+.tabs-lifted :is(.tab-active,[aria-selected=true]):not(.tab-disabled):not([disabled]):before,.tabs-lifted>.tab:is(input:checked)+.tabs-lifted .tab:is(input:checked):before{background-image:var(--radius-end);background-position:top right}.tabs-boxed{border-radius:var(--rounded-btn, .5rem);--tw-bg-opacity: 1;background-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));padding:.25rem}.tabs-boxed .tab{border-radius:var(--rounded-btn, .5rem)}.tabs-boxed :is(.tab-active,[aria-selected=true]):not(.tab-disabled):not([disabled]),.tabs-boxed :is(input:checked){--tw-bg-opacity: 1;background-color:var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)))}.table:where([dir=rtl],[dir=rtl] *){text-align:right}.table :where(th,td){padding:.75rem 1rem;vertical-align:middle}.table tr.active,.table tr.active:nth-child(2n),.table-zebra tbody tr:nth-child(2n){--tw-bg-opacity: 1;background-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)))}.table-zebra tr.active,.table-zebra tr.active:nth-child(2n),.table-zebra-zebra tbody tr:nth-child(2n){--tw-bg-opacity: 1;background-color:var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity)))}.table :where(thead tr,tbody tr:not(:last-child),tbody tr:first-child:last-child){border-bottom-width:1px;--tw-border-opacity: 1;border-bottom-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)))}.table :where(thead,tfoot){white-space:nowrap;font-size:.75rem;line-height:1rem;font-weight:700;color:var(--fallback-bc,oklch(var(--bc)/.6))}.table :where(tfoot){border-top-width:1px;--tw-border-opacity: 1;border-top-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)))}.textarea-bordered{border-color:var(--fallback-bc,oklch(var(--bc)/.2))}.textarea:focus{box-shadow:none;border-color:var(--fallback-bc,oklch(var(--bc)/.2));outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:var(--fallback-bc,oklch(var(--bc)/.2))}.textarea-disabled,.textarea:disabled,.textarea[disabled]{cursor:not-allowed;--tw-border-opacity: 1;border-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));--tw-bg-opacity: 1;background-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));color:var(--fallback-bc,oklch(var(--bc)/.4))}.textarea-disabled::-moz-placeholder,.textarea:disabled::-moz-placeholder,.textarea[disabled]::-moz-placeholder{color:var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));--tw-placeholder-opacity: .2}.textarea-disabled::placeholder,.textarea:disabled::placeholder,.textarea[disabled]::placeholder{color:var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));--tw-placeholder-opacity: .2}.toast>*{animation:toast-pop .25s ease-out}@keyframes toast-pop{0%{transform:scale(.9);opacity:0}to{transform:scale(1);opacity:1}}[dir=rtl] .toggle{--handleoffsetcalculator: calc(var(--handleoffset) * 1)}.toggle:focus-visible{outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:var(--fallback-bc,oklch(var(--bc)/.2))}.toggle:hover{background-color:currentColor}.toggle:checked,.toggle[aria-checked=true]{background-image:none;--handleoffsetcalculator: var(--handleoffset);--tw-text-opacity: 1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)))}[dir=rtl] .toggle:checked,[dir=rtl] .toggle[aria-checked=true]{--handleoffsetcalculator: calc(var(--handleoffset) * -1)}.toggle:indeterminate{--tw-text-opacity: 1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));box-shadow:calc(var(--handleoffset) / 2) 0 0 2px var(--tglbg) inset,calc(var(--handleoffset) / -2) 0 0 2px var(--tglbg) inset,0 0 0 2px var(--tglbg) inset}[dir=rtl] .toggle:indeterminate{box-shadow:calc(var(--handleoffset) / 2) 0 0 2px var(--tglbg) inset,calc(var(--handleoffset) / -2) 0 0 2px var(--tglbg) inset,0 0 0 2px var(--tglbg) inset}.toggle-primary:focus-visible{outline-color:var(--fallback-p,oklch(var(--p)/1))}.toggle-primary:checked,.toggle-primary[aria-checked=true]{border-color:var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)));--tw-border-opacity: .1;--tw-bg-opacity: 1;background-color:var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)))}.toggle-success:focus-visible{outline-color:var(--fallback-su,oklch(var(--su)/1))}.toggle-success:checked,.toggle-success[aria-checked=true]{border-color:var(--fallback-su,oklch(var(--su)/var(--tw-border-opacity)));--tw-border-opacity: .1;--tw-bg-opacity: 1;background-color:var(--fallback-su,oklch(var(--su)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-suc,oklch(var(--suc)/var(--tw-text-opacity)))}.toggle-warning:focus-visible{outline-color:var(--fallback-wa,oklch(var(--wa)/1))}.toggle-warning:checked,.toggle-warning[aria-checked=true]{border-color:var(--fallback-wa,oklch(var(--wa)/var(--tw-border-opacity)));--tw-border-opacity: .1;--tw-bg-opacity: 1;background-color:var(--fallback-wa,oklch(var(--wa)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-wac,oklch(var(--wac)/var(--tw-text-opacity)))}.toggle-info:focus-visible{outline-color:var(--fallback-in,oklch(var(--in)/1))}.toggle-info:checked,.toggle-info[aria-checked=true]{border-color:var(--fallback-in,oklch(var(--in)/var(--tw-border-opacity)));--tw-border-opacity: .1;--tw-bg-opacity: 1;background-color:var(--fallback-in,oklch(var(--in)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-inc,oklch(var(--inc)/var(--tw-text-opacity)))}.toggle:disabled{cursor:not-allowed;--tw-border-opacity: 1;border-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));background-color:transparent;opacity:.3;--togglehandleborder: 0 0 0 3px var(--fallback-bc,oklch(var(--bc)/1)) inset, var(--handleoffsetcalculator) 0 0 3px var(--fallback-bc,oklch(var(--bc)/1)) inset}:root .prose{--tw-prose-body: var(--fallback-bc,oklch(var(--bc)/.8));--tw-prose-headings: var(--fallback-bc,oklch(var(--bc)/1));--tw-prose-lead: var(--fallback-bc,oklch(var(--bc)/1));--tw-prose-links: var(--fallback-bc,oklch(var(--bc)/1));--tw-prose-bold: var(--fallback-bc,oklch(var(--bc)/1));--tw-prose-counters: var(--fallback-bc,oklch(var(--bc)/1));--tw-prose-bullets: var(--fallback-bc,oklch(var(--bc)/.5));--tw-prose-hr: var(--fallback-bc,oklch(var(--bc)/.2));--tw-prose-quotes: var(--fallback-bc,oklch(var(--bc)/1));--tw-prose-quote-borders: var(--fallback-bc,oklch(var(--bc)/.2));--tw-prose-captions: var(--fallback-bc,oklch(var(--bc)/.5));--tw-prose-code: var(--fallback-bc,oklch(var(--bc)/1));--tw-prose-pre-code: var(--fallback-nc,oklch(var(--nc)/1));--tw-prose-pre-bg: var(--fallback-n,oklch(var(--n)/1));--tw-prose-th-borders: var(--fallback-bc,oklch(var(--bc)/.5));--tw-prose-td-borders: var(--fallback-bc,oklch(var(--bc)/.2));--tw-prose-kbd: var(--fallback-bc,oklch(var(--bc)/.8))}.prose :where(code):not(:where([class~=not-prose] *,pre *)){padding:1px 8px;border-radius:var(--rounded-badge);font-weight:initial;background-color:var(--fallback-bc,oklch(var(--bc)/.1))}.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):before,.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):after{display:none}.prose pre code{border-radius:0;padding:0}.prose :where(tbody tr,thead):not(:where([class~=not-prose] *)){border-bottom-color:var(--fallback-bc,oklch(var(--bc)/.2))}.glass,.glass.btn-active{border:none;-webkit-backdrop-filter:blur(var(--glass-blur, 40px));backdrop-filter:blur(var(--glass-blur, 40px));background-color:transparent;background-image:linear-gradient(135deg,rgb(255 255 255 / var(--glass-opacity, 30%)),#0000),linear-gradient(var(--glass-reflex-degree, 100deg),rgb(255 255 255 / var(--glass-reflex-opacity, 10%)) 25%,rgb(0 0 0 / 0%) 25%);box-shadow:0 0 0 1px rgb(255 255 255 / var(--glass-border-opacity, 10%)) inset,0 0 0 2px #0000000d;text-shadow:0 1px rgb(0 0 0 / var(--glass-text-shadow-opacity, 5%))}@media (hover: hover){.glass.btn-active{border:none;-webkit-backdrop-filter:blur(var(--glass-blur, 40px));backdrop-filter:blur(var(--glass-blur, 40px));background-color:transparent;background-image:linear-gradient(135deg,rgb(255 255 255 / var(--glass-opacity, 30%)),#0000),linear-gradient(var(--glass-reflex-degree, 100deg),rgb(255 255 255 / var(--glass-reflex-opacity, 10%)) 25%,rgb(0 0 0 / 0%) 25%);box-shadow:0 0 0 1px rgb(255 255 255 / var(--glass-border-opacity, 10%)) inset,0 0 0 2px #0000000d;text-shadow:0 1px rgb(0 0 0 / var(--glass-text-shadow-opacity, 5%))}}.badge-xs{height:.75rem;font-size:.75rem;line-height:.75rem;padding-left:.313rem;padding-right:.313rem}.badge-sm{height:1rem;font-size:.75rem;line-height:1rem;padding-left:.438rem;padding-right:.438rem}.badge-lg{height:1.5rem;font-size:1rem;line-height:1.5rem;padding-left:.688rem;padding-right:.688rem}.btm-nav-xs>*:where(.active){border-top-width:1px}.btm-nav-sm>*:where(.active){border-top-width:2px}.btm-nav-md>*:where(.active){border-top-width:2px}.btm-nav-lg>*:where(.active){border-top-width:4px}.btn-xs{height:1.5rem;min-height:1.5rem;padding-left:.5rem;padding-right:.5rem;font-size:.75rem}.btn-sm{height:2rem;min-height:2rem;padding-left:.75rem;padding-right:.75rem;font-size:.875rem}.btn-square:where(.btn-xs){height:1.5rem;width:1.5rem;padding:0}.btn-square:where(.btn-sm){height:2rem;width:2rem;padding:0}.btn-square:where(.btn-md){height:3rem;width:3rem;padding:0}.btn-square:where(.btn-lg){height:4rem;width:4rem;padding:0}.btn-circle:where(.btn-xs){height:1.5rem;width:1.5rem;border-radius:9999px;padding:0}.btn-circle:where(.btn-sm){height:2rem;width:2rem;border-radius:9999px;padding:0}.btn-circle:where(.btn-md){height:3rem;width:3rem;border-radius:9999px;padding:0}.btn-circle:where(.btn-lg){height:4rem;width:4rem;border-radius:9999px;padding:0}[type=checkbox].checkbox-sm{height:1.25rem;width:1.25rem}.drawer-open>.drawer-toggle{display:none}.drawer-open>.drawer-toggle~.drawer-side{pointer-events:auto;visibility:visible;position:sticky;display:block;width:auto;overscroll-behavior:auto}.drawer-open>.drawer-toggle~.drawer-side>*:not(.drawer-overlay){transform:translate(0)}[dir=rtl] .drawer-open>.drawer-toggle~.drawer-side>*:not(.drawer-overlay){transform:translate(0)}.drawer-open>.drawer-toggle:checked~.drawer-side{pointer-events:auto;visibility:visible}.drawer-open>.drawer-side{overflow-y:auto}html:has(.drawer-toggle:checked){overflow-y:hidden;scrollbar-gutter:stable}.indicator :where(.indicator-item){bottom:auto;inset-inline-end:0px;inset-inline-start:auto;top:0;--tw-translate-y: -50%;--tw-translate-x: 50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item):where([dir=rtl],[dir=rtl] *){--tw-translate-x: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item.indicator-start){inset-inline-end:auto;inset-inline-start:0px;--tw-translate-x: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item.indicator-start):where([dir=rtl],[dir=rtl] *){--tw-translate-x: 50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item.indicator-center){inset-inline-end:50%;inset-inline-start:50%;--tw-translate-x: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item.indicator-center):where([dir=rtl],[dir=rtl] *){--tw-translate-x: 50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item.indicator-end){inset-inline-end:0px;inset-inline-start:auto;--tw-translate-x: 50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item.indicator-end):where([dir=rtl],[dir=rtl] *){--tw-translate-x: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item.indicator-bottom){bottom:0;top:auto;--tw-translate-y: 50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item.indicator-middle){bottom:50%;top:50%;--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item.indicator-top){bottom:auto;top:0;--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.input-lg{height:4rem;padding-left:1.5rem;padding-right:1.5rem;font-size:1.125rem;line-height:1.75rem;line-height:2}.input-sm{height:2rem;padding-left:.75rem;padding-right:.75rem;font-size:.875rem;line-height:2rem}.join.join-vertical{flex-direction:column}.join.join-vertical .join-item:first-child:not(:last-child),.join.join-vertical *:first-child:not(:last-child) .join-item{border-end-start-radius:0;border-end-end-radius:0;border-start-start-radius:inherit;border-start-end-radius:inherit}.join.join-vertical .join-item:last-child:not(:first-child),.join.join-vertical *:last-child:not(:first-child) .join-item{border-start-start-radius:0;border-start-end-radius:0;border-end-start-radius:inherit;border-end-end-radius:inherit}.join.join-horizontal{flex-direction:row}.join.join-horizontal .join-item:first-child:not(:last-child),.join.join-horizontal *:first-child:not(:last-child) .join-item{border-end-end-radius:0;border-start-end-radius:0;border-end-start-radius:inherit;border-start-start-radius:inherit}.join.join-horizontal .join-item:last-child:not(:first-child),.join.join-horizontal *:last-child:not(:first-child) .join-item{border-end-start-radius:0;border-start-start-radius:0;border-end-end-radius:inherit;border-start-end-radius:inherit}.kbd-xs{padding-left:.25rem;padding-right:.25rem;font-size:.75rem;line-height:1rem;min-height:1.2em;min-width:1.2em}.menu-horizontal{display:inline-flex;flex-direction:row}.menu-horizontal>li:not(.menu-title)>details>ul{position:absolute}.select-sm{height:2rem;min-height:2rem;padding-left:.75rem;padding-right:2rem;font-size:.875rem;line-height:2rem}[dir=rtl] .select-sm{padding-left:2rem;padding-right:.75rem}.stats-horizontal{grid-auto-flow:column}.stats-vertical{grid-auto-flow:row}.tabs-md :where(.tab){height:2rem;font-size:.875rem;line-height:1.25rem;line-height:2;--tab-padding: 1rem}.tabs-lg :where(.tab){height:3rem;font-size:1.125rem;line-height:1.75rem;line-height:2;--tab-padding: 1.25rem}.tabs-sm :where(.tab){height:1.5rem;font-size:.875rem;line-height:.75rem;--tab-padding: .75rem}.tabs-xs :where(.tab){height:1.25rem;font-size:.75rem;line-height:.75rem;--tab-padding: .5rem}:where(.toast){bottom:0;inset-inline-end:0px;inset-inline-start:auto;top:auto;--tw-translate-x: 0px;--tw-translate-y: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.toast:where(.toast-start){inset-inline-end:auto;inset-inline-start:0px;--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.toast:where(.toast-center){inset-inline-end:50%;inset-inline-start:50%;--tw-translate-x: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.toast:where(.toast-center):where([dir=rtl],[dir=rtl] *){--tw-translate-x: 50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.toast:where(.toast-end){inset-inline-end:0px;inset-inline-start:auto;--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.toast:where(.toast-bottom){bottom:0;top:auto;--tw-translate-y: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.toast:where(.toast-middle){bottom:auto;top:50%;--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.toast:where(.toast-top){bottom:auto;top:0;--tw-translate-y: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}[type=checkbox].toggle-sm{--handleoffset: .75rem;height:1.25rem;width:2rem}.tooltip{position:relative;display:inline-block;--tooltip-offset: calc(100% + 1px + var(--tooltip-tail, 0px))}.tooltip:before{position:absolute;pointer-events:none;z-index:1;content:var(--tw-content);--tw-content: attr(data-tip)}.tooltip:before,.tooltip-top:before{transform:translate(-50%);top:auto;left:50%;right:auto;bottom:var(--tooltip-offset)}.tooltip-left:before{transform:translateY(-50%);top:50%;left:auto;right:var(--tooltip-offset);bottom:auto}.tooltip-right:before{transform:translateY(-50%);top:50%;left:var(--tooltip-offset);right:auto;bottom:auto}.avatar.online:before{content:"";position:absolute;z-index:10;display:block;border-radius:9999px;--tw-bg-opacity: 1;background-color:var(--fallback-su,oklch(var(--su)/var(--tw-bg-opacity)));outline-style:solid;outline-width:2px;outline-color:var(--fallback-b1,oklch(var(--b1)/1));width:15%;height:15%;top:7%;right:7%}.avatar.offline:before{content:"";position:absolute;z-index:10;display:block;border-radius:9999px;--tw-bg-opacity: 1;background-color:var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity)));outline-style:solid;outline-width:2px;outline-color:var(--fallback-b1,oklch(var(--b1)/1));width:15%;height:15%;top:7%;right:7%}.card-compact .card-body{padding:1rem;font-size:.875rem;line-height:1.25rem}.card-compact .card-title{margin-bottom:.25rem}.card-normal .card-body{padding:var(--padding-card, 2rem);font-size:1rem;line-height:1.5rem}.card-normal .card-title{margin-bottom:.75rem}.drawer-open>.drawer-toggle~.drawer-side>.drawer-overlay{cursor:default;background-color:transparent}.join.join-vertical>:where(*:not(:first-child)){margin-left:0;margin-right:0;margin-top:-1px}.join.join-vertical>:where(*:not(:first-child)):is(.btn){margin-top:calc(var(--border-btn) * -1)}.join.join-horizontal>:where(*:not(:first-child)){margin-top:0;margin-bottom:0;margin-inline-start:-1px}.join.join-horizontal>:where(*:not(:first-child)):is(.btn){margin-inline-start:calc(var(--border-btn) * -1);margin-top:0}.menu-horizontal>li:not(.menu-title)>details>ul{margin-inline-start:0px;margin-top:1rem;padding-top:.5rem;padding-bottom:.5rem;padding-inline-end:.5rem}.menu-horizontal>li>details>ul:before{content:none}:where(.menu-horizontal>li:not(.menu-title)>details>ul){border-radius:var(--rounded-box, 1rem);--tw-bg-opacity: 1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.menu-xs .menu-title{padding:.25rem .5rem}.menu-sm :where(li:not(.menu-title)>*:not(ul,details,.menu-title)),.menu-sm :where(li:not(.menu-title)>details>summary:not(.menu-title)){border-radius:var(--rounded-btn, .5rem);padding:.25rem .75rem;font-size:.875rem;line-height:1.25rem}.menu-sm .menu-title{padding:.5rem .75rem}.menu-md .menu-title{padding:.5rem 1rem}.menu-lg .menu-title{padding:.75rem 1.5rem}.modal-top :where(.modal-box){width:100%;max-width:none;--tw-translate-y: -2.5rem;--tw-scale-x: 1;--tw-scale-y: 1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-bottom-right-radius:var(--rounded-box, 1rem);border-bottom-left-radius:var(--rounded-box, 1rem);border-top-left-radius:0;border-top-right-radius:0}.modal-middle :where(.modal-box){width:91.666667%;max-width:32rem;--tw-translate-y: 0px;--tw-scale-x: .9;--tw-scale-y: .9;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-top-left-radius:var(--rounded-box, 1rem);border-top-right-radius:var(--rounded-box, 1rem);border-bottom-right-radius:var(--rounded-box, 1rem);border-bottom-left-radius:var(--rounded-box, 1rem)}.modal-bottom :where(.modal-box){width:100%;max-width:none;--tw-translate-y: 2.5rem;--tw-scale-x: 1;--tw-scale-y: 1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-top-left-radius:var(--rounded-box, 1rem);border-top-right-radius:var(--rounded-box, 1rem);border-bottom-right-radius:0;border-bottom-left-radius:0}.stats-horizontal>:not([hidden])~:not([hidden]){--tw-divide-x-reverse: 0;border-right-width:calc(1px * var(--tw-divide-x-reverse));border-left-width:calc(1px * calc(1 - var(--tw-divide-x-reverse)));--tw-divide-y-reverse: 0;border-top-width:calc(0px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(0px * var(--tw-divide-y-reverse))}.stats-horizontal{overflow-x:auto}.stats-horizontal:where([dir=rtl],[dir=rtl] *){--tw-divide-x-reverse: 1}.stats-vertical>:not([hidden])~:not([hidden]){--tw-divide-x-reverse: 0;border-right-width:calc(0px * var(--tw-divide-x-reverse));border-left-width:calc(0px * calc(1 - var(--tw-divide-x-reverse)));--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.stats-vertical{overflow-y:auto}.table-sm :not(thead):not(tfoot) tr{font-size:.875rem;line-height:1.25rem}.table-sm :where(th,td){padding:.5rem .75rem}.tooltip{position:relative;display:inline-block;text-align:center;--tooltip-tail: .1875rem;--tooltip-color: var(--fallback-n,oklch(var(--n)/1));--tooltip-text-color: var(--fallback-nc,oklch(var(--nc)/1));--tooltip-tail-offset: calc(100% + .0625rem - var(--tooltip-tail))}.tooltip:before,.tooltip:after{opacity:0;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-delay:.1s;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.tooltip:after{position:absolute;content:"";border-style:solid;border-width:var(--tooltip-tail, 0);width:0;height:0;display:block}.tooltip:before{max-width:20rem;white-space:normal;border-radius:.25rem;padding:.25rem .5rem;font-size:.875rem;line-height:1.25rem;background-color:var(--tooltip-color);color:var(--tooltip-text-color);width:-moz-max-content;width:max-content}.tooltip.tooltip-open:before{opacity:1;transition-delay:75ms}.tooltip.tooltip-open:after{opacity:1;transition-delay:75ms}.tooltip:hover:before{opacity:1;transition-delay:75ms}.tooltip:hover:after{opacity:1;transition-delay:75ms}.tooltip:has(:focus-visible):after,.tooltip:has(:focus-visible):before{opacity:1;transition-delay:75ms}.tooltip:not([data-tip]):hover:before,.tooltip:not([data-tip]):hover:after{visibility:hidden;opacity:0}.tooltip:after,.tooltip-top:after{transform:translate(-50%);border-color:var(--tooltip-color) transparent transparent transparent;top:auto;left:50%;right:auto;bottom:var(--tooltip-tail-offset)}.tooltip-left:after{transform:translateY(-50%);border-color:transparent transparent transparent var(--tooltip-color);top:50%;left:auto;right:calc(var(--tooltip-tail-offset) + .0625rem);bottom:auto}.tooltip-right:after{transform:translateY(-50%);border-color:transparent var(--tooltip-color) transparent transparent;top:50%;left:calc(var(--tooltip-tail-offset) + .0625rem);right:auto;bottom:auto}.btn-primary{display:inline-flex;height:3rem;min-height:3rem;flex-shrink:0;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-wrap:wrap;align-items:center;justify-content:center;border-radius:var(--rounded-btn, .5rem);border-color:transparent;border-color:oklch(var(--btn-color, var(--b2)) / var(--tw-border-opacity));padding-left:1rem;padding-right:1rem;text-align:center;font-size:.875rem;line-height:1em;gap:.5rem;font-weight:600;text-decoration-line:none;transition-duration:.2s;transition-timing-function:cubic-bezier(0,0,.2,1);border-width:var(--border-btn, 1px);transition-property:color,background-color,border-color,opacity,box-shadow,transform;--tw-text-opacity: 1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow);outline-color:var(--fallback-bc,oklch(var(--bc)/1));background-color:oklch(var(--btn-color, var(--b2)) / var(--tw-bg-opacity));--tw-bg-opacity: 1;--tw-border-opacity: 1}.btn-primary[disabled],.btn-primary:disabled{pointer-events:none}:where(.btn-primary:is(input[type=checkbox])),:where(.btn-primary:is(input[type=radio])){width:auto;-webkit-appearance:none;-moz-appearance:none;appearance:none}.btn-primary:is(input[type=checkbox]):after,.btn-primary:is(input[type=radio]):after{--tw-content: attr(aria-label);content:var(--tw-content)}@media (hover: hover){.btn-primary:hover{--tw-border-opacity: 1;border-color:var(--fallback-b3,oklch(var(--b3)/var(--tw-border-opacity)));--tw-bg-opacity: 1;background-color:var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity)))}@supports (color: color-mix(in oklab,black,black)){.btn-primary:hover{background-color:color-mix(in oklab,oklch(var(--btn-color, var(--b2)) / var(--tw-bg-opacity, 1)) 90%,black);border-color:color-mix(in oklab,oklch(var(--btn-color, var(--b2)) / var(--tw-border-opacity, 1)) 90%,black)}}@supports not (color: oklch(0% 0 0)){.btn-primary:hover{background-color:var(--btn-color, var(--fallback-b2));border-color:var(--btn-color, var(--fallback-b2))}}.btn-primary.glass:hover{--glass-opacity: 25%;--glass-border-opacity: 15%}.btn-primary[disabled]:hover,.btn-primary:disabled:hover{--tw-border-opacity: 0;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));--tw-bg-opacity: .2;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));--tw-text-opacity: .2}@supports (color: color-mix(in oklab,black,black)){.btn-primary:is(input[type=checkbox]:checked):hover,.btn-primary:is(input[type=radio]:checked):hover{background-color:color-mix(in oklab,var(--fallback-p,oklch(var(--p)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-p,oklch(var(--p)/1)) 90%,black)}}}@media (prefers-reduced-motion: no-preference){.btn-primary{animation:button-pop var(--animation-btn, .25s) ease-out}}.btn-primary:active:hover,.btn-primary:active:focus{animation:button-pop 0s ease-out;transform:scale(var(--btn-focus-scale, .97))}@supports not (color: oklch(0% 0 0)){.btn-primary{background-color:var(--btn-color, var(--fallback-b2));border-color:var(--btn-color, var(--fallback-b2))}}.btn-primary:focus-visible{outline-style:solid;outline-width:2px;outline-offset:2px}.btn-primary.glass{--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow);outline-color:currentColor}.btn-primary.glass.btn-active{--glass-opacity: 25%;--glass-border-opacity: 15%}.btn-primary.btn-disabled,.btn-primary[disabled],.btn-primary:disabled{--tw-border-opacity: 0;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));--tw-bg-opacity: .2;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));--tw-text-opacity: .2}.btn-primary:is(input[type=checkbox]:checked),.btn-primary:is(input[type=radio]:checked){--tw-border-opacity: 1;border-color:var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)));--tw-bg-opacity: 1;background-color:var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)))}.btn-primary:is(input[type=checkbox]:checked):focus-visible,.btn-primary:is(input[type=radio]:checked):focus-visible{outline-color:var(--fallback-p,oklch(var(--p)/1))}.join>:where(*:not(:first-child)):is(.btn-primary){margin-inline-start:calc(var(--border-btn) * -1)}.join.join-vertical>:where(*:not(:first-child)):is(.btn-primary){margin-top:calc(var(--border-btn) * -1)}.join.join-horizontal>:where(*:not(:first-child)):is(.btn-primary){margin-inline-start:calc(var(--border-btn) * -1);margin-top:0}.btn-primary{--tw-bg-opacity: 1;background-color:var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity, 1)));--tw-text-opacity: 1;color:var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity, 1)))}.btn-primary:hover{opacity:.9}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.collapse{visibility:collapse}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.bottom-4{bottom:1rem}.left-3{left:.75rem}.left-4{left:1rem}.top-0{top:0}.top-1\/2{top:50%}.top-3{top:.75rem}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}.z-\[10\]{z-index:10}.z-\[1\]{z-index:1}.mx-auto{margin-left:auto;margin-right:auto}.my-2{margin-top:.5rem;margin-bottom:.5rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-1{margin-left:.25rem}.ml-12{margin-left:3rem}.ml-2{margin-left:.5rem}.ml-auto{margin-left:auto}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.mr-4{margin-right:1rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.mt-auto{margin-top:auto}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.line-clamp-3{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3}.block{display:block}.flex{display:flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-12{height:3rem}.h-16{height:4rem}.h-2{height:.5rem}.h-20{height:5rem}.h-24{height:6rem}.h-3{height:.75rem}.h-4{height:1rem}.h-48{height:12rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-64{height:16rem}.h-8{height:2rem}.h-screen{height:100vh}.max-h-40{max-height:10rem}.max-h-48{max-height:12rem}.max-h-64{max-height:16rem}.max-h-96{max-height:24rem}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-12{width:3rem}.w-16{width:4rem}.w-2{width:.5rem}.w-20{width:5rem}.w-24{width:6rem}.w-3{width:.75rem}.w-4{width:1rem}.w-40{width:10rem}.w-48{width:12rem}.w-5{width:1.25rem}.w-52{width:13rem}.w-56{width:14rem}.w-6{width:1.5rem}.w-64{width:16rem}.w-8{width:2rem}.w-96{width:24rem}.w-\[500px\]{width:500px}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-\[120px\]{min-width:120px}.min-w-\[140px\]{min-width:140px}.min-w-\[150px\]{min-width:150px}.min-w-\[160px\]{min-width:160px}.min-w-\[180px\]{min-width:180px}.max-w-2xl{max-width:42rem}.max-w-3xl{max-width:48rem}.max-w-4xl{max-width:56rem}.max-w-6xl{max-width:72rem}.max-w-7xl{max-width:80rem}.max-w-full{max-width:100%}.max-w-none{max-width:none}.max-w-sm{max-width:24rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.flex-shrink{flex-shrink:1}.flex-shrink-0,.shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-help{cursor:help}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-none{resize:none}.list-inside{list-style-position:inside}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.grid-rows-\[auto_1fr\]{grid-template-rows:auto 1fr}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.25rem * var(--tw-space-x-reverse));margin-left:calc(.25rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-3>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.75rem * var(--tw-space-x-reverse));margin-left:calc(.75rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(1rem * var(--tw-space-x-reverse));margin-left:calc(1rem * calc(1 - var(--tw-space-x-reverse)))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overflow-x-hidden{overflow-x:hidden}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-badge{border-radius:var(--rounded-badge, 1.9rem)}.rounded-box{border-radius:var(--rounded-box, 1rem)}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.border{border-width:1px}.border-0{border-width:0px}.border-b{border-bottom-width:1px}.border-l-4{border-left-width:4px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-base-200{--tw-border-opacity: 1;border-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity, 1)))}.border-base-300{--tw-border-opacity: 1;border-color:var(--fallback-b3,oklch(var(--b3)/var(--tw-border-opacity, 1)))}.border-current{border-color:currentColor}.border-error{--tw-border-opacity: 1;border-color:var(--fallback-er,oklch(var(--er)/var(--tw-border-opacity, 1)))}.border-error\/30{border-color:var(--fallback-er,oklch(var(--er)/.3))}.border-success\/30{border-color:var(--fallback-su,oklch(var(--su)/.3))}.bg-base-100{--tw-bg-opacity: 1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity, 1)))}.bg-base-100\/20{background-color:var(--fallback-b1,oklch(var(--b1)/.2))}.bg-base-200{--tw-bg-opacity: 1;background-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity, 1)))}.bg-base-200\/30{background-color:var(--fallback-b2,oklch(var(--b2)/.3))}.bg-base-300{--tw-bg-opacity: 1;background-color:var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity, 1)))}.bg-base-300\/50{background-color:var(--fallback-b3,oklch(var(--b3)/.5))}.bg-base-content\/20{background-color:var(--fallback-bc,oklch(var(--bc)/.2))}.bg-base-content\/30{background-color:var(--fallback-bc,oklch(var(--bc)/.3))}.bg-black{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity, 1))}.bg-error{--tw-bg-opacity: 1;background-color:var(--fallback-er,oklch(var(--er)/var(--tw-bg-opacity, 1)))}.bg-error\/10{background-color:var(--fallback-er,oklch(var(--er)/.1))}.bg-neutral{--tw-bg-opacity: 1;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity, 1)))}.bg-primary{--tw-bg-opacity: 1;background-color:var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity, 1)))}.bg-success{--tw-bg-opacity: 1;background-color:var(--fallback-su,oklch(var(--su)/var(--tw-bg-opacity, 1)))}.bg-success\/10{background-color:var(--fallback-su,oklch(var(--su)/.1))}.bg-warning{--tw-bg-opacity: 1;background-color:var(--fallback-wa,oklch(var(--wa)/var(--tw-bg-opacity, 1)))}.bg-opacity-50{--tw-bg-opacity: .5}.stroke-current{stroke:currentColor}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-16{padding-top:4rem;padding-bottom:4rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-20{padding-top:5rem;padding-bottom:5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pl-10{padding-left:2.5rem}.pl-12{padding-left:3rem}.pr-3{padding-right:.75rem}.pr-4{padding-right:1rem}.pt-1{padding-top:.25rem}.pt-2{padding-top:.5rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-6xl{font-size:3.75rem;line-height:1}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.capitalize{text-transform:capitalize}.italic{font-style:italic}.leading-\[1\.5rem\]{line-height:1.5rem}.text-accent{--tw-text-opacity: 1;color:var(--fallback-a,oklch(var(--a)/var(--tw-text-opacity, 1)))}.text-base-content{--tw-text-opacity: 1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity, 1)))}.text-base-content\/30{color:var(--fallback-bc,oklch(var(--bc)/.3))}.text-base-content\/40{color:var(--fallback-bc,oklch(var(--bc)/.4))}.text-base-content\/50{color:var(--fallback-bc,oklch(var(--bc)/.5))}.text-base-content\/60{color:var(--fallback-bc,oklch(var(--bc)/.6))}.text-base-content\/70{color:var(--fallback-bc,oklch(var(--bc)/.7))}.text-base-content\/80{color:var(--fallback-bc,oklch(var(--bc)/.8))}.text-error{--tw-text-opacity: 1;color:var(--fallback-er,oklch(var(--er)/var(--tw-text-opacity, 1)))}.text-info{--tw-text-opacity: 1;color:var(--fallback-in,oklch(var(--in)/var(--tw-text-opacity, 1)))}.text-inherit{color:inherit}.text-neutral-content{--tw-text-opacity: 1;color:var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity, 1)))}.text-primary{--tw-text-opacity: 1;color:var(--fallback-p,oklch(var(--p)/var(--tw-text-opacity, 1)))}.text-primary-content{--tw-text-opacity: 1;color:var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity, 1)))}.text-secondary{--tw-text-opacity: 1;color:var(--fallback-s,oklch(var(--s)/var(--tw-text-opacity, 1)))}.text-success{--tw-text-opacity: 1;color:var(--fallback-su,oklch(var(--su)/var(--tw-text-opacity, 1)))}.text-warning{--tw-text-opacity: 1;color:var(--fallback-wa,oklch(var(--wa)/var(--tw-text-opacity, 1)))}.opacity-30{opacity:.3}.opacity-40{opacity:.4}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.opacity-90{opacity:.9}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.blur{--tw-blur: blur(8px);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-filter{-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-shadow{transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}@keyframes dots{0%,20%{content:""}40%{content:"."}60%{content:".."}80%,to{content:"..."}}html,body,#app{overflow-x:hidden;width:100%;max-width:100vw}.drawer-content{overflow-x:hidden;width:100%;max-width:100%}.server-list-move,.server-list-enter-active,.server-list-leave-active{transition:all .3s cubic-bezier(.4,0,.2,1)}.server-list-enter-from{opacity:0;transform:translateY(20px) scale(.95)}.server-list-leave-to{opacity:0;transform:translateY(-20px) scale(.95)}.server-list-leave-active{position:absolute}.secret-list-move,.secret-list-enter-active,.secret-list-leave-active{transition:all .25s ease-out}.secret-list-enter-from{opacity:0;transform:translate(-10px)}.secret-list-leave-to{opacity:0;transform:translate(10px)}.tool-call-move,.tool-call-enter-active,.tool-call-leave-active{transition:all .2s ease}.tool-call-enter-from{opacity:0;transform:translateY(-5px)}.tool-call-leave-to{opacity:0;transform:translateY(5px)}.repo-card-move,.repo-card-enter-active,.repo-card-leave-active{transition:all .3s cubic-bezier(.4,0,.2,1)}.repo-card-enter-from,.repo-card-leave-to{opacity:0;transform:scale(.9)}.repo-card-leave-active{position:absolute}.hover\:badge-error:hover{border-color:transparent;--tw-bg-opacity: 1;background-color:var(--fallback-er,oklch(var(--er)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity)))}.hover\:badge-error:hover.badge-outline{--tw-text-opacity: 1;color:var(--fallback-er,oklch(var(--er)/var(--tw-text-opacity)))}@media (min-width: 1024px){.lg\:drawer-open>.drawer-toggle{display:none}.lg\:drawer-open>.drawer-toggle~.drawer-side{pointer-events:auto;visibility:visible;position:sticky;display:block;width:auto;overscroll-behavior:auto}.lg\:drawer-open>.drawer-toggle~.drawer-side>*:not(.drawer-overlay){transform:translate(0)}[dir=rtl] .lg\:drawer-open>.drawer-toggle~.drawer-side>*:not(.drawer-overlay){transform:translate(0)}.lg\:drawer-open>.drawer-toggle:checked~.drawer-side{pointer-events:auto;visibility:visible}.lg\:drawer-open>.drawer-side{overflow-y:auto}html:has(.lg\:drawer-open.lg\:drawer-open){overflow-y:auto;scrollbar-gutter:auto}.lg\:stats-horizontal{grid-auto-flow:column}.lg\:drawer-open>.drawer-toggle~.drawer-side>.drawer-overlay{cursor:default;background-color:transparent}.lg\:stats-horizontal>:not([hidden])~:not([hidden]){--tw-divide-x-reverse: 0;border-right-width:calc(1px * var(--tw-divide-x-reverse));border-left-width:calc(1px * calc(1 - var(--tw-divide-x-reverse)));--tw-divide-y-reverse: 0;border-top-width:calc(0px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(0px * var(--tw-divide-y-reverse))}.lg\:stats-horizontal{overflow-x:auto}.lg\:stats-horizontal:where([dir=rtl],[dir=rtl] *){--tw-divide-x-reverse: 1}}.last\:border-0:last-child{border-width:0px}.hover\:bg-base-200:hover{--tw-bg-opacity: 1;background-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity, 1)))}.hover\:bg-base-200\/50:hover{background-color:var(--fallback-b2,oklch(var(--b2)/.5))}.hover\:bg-base-300:hover{--tw-bg-opacity: 1;background-color:var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity, 1)))}.hover\:shadow-lg:hover{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}@media (min-width: 640px){.sm\:inline{display:inline}.sm\:flex-row{flex-direction:row}.sm\:self-end{align-self:flex-end}}@media (min-width: 768px){.md\:inline{display:inline}.md\:flex{display:flex}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width: 1024px){.lg\:flex{display:flex}.lg\:hidden{display:none}.lg\:w-64{width:16rem}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:items-start{align-items:flex-start}.lg\:justify-between{justify-content:space-between}.lg\:pl-64{padding-left:16rem}}
diff --git a/web/frontend/dist/assets/index-DMP0eryo.js b/web/frontend/dist/assets/index-DMP0eryo.js
new file mode 100644
index 00000000..1774d3d0
--- /dev/null
+++ b/web/frontend/dist/assets/index-DMP0eryo.js
@@ -0,0 +1,85 @@
+const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/Activity-3jsAMS7R.js","assets/Activity-7YqHqUto.css"])))=>i.map(i=>d[i]);
+var zd=Object.defineProperty;var Hd=(e,t,s)=>t in e?zd(e,t,{enumerable:!0,configurable:!0,writable:!0,value:s}):e[t]=s;var dt=(e,t,s)=>Hd(e,typeof t!="symbol"?t+"":t,s);(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))n(i);new MutationObserver(i=>{for(const o of i)if(o.type==="childList")for(const r of o.addedNodes)r.tagName==="LINK"&&r.rel==="modulepreload"&&n(r)}).observe(document,{childList:!0,subtree:!0});function s(i){const o={};return i.integrity&&(o.integrity=i.integrity),i.referrerPolicy&&(o.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?o.credentials="include":i.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function n(i){if(i.ep)return;i.ep=!0;const o=s(i);fetch(i.href,o)}})();/**
+* @vue/shared v3.5.21
+* (c) 2018-present Yuxi (Evan) You and Vue contributors
+* @license MIT
+**/function br(e){const t=Object.create(null);for(const s of e.split(","))t[s]=1;return s=>s in t}const Et={},Us=[],Fe=()=>{},mc=()=>!1,Qi=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),yr=e=>e.startsWith("onUpdate:"),Vt=Object.assign,_r=(e,t)=>{const s=e.indexOf(t);s>-1&&e.splice(s,1)},Bd=Object.prototype.hasOwnProperty,kt=(e,t)=>Bd.call(e,t),ot=Array.isArray,Ks=e=>Gn(e)==="[object Map]",sn=e=>Gn(e)==="[object Set]",ia=e=>Gn(e)==="[object Date]",ft=e=>typeof e=="function",$t=e=>typeof e=="string",Ne=e=>typeof e=="symbol",Tt=e=>e!==null&&typeof e=="object",vc=e=>(Tt(e)||ft(e))&&ft(e.then)&&ft(e.catch),bc=Object.prototype.toString,Gn=e=>bc.call(e),Wd=e=>Gn(e).slice(8,-1),yc=e=>Gn(e)==="[object Object]",xr=e=>$t(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,wn=br(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),to=e=>{const t=Object.create(null);return s=>t[s]||(t[s]=e(s))},Vd=/-\w/g,_e=to(e=>e.replace(Vd,t=>t.slice(1).toUpperCase())),Ud=/\B([A-Z])/g,bs=to(e=>e.replace(Ud,"-$1").toLowerCase()),eo=to(e=>e.charAt(0).toUpperCase()+e.slice(1)),Co=to(e=>e?`on${eo(e)}`:""),Xe=(e,t)=>!Object.is(e,t),Si=(e,...t)=>{for(let s=0;s{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:n,value:s})},Ti=e=>{const t=parseFloat(e);return isNaN(t)?e:t},Kd=e=>{const t=$t(e)?Number(e):NaN;return isNaN(t)?e:t};let oa;const so=()=>oa||(oa=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function no(e){if(ot(e)){const t={};for(let s=0;s{if(s){const n=s.split(qd);n.length>1&&(t[n[0].trim()]=n[1].trim())}}),t}function zt(e){let t="";if($t(e))t=e;else if(ot(e))for(let s=0;sRs(s,t))}const wc=e=>!!(e&&e.__v_isRef===!0),H=e=>$t(e)?e:e==null?"":ot(e)||Tt(e)&&(e.toString===bc||!ft(e.toString))?wc(e)?H(e.value):JSON.stringify(e,Sc,2):String(e),Sc=(e,t)=>wc(t)?Sc(e,t.value):Ks(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((s,[n,i],o)=>(s[ko(n,o)+" =>"]=i,s),{})}:sn(t)?{[`Set(${t.size})`]:[...t.values()].map(s=>ko(s))}:Ne(t)?ko(t):Tt(t)&&!ot(t)&&!yc(t)?String(t):t,ko=(e,t="")=>{var s;return Ne(e)?`Symbol(${(s=e.description)!=null?s:t})`:e};/**
+* @vue/reactivity v3.5.21
+* (c) 2018-present Yuxi (Evan) You and Vue contributors
+* @license MIT
+**/let Gt;class Cc{constructor(t=!1){this.detached=t,this._active=!0,this._on=0,this.effects=[],this.cleanups=[],this._isPaused=!1,this.parent=Gt,!t&&Gt&&(this.index=(Gt.scopes||(Gt.scopes=[])).push(this)-1)}get active(){return this._active}pause(){if(this._active){this._isPaused=!0;let t,s;if(this.scopes)for(t=0,s=this.scopes.length;t0&&--this._on===0&&(Gt=this.prevScope,this.prevScope=void 0)}stop(t){if(this._active){this._active=!1;let s,n;for(s=0,n=this.effects.length;s0)return;if(Cn){let t=Cn;for(Cn=void 0;t;){const s=t.next;t.next=void 0,t.flags&=-9,t=s}}let e;for(;Sn;){let t=Sn;for(Sn=void 0;t;){const s=t.next;if(t.next=void 0,t.flags&=-9,t.flags&1)try{t.trigger()}catch(n){e||(e=n)}t=s}}if(e)throw e}function Tc(e){for(let t=e.deps;t;t=t.nextDep)t.version=-1,t.prevActiveLink=t.dep.activeLink,t.dep.activeLink=t}function Pc(e){let t,s=e.depsTail,n=s;for(;n;){const i=n.prevDep;n.version===-1?(n===s&&(s=i),kr(n),ef(n)):t=n,n.dep.activeLink=n.prevActiveLink,n.prevActiveLink=void 0,n=i}e.deps=t,e.depsTail=s}function Go(e){for(let t=e.deps;t;t=t.nextDep)if(t.dep.version!==t.version||t.dep.computed&&(Oc(t.dep.computed)||t.dep.version!==t.version))return!0;return!!e._dirty}function Oc(e){if(e.flags&4&&!(e.flags&16)||(e.flags&=-17,e.globalVersion===Nn)||(e.globalVersion=Nn,!e.isSSR&&e.flags&128&&(!e.deps&&!e._dirty||!Go(e))))return;e.flags|=2;const t=e.dep,s=Ot,n=we;Ot=e,we=!0;try{Tc(e);const i=e.fn(e._value);(t.version===0||Xe(i,e._value))&&(e.flags|=128,e._value=i,t.version++)}catch(i){throw t.version++,i}finally{Ot=s,we=n,Pc(e),e.flags&=-3}}function kr(e,t=!1){const{dep:s,prevSub:n,nextSub:i}=e;if(n&&(n.nextSub=i,e.prevSub=void 0),i&&(i.prevSub=n,e.nextSub=void 0),s.subs===e&&(s.subs=n,!n&&s.computed)){s.computed.flags&=-5;for(let o=s.computed.deps;o;o=o.nextDep)kr(o,!0)}!t&&!--s.sc&&s.map&&s.map.delete(s.key)}function ef(e){const{prevDep:t,nextDep:s}=e;t&&(t.nextDep=s,e.prevDep=void 0),s&&(s.prevDep=t,e.nextDep=void 0)}let we=!0;const Rc=[];function Je(){Rc.push(we),we=!1}function Qe(){const e=Rc.pop();we=e===void 0?!0:e}function ra(e){const{cleanup:t}=e;if(e.cleanup=void 0,t){const s=Ot;Ot=void 0;try{t()}finally{Ot=s}}}let Nn=0;class sf{constructor(t,s){this.sub=t,this.dep=s,this.version=s.version,this.nextDep=this.prevDep=this.nextSub=this.prevSub=this.prevActiveLink=void 0}}class Ar{constructor(t){this.computed=t,this.version=0,this.activeLink=void 0,this.subs=void 0,this.map=void 0,this.key=void 0,this.sc=0,this.__v_skip=!0}track(t){if(!Ot||!we||Ot===this.computed)return;let s=this.activeLink;if(s===void 0||s.sub!==Ot)s=this.activeLink=new sf(Ot,this),Ot.deps?(s.prevDep=Ot.depsTail,Ot.depsTail.nextDep=s,Ot.depsTail=s):Ot.deps=Ot.depsTail=s,Lc(s);else if(s.version===-1&&(s.version=this.version,s.nextDep)){const n=s.nextDep;n.prevDep=s.prevDep,s.prevDep&&(s.prevDep.nextDep=n),s.prevDep=Ot.depsTail,s.nextDep=void 0,Ot.depsTail.nextDep=s,Ot.depsTail=s,Ot.deps===s&&(Ot.deps=n)}return s}trigger(t){this.version++,Nn++,this.notify(t)}notify(t){Sr();try{for(let s=this.subs;s;s=s.prevSub)s.sub.notify()&&s.sub.dep.notify()}finally{Cr()}}}function Lc(e){if(e.dep.sc++,e.sub.flags&4){const t=e.dep.computed;if(t&&!e.dep.subs){t.flags|=20;for(let n=t.deps;n;n=n.nextDep)Lc(n)}const s=e.dep.subs;s!==e&&(e.prevSub=s,s&&(s.nextSub=e)),e.dep.subs=e}}const Pi=new WeakMap,Ts=Symbol(""),Zo=Symbol(""),jn=Symbol("");function Xt(e,t,s){if(we&&Ot){let n=Pi.get(e);n||Pi.set(e,n=new Map);let i=n.get(s);i||(n.set(s,i=new Ar),i.map=n,i.key=s),i.track()}}function qe(e,t,s,n,i,o){const r=Pi.get(e);if(!r){Nn++;return}const a=l=>{l&&l.trigger()};if(Sr(),t==="clear")r.forEach(a);else{const l=ot(e),c=l&&xr(s);if(l&&s==="length"){const u=Number(n);r.forEach((d,f)=>{(f==="length"||f===jn||!Ne(f)&&f>=u)&&a(d)})}else switch((s!==void 0||r.has(void 0))&&a(r.get(s)),c&&a(r.get(jn)),t){case"add":l?c&&a(r.get("length")):(a(r.get(Ts)),Ks(e)&&a(r.get(Zo)));break;case"delete":l||(a(r.get(Ts)),Ks(e)&&a(r.get(Zo)));break;case"set":Ks(e)&&a(r.get(Ts));break}}Cr()}function nf(e,t){const s=Pi.get(e);return s&&s.get(t)}function Fs(e){const t=ht(e);return t===e?t:(Xt(t,"iterate",jn),ve(e)?t:t.map(Yt))}function io(e){return Xt(e=ht(e),"iterate",jn),e}const of={__proto__:null,[Symbol.iterator](){return Io(this,Symbol.iterator,Yt)},concat(...e){return Fs(this).concat(...e.map(t=>ot(t)?Fs(t):t))},entries(){return Io(this,"entries",e=>(e[1]=Yt(e[1]),e))},every(e,t){return je(this,"every",e,t,void 0,arguments)},filter(e,t){return je(this,"filter",e,t,s=>s.map(Yt),arguments)},find(e,t){return je(this,"find",e,t,Yt,arguments)},findIndex(e,t){return je(this,"findIndex",e,t,void 0,arguments)},findLast(e,t){return je(this,"findLast",e,t,Yt,arguments)},findLastIndex(e,t){return je(this,"findLastIndex",e,t,void 0,arguments)},forEach(e,t){return je(this,"forEach",e,t,void 0,arguments)},includes(...e){return Mo(this,"includes",e)},indexOf(...e){return Mo(this,"indexOf",e)},join(e){return Fs(this).join(e)},lastIndexOf(...e){return Mo(this,"lastIndexOf",e)},map(e,t){return je(this,"map",e,t,void 0,arguments)},pop(){return an(this,"pop")},push(...e){return an(this,"push",e)},reduce(e,...t){return aa(this,"reduce",e,t)},reduceRight(e,...t){return aa(this,"reduceRight",e,t)},shift(){return an(this,"shift")},some(e,t){return je(this,"some",e,t,void 0,arguments)},splice(...e){return an(this,"splice",e)},toReversed(){return Fs(this).toReversed()},toSorted(e){return Fs(this).toSorted(e)},toSpliced(...e){return Fs(this).toSpliced(...e)},unshift(...e){return an(this,"unshift",e)},values(){return Io(this,"values",Yt)}};function Io(e,t,s){const n=io(e),i=n[t]();return n!==e&&!ve(e)&&(i._next=i.next,i.next=()=>{const o=i._next();return o.value&&(o.value=s(o.value)),o}),i}const rf=Array.prototype;function je(e,t,s,n,i,o){const r=io(e),a=r!==e&&!ve(e),l=r[t];if(l!==rf[t]){const d=l.apply(e,o);return a?Yt(d):d}let c=s;r!==e&&(a?c=function(d,f){return s.call(this,Yt(d),f,e)}:s.length>2&&(c=function(d,f){return s.call(this,d,f,e)}));const u=l.call(r,c,n);return a&&i?i(u):u}function aa(e,t,s,n){const i=io(e);let o=s;return i!==e&&(ve(e)?s.length>3&&(o=function(r,a,l){return s.call(this,r,a,l,e)}):o=function(r,a,l){return s.call(this,r,Yt(a),l,e)}),i[t](o,...n)}function Mo(e,t,s){const n=ht(e);Xt(n,"iterate",jn);const i=n[t](...s);return(i===-1||i===!1)&&Zn(s[0])?(s[0]=ht(s[0]),n[t](...s)):i}function an(e,t,s=[]){Je(),Sr();const n=ht(e)[t].apply(e,s);return Cr(),Qe(),n}const af=br("__proto__,__v_isRef,__isVue"),Dc=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(Ne));function lf(e){Ne(e)||(e=String(e));const t=ht(this);return Xt(t,"has",e),t.hasOwnProperty(e)}class $c{constructor(t=!1,s=!1){this._isReadonly=t,this._isShallow=s}get(t,s,n){if(s==="__v_skip")return t.__v_skip;const i=this._isReadonly,o=this._isShallow;if(s==="__v_isReactive")return!i;if(s==="__v_isReadonly")return i;if(s==="__v_isShallow")return o;if(s==="__v_raw")return n===(i?o?bf:zc:o?jc:Nc).get(t)||Object.getPrototypeOf(t)===Object.getPrototypeOf(n)?t:void 0;const r=ot(t);if(!i){let l;if(r&&(l=of[s]))return l;if(s==="hasOwnProperty")return lf}const a=Reflect.get(t,s,jt(t)?t:n);return(Ne(s)?Dc.has(s):af(s))||(i||Xt(t,"get",s),o)?a:jt(a)?r&&xr(s)?a:a.value:Tt(a)?i?Bc(a):ys(a):a}}class Fc extends $c{constructor(t=!1){super(!1,t)}set(t,s,n,i){let o=t[s];if(!this._isShallow){const l=ps(o);if(!ve(n)&&!ps(n)&&(o=ht(o),n=ht(n)),!ot(t)&&jt(o)&&!jt(n))return l||(o.value=n),!0}const r=ot(t)&&xr(s)?Number(s)e,ni=e=>Reflect.getPrototypeOf(e);function hf(e,t,s){return function(...n){const i=this.__v_raw,o=ht(i),r=Ks(o),a=e==="entries"||e===Symbol.iterator&&r,l=e==="keys"&&r,c=i[e](...n),u=s?Xo:t?Oi:Yt;return!t&&Xt(o,"iterate",l?Zo:Ts),{next(){const{value:d,done:f}=c.next();return f?{value:d,done:f}:{value:a?[u(d[0]),u(d[1])]:u(d),done:f}},[Symbol.iterator](){return this}}}}function ii(e){return function(...t){return e==="delete"?!1:e==="clear"?void 0:this}}function pf(e,t){const s={get(i){const o=this.__v_raw,r=ht(o),a=ht(i);e||(Xe(i,a)&&Xt(r,"get",i),Xt(r,"get",a));const{has:l}=ni(r),c=t?Xo:e?Oi:Yt;if(l.call(r,i))return c(o.get(i));if(l.call(r,a))return c(o.get(a));o!==r&&o.get(i)},get size(){const i=this.__v_raw;return!e&&Xt(ht(i),"iterate",Ts),i.size},has(i){const o=this.__v_raw,r=ht(o),a=ht(i);return e||(Xe(i,a)&&Xt(r,"has",i),Xt(r,"has",a)),i===a?o.has(i):o.has(i)||o.has(a)},forEach(i,o){const r=this,a=r.__v_raw,l=ht(a),c=t?Xo:e?Oi:Yt;return!e&&Xt(l,"iterate",Ts),a.forEach((u,d)=>i.call(o,c(u),c(d),r))}};return Vt(s,e?{add:ii("add"),set:ii("set"),delete:ii("delete"),clear:ii("clear")}:{add(i){!t&&!ve(i)&&!ps(i)&&(i=ht(i));const o=ht(this);return ni(o).has.call(o,i)||(o.add(i),qe(o,"add",i,i)),this},set(i,o){!t&&!ve(o)&&!ps(o)&&(o=ht(o));const r=ht(this),{has:a,get:l}=ni(r);let c=a.call(r,i);c||(i=ht(i),c=a.call(r,i));const u=l.call(r,i);return r.set(i,o),c?Xe(o,u)&&qe(r,"set",i,o):qe(r,"add",i,o),this},delete(i){const o=ht(this),{has:r,get:a}=ni(o);let l=r.call(o,i);l||(i=ht(i),l=r.call(o,i)),a&&a.call(o,i);const c=o.delete(i);return l&&qe(o,"delete",i,void 0),c},clear(){const i=ht(this),o=i.size!==0,r=i.clear();return o&&qe(i,"clear",void 0,void 0),r}}),["keys","values","entries",Symbol.iterator].forEach(i=>{s[i]=hf(i,e,t)}),s}function Ir(e,t){const s=pf(e,t);return(n,i,o)=>i==="__v_isReactive"?!e:i==="__v_isReadonly"?e:i==="__v_raw"?n:Reflect.get(kt(s,i)&&i in n?s:n,i,o)}const gf={get:Ir(!1,!1)},mf={get:Ir(!1,!0)},vf={get:Ir(!0,!1)};const Nc=new WeakMap,jc=new WeakMap,zc=new WeakMap,bf=new WeakMap;function yf(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function _f(e){return e.__v_skip||!Object.isExtensible(e)?0:yf(Wd(e))}function ys(e){return ps(e)?e:Mr(e,!1,uf,gf,Nc)}function Hc(e){return Mr(e,!1,ff,mf,jc)}function Bc(e){return Mr(e,!0,df,vf,zc)}function Mr(e,t,s,n,i){if(!Tt(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;const o=_f(e);if(o===0)return e;const r=i.get(e);if(r)return r;const a=new Proxy(e,o===2?n:s);return i.set(e,a),a}function fs(e){return ps(e)?fs(e.__v_raw):!!(e&&e.__v_isReactive)}function ps(e){return!!(e&&e.__v_isReadonly)}function ve(e){return!!(e&&e.__v_isShallow)}function Zn(e){return e?!!e.__v_raw:!1}function ht(e){const t=e&&e.__v_raw;return t?ht(t):e}function Er(e){return!kt(e,"__v_skip")&&Object.isExtensible(e)&&_c(e,"__v_skip",!0),e}const Yt=e=>Tt(e)?ys(e):e,Oi=e=>Tt(e)?Bc(e):e;function jt(e){return e?e.__v_isRef===!0:!1}function it(e){return Wc(e,!1)}function Tr(e){return Wc(e,!0)}function Wc(e,t){return jt(e)?e:new xf(e,t)}class xf{constructor(t,s){this.dep=new Ar,this.__v_isRef=!0,this.__v_isShallow=!1,this._rawValue=s?t:ht(t),this._value=s?t:Yt(t),this.__v_isShallow=s}get value(){return this.dep.track(),this._value}set value(t){const s=this._rawValue,n=this.__v_isShallow||ve(t)||ps(t);t=n?t:ht(t),Xe(t,s)&&(this._rawValue=t,this._value=n?t:Yt(t),this.dep.trigger())}}function xt(e){return jt(e)?e.value:e}const wf={get:(e,t,s)=>t==="__v_raw"?e:xt(Reflect.get(e,t,s)),set:(e,t,s,n)=>{const i=e[t];return jt(i)&&!jt(s)?(i.value=s,!0):Reflect.set(e,t,s,n)}};function Vc(e){return fs(e)?e:new Proxy(e,wf)}function Sf(e){const t=ot(e)?new Array(e.length):{};for(const s in e)t[s]=kf(e,s);return t}class Cf{constructor(t,s,n){this._object=t,this._key=s,this._defaultValue=n,this.__v_isRef=!0,this._value=void 0}get value(){const t=this._object[this._key];return this._value=t===void 0?this._defaultValue:t}set value(t){this._object[this._key]=t}get dep(){return nf(ht(this._object),this._key)}}function kf(e,t,s){const n=e[t];return jt(n)?n:new Cf(e,t,s)}class Af{constructor(t,s,n){this.fn=t,this.setter=s,this._value=void 0,this.dep=new Ar(this),this.__v_isRef=!0,this.deps=void 0,this.depsTail=void 0,this.flags=16,this.globalVersion=Nn-1,this.next=void 0,this.effect=this,this.__v_isReadonly=!s,this.isSSR=n}notify(){if(this.flags|=16,!(this.flags&8)&&Ot!==this)return Ec(this,!0),!0}get value(){const t=this.dep.track();return Oc(this),t&&(t.version=this.dep.version),this._value}set value(t){this.setter&&this.setter(t)}}function If(e,t,s=!1){let n,i;return ft(e)?n=e:(n=e.get,i=e.set),new Af(n,i,s)}const oi={},Ri=new WeakMap;let As;function Mf(e,t=!1,s=As){if(s){let n=Ri.get(s);n||Ri.set(s,n=[]),n.push(e)}}function Ef(e,t,s=Et){const{immediate:n,deep:i,once:o,scheduler:r,augmentJob:a,call:l}=s,c=E=>i?E:ve(E)||i===!1||i===0?Ge(E,1):Ge(E);let u,d,f,g,b=!1,v=!1;if(jt(e)?(d=()=>e.value,b=ve(e)):fs(e)?(d=()=>c(e),b=!0):ot(e)?(v=!0,b=e.some(E=>fs(E)||ve(E)),d=()=>e.map(E=>{if(jt(E))return E.value;if(fs(E))return c(E);if(ft(E))return l?l(E,2):E()})):ft(e)?t?d=l?()=>l(e,2):e:d=()=>{if(f){Je();try{f()}finally{Qe()}}const E=As;As=u;try{return l?l(e,3,[g]):e(g)}finally{As=E}}:d=Fe,t&&i){const E=d,$=i===!0?1/0:i;d=()=>Ge(E(),$)}const _=Ac(),x=()=>{u.stop(),_&&_.active&&_r(_.effects,u)};if(o&&t){const E=t;t=(...$)=>{E(...$),x()}}let C=v?new Array(e.length).fill(oi):oi;const I=E=>{if(!(!(u.flags&1)||!u.dirty&&!E))if(t){const $=u.run();if(i||b||(v?$.some((B,G)=>Xe(B,C[G])):Xe($,C))){f&&f();const B=As;As=u;try{const G=[$,C===oi?void 0:v&&C[0]===oi?[]:C,g];C=$,l?l(t,3,G):t(...G)}finally{As=B}}}else u.run()};return a&&a(I),u=new Ic(d),u.scheduler=r?()=>r(I,!1):I,g=E=>Mf(E,!1,u),f=u.onStop=()=>{const E=Ri.get(u);if(E){if(l)l(E,4);else for(const $ of E)$();Ri.delete(u)}},t?n?I(!0):C=u.run():r?r(I.bind(null,!0),!0):u.run(),x.pause=u.pause.bind(u),x.resume=u.resume.bind(u),x.stop=x,x}function Ge(e,t=1/0,s){if(t<=0||!Tt(e)||e.__v_skip||(s=s||new Map,(s.get(e)||0)>=t))return e;if(s.set(e,t),t--,jt(e))Ge(e.value,t,s);else if(ot(e))for(let n=0;n{Ge(n,t,s)});else if(yc(e)){for(const n in e)Ge(e[n],t,s);for(const n of Object.getOwnPropertySymbols(e))Object.prototype.propertyIsEnumerable.call(e,n)&&Ge(e[n],t,s)}return e}/**
+* @vue/runtime-core v3.5.21
+* (c) 2018-present Yuxi (Evan) You and Vue contributors
+* @license MIT
+**/function Xn(e,t,s,n){try{return n?e(...n):e()}catch(i){oo(i,t,s)}}function Se(e,t,s,n){if(ft(e)){const i=Xn(e,t,s,n);return i&&vc(i)&&i.catch(o=>{oo(o,t,s)}),i}if(ot(e)){const i=[];for(let o=0;o>>1,i=ee[n],o=zn(i);o=zn(s)?ee.push(e):ee.splice(Pf(t),0,e),e.flags|=1,Kc()}}function Kc(){Li||(Li=Uc.then(qc))}function Of(e){ot(e)?Ys.push(...e):as&&e.id===-1?as.splice(Bs+1,0,e):e.flags&1||(Ys.push(e),e.flags|=1),Kc()}function la(e,t,s=Re+1){for(;szn(s)-zn(n));if(Ys.length=0,as){as.push(...t);return}for(as=t,Bs=0;Bse.id==null?e.flags&2?-1:1/0:e.id;function qc(e){try{for(Re=0;Re{n._d&&Ni(-1);const o=Di(t);let r;try{r=e(...i)}finally{Di(o),n._d&&Ni(1)}return r};return n._n=!0,n._c=!0,n._d=!0,n}function Kt(e,t){if(he===null)return e;const s=fo(he),n=e.dirs||(e.dirs=[]);for(let i=0;ie.__isTeleport,Ke=Symbol("_leaveCb"),ri=Symbol("_enterCb");function Xc(){const e={isMounted:!1,isLeaving:!1,isUnmounting:!1,leavingVNodes:new Map};return nn(()=>{e.isMounted=!0}),ou(()=>{e.isUnmounting=!0}),e}const pe=[Function,Array],Jc={mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:pe,onEnter:pe,onAfterEnter:pe,onEnterCancelled:pe,onBeforeLeave:pe,onLeave:pe,onAfterLeave:pe,onLeaveCancelled:pe,onBeforeAppear:pe,onAppear:pe,onAfterAppear:pe,onAppearCancelled:pe},Qc=e=>{const t=e.subTree;return t.component?Qc(t.component):t},Lf={name:"BaseTransition",props:Jc,setup(e,{slots:t}){const s=uo(),n=Xc();return()=>{const i=t.default&&Or(t.default(),!0);if(!i||!i.length)return;const o=tu(i),r=ht(e),{mode:a}=r;if(n.isLeaving)return Eo(o);const l=ca(o);if(!l)return Eo(o);let c=Hn(l,r,n,s,d=>c=d);l.type!==se&&Ls(l,c);let u=s.subTree&&ca(s.subTree);if(u&&u.type!==se&&!Ms(u,l)&&Qc(s).type!==se){let d=Hn(u,r,n,s);if(Ls(u,d),a==="out-in"&&l.type!==se)return n.isLeaving=!0,d.afterLeave=()=>{n.isLeaving=!1,s.job.flags&8||s.update(),delete d.afterLeave,u=void 0},Eo(o);a==="in-out"&&l.type!==se?d.delayLeave=(f,g,b)=>{const v=eu(n,u);v[String(u.key)]=u,f[Ke]=()=>{g(),f[Ke]=void 0,delete c.delayedLeave,u=void 0},c.delayedLeave=()=>{b(),delete c.delayedLeave,u=void 0}}:u=void 0}else u&&(u=void 0);return o}}};function tu(e){let t=e[0];if(e.length>1){for(const s of e)if(s.type!==se){t=s;break}}return t}const Df=Lf;function eu(e,t){const{leavingVNodes:s}=e;let n=s.get(t.type);return n||(n=Object.create(null),s.set(t.type,n)),n}function Hn(e,t,s,n,i){const{appear:o,mode:r,persisted:a=!1,onBeforeEnter:l,onEnter:c,onAfterEnter:u,onEnterCancelled:d,onBeforeLeave:f,onLeave:g,onAfterLeave:b,onLeaveCancelled:v,onBeforeAppear:_,onAppear:x,onAfterAppear:C,onAppearCancelled:I}=t,E=String(e.key),$=eu(s,e),B=(y,A)=>{y&&Se(y,n,9,A)},G=(y,A)=>{const k=A[1];B(y,A),ot(y)?y.every(T=>T.length<=1)&&k():y.length<=1&&k()},M={mode:r,persisted:a,beforeEnter(y){let A=l;if(!s.isMounted)if(o)A=_||l;else return;y[Ke]&&y[Ke](!0);const k=$[E];k&&Ms(e,k)&&k.el[Ke]&&k.el[Ke](),B(A,[y])},enter(y){let A=c,k=u,T=d;if(!s.isMounted)if(o)A=x||c,k=C||u,T=I||d;else return;let z=!1;const at=y[ri]=tt=>{z||(z=!0,tt?B(T,[y]):B(k,[y]),M.delayedLeave&&M.delayedLeave(),y[ri]=void 0)};A?G(A,[y,at]):at()},leave(y,A){const k=String(e.key);if(y[ri]&&y[ri](!0),s.isUnmounting)return A();B(f,[y]);let T=!1;const z=y[Ke]=at=>{T||(T=!0,A(),at?B(v,[y]):B(b,[y]),y[Ke]=void 0,$[k]===e&&delete $[k])};$[k]=e,g?G(g,[y,z]):z()},clone(y){const A=Hn(y,t,s,n,i);return i&&i(A),A}};return M}function Eo(e){if(ro(e))return e=gs(e),e.children=null,e}function ca(e){if(!ro(e))return Zc(e.type)&&e.children?tu(e.children):e;if(e.component)return e.component.subTree;const{shapeFlag:t,children:s}=e;if(s){if(t&16)return s[0];if(t&32&&ft(s.default))return s.default()}}function Ls(e,t){e.shapeFlag&6&&e.component?(e.transition=t,Ls(e.component.subTree,t)):e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function Or(e,t=!1,s){let n=[],i=0;for(let o=0;o1)for(let o=0;okn(b,t&&(ot(t)?t[v]:t),s,n,i));return}if(An(n)&&!i){n.shapeFlag&512&&n.type.__asyncResolved&&n.component.subTree.component&&kn(e,t,s,n.component.subTree);return}const o=n.shapeFlag&4?fo(n.component):n.el,r=i?null:o,{i:a,r:l}=e,c=t&&t.r,u=a.refs===Et?a.refs={}:a.refs,d=a.setupState,f=ht(d),g=d===Et?mc:b=>kt(f,b);if(c!=null&&c!==l){if(ua(t),$t(c))u[c]=null,g(c)&&(d[c]=null);else if(jt(c)){c.value=null;const b=t;b.k&&(u[b.k]=null)}}if(ft(l))Xn(l,a,12,[r,u]);else{const b=$t(l),v=jt(l);if(b||v){const _=()=>{if(e.f){const x=b?g(l)?d[l]:u[l]:l.value;if(i)ot(x)&&_r(x,o);else if(ot(x))x.includes(o)||x.push(o);else if(b)u[l]=[o],g(l)&&(d[l]=u[l]);else{const C=[o];l.value=C,e.k&&(u[e.k]=C)}}else b?(u[l]=r,g(l)&&(d[l]=r)):v&&(l.value=r,e.k&&(u[e.k]=r))};if(r){const x=()=>{_(),$i.delete(e)};x.id=-1,$i.set(e,x),de(x,s)}else ua(e),_()}}}function ua(e){const t=$i.get(e);t&&(t.flags|=8,$i.delete(e))}so().requestIdleCallback;so().cancelIdleCallback;const An=e=>!!e.type.__asyncLoader,ro=e=>e.type.__isKeepAlive;function $f(e,t){nu(e,"a",t)}function Ff(e,t){nu(e,"da",t)}function nu(e,t,s=Jt){const n=e.__wdc||(e.__wdc=()=>{let i=s;for(;i;){if(i.isDeactivated)return;i=i.parent}return e()});if(ao(t,n,s),s){let i=s.parent;for(;i&&i.parent;)ro(i.parent.vnode)&&Nf(n,t,s,i),i=i.parent}}function Nf(e,t,s,n){const i=ao(t,e,n,!0);Qn(()=>{_r(n[t],i)},s)}function ao(e,t,s=Jt,n=!1){if(s){const i=s[e]||(s[e]=[]),o=t.__weh||(t.__weh=(...r)=>{Je();const a=ti(s),l=Se(t,s,e,r);return a(),Qe(),l});return n?i.unshift(o):i.push(o),o}}const es=e=>(t,s=Jt)=>{(!Bn||e==="sp")&&ao(e,(...n)=>t(...n),s)},jf=es("bm"),nn=es("m"),zf=es("bu"),iu=es("u"),ou=es("bum"),Qn=es("um"),Hf=es("sp"),Bf=es("rtg"),Wf=es("rtc");function Vf(e,t=Jt){ao("ec",e,t)}const ru="components";function Rr(e,t){return lu(ru,e,!0,t)||e}const au=Symbol.for("v-ndc");function Uf(e){return $t(e)?lu(ru,e,!1)||e:e||au}function lu(e,t,s=!0,n=!1){const i=he||Jt;if(i){const o=i.type;{const a=Rh(o,!1);if(a&&(a===t||a===_e(t)||a===eo(_e(t))))return o}const r=da(i[e]||o[e],t)||da(i.appContext[e],t);return!r&&n?o:r}}function da(e,t){return e&&(e[t]||e[_e(t)]||e[eo(_e(t))])}function Ut(e,t,s,n){let i;const o=s&&s[n],r=ot(e);if(r||$t(e)){const a=r&&fs(e);let l=!1,c=!1;a&&(l=!ve(e),c=ps(e),e=io(e)),i=new Array(e.length);for(let u=0,d=e.length;ut(a,l,void 0,o&&o[l]));else{const a=Object.keys(e);i=new Array(a.length);for(let l=0,c=a.length;le?Mu(e)?fo(e):Jo(e.parent):null,In=Vt(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>Jo(e.parent),$root:e=>Jo(e.root),$host:e=>e.ce,$emit:e=>e.emit,$options:e=>uu(e),$forceUpdate:e=>e.f||(e.f=()=>{Pr(e.update)}),$nextTick:e=>e.n||(e.n=Jn.bind(e.proxy)),$watch:e=>hh.bind(e)}),To=(e,t)=>e!==Et&&!e.__isScriptSetup&&kt(e,t),Kf={get({_:e},t){if(t==="__v_skip")return!0;const{ctx:s,setupState:n,data:i,props:o,accessCache:r,type:a,appContext:l}=e;let c;if(t[0]!=="$"){const g=r[t];if(g!==void 0)switch(g){case 1:return n[t];case 2:return i[t];case 4:return s[t];case 3:return o[t]}else{if(To(n,t))return r[t]=1,n[t];if(i!==Et&&kt(i,t))return r[t]=2,i[t];if((c=e.propsOptions[0])&&kt(c,t))return r[t]=3,o[t];if(s!==Et&&kt(s,t))return r[t]=4,s[t];Qo&&(r[t]=0)}}const u=In[t];let d,f;if(u)return t==="$attrs"&&Xt(e.attrs,"get",""),u(e);if((d=a.__cssModules)&&(d=d[t]))return d;if(s!==Et&&kt(s,t))return r[t]=4,s[t];if(f=l.config.globalProperties,kt(f,t))return f[t]},set({_:e},t,s){const{data:n,setupState:i,ctx:o}=e;return To(i,t)?(i[t]=s,!0):n!==Et&&kt(n,t)?(n[t]=s,!0):kt(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(o[t]=s,!0)},has({_:{data:e,setupState:t,accessCache:s,ctx:n,appContext:i,propsOptions:o,type:r}},a){let l,c;return!!(s[a]||e!==Et&&a[0]!=="$"&&kt(e,a)||To(t,a)||(l=o[0])&&kt(l,a)||kt(n,a)||kt(In,a)||kt(i.config.globalProperties,a)||(c=r.__cssModules)&&c[a])},defineProperty(e,t,s){return s.get!=null?e._.accessCache[t]=0:kt(s,"value")&&this.set(e,t,s.value,null),Reflect.defineProperty(e,t,s)}};function fa(e){return ot(e)?e.reduce((t,s)=>(t[s]=null,t),{}):e}let Qo=!0;function Yf(e){const t=uu(e),s=e.proxy,n=e.ctx;Qo=!1,t.beforeCreate&&ha(t.beforeCreate,e,"bc");const{data:i,computed:o,methods:r,watch:a,provide:l,inject:c,created:u,beforeMount:d,mounted:f,beforeUpdate:g,updated:b,activated:v,deactivated:_,beforeDestroy:x,beforeUnmount:C,destroyed:I,unmounted:E,render:$,renderTracked:B,renderTriggered:G,errorCaptured:M,serverPrefetch:y,expose:A,inheritAttrs:k,components:T,directives:z,filters:at}=t;if(c&&qf(c,n,null),r)for(const X in r){const ct=r[X];ft(ct)&&(n[X]=ct.bind(s))}if(i){const X=i.call(s,s);Tt(X)&&(e.data=ys(X))}if(Qo=!0,o)for(const X in o){const ct=o[X],St=ft(ct)?ct.bind(s,s):ft(ct.get)?ct.get.bind(s,s):Fe,N=!ft(ct)&&ft(ct.set)?ct.set.bind(s):Fe,F=lt({get:St,set:N});Object.defineProperty(n,X,{enumerable:!0,configurable:!0,get:()=>F.value,set:st=>F.value=st})}if(a)for(const X in a)cu(a[X],n,s,X);if(l){const X=ft(l)?l.call(s):l;Reflect.ownKeys(X).forEach(ct=>{Ci(ct,X[ct])})}u&&ha(u,e,"c");function et(X,ct){ot(ct)?ct.forEach(St=>X(St.bind(s))):ct&&X(ct.bind(s))}if(et(jf,d),et(nn,f),et(zf,g),et(iu,b),et($f,v),et(Ff,_),et(Vf,M),et(Wf,B),et(Bf,G),et(ou,C),et(Qn,E),et(Hf,y),ot(A))if(A.length){const X=e.exposed||(e.exposed={});A.forEach(ct=>{Object.defineProperty(X,ct,{get:()=>s[ct],set:St=>s[ct]=St,enumerable:!0})})}else e.exposed||(e.exposed={});$&&e.render===Fe&&(e.render=$),k!=null&&(e.inheritAttrs=k),T&&(e.components=T),z&&(e.directives=z),y&&su(e)}function qf(e,t,s=Fe){ot(e)&&(e=tr(e));for(const n in e){const i=e[n];let o;Tt(i)?"default"in i?o=be(i.from||n,i.default,!0):o=be(i.from||n):o=be(i),jt(o)?Object.defineProperty(t,n,{enumerable:!0,configurable:!0,get:()=>o.value,set:r=>o.value=r}):t[n]=o}}function ha(e,t,s){Se(ot(e)?e.map(n=>n.bind(t.proxy)):e.bind(t.proxy),t,s)}function cu(e,t,s,n){let i=n.includes(".")?Su(s,n):()=>s[n];if($t(e)){const o=t[e];ft(o)&&xe(i,o)}else if(ft(e))xe(i,e.bind(s));else if(Tt(e))if(ot(e))e.forEach(o=>cu(o,t,s,n));else{const o=ft(e.handler)?e.handler.bind(s):t[e.handler];ft(o)&&xe(i,o,e)}}function uu(e){const t=e.type,{mixins:s,extends:n}=t,{mixins:i,optionsCache:o,config:{optionMergeStrategies:r}}=e.appContext,a=o.get(t);let l;return a?l=a:!i.length&&!s&&!n?l=t:(l={},i.length&&i.forEach(c=>Fi(l,c,r,!0)),Fi(l,t,r)),Tt(t)&&o.set(t,l),l}function Fi(e,t,s,n=!1){const{mixins:i,extends:o}=t;o&&Fi(e,o,s,!0),i&&i.forEach(r=>Fi(e,r,s,!0));for(const r in t)if(!(n&&r==="expose")){const a=Gf[r]||s&&s[r];e[r]=a?a(e[r],t[r]):t[r]}return e}const Gf={data:pa,props:ga,emits:ga,methods:gn,computed:gn,beforeCreate:te,created:te,beforeMount:te,mounted:te,beforeUpdate:te,updated:te,beforeDestroy:te,beforeUnmount:te,destroyed:te,unmounted:te,activated:te,deactivated:te,errorCaptured:te,serverPrefetch:te,components:gn,directives:gn,watch:Xf,provide:pa,inject:Zf};function pa(e,t){return t?e?function(){return Vt(ft(e)?e.call(this,this):e,ft(t)?t.call(this,this):t)}:t:e}function Zf(e,t){return gn(tr(e),tr(t))}function tr(e){if(ot(e)){const t={};for(let s=0;s1)return s&&ft(t)?t.call(n&&n.proxy):t}}function th(){return!!(uo()||Ps)}const fu={},hu=()=>Object.create(fu),pu=e=>Object.getPrototypeOf(e)===fu;function eh(e,t,s,n=!1){const i={},o=hu();e.propsDefaults=Object.create(null),gu(e,t,i,o);for(const r in e.propsOptions[0])r in i||(i[r]=void 0);s?e.props=n?i:Hc(i):e.type.props?e.props=i:e.props=o,e.attrs=o}function sh(e,t,s,n){const{props:i,attrs:o,vnode:{patchFlag:r}}=e,a=ht(i),[l]=e.propsOptions;let c=!1;if((n||r>0)&&!(r&16)){if(r&8){const u=e.vnode.dynamicProps;for(let d=0;d{l=!0;const[f,g]=mu(d,t,!0);Vt(r,f),g&&a.push(...g)};!s&&t.mixins.length&&t.mixins.forEach(u),e.extends&&u(e.extends),e.mixins&&e.mixins.forEach(u)}if(!o&&!l)return Tt(e)&&n.set(e,Us),Us;if(ot(o))for(let u=0;ue==="_"||e==="_ctx"||e==="$stable",Dr=e=>ot(e)?e.map($e):[$e(e)],ih=(e,t,s)=>{if(t._n)return t;const n=Zt((...i)=>Dr(t(...i)),s);return n._c=!1,n},vu=(e,t,s)=>{const n=e._ctx;for(const i in e){if(Lr(i))continue;const o=e[i];if(ft(o))t[i]=ih(i,o,n);else if(o!=null){const r=Dr(o);t[i]=()=>r}}},bu=(e,t)=>{const s=Dr(t);e.slots.default=()=>s},yu=(e,t,s)=>{for(const n in t)(s||!Lr(n))&&(e[n]=t[n])},oh=(e,t,s)=>{const n=e.slots=hu();if(e.vnode.shapeFlag&32){const i=t._;i?(yu(n,t,s),s&&_c(n,"_",i,!0)):vu(t,n)}else t&&bu(e,t)},rh=(e,t,s)=>{const{vnode:n,slots:i}=e;let o=!0,r=Et;if(n.shapeFlag&32){const a=t._;a?s&&a===1?o=!1:yu(i,t,s):(o=!t.$stable,vu(t,i)),r=t}else t&&(bu(e,t),r={default:1});if(o)for(const a in i)!Lr(a)&&r[a]==null&&delete i[a]},de=xh;function ah(e){return lh(e)}function lh(e,t){const s=so();s.__VUE__=!0;const{insert:n,remove:i,patchProp:o,createElement:r,createText:a,createComment:l,setText:c,setElementText:u,parentNode:d,nextSibling:f,setScopeId:g=Fe,insertStaticContent:b}=e,v=(m,h,w,P=null,S=null,O=null,q=void 0,U=null,K=!!h.dynamicChildren)=>{if(m===h)return;m&&!Ms(m,h)&&(P=D(m),st(m,S,O,!0),m=null),h.patchFlag===-2&&(K=!1,h.dynamicChildren=null);const{type:j,ref:rt,shapeFlag:Z}=h;switch(j){case co:_(m,h,w,P);break;case se:x(m,h,w,P);break;case ki:m==null&&C(h,w,P,q);break;case wt:T(m,h,w,P,S,O,q,U,K);break;default:Z&1?$(m,h,w,P,S,O,q,U,K):Z&6?z(m,h,w,P,S,O,q,U,K):(Z&64||Z&128)&&j.process(m,h,w,P,S,O,q,U,K,J)}rt!=null&&S?kn(rt,m&&m.ref,O,h||m,!h):rt==null&&m&&m.ref!=null&&kn(m.ref,null,O,m,!0)},_=(m,h,w,P)=>{if(m==null)n(h.el=a(h.children),w,P);else{const S=h.el=m.el;h.children!==m.children&&c(S,h.children)}},x=(m,h,w,P)=>{m==null?n(h.el=l(h.children||""),w,P):h.el=m.el},C=(m,h,w,P)=>{[m.el,m.anchor]=b(m.children,h,w,P,m.el,m.anchor)},I=({el:m,anchor:h},w,P)=>{let S;for(;m&&m!==h;)S=f(m),n(m,w,P),m=S;n(h,w,P)},E=({el:m,anchor:h})=>{let w;for(;m&&m!==h;)w=f(m),i(m),m=w;i(h)},$=(m,h,w,P,S,O,q,U,K)=>{h.type==="svg"?q="svg":h.type==="math"&&(q="mathml"),m==null?B(h,w,P,S,O,q,U,K):y(m,h,S,O,q,U,K)},B=(m,h,w,P,S,O,q,U)=>{let K,j;const{props:rt,shapeFlag:Z,transition:nt,dirs:ut}=m;if(K=m.el=r(m.type,O,rt&&rt.is,rt),Z&8?u(K,m.children):Z&16&&M(m.children,K,null,P,S,Po(m,O),q,U),ut&&_s(m,null,P,"created"),G(K,m,m.scopeId,q,P),rt){for(const Pt in rt)Pt!=="value"&&!wn(Pt)&&o(K,Pt,null,rt[Pt],O,P);"value"in rt&&o(K,"value",null,rt.value,O),(j=rt.onVnodeBeforeMount)&&Ee(j,P,m)}ut&&_s(m,null,P,"beforeMount");const vt=ch(S,nt);vt&&nt.beforeEnter(K),n(K,h,w),((j=rt&&rt.onVnodeMounted)||vt||ut)&&de(()=>{j&&Ee(j,P,m),vt&&nt.enter(K),ut&&_s(m,null,P,"mounted")},S)},G=(m,h,w,P,S)=>{if(w&&g(m,w),P)for(let O=0;O{for(let j=K;j{const U=h.el=m.el;let{patchFlag:K,dynamicChildren:j,dirs:rt}=h;K|=m.patchFlag&16;const Z=m.props||Et,nt=h.props||Et;let ut;if(w&&xs(w,!1),(ut=nt.onVnodeBeforeUpdate)&&Ee(ut,w,h,m),rt&&_s(h,m,w,"beforeUpdate"),w&&xs(w,!0),(Z.innerHTML&&nt.innerHTML==null||Z.textContent&&nt.textContent==null)&&u(U,""),j?A(m.dynamicChildren,j,U,w,P,Po(h,S),O):q||ct(m,h,U,null,w,P,Po(h,S),O,!1),K>0){if(K&16)k(U,Z,nt,w,S);else if(K&2&&Z.class!==nt.class&&o(U,"class",null,nt.class,S),K&4&&o(U,"style",Z.style,nt.style,S),K&8){const vt=h.dynamicProps;for(let Pt=0;Pt{ut&&Ee(ut,w,h,m),rt&&_s(h,m,w,"updated")},P)},A=(m,h,w,P,S,O,q)=>{for(let U=0;U{if(h!==w){if(h!==Et)for(const O in h)!wn(O)&&!(O in w)&&o(m,O,h[O],null,S,P);for(const O in w){if(wn(O))continue;const q=w[O],U=h[O];q!==U&&O!=="value"&&o(m,O,U,q,S,P)}"value"in w&&o(m,"value",h.value,w.value,S)}},T=(m,h,w,P,S,O,q,U,K)=>{const j=h.el=m?m.el:a(""),rt=h.anchor=m?m.anchor:a("");let{patchFlag:Z,dynamicChildren:nt,slotScopeIds:ut}=h;ut&&(U=U?U.concat(ut):ut),m==null?(n(j,w,P),n(rt,w,P),M(h.children||[],w,rt,S,O,q,U,K)):Z>0&&Z&64&&nt&&m.dynamicChildren?(A(m.dynamicChildren,nt,w,S,O,q,U),(h.key!=null||S&&h===S.subTree)&&_u(m,h,!0)):ct(m,h,w,rt,S,O,q,U,K)},z=(m,h,w,P,S,O,q,U,K)=>{h.slotScopeIds=U,m==null?h.shapeFlag&512?S.ctx.activate(h,w,P,q,K):at(h,w,P,S,O,q,K):tt(m,h,K)},at=(m,h,w,P,S,O,q)=>{const U=m.component=Mh(m,P,S);if(ro(m)&&(U.ctx.renderer=J),Eh(U,!1,q),U.asyncDep){if(S&&S.registerDep(U,et,q),!m.el){const K=U.subTree=bt(se);x(null,K,h,w),m.placeholder=K.el}}else et(U,m,h,w,S,O,q)},tt=(m,h,w)=>{const P=h.component=m.component;if(yh(m,h,w))if(P.asyncDep&&!P.asyncResolved){X(P,h,w);return}else P.next=h,P.update();else h.el=m.el,P.vnode=h},et=(m,h,w,P,S,O,q)=>{const U=()=>{if(m.isMounted){let{next:Z,bu:nt,u:ut,parent:vt,vnode:Pt}=m;{const Ie=xu(m);if(Ie){Z&&(Z.el=Pt.el,X(m,Z,q)),Ie.asyncDep.then(()=>{m.isUnmounted||U()});return}}let At=Z,ie;xs(m,!1),Z?(Z.el=Pt.el,X(m,Z,q)):Z=Pt,nt&&Si(nt),(ie=Z.props&&Z.props.onVnodeBeforeUpdate)&&Ee(ie,vt,Z,Pt),xs(m,!0);const oe=ba(m),Ae=m.subTree;m.subTree=oe,v(Ae,oe,d(Ae.el),D(Ae),m,S,O),Z.el=oe.el,At===null&&_h(m,oe.el),ut&&de(ut,S),(ie=Z.props&&Z.props.onVnodeUpdated)&&de(()=>Ee(ie,vt,Z,Pt),S)}else{let Z;const{el:nt,props:ut}=h,{bm:vt,m:Pt,parent:At,root:ie,type:oe}=m,Ae=An(h);xs(m,!1),vt&&Si(vt),!Ae&&(Z=ut&&ut.onVnodeBeforeMount)&&Ee(Z,At,h),xs(m,!0);{ie.ce&&ie.ce._def.shadowRoot!==!1&&ie.ce._injectChildStyle(oe);const Ie=m.subTree=ba(m);v(null,Ie,w,P,m,S,O),h.el=Ie.el}if(Pt&&de(Pt,S),!Ae&&(Z=ut&&ut.onVnodeMounted)){const Ie=h;de(()=>Ee(Z,At,Ie),S)}(h.shapeFlag&256||At&&An(At.vnode)&&At.vnode.shapeFlag&256)&&m.a&&de(m.a,S),m.isMounted=!0,h=w=P=null}};m.scope.on();const K=m.effect=new Ic(U);m.scope.off();const j=m.update=K.run.bind(K),rt=m.job=K.runIfDirty.bind(K);rt.i=m,rt.id=m.uid,K.scheduler=()=>Pr(rt),xs(m,!0),j()},X=(m,h,w)=>{h.component=m;const P=m.vnode.props;m.vnode=h,m.next=null,sh(m,h.props,P,w),rh(m,h.children,w),Je(),la(m),Qe()},ct=(m,h,w,P,S,O,q,U,K=!1)=>{const j=m&&m.children,rt=m?m.shapeFlag:0,Z=h.children,{patchFlag:nt,shapeFlag:ut}=h;if(nt>0){if(nt&128){N(j,Z,w,P,S,O,q,U,K);return}else if(nt&256){St(j,Z,w,P,S,O,q,U,K);return}}ut&8?(rt&16&&mt(j,S,O),Z!==j&&u(w,Z)):rt&16?ut&16?N(j,Z,w,P,S,O,q,U,K):mt(j,S,O,!0):(rt&8&&u(w,""),ut&16&&M(Z,w,P,S,O,q,U,K))},St=(m,h,w,P,S,O,q,U,K)=>{m=m||Us,h=h||Us;const j=m.length,rt=h.length,Z=Math.min(j,rt);let nt;for(nt=0;ntrt?mt(m,S,O,!0,!1,Z):M(h,w,P,S,O,q,U,K,Z)},N=(m,h,w,P,S,O,q,U,K)=>{let j=0;const rt=h.length;let Z=m.length-1,nt=rt-1;for(;j<=Z&&j<=nt;){const ut=m[j],vt=h[j]=K?ls(h[j]):$e(h[j]);if(Ms(ut,vt))v(ut,vt,w,null,S,O,q,U,K);else break;j++}for(;j<=Z&&j<=nt;){const ut=m[Z],vt=h[nt]=K?ls(h[nt]):$e(h[nt]);if(Ms(ut,vt))v(ut,vt,w,null,S,O,q,U,K);else break;Z--,nt--}if(j>Z){if(j<=nt){const ut=nt+1,vt=ut