Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion packages/api/alayaos_api/routers/health.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@
"""Health check endpoints."""

from functools import lru_cache
from typing import Annotated

from fastapi import APIRouter, Depends
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession

from alayaos_api.deps import get_session
from alayaos_core.config import Settings

router = APIRouter(tags=["health"])


@lru_cache
def get_settings() -> Settings:
return Settings()


@router.get("/health/live")
async def health_live():
return {"status": "ok"}


@router.get("/health/ready")
async def health_ready(session: Annotated[AsyncSession, Depends(get_session)]):
settings = get_settings()
checks = {}

# Database
Expand Down Expand Up @@ -57,4 +65,6 @@ async def health_ready(session: Annotated[AsyncSession, Depends(get_session)]):
ok_checks = [v for v in checks.values() if v != "unavailable"]
overall = "ok" if ok_checks and all(v == "ok" for v in ok_checks) else "degraded"

return {"status": overall, "checks": checks, "first_run": first_run}
if settings.HEALTH_READY_VERBOSE:
return {"status": overall, "checks": checks, "first_run": first_run}
return {"status": overall}
78 changes: 78 additions & 0 deletions packages/api/tests/test_routers_health.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
"""Tests for health endpoints."""

from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock

from fastapi import FastAPI
from fastapi.testclient import TestClient

from alayaos_api.routers import health
from alayaos_api.routers.health import router


def make_scalar_result(value):
result = MagicMock()
result.scalar_one.return_value = value
result.scalar_one_or_none.return_value = value
return result


def make_test_app():
app = FastAPI()

Expand Down Expand Up @@ -43,3 +52,72 @@ def test_health_live_no_auth_required() -> None:
client = TestClient(app)
response = client.get("/health/live")
assert response.status_code == 200


def test_health_ready_redacts_details_by_default(monkeypatch) -> None:
monkeypatch.delenv("ALAYA_HEALTH_READY_VERBOSE", raising=False)
health.get_settings.cache_clear()
app, session_mock = make_test_app()
session_mock.execute.side_effect = [
MagicMock(),
make_scalar_result("0004"),
make_scalar_result(1),
make_scalar_result(0),
]

client = TestClient(app)
response = client.get("/health/ready")

assert response.status_code == 200
assert response.json() == {"status": "ok"}


def test_health_ready_includes_checks_when_verbose(monkeypatch) -> None:
monkeypatch.setenv("ALAYA_HEALTH_READY_VERBOSE", "true")
health.get_settings.cache_clear()
app, session_mock = make_test_app()
session_mock.execute.side_effect = [
MagicMock(),
make_scalar_result("0004"),
make_scalar_result(1),
make_scalar_result(0),
]

client = TestClient(app)
response = client.get("/health/ready")

assert response.status_code == 200
body = response.json()
assert body["status"] == "ok"
assert body["checks"]["database"] == "ok"
assert body["checks"]["migrations"] == "ok"
assert body["checks"]["seeds"] == "ok"
assert body["first_run"] is True


def test_health_ready_uses_cached_settings(monkeypatch) -> None:
settings_factory = MagicMock(return_value=SimpleNamespace(HEALTH_READY_VERBOSE=False))
monkeypatch.setattr(health, "Settings", settings_factory)
health.get_settings.cache_clear()

app, session_mock = make_test_app()
session_mock.execute.side_effect = [
MagicMock(),
make_scalar_result("0004"),
make_scalar_result(1),
make_scalar_result(0),
MagicMock(),
make_scalar_result("0004"),
make_scalar_result(1),
make_scalar_result(0),
]

client = TestClient(app)
first = client.get("/health/ready")
second = client.get("/health/ready")

assert first.status_code == 200
assert second.status_code == 200
assert settings_factory.call_count == 1

health.get_settings.cache_clear()
57 changes: 42 additions & 15 deletions packages/cli-go/internal/cmd/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import (
)

var setupProfile string
var setupShowSecret bool

const storedKeyPlaceholder = "<stored in keyring; rerun with --show-secret to print>"

var setupCmd = &cobra.Command{
Use: "setup",
Expand Down Expand Up @@ -49,20 +52,10 @@ var setupAgentCmd = &cobra.Command{
fmt.Println("API key created and stored.")
apiKey = newKey
}
switch strings.ToLower(setupProfile) {
case "claude-code":
fmt.Printf("# Add to .claude/settings.json:\n")
fmt.Printf(`{"mcpServers":{"alaya":{"command":"alaya","args":["mcp"],"env":{"ALAYA_SERVER_URL":"%s","ALAYA_API_KEY":"%s"}}}}`, baseURL, apiKey)
fmt.Println()
case "codex":
fmt.Printf("export ALAYA_SERVER_URL=%s\nexport ALAYA_API_KEY=%s\n", baseURL, apiKey)
case "cursor":
fmt.Printf("# Add to .cursor/mcp.json:\n")
fmt.Printf(`{"mcpServers":{"alaya":{"command":"alaya","args":["mcp"],"env":{"ALAYA_SERVER_URL":"%s","ALAYA_API_KEY":"%s"}}}}`, baseURL, apiKey)
fmt.Println()
default:
fmt.Printf("ALAYA_SERVER_URL=%s\nALAYA_API_KEY=%s\nALAYA_API_BASE=%s/api/v1\n", baseURL, apiKey, baseURL)
if err := warnShowSecret(cmd, setupShowSecret); err != nil {
return err
}
fmt.Print(renderAgentSetup(setupProfile, baseURL, apiKey, setupShowSecret))
return nil
},
}
Expand All @@ -71,6 +64,33 @@ func init() {
rootCmd.AddCommand(setupCmd)
setupCmd.AddCommand(setupAgentCmd)
setupAgentCmd.Flags().StringVar(&setupProfile, "profile", "generic", "Agent profile (claude-code|codex|cursor|generic)")
setupAgentCmd.Flags().BoolVar(&setupShowSecret, "show-secret", false, "Print the API key in generated output")
}

func renderAgentSetup(profile, baseURL, apiKey string, showSecret bool) string {
renderedKey := storedKeyPlaceholder
if showSecret {
renderedKey = apiKey
}

switch strings.ToLower(profile) {
case "claude-code":
return fmt.Sprintf("# Add to .claude/settings.json:\n{\"mcpServers\":{\"alaya\":{\"command\":\"alaya\",\"args\":[\"mcp\"],\"env\":{\"ALAYA_SERVER_URL\":\"%s\",\"ALAYA_API_KEY\":\"%s\"}}}}\n", baseURL, renderedKey)
case "codex":
return fmt.Sprintf("export ALAYA_SERVER_URL=%s\nexport ALAYA_API_KEY=%s\n", baseURL, renderedKey)
case "cursor":
return fmt.Sprintf("# Add to .cursor/mcp.json:\n{\"mcpServers\":{\"alaya\":{\"command\":\"alaya\",\"args\":[\"mcp\"],\"env\":{\"ALAYA_SERVER_URL\":\"%s\",\"ALAYA_API_KEY\":\"%s\"}}}}\n", baseURL, renderedKey)
default:
return fmt.Sprintf("ALAYA_SERVER_URL=%s\nALAYA_API_KEY=%s\nALAYA_API_BASE=%s/api/v1\n", baseURL, renderedKey, baseURL)
}
}

func warnShowSecret(cmd *cobra.Command, showSecret bool) error {
if !showSecret {
return nil
}
_, err := fmt.Fprintln(cmd.ErrOrStderr(), "Warning: --show-secret prints the API key to stdout. Use only in a trusted terminal.")
return err
}

// createAPIKeyViaBootstrap calls POST /api-keys using the bootstrap key and returns the raw key.
Expand All @@ -84,13 +104,20 @@ func createAPIKeyViaBootstrap(baseURL, bootstrapKey string) (string, error) {
return "", err
}
var resp struct {
Data struct {
RawKey string `json:"raw_key"`
} `json:"data"`
RawKey string `json:"raw_key"`
}
if err := json.Unmarshal(data, &resp); err != nil {
return "", fmt.Errorf("parse key response: %w", err)
}
if resp.RawKey == "" {
rawKey := resp.Data.RawKey
if rawKey == "" {
rawKey = resp.RawKey
}
if rawKey == "" {
return "", fmt.Errorf("server did not return raw_key in response")
}
return resp.RawKey, nil
return rawKey, nil
}
61 changes: 58 additions & 3 deletions packages/cli-go/internal/cmd/setup_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package cmd

import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/spf13/cobra"
)

func TestCreateAPIKeyViaBootstrap_Success(t *testing.T) {
Expand All @@ -18,7 +22,9 @@ func TestCreateAPIKeyViaBootstrap_Success(t *testing.T) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{
"raw_key": "ak_newly_created_key",
"data": map[string]interface{}{
"raw_key": "ak_newly_created_key",
},
})
}))
defer ts.Close()
Expand Down Expand Up @@ -49,9 +55,10 @@ func TestCreateAPIKeyViaBootstrap_MissingRawKey(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
// Response missing raw_key field
json.NewEncoder(w).Encode(map[string]interface{}{
"id": "key-uuid",
"data": map[string]interface{}{
"id": "key-uuid",
},
})
}))
defer ts.Close()
Expand All @@ -61,3 +68,51 @@ func TestCreateAPIKeyViaBootstrap_MissingRawKey(t *testing.T) {
t.Fatal("expected error when raw_key is missing from response")
}
}

func TestRenderAgentSetup_RedactsSecretByDefault(t *testing.T) {
output := renderAgentSetup("generic", "https://alaya.example", "ak_super_secret", false)

if !strings.Contains(output, "ALAYA_SERVER_URL=https://alaya.example") {
t.Fatalf("expected server URL in output, got %q", output)
}
if !strings.Contains(output, "<stored in keyring; rerun with --show-secret to print>") {
t.Fatalf("expected redaction placeholder in output, got %q", output)
}
if strings.Contains(output, "ak_super_secret") {
t.Fatalf("expected secret to be redacted, got %q", output)
}
}

func TestRenderAgentSetup_IncludesSecretWhenOptedIn(t *testing.T) {
output := renderAgentSetup("codex", "https://alaya.example", "ak_super_secret", true)

if !strings.Contains(output, "ALAYA_API_KEY=ak_super_secret") {
t.Fatalf("expected secret in output, got %q", output)
}
}

func TestWarnShowSecret_PrintsToStderr(t *testing.T) {
cmd := &cobra.Command{}
var stderr bytes.Buffer
cmd.SetErr(&stderr)

if err := warnShowSecret(cmd, true); err != nil {
t.Fatalf("warnShowSecret() error: %v", err)
}
if !strings.Contains(stderr.String(), "--show-secret prints the API key to stdout") {
t.Fatalf("expected warning on stderr, got %q", stderr.String())
}
}

func TestWarnShowSecret_SkipsWhenDisabled(t *testing.T) {
cmd := &cobra.Command{}
var stderr bytes.Buffer
cmd.SetErr(&stderr)

if err := warnShowSecret(cmd, false); err != nil {
t.Fatalf("warnShowSecret() error: %v", err)
}
if stderr.Len() != 0 {
t.Fatalf("expected no stderr output, got %q", stderr.String())
}
}
1 change: 1 addition & 0 deletions packages/core/alayaos_core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class Settings(BaseSettings):
DB_POOL_TIMEOUT: int = 30
DB_ECHO: bool = False
LOG_LEVEL: str = "INFO"
HEALTH_READY_VERBOSE: bool = False

# LLM Provider
EXTRACTION_LLM_PROVIDER: str = "anthropic" # anthropic|openai|ollama|vllm
Expand Down
Loading