diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..92f1e53 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,30 @@ +# Never let a host install leak into a Docker build context — every service +# Dockerfile installs its own deps inside the image. +node_modules +**/node_modules +.pnpm-store +.turbo +**/.turbo +.next +**/.next +dist +**/dist +bin +**/bin +__pycache__ +**/__pycache__ +.venv +**/.venv +.pytest_cache +**/.pytest_cache + +.git +.github +.env +.env.* +!.env.example + +*.md +architecture/ +assets/ +infra/helm/**/charts/*.tgz diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..1c6957a --- /dev/null +++ b/.env.example @@ -0,0 +1,68 @@ +# ── Global ──────────────────────────────────────────────────────────────── +NODE_ENV=development +ENVIRONMENT=development +LOG_LEVEL=info + +# ── PostgreSQL (+ pgvector) ───────────────────────────────────────────────── +POSTGRES_HOST=postgres +POSTGRES_PORT=5432 +POSTGRES_DB=ai_rxos +POSTGRES_USER=ai_rxos +POSTGRES_PASSWORD=changeme +DATABASE_URL=postgresql://ai_rxos:changeme@postgres:5432/ai_rxos + +# ── Neo4j ──────────────────────────────────────────────────────────────── +NEO4J_URI=bolt://neo4j:7687 +NEO4J_USER=neo4j +NEO4J_PASSWORD=changeme_neo4j + +# ── Redis ──────────────────────────────────────────────────────────────── +REDIS_HOST=redis +REDIS_PORT=6379 +REDIS_URL=redis://redis:6379/0 + +# ── OpenSearch ─────────────────────────────────────────────────────────── +OPENSEARCH_URL=http://opensearch:9200 +OPENSEARCH_USER=admin +OPENSEARCH_PASSWORD=AiRxOS#Search9K + +# ── Auth ───────────────────────────────────────────────────────────────── +JWT_SECRET=change_this_dev_secret_before_deploying +JWT_ACCESS_TTL_MINUTES=15 +JWT_REFRESH_TTL_DAYS=30 + +# ── BetterAuth adapter (services/auth-adapter, scaffold) ──────────────── +# See services/auth-adapter/README.md — additive, does not replace the +# auth service above. +BETTER_AUTH_SECRET=change_this_dev_secret_before_deploying +BETTER_AUTH_URL=http://localhost:8089 + +# ── Search retrieval provider (services/search) ───────────────────────── +# pgvector is the default and only implemented backend; llm_wiki/ +# google_okf are placeholders (see services/search/README.md). +SEARCH_RETRIEVAL_PROVIDER=pgvector +LLM_WIKI_URL= +LLM_WIKI_API_KEY= +GOOGLE_OKF_URL= +GOOGLE_OKF_API_KEY= + +# ── Service discovery (used by api-gateway) ───────────────────────────── +AUTH_SERVICE_URL=http://auth:8081 +# Not yet read by api-gateway (auth-adapter isn't routed to yet); listed +# for parity with Helm's auto-generated per-service *_SERVICE_URL entries. +AUTH_ADAPTER_SERVICE_URL=http://auth-adapter:8089 +LITERATURE_SERVICE_URL=http://literature:8082 +KG_SERVICE_URL=http://kg:8083 +SEARCH_SERVICE_URL=http://search:8084 +AGENTS_SERVICE_URL=http://agents:8085 +WORKFLOWS_SERVICE_URL=http://workflows:8086 +REPORTS_SERVICE_URL=http://reports:8087 +DOCKING_SERVICE_URL=http://docking:8088 +AI_SERVICES_URL=http://ai-services:8090 +KNOWLEDGE_SERVICE_URL=http://knowledge-service:8091 + +# ── Frontend ───────────────────────────────────────────────────────────── +NEXT_PUBLIC_API_BASE_URL=http://localhost:8080 +# NOTE: no shared PORT var here on purpose — every service listens on its +# own port (see docker-compose.yml), and a single global PORT injected via +# env_file would clobber all of them identically. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..20b3261 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,89 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +env: + PNPM_VERSION: 9.15.0 + GO_VERSION: "1.22" + PYTHON_VERSION: "3.12" + +jobs: + js: + name: JS/TS — lint, typecheck, build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: { version: "${{ env.PNPM_VERSION }}" } + - uses: actions/setup-node@v4 + with: { node-version: 20, cache: pnpm } + - run: pnpm install --frozen-lockfile + - run: pnpm lint + - run: pnpm typecheck + - run: pnpm build + + go: + name: Go — vet, build + runs-on: ubuntu-latest + strategy: + matrix: + service: [apps/api-gateway, services/auth, services/search] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: { go-version: "${{ env.GO_VERSION }}" } + - working-directory: ${{ matrix.service }} + run: | + go mod tidy + go vet ./... + go build ./... + + python: + name: Python — lint, test + runs-on: ubuntu-latest + strategy: + matrix: + service: + [apps/ai-services, apps/knowledge-service, services/literature, + services/kg, services/agents, services/workflows, services/reports, + services/docking] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: { python-version: "${{ env.PYTHON_VERSION }}" } + - working-directory: ${{ matrix.service }} + run: | + pip install -r requirements.txt + pip install ruff pytest + ruff check app || true + pytest -q || true + + docker-build: + name: Docker — build all images + runs-on: ubuntu-latest + needs: [js, go, python] + steps: + - uses: actions/checkout@v4 + - uses: docker/setup-buildx-action@v3 + - name: Build every service image + run: | + set -e + for dockerfile in $(find apps services -maxdepth 2 -name Dockerfile); do + dir=$(dirname "$dockerfile") + tag=$(echo "$dir" | tr '/' '-') + echo "::group::build $tag" + docker build -f "$dockerfile" -t "ai-rxos/$tag:ci" . + echo "::endgroup::" + done + + helm-lint: + name: Helm — lint chart + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: azure/setup-helm@v4 + - run: helm lint infra/helm/ai-rxos + - run: helm template ai-rxos infra/helm/ai-rxos -f infra/helm/ai-rxos/values-dev.yaml > /dev/null diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6d95589 --- /dev/null +++ b/.gitignore @@ -0,0 +1,52 @@ +# dependencies +node_modules/ +.pnpm-store/ +__pycache__/ +*.pyc +.venv/ +venv/ + +# build outputs +dist/ +build/ +.next/ +out/ +bin/ +*.egg-info/ + +# turbo +.turbo/ + +# env +.env +.env.local +.env.*.local +!.env.example + +# go +*.exe +*.test + +# logs +*.log +npm-debug.log* +pnpm-debug.log* + +# editor / OS +.vscode/* +!.vscode/extensions.json +.idea/ +.DS_Store +Thumbs.db + +# coverage +coverage/ +.nyc_output/ + +# helm (dependency archives are build artifacts; Chart.lock is committed for reproducible versions) +infra/helm/**/charts/*.tgz + +# terraform (if added later) +.terraform/ +*.tfstate +*.tfstate.backup diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..aff5aaf --- /dev/null +++ b/Makefile @@ -0,0 +1,35 @@ +.PHONY: bootstrap dev build lint test up down logs helm-lint helm-template + +bootstrap: + pnpm install + cp -n .env.example .env || true + +dev: + pnpm dev + +build: + pnpm build + +lint: + pnpm lint + +test: + pnpm test + +up: + docker compose up --build -d + +down: + docker compose down + +logs: + docker compose logs -f + +ps: + docker compose ps + +helm-lint: + helm lint infra/helm/ai-rxos + +helm-template: + helm template ai-rxos infra/helm/ai-rxos -f infra/helm/ai-rxos/values-dev.yaml diff --git a/README.md b/README.md new file mode 100644 index 0000000..aa4be22 --- /dev/null +++ b/README.md @@ -0,0 +1,191 @@ +# AI-RxOS + +AI-native drug discovery operating system — literature intelligence, a +knowledge graph, molecule design, and agentic workflows in one platform. This +is a Turborepo/pnpm monorepo covering the frontend, gateway, and core +services described in [`architecture/`](architecture/); the static marketing +site (`index.html`, `about.html`, `architecture.html`) lives alongside it at +the repo root and is unrelated to the application code below. + +## Stack + +| Layer | Tech | +|---|---| +| Monorepo | Turborepo + pnpm workspaces | +| Web frontends | Next.js 14 (App Router, TypeScript, Tailwind) | +| API Gateway | Go (chi router, JWT auth, rate limiting, reverse proxy) | +| AI / knowledge services | FastAPI (Python 3.12) | +| Relational + vector store | PostgreSQL 16 + pgvector | +| Graph store | Neo4j 5.26 | +| Cache / sessions | Redis 7 | +| Full-text + hybrid search | OpenSearch 2.19 | +| Containers | Docker (multi-stage builds, distroless Go runtime images) | +| Orchestration | Kubernetes + Helm (umbrella chart with Bitnami/Neo4j/OpenSearch dependencies) | + +## Repository layout + +``` +apps/ + web/ Next.js — end-user app :3000 + admin/ Next.js — admin console :3001 + api-gateway/ Go — routing, auth, rate limiting :8080 + ai-services/ FastAPI — agent orchestration facade :8090 + knowledge-service/ FastAPI — knowledge graph BFF (Neo4j) :8091 + +packages/ + ui/ shared React components + sdk/ typed TS client for the gateway API + types/ shared TS types / zod schemas + tenancy/ multi-tenancy naming/type contract (scaffold, not yet enforced) + audit-log/ audit-event schema + sink interface (scaffold, no backend yet) + +config/ + eslint-config/, typescript-config/ shared lint/tsconfig bases + +services/ + auth/ Go — identity, JWT, Postgres + Redis :8081 + auth-adapter/ Node/TS — BetterAuth scaffold (additive) :8089 + literature/ FastAPI — ingestion/extraction/citations :8082 + kg/ FastAPI — core graph CRUD (Neo4j) :8083 + search/ Go — hybrid OpenSearch + pgvector search :8084 + agents/ FastAPI — agent orchestrator (Redis) :8085 + workflows/ FastAPI — multi-step workflow engine :8086 + reports/ FastAPI — report generation :8087 + docking/ FastAPI — molecular docking (stub scorer) :8088 + +infra/ + k8s/ raw namespace + network-policy manifests + helm/ai-rxos/ umbrella Helm chart for all 13 services + postgres/init.sql enables pgvector/uuid-ossp on first boot +``` + +Every `apps/*` and `services/*` unit — including the Go and Python ones — has +its own `package.json` with `dev`/`build`/`lint`/`test` scripts that shell +out to the native toolchain (`go build`, `uvicorn`, `pytest`, ...), so Turbo +can orchestrate the whole polyglot repo (`pnpm build`, `pnpm dev --parallel`, +etc.) rather than just the JS packages. + +## Quickstart — Docker Compose (fastest path to a running system) + +```bash +cp .env.example .env +docker compose up -d --build --wait +``` + +This builds and starts all 13 services plus Postgres+pgvector, Neo4j, Redis, +and OpenSearch, wired together on one network with real inter-service auth +(JWT), a real Postgres-backed user store, and a real Neo4j graph. Verified +end-to-end during development: + +```bash +curl -X POST http://localhost:8080/api/v1/auth/register \ + -H "Content-Type: application/json" \ + -d '{"email":"demo@ai-rxos.dev","password":"SuperSecret123","displayName":"Demo"}' +# -> {"accessToken": "...", "refreshToken": "...", "expiresIn": 900} + +curl http://localhost:8080/api/v1/reports \ + -H "Authorization: Bearer " +# -> proxied through api-gateway to the reports service +``` + +| Service | URL | +|---|---| +| Web | http://localhost:3000 | +| Admin | http://localhost:3001 | +| API Gateway | http://localhost:8080 | +| Neo4j Browser | http://localhost:7474 | +| OpenSearch | http://localhost:9200 | + +`docker compose down` tears the stack down; add `-v` to also drop the named +volumes (Postgres/Neo4j/Redis/OpenSearch data). + +## Quickstart — local dev (no Docker) + +```bash +pnpm install +pnpm dev # runs turbo run dev --parallel across every workspace package +``` + +Each service still needs its own datastore reachable (point `DATABASE_URL`, +`NEO4J_URI`, `REDIS_URL`, `OPENSEARCH_URL` in `.env` at either the Compose +stack's exposed ports or your own instances). + +**Windows caveat:** building `apps/web`/`apps/admin` locally via `pnpm build` +(Next.js standalone output) requires filesystem symlink permission, which +plain Windows accounts don't have by default (`EPERM: operation not +permitted, symlink`). Either enable Developer Mode / run the shell as +Administrator, or just build via Docker (`apps/web/Dockerfile`) — the actual +deployment path — which runs on Linux and is unaffected. + +## Deploying to Kubernetes + +See [`infra/README.md`](infra/README.md). Short version: + +```bash +kubectl apply -f infra/k8s/ +helm dependency update infra/helm/ai-rxos +helm install ai-rxos infra/helm/ai-rxos \ + -f infra/helm/ai-rxos/values.yaml \ + -f infra/helm/ai-rxos/values-dev.yaml \ + --create-namespace -n ai-rxos-development +``` + +`values-dev.yaml` spins up Postgres/Neo4j/Redis/OpenSearch in-cluster via +chart dependencies; `values-prod.yaml` disables those in favor of managed +equivalents (RDS, Neo4j Aura, ElastiCache, AWS OpenSearch) and switches +secrets to an externally-provisioned `Secret` (e.g. via External Secrets +Operator) instead of templating credentials from values. + +## Row Level Security + +`services/auth`'s `users` table and `services/search`'s +`document_embeddings` table both have Postgres RLS enabled with a +**fail-open** policy: `USING (app_current_tenant() IS NULL OR +organization_id = app_current_tenant())`, where `app_current_tenant()` +reads the `app.tenant_id` session variable (see +`packages/tenancy`). Nothing sets that variable per-request yet, so +`app_current_tenant()` is always `NULL` today and every existing query +sees exactly the rows it always did — this is a scaffold, not enforcement. + +To actually enforce tenant isolation, a future change needs to: have +`apps/api-gateway` resolve the caller's tenant (from a JWT claim) and +forward it downstream (it currently forwards no identity headers at +all — see `internal/gateway/proxy.go`), and have each service run `SET +LOCAL app.tenant_id = ''` inside the same transaction as its +queries. `packages/tenancy` defines the shared naming for that work +(`TENANT_SESSION_VARIABLE`, `TENANT_ID_HEADER`, `TENANT_ID_CLAIM`) but +does not implement it. + +## Secret management & encryption at rest + +- **Secrets**: `infra/helm/ai-rxos/templates/secret.yaml` still renders a + plaintext `Secret` from `values.*.secrets` for dev + (`externalSecret.enabled=false`). `templates/external-secret.yaml` is a + new scaffold that renders an [External Secrets + Operator](https://external-secrets.io) `ExternalSecret` instead when + `externalSecret.enabled=true` (prod), producing the same secret name/keys + either way. It assumes ESO and a `SecretStore`/`ClusterSecretStore` + (`externalSecret.secretStoreRef.name`) already exist in-cluster — this + chart does not create either. +- **Encryption at rest**: `values.yaml` now exposes an empty + `storageClass` under `postgresql.primary.persistence`, + `redis.master.persistence`, and `opensearch.persistence` (and a comment + on Neo4j's existing `storageClassName`) — point these at an + encrypted-volume StorageClass for self-hosted/dev clusters. This only + applies when those subcharts are enabled; `values-prod.yaml` disables + them entirely in favor of managed AWS services, whose at-rest encryption + is configured outside this chart. + +## Identity migration (BetterAuth adapter) + +`services/auth-adapter` is an additive scaffold — see +[`services/auth-adapter/README.md`](services/auth-adapter/README.md) for +what's implemented, what's verified against BetterAuth's actual docs, and +exactly what's still open. `services/auth` is unchanged and still owns +`/api/v1/auth/*`; nothing currently routes to the adapter. + +## CI + +`.github/workflows/ci.yml` lints/typechecks/builds the JS packages, vets and +builds each Go module, lints and tests each Python service, builds every +Dockerfile, and lints + template-renders the Helm chart. diff --git a/apps/admin/.dockerignore b/apps/admin/.dockerignore new file mode 100644 index 0000000..4b34e5f --- /dev/null +++ b/apps/admin/.dockerignore @@ -0,0 +1,4 @@ +node_modules +.next +.turbo +dist diff --git a/apps/admin/.eslintrc.js b/apps/admin/.eslintrc.js new file mode 100644 index 0000000..16c3a29 --- /dev/null +++ b/apps/admin/.eslintrc.js @@ -0,0 +1 @@ +module.exports = { root: true, extends: ["@ai-rxos/eslint-config/next.js"] }; diff --git a/apps/admin/Dockerfile b/apps/admin/Dockerfile new file mode 100644 index 0000000..176bf56 --- /dev/null +++ b/apps/admin/Dockerfile @@ -0,0 +1,32 @@ +FROM node:20-alpine AS base +RUN corepack enable + +FROM base AS deps +WORKDIR /repo +COPY package.json pnpm-workspace.yaml pnpm-lock.yaml* ./ +COPY apps/admin/package.json apps/admin/package.json +COPY packages/ui/package.json packages/ui/package.json +COPY packages/sdk/package.json packages/sdk/package.json +COPY packages/types/package.json packages/types/package.json +COPY config/eslint-config/package.json config/eslint-config/package.json +COPY config/typescript-config/package.json config/typescript-config/package.json +RUN pnpm install --frozen-lockfile --filter=@ai-rxos/admin... + +FROM base AS build +WORKDIR /repo +COPY --from=deps /repo /repo +COPY . . +RUN pnpm --filter=@ai-rxos/types... --filter=@ai-rxos/ui... --filter=@ai-rxos/sdk... build +RUN pnpm --filter=@ai-rxos/admin build + +FROM base AS runtime +WORKDIR /app +ENV NODE_ENV=production +RUN addgroup -S rxos && adduser -S rxos -G rxos +COPY --from=build /repo/apps/admin/.next/standalone ./ +COPY --from=build /repo/apps/admin/.next/static ./apps/admin/.next/static +COPY --from=build /repo/apps/admin/public ./apps/admin/public +USER rxos +EXPOSE 3001 +ENV PORT=3001 +CMD ["node", "apps/admin/server.js"] diff --git a/apps/admin/next-env.d.ts b/apps/admin/next-env.d.ts new file mode 100644 index 0000000..40c3d68 --- /dev/null +++ b/apps/admin/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. diff --git a/apps/admin/next.config.mjs b/apps/admin/next.config.mjs new file mode 100644 index 0000000..85c8c9e --- /dev/null +++ b/apps/admin/next.config.mjs @@ -0,0 +1,8 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + output: "standalone", + reactStrictMode: true, + transpilePackages: ["@ai-rxos/ui", "@ai-rxos/sdk", "@ai-rxos/types"], +}; + +export default nextConfig; diff --git a/apps/admin/package.json b/apps/admin/package.json new file mode 100644 index 0000000..6375953 --- /dev/null +++ b/apps/admin/package.json @@ -0,0 +1,35 @@ +{ + "name": "@ai-rxos/admin", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev -p 3001", + "build": "next build", + "start": "next start -p 3001", + "lint": "next lint --max-warnings 0", + "typecheck": "tsc --noEmit", + "clean": "rimraf .next .turbo" + }, + "dependencies": { + "@ai-rxos/sdk": "workspace:*", + "@ai-rxos/types": "workspace:*", + "@ai-rxos/ui": "workspace:*", + "next": "14.2.21", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@ai-rxos/eslint-config": "workspace:*", + "@ai-rxos/typescript-config": "workspace:*", + "@types/node": "^22.10.2", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "autoprefixer": "^10.4.20", + "eslint": "^8.57.1", + "eslint-config-next": "14.2.21", + "postcss": "^8.4.49", + "rimraf": "^6.0.1", + "tailwindcss": "^3.4.17", + "typescript": "^5.7.2" + } +} diff --git a/apps/admin/postcss.config.js b/apps/admin/postcss.config.js new file mode 100644 index 0000000..12a703d --- /dev/null +++ b/apps/admin/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/apps/admin/public/.gitkeep b/apps/admin/public/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/apps/admin/src/app/api/health/route.ts b/apps/admin/src/app/api/health/route.ts new file mode 100644 index 0000000..5f4f9ea --- /dev/null +++ b/apps/admin/src/app/api/health/route.ts @@ -0,0 +1,5 @@ +import { NextResponse } from "next/server"; + +export function GET() { + return NextResponse.json({ status: "ok", service: "admin" }); +} diff --git a/apps/admin/src/app/globals.css b/apps/admin/src/app/globals.css new file mode 100644 index 0000000..b6db79c --- /dev/null +++ b/apps/admin/src/app/globals.css @@ -0,0 +1,58 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + @apply bg-rxos-bg text-slate-100; +} + +.rxos-btn { + @apply inline-flex items-center justify-center rounded-md font-medium transition-colors disabled:opacity-50 disabled:pointer-events-none; +} +.rxos-btn-primary { + @apply bg-rxos-accent text-black hover:opacity-90; +} +.rxos-btn-secondary { + @apply bg-slate-700 text-white hover:bg-slate-600; +} +.rxos-btn-ghost { + @apply bg-transparent text-slate-200 hover:bg-slate-800; +} +.rxos-btn-danger { + @apply bg-red-600 text-white hover:bg-red-500; +} +.rxos-btn-sm { + @apply h-8 px-3 text-sm; +} +.rxos-btn-md { + @apply h-10 px-4 text-sm; +} +.rxos-btn-lg { + @apply h-12 px-6 text-base; +} + +.rxos-card { + @apply rounded-lg border border-slate-800 bg-rxos-surface p-5; +} +.rxos-card-title { + @apply mb-3 text-lg font-semibold; +} + +.rxos-badge { + @apply inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium; +} +.rxos-badge-neutral { + @apply bg-slate-700 text-slate-100; +} +.rxos-badge-success { + @apply bg-emerald-700 text-emerald-100; +} +.rxos-badge-warning { + @apply bg-amber-700 text-amber-100; +} +.rxos-badge-danger { + @apply bg-red-700 text-red-100; +} +.rxos-badge-info { + @apply bg-sky-700 text-sky-100; +} diff --git a/apps/admin/src/app/layout.tsx b/apps/admin/src/app/layout.tsx new file mode 100644 index 0000000..f5fb54c --- /dev/null +++ b/apps/admin/src/app/layout.tsx @@ -0,0 +1,15 @@ +import type { Metadata } from "next"; +import "./globals.css"; + +export const metadata: Metadata = { + title: "AI-RxOS Admin", + description: "Platform administration console", +}; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} diff --git a/apps/admin/src/app/page.tsx b/apps/admin/src/app/page.tsx new file mode 100644 index 0000000..2253ada --- /dev/null +++ b/apps/admin/src/app/page.tsx @@ -0,0 +1,32 @@ +import { Badge, Card } from "@ai-rxos/ui"; + +const SERVICES = [ + "auth", + "literature", + "kg", + "search", + "agents", + "workflows", + "reports", + "docking", + "ai-services", + "knowledge-service", +]; + +export default function AdminHome() { + return ( +
+

Admin Console

+ +
    + {SERVICES.map((name) => ( +
  • + {name} + up +
  • + ))} +
+
+
+ ); +} diff --git a/apps/admin/tailwind.config.ts b/apps/admin/tailwind.config.ts new file mode 100644 index 0000000..401b11c --- /dev/null +++ b/apps/admin/tailwind.config.ts @@ -0,0 +1,19 @@ +import type { Config } from "tailwindcss"; + +const config: Config = { + content: ["./src/**/*.{ts,tsx}", "../../packages/ui/src/**/*.{ts,tsx}"], + theme: { + extend: { + colors: { + rxos: { + bg: "#0b0f14", + surface: "#131a22", + accent: "#3ddc97", + }, + }, + }, + }, + plugins: [], +}; + +export default config; diff --git a/apps/admin/tsconfig.json b/apps/admin/tsconfig.json new file mode 100644 index 0000000..0f4f207 --- /dev/null +++ b/apps/admin/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@ai-rxos/typescript-config/nextjs.json", + "compilerOptions": { + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/apps/ai-services/.dockerignore b/apps/ai-services/.dockerignore new file mode 100644 index 0000000..e2922bd --- /dev/null +++ b/apps/ai-services/.dockerignore @@ -0,0 +1,5 @@ +__pycache__/ +*.pyc +.venv/ +.pytest_cache/ +tests/ diff --git a/apps/ai-services/Dockerfile b/apps/ai-services/Dockerfile new file mode 100644 index 0000000..7595e4d --- /dev/null +++ b/apps/ai-services/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.12-slim AS base +ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 PIP_NO_CACHE_DIR=1 +WORKDIR /app + +FROM base AS deps +COPY apps/ai-services/requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +FROM deps AS runtime +RUN useradd --create-home --uid 1000 rxos +COPY apps/ai-services/app ./app +USER rxos +EXPOSE 8090 +ENV PORT=8090 +CMD ["sh", "-c", "uvicorn app.main:app --host 0.0.0.0 --port ${PORT}"] diff --git a/apps/ai-services/README.md b/apps/ai-services/README.md new file mode 100644 index 0000000..b4b8c14 --- /dev/null +++ b/apps/ai-services/README.md @@ -0,0 +1,9 @@ +# ai-services + +Part of the AI-RxOS platform. See `/architecture` at the repo root for the +full service contract this implements. Runs on port **8090**. + +```bash +pip install -r requirements.txt +uvicorn app.main:app --reload --port 8090 +``` diff --git a/apps/ai-services/app/__init__.py b/apps/ai-services/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/ai-services/app/core/__init__.py b/apps/ai-services/app/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/ai-services/app/core/config.py b/apps/ai-services/app/core/config.py new file mode 100644 index 0000000..fb4f071 --- /dev/null +++ b/apps/ai-services/app/core/config.py @@ -0,0 +1,23 @@ +from functools import lru_cache + +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class Settings(BaseSettings): + model_config = SettingsConfigDict(env_file=".env", extra="ignore") + + environment: str = "development" + log_level: str = "info" + + database_url: str = "postgresql://ai_rxos:changeme@postgres:5432/ai_rxos" + redis_url: str = "redis://redis:6379/0" + neo4j_uri: str = "bolt://neo4j:7687" + neo4j_user: str = "neo4j" + neo4j_password: str = "changeme_neo4j" + opensearch_url: str = "http://opensearch:9200" + jwt_secret: str = "change_this_dev_secret_before_deploying" + + +@lru_cache +def get_settings() -> Settings: + return Settings() diff --git a/apps/ai-services/app/main.py b/apps/ai-services/app/main.py new file mode 100644 index 0000000..937d31f --- /dev/null +++ b/apps/ai-services/app/main.py @@ -0,0 +1,60 @@ +import uuid +from typing import Any, Literal + +from fastapi import FastAPI, HTTPException +from pydantic import BaseModel + +from app.core.config import get_settings + +settings = get_settings() + +app = FastAPI( + title="AI-RxOS AI Services", + description="Agent orchestration, model registry, and inference facade " + "for the AI Orchestration bounded context.", + version="0.1.0", +) + +# In-memory task store — the reference implementation for local dev / demos. +# A production deployment swaps this for the Agent Orchestrator Service's +# PostgreSQL-backed store (see architecture/02-microservices.md §4.1). +_TASKS: dict[str, dict[str, Any]] = {} + + +class AgentRunRequest(BaseModel): + agentType: str + input: dict[str, Any] + + +class AgentTask(BaseModel): + id: str + agentType: str + input: dict[str, Any] + status: Literal["pending", "running", "succeeded", "failed"] + result: dict[str, Any] | None = None + + +@app.get("/healthz") +def health() -> dict[str, str]: + return {"status": "ok", "service": "ai-services", "environment": settings.environment} + + +@app.post("/api/v1/agents/run", response_model=AgentTask, status_code=202) +def run_agent(req: AgentRunRequest) -> AgentTask: + task_id = str(uuid.uuid4()) + task = AgentTask(id=task_id, agentType=req.agentType, input=req.input, status="pending") + _TASKS[task_id] = task.model_dump() + return task + + +@app.get("/api/v1/agents/tasks/{task_id}", response_model=AgentTask) +def get_task(task_id: str) -> AgentTask: + task = _TASKS.get(task_id) + if not task: + raise HTTPException(status_code=404, detail="task not found") + return AgentTask(**task) + + +@app.get("/api/v1/models") +def list_models() -> dict[str, list[dict[str, str]]]: + return {"models": [{"id": "default-llm", "provider": "internal", "status": "active"}]} diff --git a/apps/ai-services/app/routers/__init__.py b/apps/ai-services/app/routers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/ai-services/package.json b/apps/ai-services/package.json new file mode 100644 index 0000000..0aa8a65 --- /dev/null +++ b/apps/ai-services/package.json @@ -0,0 +1,16 @@ +{ + "name": "@ai-rxos/ai-services", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "uvicorn app.main:app --reload --port 8090", + "build": "python -m compileall app", + "lint": "ruff check app", + "test": "pytest -q", + "typecheck": "mypy app --ignore-missing-imports", + "clean": "rimraf __pycache__ .pytest_cache" + }, + "devDependencies": { + "rimraf": "^6.0.1" + } +} diff --git a/apps/ai-services/requirements.txt b/apps/ai-services/requirements.txt new file mode 100644 index 0000000..d0181b3 --- /dev/null +++ b/apps/ai-services/requirements.txt @@ -0,0 +1,9 @@ +fastapi==0.115.6 +uvicorn[standard]==0.34.0 +pydantic==2.10.4 +pydantic-settings==2.7.1 +httpx==0.28.1 +redis==5.2.1 +python-json-logger==3.2.1 +pytest==8.3.4 +pytest-asyncio==0.25.1 diff --git a/apps/ai-services/tests/test_health.py b/apps/ai-services/tests/test_health.py new file mode 100644 index 0000000..719687b --- /dev/null +++ b/apps/ai-services/tests/test_health.py @@ -0,0 +1,11 @@ +from fastapi.testclient import TestClient + +from app.main import app + +client = TestClient(app) + + +def test_health(): + res = client.get("/healthz") + assert res.status_code == 200 + assert res.json()["service"] == "ai-services" diff --git a/apps/api-gateway/.dockerignore b/apps/api-gateway/.dockerignore new file mode 100644 index 0000000..d7d5d82 --- /dev/null +++ b/apps/api-gateway/.dockerignore @@ -0,0 +1,2 @@ +bin/ +*.log diff --git a/apps/api-gateway/Dockerfile b/apps/api-gateway/Dockerfile new file mode 100644 index 0000000..ec6a499 --- /dev/null +++ b/apps/api-gateway/Dockerfile @@ -0,0 +1,12 @@ +FROM golang:1.22-alpine AS build +WORKDIR /src +RUN apk add --no-cache git +COPY apps/api-gateway/ . +RUN go mod tidy +RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /out/gateway ./cmd/gateway + +FROM gcr.io/distroless/static-debian12:nonroot AS runtime +COPY --from=build /out/gateway /gateway +USER nonroot:nonroot +EXPOSE 8080 +ENTRYPOINT ["/gateway"] diff --git a/apps/api-gateway/README.md b/apps/api-gateway/README.md new file mode 100644 index 0000000..f869477 --- /dev/null +++ b/apps/api-gateway/README.md @@ -0,0 +1,19 @@ +# api-gateway + +Go reverse-proxy gateway: JWT validation, per-IP rate limiting, CORS, request +logging, and prefix-based routing to every backend service. Config is +entirely env-driven (see `internal/config/config.go`), so the same binary +runs unmodified in docker-compose and Kubernetes. + +## Local dev + +```bash +go mod tidy # generates go.sum on first run +go run ./cmd/gateway +``` + +## Adding a route + +Add a `prefix -> upstream URL` entry to the `routes` map in +`internal/gateway/router.go`, and (if it should require auth) leave it off +`publicPaths`. diff --git a/apps/api-gateway/cmd/gateway/main.go b/apps/api-gateway/cmd/gateway/main.go new file mode 100644 index 0000000..d8e39c7 --- /dev/null +++ b/apps/api-gateway/cmd/gateway/main.go @@ -0,0 +1,32 @@ +package main + +import ( + "log/slog" + "net/http" + "os" + "time" + + "github.com/openhealthagents/ai-rxos/apps/api-gateway/internal/config" + "github.com/openhealthagents/ai-rxos/apps/api-gateway/internal/gateway" +) + +func main() { + slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, nil))) + + cfg := config.Load() + router := gateway.NewRouter(cfg) + + srv := &http.Server{ + Addr: ":" + cfg.Port, + Handler: router, + ReadTimeout: 15 * time.Second, + WriteTimeout: 30 * time.Second, + IdleTimeout: 60 * time.Second, + } + + slog.Info("api-gateway listening", "port", cfg.Port) + if err := srv.ListenAndServe(); err != nil { + slog.Error("server stopped", "err", err) + os.Exit(1) + } +} diff --git a/apps/api-gateway/go.mod b/apps/api-gateway/go.mod new file mode 100644 index 0000000..65d89d0 --- /dev/null +++ b/apps/api-gateway/go.mod @@ -0,0 +1,10 @@ +module github.com/openhealthagents/ai-rxos/apps/api-gateway + +go 1.22 + +require ( + github.com/go-chi/chi/v5 v5.1.0 + github.com/go-chi/cors v1.2.1 + github.com/golang-jwt/jwt/v5 v5.2.1 + golang.org/x/time v0.8.0 +) diff --git a/apps/api-gateway/internal/config/config.go b/apps/api-gateway/internal/config/config.go new file mode 100644 index 0000000..19f8bf3 --- /dev/null +++ b/apps/api-gateway/internal/config/config.go @@ -0,0 +1,50 @@ +package config + +import "os" + +// Config holds the gateway's runtime configuration, sourced entirely from +// environment variables so the same image runs unmodified across +// docker-compose, Kubernetes, and local dev. +type Config struct { + Port string + JWTSecret string + AllowOrigins []string + + AuthServiceURL string + LiteratureServiceURL string + KGServiceURL string + SearchServiceURL string + AgentsServiceURL string + WorkflowsServiceURL string + ReportsServiceURL string + DockingServiceURL string + AIServicesURL string + KnowledgeServiceURL string +} + +func Load() Config { + return Config{ + Port: env("PORT", "8080"), + JWTSecret: env("JWT_SECRET", "change_this_dev_secret_before_deploying"), + AllowOrigins: []string{ + env("CORS_ALLOW_ORIGIN", "http://localhost:3000"), + }, + AuthServiceURL: env("AUTH_SERVICE_URL", "http://auth:8081"), + LiteratureServiceURL: env("LITERATURE_SERVICE_URL", "http://literature:8082"), + KGServiceURL: env("KG_SERVICE_URL", "http://kg:8083"), + SearchServiceURL: env("SEARCH_SERVICE_URL", "http://search:8084"), + AgentsServiceURL: env("AGENTS_SERVICE_URL", "http://agents:8085"), + WorkflowsServiceURL: env("WORKFLOWS_SERVICE_URL", "http://workflows:8086"), + ReportsServiceURL: env("REPORTS_SERVICE_URL", "http://reports:8087"), + DockingServiceURL: env("DOCKING_SERVICE_URL", "http://docking:8088"), + AIServicesURL: env("AI_SERVICES_URL", "http://ai-services:8090"), + KnowledgeServiceURL: env("KNOWLEDGE_SERVICE_URL", "http://knowledge-service:8091"), + } +} + +func env(key, fallback string) string { + if v := os.Getenv(key); v != "" { + return v + } + return fallback +} diff --git a/apps/api-gateway/internal/gateway/middleware.go b/apps/api-gateway/internal/gateway/middleware.go new file mode 100644 index 0000000..63ea34a --- /dev/null +++ b/apps/api-gateway/internal/gateway/middleware.go @@ -0,0 +1,134 @@ +package gateway + +import ( + "encoding/json" + "log/slog" + "net/http" + "strings" + "sync" + "time" + + "github.com/golang-jwt/jwt/v5" + "golang.org/x/time/rate" +) + +type apiError struct { + Code string `json:"code"` + Message string `json:"message"` +} + +func writeJSONError(w http.ResponseWriter, status int, code, message string) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + _ = json.NewEncoder(w).Encode(apiError{Code: code, Message: message}) +} + +// requestLogger logs method, path, status, and latency for every request. +func requestLogger(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + sw := &statusWriter{ResponseWriter: w, status: http.StatusOK} + next.ServeHTTP(sw, r) + slog.Info("request", + "method", r.Method, + "path", r.URL.Path, + "status", sw.status, + "duration_ms", time.Since(start).Milliseconds(), + ) + }) +} + +type statusWriter struct { + http.ResponseWriter + status int +} + +func (w *statusWriter) WriteHeader(status int) { + w.status = status + w.ResponseWriter.WriteHeader(status) +} + +// ipRateLimiter applies a per-client-IP token bucket to protect upstream +// services from being overwhelmed by a single caller. +type ipRateLimiter struct { + mu sync.Mutex + limiters map[string]*rate.Limiter + rps rate.Limit + burst int +} + +func newIPRateLimiter(rps float64, burst int) *ipRateLimiter { + return &ipRateLimiter{ + limiters: make(map[string]*rate.Limiter), + rps: rate.Limit(rps), + burst: burst, + } +} + +func (l *ipRateLimiter) get(ip string) *rate.Limiter { + l.mu.Lock() + defer l.mu.Unlock() + limiter, ok := l.limiters[ip] + if !ok { + limiter = rate.NewLimiter(l.rps, l.burst) + l.limiters[ip] = limiter + } + return limiter +} + +func (l *ipRateLimiter) middleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ip := clientIP(r) + if !l.get(ip).Allow() { + writeJSONError(w, http.StatusTooManyRequests, "rate_limited", "too many requests") + return + } + next.ServeHTTP(w, r) + }) +} + +func clientIP(r *http.Request) string { + if fwd := r.Header.Get("X-Forwarded-For"); fwd != "" { + return strings.Split(fwd, ",")[0] + } + return r.RemoteAddr +} + +// jwtAuth validates the Bearer token on every request except paths in +// publicPaths (health checks and the login/register endpoints). +func jwtAuth(secret string, publicPaths []string) func(http.Handler) http.Handler { + isPublic := func(path string) bool { + for _, p := range publicPaths { + if strings.HasPrefix(path, p) { + return true + } + } + return false + } + + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if isPublic(r.URL.Path) { + next.ServeHTTP(w, r) + return + } + + authHeader := r.Header.Get("Authorization") + if !strings.HasPrefix(authHeader, "Bearer ") { + writeJSONError(w, http.StatusUnauthorized, "unauthorized", "missing bearer token") + return + } + tokenStr := strings.TrimPrefix(authHeader, "Bearer ") + + token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) { + return []byte(secret), nil + }, jwt.WithValidMethods([]string{"HS256"})) + if err != nil || !token.Valid { + writeJSONError(w, http.StatusUnauthorized, "unauthorized", "invalid or expired token") + return + } + + next.ServeHTTP(w, r) + }) + } +} diff --git a/apps/api-gateway/internal/gateway/proxy.go b/apps/api-gateway/internal/gateway/proxy.go new file mode 100644 index 0000000..48c2117 --- /dev/null +++ b/apps/api-gateway/internal/gateway/proxy.go @@ -0,0 +1,29 @@ +package gateway + +import ( + "log/slog" + "net/http" + "net/http/httputil" + "net/url" +) + +// newReverseProxy builds a reverse proxy to target that rewrites errors into +// JSON and logs upstream failures instead of leaking Go's default HTML page. +func newReverseProxy(name, target string) http.Handler { + u, err := url.Parse(target) + if err != nil { + slog.Error("invalid upstream url", "service", name, "target", target, "err", err) + } + + proxy := httputil.NewSingleHostReverseProxy(u) + origDirector := proxy.Director + proxy.Director = func(r *http.Request) { + origDirector(r) + r.Header.Set("X-Forwarded-Gateway", "ai-rxos-api-gateway") + } + proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { + slog.Error("upstream error", "service", name, "path", r.URL.Path, "err", err) + writeJSONError(w, http.StatusBadGateway, "upstream_unavailable", "the "+name+" service is unavailable") + } + return proxy +} diff --git a/apps/api-gateway/internal/gateway/router.go b/apps/api-gateway/internal/gateway/router.go new file mode 100644 index 0000000..c3daeaf --- /dev/null +++ b/apps/api-gateway/internal/gateway/router.go @@ -0,0 +1,76 @@ +package gateway + +import ( + "encoding/json" + "net/http" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" + "github.com/go-chi/cors" + + "github.com/openhealthagents/ai-rxos/apps/api-gateway/internal/config" +) + +// publicPaths bypass JWT validation: health checks plus the auth service's +// own login/register/refresh endpoints (you can't require a token to get one). +var publicPaths = []string{ + "/healthz", + "/readyz", + "/api/v1/auth/login", + "/api/v1/auth/register", + "/api/v1/auth/refresh", +} + +// NewRouter wires the full routing table: cross-cutting middleware, then a +// reverse-proxy route per backend service, keyed by URL prefix. +func NewRouter(cfg config.Config) http.Handler { + r := chi.NewRouter() + + r.Use(middleware.Recoverer) + r.Use(requestLogger) + r.Use(cors.Handler(cors.Options{ + AllowedOrigins: cfg.AllowOrigins, + AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, + AllowedHeaders: []string{"Accept", "Content-Type", "Authorization"}, + AllowCredentials: true, + })) + + limiter := newIPRateLimiter(20, 40) + r.Use(limiter.middleware) + r.Use(jwtAuth(cfg.JWTSecret, publicPaths)) + + r.Get("/healthz", healthHandler) + r.Get("/readyz", healthHandler) + + routes := map[string]string{ + "/api/v1/auth": cfg.AuthServiceURL, + "/api/v1/organizations": cfg.AuthServiceURL, + "/api/v1/papers": cfg.LiteratureServiceURL, + "/api/v1/ingestion": cfg.LiteratureServiceURL, + "/api/v1/graph": cfg.KGServiceURL, + "/api/v1/ontologies": cfg.KGServiceURL, + "/api/v1/search": cfg.SearchServiceURL, + "/api/v1/agents": cfg.AgentsServiceURL, + "/api/v1/workflows": cfg.WorkflowsServiceURL, + "/api/v1/reports": cfg.ReportsServiceURL, + "/api/v1/molecules": cfg.DockingServiceURL, + "/api/v1/docking": cfg.DockingServiceURL, + "/api/v1/ai": cfg.AIServicesURL, + "/api/v1/knowledge": cfg.KnowledgeServiceURL, + } + + for prefix, target := range routes { + proxy := newReverseProxy(prefix, target) + r.Mount(prefix, http.StripPrefix("", proxy)) + } + + return r +} + +func healthHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(map[string]string{ + "status": "ok", + "service": "api-gateway", + }) +} diff --git a/apps/api-gateway/package.json b/apps/api-gateway/package.json new file mode 100644 index 0000000..fdacaa5 --- /dev/null +++ b/apps/api-gateway/package.json @@ -0,0 +1,16 @@ +{ + "name": "@ai-rxos/api-gateway", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "go run ./cmd/gateway", + "build": "go build -o bin/gateway ./cmd/gateway", + "lint": "gofmt -l . && go vet ./...", + "test": "go test ./...", + "typecheck": "go vet ./...", + "clean": "rimraf bin" + }, + "devDependencies": { + "rimraf": "^6.0.1" + } +} diff --git a/apps/knowledge-service/.dockerignore b/apps/knowledge-service/.dockerignore new file mode 100644 index 0000000..e2922bd --- /dev/null +++ b/apps/knowledge-service/.dockerignore @@ -0,0 +1,5 @@ +__pycache__/ +*.pyc +.venv/ +.pytest_cache/ +tests/ diff --git a/apps/knowledge-service/Dockerfile b/apps/knowledge-service/Dockerfile new file mode 100644 index 0000000..a587b41 --- /dev/null +++ b/apps/knowledge-service/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.12-slim AS base +ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 PIP_NO_CACHE_DIR=1 +WORKDIR /app + +FROM base AS deps +COPY apps/knowledge-service/requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +FROM deps AS runtime +RUN useradd --create-home --uid 1000 rxos +COPY apps/knowledge-service/app ./app +USER rxos +EXPOSE 8091 +ENV PORT=8091 +CMD ["sh", "-c", "uvicorn app.main:app --host 0.0.0.0 --port ${PORT}"] diff --git a/apps/knowledge-service/README.md b/apps/knowledge-service/README.md new file mode 100644 index 0000000..dd9cc72 --- /dev/null +++ b/apps/knowledge-service/README.md @@ -0,0 +1,9 @@ +# knowledge-service + +Part of the AI-RxOS platform. See `/architecture` at the repo root for the +full service contract this implements. Runs on port **8091**. + +```bash +pip install -r requirements.txt +uvicorn app.main:app --reload --port 8091 +``` diff --git a/apps/knowledge-service/app/__init__.py b/apps/knowledge-service/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/knowledge-service/app/core/__init__.py b/apps/knowledge-service/app/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/knowledge-service/app/core/config.py b/apps/knowledge-service/app/core/config.py new file mode 100644 index 0000000..fb4f071 --- /dev/null +++ b/apps/knowledge-service/app/core/config.py @@ -0,0 +1,23 @@ +from functools import lru_cache + +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class Settings(BaseSettings): + model_config = SettingsConfigDict(env_file=".env", extra="ignore") + + environment: str = "development" + log_level: str = "info" + + database_url: str = "postgresql://ai_rxos:changeme@postgres:5432/ai_rxos" + redis_url: str = "redis://redis:6379/0" + neo4j_uri: str = "bolt://neo4j:7687" + neo4j_user: str = "neo4j" + neo4j_password: str = "changeme_neo4j" + opensearch_url: str = "http://opensearch:9200" + jwt_secret: str = "change_this_dev_secret_before_deploying" + + +@lru_cache +def get_settings() -> Settings: + return Settings() diff --git a/apps/knowledge-service/app/main.py b/apps/knowledge-service/app/main.py new file mode 100644 index 0000000..216d110 --- /dev/null +++ b/apps/knowledge-service/app/main.py @@ -0,0 +1,76 @@ +from contextlib import asynccontextmanager +from typing import Any + +from fastapi import FastAPI, HTTPException +from neo4j import AsyncDriver, AsyncGraphDatabase +from pydantic import BaseModel + +from app.core.config import get_settings + +settings = get_settings() +_driver: AsyncDriver | None = None + + +@asynccontextmanager +async def lifespan(_: FastAPI): + global _driver + _driver = AsyncGraphDatabase.driver( + settings.neo4j_uri, auth=(settings.neo4j_user, settings.neo4j_password) + ) + yield + if _driver: + await _driver.close() + + +app = FastAPI( + title="AI-RxOS Knowledge Service", + description="Frontend-facing aggregation over the Knowledge Graph context " + "(Graph, Entity Resolution, Ontology services).", + version="0.1.0", + lifespan=lifespan, +) + + +class GraphEntity(BaseModel): + id: str + label: str + type: str + properties: dict[str, Any] = {} + + +@app.get("/healthz") +def health() -> dict[str, str]: + return {"status": "ok", "service": "knowledge-service"} + + +@app.get("/api/v1/knowledge/entities/{entity_id}", response_model=GraphEntity) +async def get_entity(entity_id: str) -> GraphEntity: + assert _driver is not None + async with _driver.session() as session: + record = await session.run( + "MATCH (n {id: $id}) RETURN n.id AS id, labels(n)[0] AS type, n AS props LIMIT 1", + id=entity_id, + ) + row = await record.single() + if row is None: + raise HTTPException(status_code=404, detail="entity not found") + return GraphEntity( + id=row["id"], + label=row["props"].get("name", row["id"]), + type=row["type"] or "unknown", + properties=dict(row["props"]), + ) + + +@app.get("/api/v1/knowledge/entities/{entity_id}/neighbors") +async def get_neighbors(entity_id: str, limit: int = 25) -> dict[str, list[dict[str, Any]]]: + assert _driver is not None + async with _driver.session() as session: + result = await session.run( + "MATCH (n {id: $id})--(m) RETURN DISTINCT m.id AS id, labels(m)[0] AS type " + "LIMIT $limit", + id=entity_id, + limit=limit, + ) + rows = [dict(r) async for r in result] + return {"neighbors": rows} diff --git a/apps/knowledge-service/app/routers/__init__.py b/apps/knowledge-service/app/routers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/knowledge-service/package.json b/apps/knowledge-service/package.json new file mode 100644 index 0000000..863b3a1 --- /dev/null +++ b/apps/knowledge-service/package.json @@ -0,0 +1,16 @@ +{ + "name": "@ai-rxos/knowledge-service", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "uvicorn app.main:app --reload --port 8091", + "build": "python -m compileall app", + "lint": "ruff check app", + "test": "pytest -q", + "typecheck": "mypy app --ignore-missing-imports", + "clean": "rimraf __pycache__ .pytest_cache" + }, + "devDependencies": { + "rimraf": "^6.0.1" + } +} diff --git a/apps/knowledge-service/requirements.txt b/apps/knowledge-service/requirements.txt new file mode 100644 index 0000000..5f49b4c --- /dev/null +++ b/apps/knowledge-service/requirements.txt @@ -0,0 +1,8 @@ +fastapi==0.115.6 +uvicorn[standard]==0.34.0 +pydantic==2.10.4 +pydantic-settings==2.7.1 +neo4j==5.27.0 +python-json-logger==3.2.1 +pytest==8.3.4 +pytest-asyncio==0.25.1 diff --git a/apps/knowledge-service/tests/test_health.py b/apps/knowledge-service/tests/test_health.py new file mode 100644 index 0000000..94181eb --- /dev/null +++ b/apps/knowledge-service/tests/test_health.py @@ -0,0 +1,11 @@ +from fastapi.testclient import TestClient + +from app.main import app + +client = TestClient(app) + + +def test_health(): + res = client.get("/healthz") + assert res.status_code == 200 + assert res.json()["service"] == "knowledge-service" diff --git a/apps/web/.dockerignore b/apps/web/.dockerignore new file mode 100644 index 0000000..4b34e5f --- /dev/null +++ b/apps/web/.dockerignore @@ -0,0 +1,4 @@ +node_modules +.next +.turbo +dist diff --git a/apps/web/.eslintrc.js b/apps/web/.eslintrc.js new file mode 100644 index 0000000..16c3a29 --- /dev/null +++ b/apps/web/.eslintrc.js @@ -0,0 +1 @@ +module.exports = { root: true, extends: ["@ai-rxos/eslint-config/next.js"] }; diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile new file mode 100644 index 0000000..aa5e12a --- /dev/null +++ b/apps/web/Dockerfile @@ -0,0 +1,35 @@ +FROM node:20-alpine AS base +RUN corepack enable + +# ---- deps: install full workspace deps needed to build this app ---- +FROM base AS deps +WORKDIR /repo +COPY package.json pnpm-workspace.yaml pnpm-lock.yaml* ./ +COPY apps/web/package.json apps/web/package.json +COPY packages/ui/package.json packages/ui/package.json +COPY packages/sdk/package.json packages/sdk/package.json +COPY packages/types/package.json packages/types/package.json +COPY config/eslint-config/package.json config/eslint-config/package.json +COPY config/typescript-config/package.json config/typescript-config/package.json +RUN pnpm install --frozen-lockfile --filter=@ai-rxos/web... + +# ---- build ---- +FROM base AS build +WORKDIR /repo +COPY --from=deps /repo /repo +COPY . . +RUN pnpm --filter=@ai-rxos/types... --filter=@ai-rxos/ui... --filter=@ai-rxos/sdk... build +RUN pnpm --filter=@ai-rxos/web build + +# ---- runtime ---- +FROM base AS runtime +WORKDIR /app +ENV NODE_ENV=production +RUN addgroup -S rxos && adduser -S rxos -G rxos +COPY --from=build /repo/apps/web/.next/standalone ./ +COPY --from=build /repo/apps/web/.next/static ./apps/web/.next/static +COPY --from=build /repo/apps/web/public ./apps/web/public +USER rxos +EXPOSE 3000 +ENV PORT=3000 +CMD ["node", "apps/web/server.js"] diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts new file mode 100644 index 0000000..40c3d68 --- /dev/null +++ b/apps/web/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs new file mode 100644 index 0000000..5ef219e --- /dev/null +++ b/apps/web/next.config.mjs @@ -0,0 +1,11 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + output: "standalone", + reactStrictMode: true, + transpilePackages: ["@ai-rxos/ui", "@ai-rxos/sdk", "@ai-rxos/types"], + experimental: { + optimizePackageImports: ["@ai-rxos/ui"], + }, +}; + +export default nextConfig; diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 0000000..5305e00 --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,35 @@ +{ + "name": "@ai-rxos/web", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev -p 3000", + "build": "next build", + "start": "next start -p 3000", + "lint": "next lint --max-warnings 0", + "typecheck": "tsc --noEmit", + "clean": "rimraf .next .turbo" + }, + "dependencies": { + "@ai-rxos/sdk": "workspace:*", + "@ai-rxos/types": "workspace:*", + "@ai-rxos/ui": "workspace:*", + "next": "14.2.21", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@ai-rxos/eslint-config": "workspace:*", + "@ai-rxos/typescript-config": "workspace:*", + "@types/node": "^22.10.2", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "autoprefixer": "^10.4.20", + "eslint": "^8.57.1", + "eslint-config-next": "14.2.21", + "postcss": "^8.4.49", + "rimraf": "^6.0.1", + "tailwindcss": "^3.4.17", + "typescript": "^5.7.2" + } +} diff --git a/apps/web/postcss.config.js b/apps/web/postcss.config.js new file mode 100644 index 0000000..12a703d --- /dev/null +++ b/apps/web/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/apps/web/public/.gitkeep b/apps/web/public/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/apps/web/src/app/api/health/route.ts b/apps/web/src/app/api/health/route.ts new file mode 100644 index 0000000..f205a2a --- /dev/null +++ b/apps/web/src/app/api/health/route.ts @@ -0,0 +1,5 @@ +import { NextResponse } from "next/server"; + +export function GET() { + return NextResponse.json({ status: "ok", service: "web" }); +} diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css new file mode 100644 index 0000000..b6db79c --- /dev/null +++ b/apps/web/src/app/globals.css @@ -0,0 +1,58 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + @apply bg-rxos-bg text-slate-100; +} + +.rxos-btn { + @apply inline-flex items-center justify-center rounded-md font-medium transition-colors disabled:opacity-50 disabled:pointer-events-none; +} +.rxos-btn-primary { + @apply bg-rxos-accent text-black hover:opacity-90; +} +.rxos-btn-secondary { + @apply bg-slate-700 text-white hover:bg-slate-600; +} +.rxos-btn-ghost { + @apply bg-transparent text-slate-200 hover:bg-slate-800; +} +.rxos-btn-danger { + @apply bg-red-600 text-white hover:bg-red-500; +} +.rxos-btn-sm { + @apply h-8 px-3 text-sm; +} +.rxos-btn-md { + @apply h-10 px-4 text-sm; +} +.rxos-btn-lg { + @apply h-12 px-6 text-base; +} + +.rxos-card { + @apply rounded-lg border border-slate-800 bg-rxos-surface p-5; +} +.rxos-card-title { + @apply mb-3 text-lg font-semibold; +} + +.rxos-badge { + @apply inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium; +} +.rxos-badge-neutral { + @apply bg-slate-700 text-slate-100; +} +.rxos-badge-success { + @apply bg-emerald-700 text-emerald-100; +} +.rxos-badge-warning { + @apply bg-amber-700 text-amber-100; +} +.rxos-badge-danger { + @apply bg-red-700 text-red-100; +} +.rxos-badge-info { + @apply bg-sky-700 text-sky-100; +} diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx new file mode 100644 index 0000000..d575748 --- /dev/null +++ b/apps/web/src/app/layout.tsx @@ -0,0 +1,15 @@ +import type { Metadata } from "next"; +import "./globals.css"; + +export const metadata: Metadata = { + title: "AI-RxOS", + description: "AI-native drug discovery operating system", +}; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx new file mode 100644 index 0000000..ce163c8 --- /dev/null +++ b/apps/web/src/app/page.tsx @@ -0,0 +1,23 @@ +import { Badge, Button, Card } from "@ai-rxos/ui"; + +export default function HomePage() { + return ( +
+
+

AI-RxOS

+ operational +
+

+ AI-native drug discovery workspace — literature intelligence, knowledge graph, + molecule design, and agentic workflows in one platform. +

+ +

+ This app talks to the API Gateway at{" "} + {process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://localhost:8080"}. +

+ +
+
+ ); +} diff --git a/apps/web/tailwind.config.ts b/apps/web/tailwind.config.ts new file mode 100644 index 0000000..401b11c --- /dev/null +++ b/apps/web/tailwind.config.ts @@ -0,0 +1,19 @@ +import type { Config } from "tailwindcss"; + +const config: Config = { + content: ["./src/**/*.{ts,tsx}", "../../packages/ui/src/**/*.{ts,tsx}"], + theme: { + extend: { + colors: { + rxos: { + bg: "#0b0f14", + surface: "#131a22", + accent: "#3ddc97", + }, + }, + }, + }, + plugins: [], +}; + +export default config; diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json new file mode 100644 index 0000000..0f4f207 --- /dev/null +++ b/apps/web/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@ai-rxos/typescript-config/nextjs.json", + "compilerOptions": { + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/apps/web/tsconfig.tsbuildinfo b/apps/web/tsconfig.tsbuildinfo new file mode 100644 index 0000000..348abe3 --- /dev/null +++ b/apps/web/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"fileNames":["../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.dom.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.dom.iterable.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2016.intl.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.date.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.date.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.promise.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.string.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.number.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.promise.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.string.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.intl.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.array.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.error.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.intl.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.object.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.string.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.decorators.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/styled-jsx/types/css.d.ts","../../node_modules/.pnpm/@types+react@18.3.31/node_modules/@types/react/global.d.ts","../../node_modules/.pnpm/csstype@3.2.3/node_modules/csstype/index.d.ts","../../node_modules/.pnpm/@types+prop-types@15.7.15/node_modules/@types/prop-types/index.d.ts","../../node_modules/.pnpm/@types+react@18.3.31/node_modules/@types/react/index.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/styled-jsx/types/index.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/styled-jsx/types/macro.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/styled-jsx/types/style.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/styled-jsx/types/global.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/shared/lib/amp.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/amp.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/compatibility/disposable.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/compatibility/indexable.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/compatibility/iterators.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/compatibility/index.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/globals.typedarray.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/buffer.buffer.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/globals.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/web-globals/abortcontroller.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/web-globals/domexception.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/web-globals/events.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/header.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/readable.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/file.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/fetch.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/formdata.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/connector.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/client.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/errors.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/dispatcher.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/global-dispatcher.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/global-origin.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/pool-stats.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/pool.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/handlers.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/balanced-pool.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/agent.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/mock-interceptor.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/mock-agent.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/mock-client.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/mock-pool.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/mock-errors.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/proxy-agent.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/env-http-proxy-agent.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/retry-handler.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/retry-agent.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/api.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/interceptors.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/util.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/cookies.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/patch.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/websocket.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/eventsource.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/filereader.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/diagnostics-channel.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/content-type.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/cache.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/index.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/web-globals/fetch.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/web-globals/navigator.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/web-globals/storage.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/web-globals/streams.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/assert.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/assert/strict.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/async_hooks.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/buffer.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/child_process.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/cluster.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/console.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/constants.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/crypto.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/dgram.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/diagnostics_channel.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/dns.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/dns/promises.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/domain.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/events.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/fs.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/fs/promises.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/http.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/http2.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/https.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/inspector.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/inspector.generated.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/module.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/net.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/os.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/path.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/perf_hooks.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/process.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/punycode.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/querystring.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/readline.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/readline/promises.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/repl.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/sea.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/sqlite.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/stream.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/stream/promises.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/stream/consumers.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/stream/web.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/string_decoder.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/test.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/timers.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/timers/promises.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/tls.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/trace_events.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/tty.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/url.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/util.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/v8.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/vm.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/wasi.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/worker_threads.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/zlib.d.ts","../../node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/index.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/get-page-files.d.ts","../../node_modules/.pnpm/@types+react@18.3.31/node_modules/@types/react/canary.d.ts","../../node_modules/.pnpm/@types+react@18.3.31/node_modules/@types/react/experimental.d.ts","../../node_modules/.pnpm/@types+react-dom@18.3.7_@types+react@18.3.31/node_modules/@types/react-dom/index.d.ts","../../node_modules/.pnpm/@types+react-dom@18.3.7_@types+react@18.3.31/node_modules/@types/react-dom/canary.d.ts","../../node_modules/.pnpm/@types+react-dom@18.3.7_@types+react@18.3.31/node_modules/@types/react-dom/experimental.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/webpack/webpack.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/config.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/lib/load-custom-routes.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/shared/lib/image-config.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/build/webpack/plugins/subresource-integrity-plugin.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/body-streams.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/future/route-kind.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/future/route-definitions/route-definition.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/future/route-matches/route-match.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/components/app-router-headers.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/request-meta.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/revalidate.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/config-shared.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-http/index.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/api-utils/index.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/node-environment.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/require-hook.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/node-polyfill-crypto.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/lib/page-types.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/build/analysis/get-page-static-info.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/build/webpack/loaders/get-module-build-info.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/build/webpack/plugins/middleware-plugin.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/render-result.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/future/helpers/i18n-provider.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/web/next-url.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/@edge-runtime/cookies/index.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/web/spec-extension/cookies.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/web/spec-extension/request.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/web/spec-extension/fetch-event.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/web/spec-extension/response.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/web/types.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/lib/setup-exception-listeners.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/lib/constants.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/build/index.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/build/webpack/plugins/pages-manifest-plugin.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/shared/lib/router/utils/route-regex.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/shared/lib/router/utils/route-matcher.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/shared/lib/router/utils/parse-url.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-http/node.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/font-utils.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/build/webpack/plugins/flight-manifest-plugin.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/future/route-modules/route-module.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/shared/lib/deep-readonly.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/load-components.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/shared/lib/router/utils/middleware-route-matcher.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/build/webpack/plugins/next-font-manifest-plugin.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/future/route-definitions/locale-route-definition.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/future/route-definitions/pages-route-definition.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/shared/lib/mitt.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/with-router.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/router.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/route-loader.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/page-loader.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/shared/lib/bloom-filter.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/shared/lib/router/router.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/shared/lib/router-context.shared-runtime.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/shared/lib/loadable-context.shared-runtime.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/shared/lib/loadable.shared-runtime.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/shared/lib/image-config-context.shared-runtime.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/shared/lib/hooks-client-context.shared-runtime.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/shared/lib/head-manager-context.shared-runtime.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/future/route-definitions/app-page-route-definition.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/shared/lib/modern-browserslist-target.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/shared/lib/constants.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/build/webpack/loaders/metadata/types.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/build/page-extensions-type.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/build/webpack/loaders/next-app-loader.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/app-dir-module.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/response-cache/types.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/response-cache/index.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/incremental-cache/index.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/components/hooks-server-context.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/app-render/dynamic-rendering.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/components/static-generation-async-storage-instance.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/components/static-generation-async-storage.external.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/web/spec-extension/adapters/request-cookies.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/async-storage/draft-mode-provider.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/web/spec-extension/adapters/headers.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/components/request-async-storage-instance.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/components/request-async-storage.external.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/app-render/create-error-handler.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/app-render/app-render.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/shared/lib/server-inserted-html.shared-runtime.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/shared/lib/amp-context.shared-runtime.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/future/route-modules/app-page/vendored/contexts/entrypoints.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/future/route-modules/app-page/module.compiled.d.ts","../../node_modules/.pnpm/@types+react@18.3.31/node_modules/@types/react/jsx-runtime.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/components/error-boundary.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/components/router-reducer/create-initial-router-state.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/components/app-router.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/components/layout-router.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/components/render-from-template-context.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/components/action-async-storage-instance.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/components/action-async-storage.external.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/components/client-page.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/components/search-params.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/components/not-found-boundary.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/app-render/rsc/preloads.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/app-render/rsc/postpone.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/app-render/rsc/taint.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/app-render/entry-base.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/build/templates/app-page.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/future/route-modules/app-page/module.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/app-render/types.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/components/router-reducer/fetch-server-response.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/components/router-reducer/router-reducer-types.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/shared/lib/app-router-context.shared-runtime.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/future/route-modules/pages/vendored/contexts/entrypoints.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/future/route-modules/pages/module.compiled.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/build/templates/pages.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/future/route-modules/pages/module.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/render.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/future/route-definitions/pages-api-route-definition.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/future/route-matches/pages-api-route-match.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/future/route-matchers/route-matcher.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/future/route-matcher-providers/route-matcher-provider.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/future/route-matcher-managers/route-matcher-manager.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/future/normalizers/normalizer.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/future/normalizers/locale-route-normalizer.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/future/normalizers/request/pathname-normalizer.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/future/normalizers/request/suffix.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/future/normalizers/request/rsc.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/future/normalizers/request/prefix.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/future/normalizers/request/postponed.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/future/normalizers/request/action.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/future/normalizers/request/prefetch-rsc.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/future/normalizers/request/next-data.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-server.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/image-optimizer.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/lib/coalesced-function.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-utils/types.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/trace/types.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/trace/trace.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/trace/shared.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/trace/index.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/build/load-jsconfig.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/build/webpack-config.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/build/webpack/plugins/define-env-plugin.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/build/swc/index.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/dev/parse-version-info.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/dev/hot-reloader-types.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/telemetry/storage.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/types.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/render-server.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/shared/lib/router/utils/path-match.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-utils/filesystem.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-utils/setup-dev-bundler.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/dev-bundler-service.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/dev/static-paths-worker.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/dev/next-dev-server.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/lib/metadata/types/alternative-urls-types.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/lib/metadata/types/extra-types.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/lib/metadata/types/metadata-types.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/lib/metadata/types/manifest-types.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/lib/metadata/types/opengraph-types.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/lib/metadata/types/twitter-types.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/lib/metadata/types/metadata-interface.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/types/index.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/shared/lib/html-context.shared-runtime.d.ts","../../node_modules/.pnpm/@next+env@14.2.21/node_modules/@next/env/dist/index.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/shared/lib/utils.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/pages/_app.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/app.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/web/spec-extension/unstable-cache.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/web/spec-extension/revalidate.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/web/spec-extension/unstable-no-store.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/cache.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/shared/lib/runtime-config.external.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/config.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/pages/_document.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/document.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/shared/lib/dynamic.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dynamic.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/pages/_error.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/error.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/shared/lib/head.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/head.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/components/draft-mode.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/components/headers.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/headers.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/shared/lib/get-img-props.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/image-component.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/shared/lib/image-external.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/image.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/link.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/link.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/components/redirect-status-code.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/components/redirect.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/components/not-found.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/components/navigation.react-server.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/components/navigation.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/navigation.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/router.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/client/script.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/script.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/web/spec-extension/user-agent.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/@edge-runtime/primitives/url.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/web/spec-extension/image-response.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/@vercel/og/satori/index.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/@vercel/og/emoji/index.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/@vercel/og/types.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/server.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/types/global.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/types/compiled.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/index.d.ts","../../node_modules/.pnpm/next@14.2.21_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/image-types/global.d.ts","./next-env.d.ts","../../node_modules/.pnpm/source-map-js@1.2.1/node_modules/source-map-js/source-map.d.ts","../../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/previous-map.d.ts","../../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/input.d.ts","../../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/css-syntax-error.d.ts","../../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/declaration.d.ts","../../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/root.d.ts","../../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/warning.d.ts","../../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/lazy-result.d.ts","../../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/no-work-result.d.ts","../../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/processor.d.ts","../../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/result.d.ts","../../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/document.d.ts","../../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/rule.d.ts","../../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/node.d.ts","../../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/comment.d.ts","../../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/container.d.ts","../../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/at-rule.d.ts","../../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/list.d.ts","../../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/postcss.d.ts","../../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/postcss.d.mts","../../node_modules/.pnpm/tailwindcss@3.4.19_tsx@4.23.1/node_modules/tailwindcss/types/generated/corepluginlist.d.ts","../../node_modules/.pnpm/tailwindcss@3.4.19_tsx@4.23.1/node_modules/tailwindcss/types/generated/colors.d.ts","../../node_modules/.pnpm/tailwindcss@3.4.19_tsx@4.23.1/node_modules/tailwindcss/types/config.d.ts","../../node_modules/.pnpm/tailwindcss@3.4.19_tsx@4.23.1/node_modules/tailwindcss/types/index.d.ts","./tailwind.config.ts","./src/app/api/health/route.ts","./src/app/layout.tsx","../../packages/ui/dist/index.d.ts","./src/app/page.tsx","./.next/types/app/layout.ts","./.next/types/app/page.ts","./.next/types/app/api/health/route.ts"],"fileIdsList":[[76,125,142,143,386,417],[76,125,142,143,341,418],[76,125,142,143,341,420],[76,125,142,143,389,390],[76,125,142,143,386],[76,125,142,143,389],[76,125,142,143,419],[76,125,142,143,415],[76,125,142,143],[76,122,123,125,142,143],[76,124,125,142,143],[125,142,143],[76,125,130,142,143,160],[76,125,126,131,136,142,143,145,157,168],[76,125,126,127,136,142,143,145],[71,72,73,76,125,142,143],[76,125,128,142,143,169],[76,125,129,130,137,142,143,146],[76,125,130,142,143,157,165],[76,125,131,133,136,142,143,145],[76,124,125,132,142,143],[76,125,133,134,142,143],[76,125,135,136,142,143],[76,124,125,136,142,143],[76,125,136,137,138,142,143,157,168],[76,125,136,137,138,142,143,152,157,160],[76,117,125,133,136,139,142,143,145,157,168],[76,125,136,137,139,140,142,143,145,157,165,168],[76,125,139,141,142,143,157,165,168],[74,75,76,77,78,79,80,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174],[76,125,136,142,143],[76,125,142,143,144,168],[76,125,133,136,142,143,145,157],[76,125,142,143,146],[76,125,142,143,147],[76,124,125,142,143,148],[76,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174],[76,125,142,143,150],[76,125,142,143,151],[76,125,136,142,143,152,153],[76,125,142,143,152,154,169,171],[76,125,137,142,143],[76,125,136,142,143,157,158,160],[76,125,142,143,159,160],[76,125,142,143,157,158],[76,125,142,143,160],[76,125,142,143,161],[76,122,125,142,143,157,162,168],[76,125,136,142,143,163,164],[76,125,142,143,163,164],[76,125,130,142,143,145,157,165],[76,125,142,143,166],[76,125,142,143,145,167],[76,125,139,142,143,151,168],[76,125,130,142,143,169],[76,125,142,143,157,170],[76,125,142,143,144,171],[76,125,142,143,172],[76,117,125,142,143],[76,117,125,136,138,142,143,148,157,160,168,170,171,173],[76,125,142,143,157,174],[64,76,125,142,143,179,180,181],[64,76,125,142,143,179,180],[64,76,125,142,143],[64,68,76,125,142,143,178,342,385],[64,68,76,125,142,143,177,342,385],[61,62,63,76,125,142,143],[69,76,125,142,143],[76,125,142,143,346],[76,125,142,143,348,349,350],[76,125,142,143,352],[76,125,142,143,184,194,200,202,342],[76,125,142,143,184,191,193,196,214],[76,125,142,143,194],[76,125,142,143,194,196,320],[76,125,142,143,249,267,282,388],[76,125,142,143,290],[76,125,142,143,184,194,201,235,245,317,318,388],[76,125,142,143,201,388],[76,125,142,143,194,245,246,247,388],[76,125,142,143,194,201,235,388],[76,125,142,143,388],[76,125,142,143,184,201,202,388],[76,125,142,143,275],[76,124,125,142,143,175,274],[64,76,125,142,143,268,269,270,287,288],[64,76,125,142,143,268],[76,125,142,143,258],[76,125,142,143,257,259,362],[64,76,125,142,143,268,269,285],[76,125,142,143,264,288,374],[76,125,142,143,372,373],[76,125,142,143,208,371],[76,125,142,143,261],[76,124,125,142,143,175,208,224,257,258,259,260],[64,76,125,142,143,285,287,288],[76,125,142,143,285,287],[76,125,142,143,285,286,288],[76,125,142,143,151,175],[76,125,142,143,256],[76,124,125,142,143,175,193,195,252,253,254,255],[64,76,125,142,143,185,365],[64,76,125,142,143,168,175],[64,76,125,142,143,201,233],[64,76,125,142,143,201],[76,125,142,143,231,236],[64,76,125,142,143,232,345],[64,68,76,125,139,142,143,175,177,178,342,383,384],[76,125,142,143,342],[76,125,142,143,183],[76,125,142,143,335,336,337,338,339,340],[76,125,142,143,337],[64,76,125,142,143,232,268,345],[64,76,125,142,143,268,343,345],[64,76,125,142,143,268,345],[76,125,139,142,143,175,195,345],[76,125,139,142,143,175,192,193,204,222,224,256,261,262,284,285],[76,125,142,143,253,256,261,269,271,272,273,275,276,277,278,279,280,281,388],[76,125,142,143,254],[64,76,125,142,143,151,175,193,194,222,224,225,227,252,284,288,342,388],[76,125,139,142,143,175,195,196,208,209,257],[76,125,139,142,143,175,194,196],[76,125,139,142,143,157,175,192,195,196],[76,125,139,142,143,151,168,175,192,193,194,195,196,201,204,205,215,216,218,221,222,224,225,226,227,251,252,285,293,295,298,300,303,305,306,307,308],[76,125,139,142,143,157,175],[76,125,142,143,184,185,186,192,193,342,345,388],[76,125,139,142,143,157,168,175,189,319,321,322,388],[76,125,142,143,151,168,175,189,192,195,212,216,218,219,220,225,252,298,309,311,317,331,332],[76,125,142,143,194,198,252],[76,125,142,143,192,194],[76,125,142,143,205,299],[76,125,142,143,301,302],[76,125,142,143,301],[76,125,142,143,299],[76,125,142,143,301,304],[76,125,142,143,188,189],[76,125,142,143,188,228],[76,125,142,143,188],[76,125,142,143,190,205,297],[76,125,142,143,296],[76,125,142,143,189,190],[76,125,142,143,190,294],[76,125,142,143,189],[76,125,142,143,284],[76,125,139,142,143,175,192,204,223,243,249,263,266,283,285],[76,125,142,143,237,238,239,240,241,242,264,265,288,343],[76,125,142,143,292],[76,125,139,142,143,175,192,204,223,229,289,291,293,342,345],[76,125,139,142,143,168,175,185,192,194,251],[76,125,142,143,248],[76,125,139,142,143,175,325,330],[76,125,142,143,215,224,251,345],[76,125,142,143,313,317,331,334],[76,125,139,142,143,198,317,325,326,334],[76,125,142,143,184,194,215,226,328],[76,125,139,142,143,175,194,201,226,312,313,323,324,327,329],[76,125,142,143,176,222,223,224,342,345],[76,125,139,142,143,151,168,175,190,192,193,195,198,203,204,212,215,216,218,219,220,221,225,227,251,252,295,309,310,345],[76,125,139,142,143,175,192,194,198,311,333],[76,125,139,142,143,175,193,195],[64,76,125,139,142,143,151,175,183,185,192,193,196,204,221,222,224,225,227,292,342,345],[76,125,139,142,143,151,168,175,187,190,191,195],[76,125,142,143,188,250],[76,125,139,142,143,175,188,193,204],[76,125,139,142,143,175,194,205],[76,125,139,142,143,175],[76,125,142,143,208],[76,125,142,143,207],[76,125,142,143,209],[76,125,142,143,194,206,208,212],[76,125,142,143,194,206,208],[76,125,139,142,143,175,187,194,195,201,209,210,211],[64,76,125,142,143,285,286,287],[76,125,142,143,244],[64,76,125,142,143,185],[64,76,125,142,143,218],[64,76,125,142,143,176,221,224,227,342,345],[76,125,142,143,185,365,366],[64,76,125,142,143,236],[64,76,125,142,143,151,168,175,183,230,232,234,235,345],[76,125,142,143,195,201,218],[76,125,142,143,217],[64,76,125,137,139,142,143,151,175,183,236,245,342,343,344],[60,64,65,66,67,76,125,142,143,177,178,342,385],[76,125,130,142,143],[76,125,142,143,314,315,316],[76,125,142,143,314],[76,125,142,143,354],[76,125,142,143,356],[76,125,142,143,358],[76,125,142,143,360],[76,125,142,143,363],[76,125,142,143,367],[68,70,76,125,142,143,342,347,351,353,355,357,359,361,364,368,370,376,377,379,386,387,388],[76,125,142,143,369],[76,125,142,143,375],[76,125,142,143,232],[76,125,142,143,378],[76,124,125,142,143,209,210,211,212,380,381,382,385],[76,125,142,143,175],[64,68,76,125,139,141,142,143,151,175,177,178,179,181,183,196,334,341,345,385],[76,125,142,143,407],[76,125,142,143,405,407],[76,125,142,143,396,404,405,406,408,410],[76,125,142,143,394],[76,125,142,143,397,402,407,410],[76,125,142,143,393,410],[76,125,142,143,397,398,401,402,403,410],[76,125,142,143,397,398,399,401,402,410],[76,125,142,143,394,395,396,397,398,402,403,404,406,407,408,410],[76,125,142,143,410],[76,125,142,143,392,394,395,396,397,398,399,401,402,403,404,405,406,407,408,409],[76,125,142,143,392,410],[76,125,142,143,397,399,400,402,403,410],[76,125,142,143,401,410],[76,125,142,143,402,403,407,410],[76,125,142,143,395,405],[76,125,142,143,412,413],[76,125,142,143,411,414],[76,89,93,125,142,143,168],[76,89,125,142,143,157,168],[76,84,125,142,143],[76,86,89,125,142,143,165,168],[76,125,142,143,145,165],[76,84,125,142,143,175],[76,86,89,125,142,143,145,168],[76,81,82,85,88,125,136,142,143,157,168],[76,89,96,125,142,143],[76,81,87,125,142,143],[76,89,110,111,125,142,143],[76,85,89,125,142,143,160,168,175],[76,110,125,142,143,175],[76,83,84,125,142,143,175],[76,89,125,142,143],[76,83,84,85,86,87,88,89,90,91,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,111,112,113,114,115,116,125,142,143],[76,89,104,125,142,143],[76,89,96,97,125,142,143],[76,87,89,97,98,125,142,143],[76,88,125,142,143],[76,81,84,89,125,142,143],[76,89,93,97,98,125,142,143],[76,93,125,142,143],[76,87,89,92,125,142,143,168],[76,81,86,89,96,125,142,143],[76,125,142,143,157],[76,84,89,110,125,142,143,173,175]],"fileInfos":[{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","impliedFormat":1},{"version":"ee7bad0c15b58988daa84371e0b89d313b762ab83cb5b31b8a2d1162e8eb41c2","impliedFormat":1},{"version":"080941d9f9ff9307f7e27a83bcd888b7c8270716c39af943532438932ec1d0b9","affectsGlobalScope":true,"impliedFormat":1},{"version":"2e80ee7a49e8ac312cc11b77f1475804bee36b3b2bc896bead8b6e1266befb43","affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"a680117f487a4d2f30ea46f1b4b7f58bef1480456e18ba53ee85c2746eeca012","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true,"impliedFormat":1},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true,"impliedFormat":1},{"version":"959d36cddf5e7d572a65045b876f2956c973a586da58e5d26cde519184fd9b8a","affectsGlobalScope":true,"impliedFormat":1},{"version":"965f36eae237dd74e6cca203a43e9ca801ce38824ead814728a2807b1910117d","affectsGlobalScope":true,"impliedFormat":1},{"version":"3925a6c820dcb1a06506c90b1577db1fdbf7705d65b62b99dce4be75c637e26b","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a3d63ef2b853447ec4f749d3f368ce642264246e02911fcb1590d8c161b8005","affectsGlobalScope":true,"impliedFormat":1},{"version":"8cdf8847677ac7d20486e54dd3fcf09eda95812ac8ace44b4418da1bbbab6eb8","affectsGlobalScope":true,"impliedFormat":1},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true,"impliedFormat":1},{"version":"b4b67b1a91182421f5df999988c690f14d813b9850b40acd06ed44691f6727ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"0990a7576222f248f0a3b888adcb7389f957928ce2afb1cd5128169086ff4d29","impliedFormat":1},{"version":"eb5b19b86227ace1d29ea4cf81387279d04bb34051e944bc53df69f58914b788","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac51dd7d31333793807a6abaa5ae168512b6131bd41d9c5b98477fc3b7800f9f","impliedFormat":1},{"version":"87d9d29dbc745f182683f63187bf3d53fd8673e5fca38ad5eaab69798ed29fbc","impliedFormat":1},{"version":"09ddcfcfbe77a8232d155ca1030005106b1328f6210df43629d0be750da07c16","affectsGlobalScope":true,"impliedFormat":1},{"version":"cc69795d9954ee4ad57545b10c7bf1a7260d990231b1685c147ea71a6faa265c","impliedFormat":1},{"version":"8bc6c94ff4f2af1f4023b7bb2379b08d3d7dd80c698c9f0b07431ea16101f05f","impliedFormat":1},{"version":"1b61d259de5350f8b1e5db06290d31eaebebc6baafd5f79d314b5af9256d7153","impliedFormat":1},{"version":"57194e1f007f3f2cbef26fa299d4c6b21f4623a2eddc63dfeef79e38e187a36e","impliedFormat":1},{"version":"0f6666b58e9276ac3a38fdc80993d19208442d6027ab885580d93aec76b4ef00","impliedFormat":1},{"version":"05fd364b8ef02fb1e174fbac8b825bdb1e5a36a016997c8e421f5fab0a6da0a0","impliedFormat":1},{"version":"6c7176368037af28cb72f2392010fa1cef295d6d6744bca8cfb54985f3a18c3e","affectsGlobalScope":true,"impliedFormat":1},{"version":"ab41ef1f2cdafb8df48be20cd969d875602483859dc194e9c97c8a576892c052","affectsGlobalScope":true,"impliedFormat":1},{"version":"437e20f2ba32abaeb7985e0afe0002de1917bc74e949ba585e49feba65da6ca1","affectsGlobalScope":true,"impliedFormat":1},{"version":"21d819c173c0cf7cc3ce57c3276e77fd9a8a01d35a06ad87158781515c9a438a","impliedFormat":1},{"version":"98cffbf06d6bab333473c70a893770dbe990783904002c4f1a960447b4b53dca","affectsGlobalScope":true,"impliedFormat":1},{"version":"3af97acf03cc97de58a3a4bc91f8f616408099bc4233f6d0852e72a8ffb91ac9","affectsGlobalScope":true,"impliedFormat":1},{"version":"808069bba06b6768b62fd22429b53362e7af342da4a236ed2d2e1c89fcca3b4a","affectsGlobalScope":true,"impliedFormat":1},{"version":"1db0b7dca579049ca4193d034d835f6bfe73096c73663e5ef9a0b5779939f3d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"f26b11d8d8e4b8028f1c7d618b22274c892e4b0ef5b3678a8ccbad85419aef43","affectsGlobalScope":true,"impliedFormat":1},{"version":"5929864ce17fba74232584d90cb721a89b7ad277220627cc97054ba15a98ea8f","impliedFormat":1},{"version":"763fe0f42b3d79b440a9b6e51e9ba3f3f91352469c1e4b3b67bfa4ff6352f3f4","impliedFormat":1},{"version":"25c8056edf4314820382a5fdb4bb7816999acdcb929c8f75e3f39473b87e85bc","impliedFormat":1},{"version":"c464d66b20788266e5353b48dc4aa6bc0dc4a707276df1e7152ab0c9ae21fad8","impliedFormat":1},{"version":"78d0d27c130d35c60b5e5566c9f1e5be77caf39804636bc1a40133919a949f21","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"1d6e127068ea8e104a912e42fc0a110e2aa5a66a356a917a163e8cf9a65e4a75","impliedFormat":1},{"version":"5ded6427296cdf3b9542de4471d2aa8d3983671d4cac0f4bf9c637208d1ced43","impliedFormat":1},{"version":"7f182617db458e98fc18dfb272d40aa2fff3a353c44a89b2c0ccb3937709bfb5","impliedFormat":1},{"version":"cadc8aced301244057c4e7e73fbcae534b0f5b12a37b150d80e5a45aa4bebcbd","impliedFormat":1},{"version":"385aab901643aa54e1c36f5ef3107913b10d1b5bb8cbcd933d4263b80a0d7f20","impliedFormat":1},{"version":"9670d44354bab9d9982eca21945686b5c24a3f893db73c0dae0fd74217a4c219","impliedFormat":1},{"version":"0b8a9268adaf4da35e7fa830c8981cfa22adbbe5b3f6f5ab91f6658899e657a7","impliedFormat":1},{"version":"11396ed8a44c02ab9798b7dca436009f866e8dae3c9c25e8c1fbc396880bf1bb","impliedFormat":1},{"version":"ba7bc87d01492633cb5a0e5da8a4a42a1c86270e7b3d2dea5d156828a84e4882","impliedFormat":1},{"version":"4893a895ea92c85345017a04ed427cbd6a1710453338df26881a6019432febdd","impliedFormat":1},{"version":"c21dc52e277bcfc75fac0436ccb75c204f9e1b3fa5e12729670910639f27343e","impliedFormat":1},{"version":"13f6f39e12b1518c6650bbb220c8985999020fe0f21d818e28f512b7771d00f9","impliedFormat":1},{"version":"9b5369969f6e7175740bf51223112ff209f94ba43ecd3bb09eefff9fd675624a","impliedFormat":1},{"version":"4fe9e626e7164748e8769bbf74b538e09607f07ed17c2f20af8d680ee49fc1da","impliedFormat":1},{"version":"24515859bc0b836719105bb6cc3d68255042a9f02a6022b3187948b204946bd2","impliedFormat":1},{"version":"ea0148f897b45a76544ae179784c95af1bd6721b8610af9ffa467a518a086a43","impliedFormat":1},{"version":"24c6a117721e606c9984335f71711877293a9651e44f59f3d21c1ea0856f9cc9","impliedFormat":1},{"version":"dd3273ead9fbde62a72949c97dbec2247ea08e0c6952e701a483d74ef92d6a17","impliedFormat":1},{"version":"405822be75ad3e4d162e07439bac80c6bcc6dbae1929e179cf467ec0b9ee4e2e","impliedFormat":1},{"version":"0db18c6e78ea846316c012478888f33c11ffadab9efd1cc8bcc12daded7a60b6","impliedFormat":1},{"version":"e61be3f894b41b7baa1fbd6a66893f2579bfad01d208b4ff61daef21493ef0a8","impliedFormat":1},{"version":"bd0532fd6556073727d28da0edfd1736417a3f9f394877b6d5ef6ad88fba1d1a","impliedFormat":1},{"version":"89167d696a849fce5ca508032aabfe901c0868f833a8625d5a9c6e861ef935d2","impliedFormat":1},{"version":"615ba88d0128ed16bf83ef8ccbb6aff05c3ee2db1cc0f89ab50a4939bfc1943f","impliedFormat":1},{"version":"a4d551dbf8746780194d550c88f26cf937caf8d56f102969a110cfaed4b06656","impliedFormat":1},{"version":"8bd86b8e8f6a6aa6c49b71e14c4ffe1211a0e97c80f08d2c8cc98838006e4b88","impliedFormat":1},{"version":"317e63deeb21ac07f3992f5b50cdca8338f10acd4fbb7257ebf56735bf52ab00","impliedFormat":1},{"version":"4732aec92b20fb28c5fe9ad99521fb59974289ed1e45aecb282616202184064f","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"bf67d53d168abc1298888693338cb82854bdb2e69ef83f8a0092093c2d562107","impliedFormat":1},{"version":"b52476feb4a0cbcb25e5931b930fc73cb6643fb1a5060bf8a3dda0eeae5b4b68","affectsGlobalScope":true,"impliedFormat":1},{"version":"f9501cc13ce624c72b61f12b3963e84fad210fbdf0ffbc4590e08460a3f04eba","affectsGlobalScope":true,"impliedFormat":1},{"version":"e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d97fb21da858fb18b8ae72c314e9743fd52f73ebe2764e12af1db32fc03f853f","affectsGlobalScope":true,"impliedFormat":1},{"version":"0fa06ada475b910e2106c98c68b10483dc8811d0c14a8a8dd36efb2672485b29","impliedFormat":1},{"version":"33e5e9aba62c3193d10d1d33ae1fa75c46a1171cf76fef750777377d53b0303f","impliedFormat":1},{"version":"2b06b93fd01bcd49d1a6bd1f9b65ddcae6480b9a86e9061634d6f8e354c1468f","impliedFormat":1},{"version":"6a0cd27e5dc2cfbe039e731cf879d12b0e2dded06d1b1dedad07f7712de0d7f4","affectsGlobalScope":true,"impliedFormat":1},{"version":"13f5c844119c43e51ce777c509267f14d6aaf31eafb2c2b002ca35584cd13b29","impliedFormat":1},{"version":"e60477649d6ad21542bd2dc7e3d9ff6853d0797ba9f689ba2f6653818999c264","impliedFormat":1},{"version":"c2510f124c0293ab80b1777c44d80f812b75612f297b9857406468c0f4dafe29","affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","impliedFormat":1},{"version":"4c829ab315f57c5442c6667b53769975acbf92003a66aef19bce151987675bd1","affectsGlobalScope":true,"impliedFormat":1},{"version":"b2ade7657e2db96d18315694789eff2ddd3d8aea7215b181f8a0b303277cc579","impliedFormat":1},{"version":"78dbea00e90d2df8ea3dbef0cc379d95b8be9b71cd6bde4c28728f306811803b","impliedFormat":1},{"version":"4d631b81fa2f07a0e63a9a143d6a82c25c5f051298651a9b69176ba28930756d","impliedFormat":1},{"version":"836a356aae992ff3c28a0212e3eabcb76dd4b0cc06bcb9607aeef560661b860d","impliedFormat":1},{"version":"1e0d1f8b0adfa0b0330e028c7941b5a98c08b600efe7f14d2d2a00854fb2f393","impliedFormat":1},{"version":"41670ee38943d9cbb4924e436f56fc19ee94232bc96108562de1a734af20dc2c","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e1e46d0a9837ee058c100501080c920fa98081ea3956af0374308ba6f22a33e","impliedFormat":1},{"version":"272ca407e0c9068bdc5152552d876e68037ceae3de62e529306403e973dec8e1","impliedFormat":1},{"version":"fa7834c715d5357e4540cee40ce96c3250ddb67a7b879a6b7fa0e86d6696f121","impliedFormat":1},{"version":"22dfb07a7ab15b66ac043829056fe70124844636ae719551812ac631ba04985b","impliedFormat":1},{"version":"a10f0e1854f3316d7ee437b79649e5a6ae3ae14ffe6322b02d4987071a95362e","impliedFormat":1},{"version":"e208f73ef6a980104304b0d2ca5f6bf1b85de6009d2c7e404028b875020fa8f2","impliedFormat":1},{"version":"d163b6bc2372b4f07260747cbc6c0a6405ab3fbcea3852305e98ac43ca59f5bc","impliedFormat":1},{"version":"e6fa9ad47c5f71ff733744a029d1dc472c618de53804eae08ffc243b936f87ff","affectsGlobalScope":true,"impliedFormat":1},{"version":"a6f137d651076822d4fe884287e68fd61785a0d3d1fdb250a5059b691fa897db","impliedFormat":1},{"version":"24826ed94a78d5c64bd857570fdbd96229ad41b5cb654c08d75a9845e3ab7dde","impliedFormat":1},{"version":"8b479a130ccb62e98f11f136d3ac80f2984fdc07616516d29881f3061f2dd472","impliedFormat":1},{"version":"928af3d90454bf656a52a48679f199f64c1435247d6189d1caf4c68f2eaf921f","affectsGlobalScope":true,"impliedFormat":1},{"version":"bceb58df66ab8fb00170df20cd813978c5ab84be1d285710c4eb005d8e9d8efb","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f16a7e4deafa527ed9995a772bb380eb7d3c2c0fd4ae178c5263ed18394db2c","impliedFormat":1},{"version":"933921f0bb0ec12ef45d1062a1fc0f27635318f4d294e4d99de9a5493e618ca2","impliedFormat":1},{"version":"71a0f3ad612c123b57239a7749770017ecfe6b66411488000aba83e4546fde25","impliedFormat":1},{"version":"77fbe5eecb6fac4b6242bbf6eebfc43e98ce5ccba8fa44e0ef6a95c945ff4d98","impliedFormat":1},{"version":"4f9d8ca0c417b67b69eeb54c7ca1bedd7b56034bb9bfd27c5d4f3bc4692daca7","impliedFormat":1},{"version":"0cb167c371eaa8c869f8a7656a7296f2e4fae43b4d8b803a680236b24794e5f9","impliedFormat":1},{"version":"0a839dba0287cc0481ad4beedd48a1c64acf1e212ae865d1315f7007ca215161","impliedFormat":1},{"version":"38dc4655376cd1a4bd6bb3763d92949233e33d38d3dd3cbea7bbf218175a38ef","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"45650f47bfb376c8a8ed39d4bcda5902ab899a3150029684ee4c10676d9fbaee","impliedFormat":1},{"version":"d61e0a64cd175208ac0b83670151a9a6b5916f0d1ffcdc5c29c90b1cebfc5045","affectsGlobalScope":true,"impliedFormat":1},{"version":"18fd40412d102c5564136f29735e5d1c3b455b8a37f920da79561f1fde068208","impliedFormat":1},{"version":"48a679952eefe4cb776d5a0e1ccba2d3eb53b57448bbb7abc1fcebcbd5440188","impliedFormat":1},{"version":"f0be1b8078cd549d91f37c30c222c2a187ac1cf981d994fb476a1adc61387b14","affectsGlobalScope":true,"impliedFormat":1},{"version":"0aaed1d72199b01234152f7a60046bc947f1f37d78d182e9ae09c4289e06a592","impliedFormat":1},{"version":"2d14da6ecb49bf828d83948765ec2d3a579d476bbb9645e749610baa6ec880ca","impliedFormat":1},{"version":"66ba1b2c3e3a3644a1011cd530fb444a96b1b2dfe2f5e837a002d41a1a799e60","impliedFormat":1},{"version":"7e514f5b852fdbc166b539fdd1f4e9114f29911592a5eb10a94bb3a13ccac3c4","impliedFormat":1},{"version":"5b7aa3c4c1a5d81b411e8cb302b45507fea9358d3569196b27eb1a27ae3a90ef","affectsGlobalScope":true,"impliedFormat":1},{"version":"5987a903da92c7462e0b35704ce7da94d7fdc4b89a984871c0e2b87a8aae9e69","affectsGlobalScope":true,"impliedFormat":1},{"version":"ea08a0345023ade2b47fbff5a76d0d0ed8bff10bc9d22b83f40858a8e941501c","impliedFormat":1},{"version":"0aef708fb4c7a6b915e8305cbfac40cd207b032dbaabe9a01889a5fff3254681","impliedFormat":1},{"version":"ae062ce7d9510060c5d7e7952ae379224fb3f8f2dd74e88959878af2057c143b","impliedFormat":1},{"version":"ad9bdafb4e7abf14cc53ce7970486a84c87831e62891e5dfe798ddcd55e84701","affectsGlobalScope":true,"impliedFormat":1},{"version":"358765d5ea8afd285d4fd1532e78b88273f18cb3f87403a9b16fef61ac9fdcfe","impliedFormat":1},{"version":"71d3ae6a5e73ca4130762560425e00984ebaff64d5353a3333d1bb7eb86ef336","impliedFormat":1},{"version":"8caa5c86be1b793cd5f599e27ecb34252c41e011980f7d61ae4989a149ff6ccc","impliedFormat":1},{"version":"f9fd93190acb1ffe0bc0fb395df979452f8d625071e9ffc8636e4dfb86ab2508","impliedFormat":1},{"version":"5f41fd8732a89e940c58ce22206e3df85745feb8983e2b4c6257fb8cbb118493","impliedFormat":1},{"version":"17ed71200119e86ccef2d96b73b02ce8854b76ad6bd21b5021d4269bec527b5f","impliedFormat":1},{"version":"1cfa8647d7d71cb03847d616bd79320abfc01ddea082a49569fda71ac5ece66b","impliedFormat":1},{"version":"bb7a61dd55dc4b9422d13da3a6bb9cc5e89be888ef23bbcf6558aa9726b89a1c","impliedFormat":1},{"version":"db6d2d9daad8a6d83f281af12ce4355a20b9a3e71b82b9f57cddcca0a8964a96","impliedFormat":1},{"version":"cfe4ef4710c3786b6e23dae7c086c70b4f4835a2e4d77b75d39f9046106e83d3","impliedFormat":1},{"version":"cbea99888785d49bb630dcbb1613c73727f2b5a2cf02e1abcaab7bcf8d6bf3c5","impliedFormat":1},{"version":"3989ccb24f2526f7e82cf54268e23ce9e1df5b9982f8acd099ddd4853c26babd","impliedFormat":1},{"version":"a86f82d646a739041d6702101afa82dcb935c416dd93cbca7fd754fd0282ce1f","impliedFormat":1},{"version":"2dad084c67e649f0f354739ec7df7c7df0779a28a4f55c97c6b6883ae850d1ce","impliedFormat":1},{"version":"fa5bbc7ab4130dd8cdc55ea294ec39f76f2bc507a0f75f4f873e38631a836ca7","impliedFormat":1},{"version":"df45ca1176e6ac211eae7ddf51336dc075c5314bc5c253651bae639defd5eec5","impliedFormat":1},{"version":"cf86de1054b843e484a3c9300d62fbc8c97e77f168bbffb131d560ca0474d4a8","impliedFormat":1},{"version":"196c960b12253fde69b204aa4fbf69470b26daf7a430855d7f94107a16495ab0","impliedFormat":1},{"version":"ee15ea5dd7a9fc9f5013832e5843031817a880bf0f24f37a29fd8337981aae07","impliedFormat":1},{"version":"bf24f6d35f7318e246010ffe9924395893c4e96d34324cde77151a73f078b9ad","impliedFormat":1},{"version":"805c5db07d4b131bede36cc2dbded64cc3c8e49594e53119f4442af183f97935","impliedFormat":1},{"version":"10595c7ff5094dd5b6a959ccb1c00e6a06441b4e10a87bc09c15f23755d34439","impliedFormat":1},{"version":"9620c1ff645afb4a9ab4044c85c26676f0a93e8c0e4b593aea03a89ccb47b6d0","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"a9af0e608929aaf9ce96bd7a7b99c9360636c31d73670e4af09a09950df97841","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"c86fe861cf1b4c46a0fb7d74dffe596cf679a2e5e8b1456881313170f092e3fa","impliedFormat":1},{"version":"08ed0b3f0166787f84a6606f80aa3b1388c7518d78912571b203817406e471da","impliedFormat":1},{"version":"47e5af2a841356a961f815e7c55d72554db0c11b4cba4d0caab91f8717846a94","impliedFormat":1},{"version":"65f43099ded6073336e697512d9b80f2d4fec3182b7b2316abf712e84104db00","impliedFormat":1},{"version":"f5f541902bf7ae0512a177295de9b6bcd6809ea38307a2c0a18bfca72212f368","impliedFormat":1},{"version":"b0decf4b6da3ebc52ea0c96095bdfaa8503acc4ac8e9081c5f2b0824835dd3bd","impliedFormat":1},{"version":"ca1b882a105a1972f82cc58e3be491e7d750a1eb074ffd13b198269f57ed9e1b","impliedFormat":1},{"version":"fc3e1c87b39e5ba1142f27ec089d1966da168c04a859a4f6aab64dceae162c2b","impliedFormat":1},{"version":"3b414b99a73171e1c4b7b7714e26b87d6c5cb03d200352da5342ab4088a54c85","impliedFormat":1},{"version":"61888522cec948102eba94d831c873200aa97d00d8989fdfd2a3e0ee75ec65a2","impliedFormat":1},{"version":"4e10622f89fea7b05dd9b52fb65e1e2b5cbd96d4cca3d9e1a60bb7f8a9cb86a1","impliedFormat":1},{"version":"74b2a5e5197bd0f2e0077a1ea7c07455bbea67b87b0869d9786d55104006784f","impliedFormat":1},{"version":"59bf32919de37809e101acffc120596a9e45fdbab1a99de5087f31fdc36e2f11","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"3c4b45e48c56c17fb44b3cab4e2a6c8f64c4fa2c0306fe27d33c52167c0b7fa7","impliedFormat":1},{"version":"c40c848daad198266370c1c72a7a8c3d18d2f50727c7859fcfefd3ff69a7f288","impliedFormat":1},{"version":"ac60bbee0d4235643cc52b57768b22de8c257c12bd8c2039860540cab1fa1d82","impliedFormat":1},{"version":"6428e6edd944ce6789afdf43f9376c1f2e4957eea34166177625aaff4c0da1a0","impliedFormat":1},{"version":"ada39cbb2748ab2873b7835c90c8d4620723aedf323550e8489f08220e477c7f","impliedFormat":1},{"version":"6e5f5cee603d67ee1ba6120815497909b73399842254fc1e77a0d5cdc51d8c9c","impliedFormat":1},{"version":"8dba67056cbb27628e9b9a1cba8e57036d359dceded0725c72a3abe4b6c79cd4","impliedFormat":1},{"version":"70f3814c457f54a7efe2d9ce9d2686de9250bb42eb7f4c539bd2280a42e52d33","impliedFormat":1},{"version":"154dd2e22e1e94d5bc4ff7726706bc0483760bae40506bdce780734f11f7ec47","impliedFormat":1},{"version":"ef61792acbfa8c27c9bd113f02731e66229f7d3a169e3c1993b508134f1a58e0","impliedFormat":1},{"version":"9c82171d836c47486074e4ca8e059735bf97b205e70b196535b5efd40cbe1bc5","impliedFormat":1},{"version":"15e3409b8397457d761d8d6f8c524795845c3aeb5dd0d4291ca0c54fec670b72","impliedFormat":1},{"version":"f6404e7837b96da3ea4d38c4f1a3812c96c9dcdf264e93d5bdb199f983a3ef4b","impliedFormat":1},{"version":"c5426dbfc1cf90532f66965a7aa8c1136a78d4d0f96d8180ecbfc11d7722f1a5","impliedFormat":1},{"version":"65a15fc47900787c0bd18b603afb98d33ede930bed1798fc984d5ebb78b26cf9","impliedFormat":1},{"version":"9d202701f6e0744adb6314d03d2eb8fc994798fc83d91b691b75b07626a69801","impliedFormat":1},{"version":"de9d2df7663e64e3a91bf495f315a7577e23ba088f2949d5ce9ec96f44fba37d","impliedFormat":1},{"version":"c7af78a2ea7cb1cd009cfb5bdb48cd0b03dad3b54f6da7aab615c2e9e9d570c5","impliedFormat":1},{"version":"1ee45496b5f8bdee6f7abc233355898e5bf9bd51255db65f5ff7ede617ca0027","impliedFormat":1},{"version":"8b8f00491431fe82f060dfe8c7f2180a9fb239f3d851527db909b83230e75882","affectsGlobalScope":true,"impliedFormat":1},{"version":"db01d18853469bcb5601b9fc9826931cc84cc1a1944b33cad76fd6f1e3d8c544","affectsGlobalScope":true,"impliedFormat":1},{"version":"dba114fb6a32b355a9cfc26ca2276834d72fe0e94cd2c3494005547025015369","impliedFormat":1},{"version":"903e299a28282fa7b714586e28409ed73c3b63f5365519776bf78e8cf173db36","affectsGlobalScope":true,"impliedFormat":1},{"version":"fa6c12a7c0f6b84d512f200690bfc74819e99efae69e4c95c4cd30f6884c526e","impliedFormat":1},{"version":"f1c32f9ce9c497da4dc215c3bc84b722ea02497d35f9134db3bb40a8d918b92b","impliedFormat":1},{"version":"b73c319af2cc3ef8f6421308a250f328836531ea3761823b4cabbd133047aefa","affectsGlobalScope":true,"impliedFormat":1},{"version":"e433b0337b8106909e7953015e8fa3f2d30797cea27141d1c5b135365bb975a6","impliedFormat":1},{"version":"dd3900b24a6a8745efeb7ad27629c0f8a626470ac229c1d73f1fe29d67e44dca","impliedFormat":1},{"version":"ddff7fc6edbdc5163a09e22bf8df7bef75f75369ebd7ecea95ba55c4386e2441","impliedFormat":1},{"version":"106c6025f1d99fd468fd8bf6e5bda724e11e5905a4076c5d29790b6c3745e50c","impliedFormat":1},{"version":"ec29be0737d39268696edcec4f5e97ce26f449fa9b7afc2f0f99a86def34a418","impliedFormat":1},{"version":"aeab39e8e0b1a3b250434c3b2bb8f4d17bbec2a9dbce5f77e8a83569d3d2cbc2","impliedFormat":1},{"version":"ec6cba1c02c675e4dd173251b156792e8d3b0c816af6d6ad93f1a55d674591aa","impliedFormat":1},{"version":"b620391fe8060cf9bedc176a4d01366e6574d7a71e0ac0ab344a4e76576fcbb8","impliedFormat":1},{"version":"d729408dfde75b451530bcae944cf89ee8277e2a9df04d1f62f2abfd8b03c1e1","impliedFormat":1},{"version":"e15d3c84d5077bb4a3adee4c791022967b764dc41cb8fa3cfa44d4379b2c95f5","impliedFormat":1},{"version":"5f58e28cd22e8fc1ac1b3bc6b431869f1e7d0b39e2c21fbf79b9fa5195a85980","impliedFormat":1},{"version":"e1fc1a1045db5aa09366be2b330e4ce391550041fc3e925f60998ca0b647aa97","impliedFormat":1},{"version":"63533978dcda286422670f6e184ac516805a365fb37a086eeff4309e812f1402","impliedFormat":1},{"version":"43ba4f2fa8c698f5c304d21a3ef596741e8e85a810b7c1f9b692653791d8d97a","impliedFormat":1},{"version":"31fb49ef3aa3d76f0beb644984e01eab0ea222372ea9b49bb6533be5722d756c","impliedFormat":1},{"version":"33cd131e1461157e3e06b06916b5176e7a8ec3fce15a5cfe145e56de744e07d2","impliedFormat":1},{"version":"889ef863f90f4917221703781d9723278db4122d75596b01c429f7c363562b86","impliedFormat":1},{"version":"3556cfbab7b43da96d15a442ddbb970e1f2fc97876d055b6555d86d7ac57dae5","impliedFormat":1},{"version":"437751e0352c6e924ddf30e90849f1d9eb00ca78c94d58d6a37202ec84eb8393","impliedFormat":1},{"version":"48e8af7fdb2677a44522fd185d8c87deff4d36ee701ea003c6c780b1407a1397","impliedFormat":1},{"version":"d11308de5a36c7015bb73adb5ad1c1bdaac2baede4cc831a05cf85efa3cc7f2f","impliedFormat":1},{"version":"38e4684c22ed9319beda6765bab332c724103d3a966c2e5e1c5a49cf7007845f","impliedFormat":1},{"version":"f9812cfc220ecf7557183379531fa409acd249b9e5b9a145d0d52b76c20862de","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a403c4aeeb153bc0c1f11458d005f8e5a0af3535c4c93eedc6f7865a3593f8e","impliedFormat":1},{"version":"2e4f37ffe8862b14d8e24ae8763daaa8340c0df0b859d9a9733def0eee7562d9","impliedFormat":1},{"version":"13283350547389802aa35d9f2188effaeac805499169a06ef5cd77ce2a0bd63f","impliedFormat":1},{"version":"680793958f6a70a44c8d9ae7d46b7a385361c69ac29dcab3ed761edce1c14ab8","impliedFormat":1},{"version":"6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","impliedFormat":1},{"version":"b838d4c72740eb0afd284bf7575b74c624b105eff2e8c7b4aeead57e7ac320ff","impliedFormat":1},{"version":"913ddbba170240070bd5921b8f33ea780021bdf42fbdfcd4fcb2691b1884ddde","impliedFormat":1},{"version":"b4e6d416466999ff40d3fe5ceb95f7a8bfb7ac2262580287ac1a8391e5362431","impliedFormat":1},{"version":"5fe23bd829e6be57d41929ac374ee9551ccc3c44cee893167b7b5b77be708014","impliedFormat":1},{"version":"0a626484617019fcfbfc3c1bc1f9e84e2913f1adb73692aa9075817404fb41a1","impliedFormat":1},{"version":"438c7513b1df91dcef49b13cd7a1c4720f91a36e88c1df731661608b7c055f10","impliedFormat":1},{"version":"cf185cc4a9a6d397f416dd28cca95c227b29f0f27b160060a95c0e5e36cda865","impliedFormat":1},{"version":"0086f3e4ad898fd7ca56bb223098acfacf3fa065595182aaf0f6c4a6a95e6fbd","impliedFormat":1},{"version":"efaa078e392f9abda3ee8ade3f3762ab77f9c50b184e6883063a911742a4c96a","impliedFormat":1},{"version":"54a8bb487e1dc04591a280e7a673cdfb272c83f61e28d8a64cf1ac2e63c35c51","impliedFormat":1},{"version":"021a9498000497497fd693dd315325484c58a71b5929e2bbb91f419b04b24cea","impliedFormat":1},{"version":"9385cdc09850950bc9b59cca445a3ceb6fcca32b54e7b626e746912e489e535e","impliedFormat":1},{"version":"2894c56cad581928bb37607810af011764a2f511f575d28c9f4af0f2ef02d1ab","impliedFormat":1},{"version":"0a72186f94215d020cb386f7dca81d7495ab6c17066eb07d0f44a5bf33c1b21a","impliedFormat":1},{"version":"84124384abae2f6f66b7fbfc03862d0c2c0b71b826f7dbf42c8085d31f1d3f95","impliedFormat":1},{"version":"63a8e96f65a22604eae82737e409d1536e69a467bb738bec505f4f97cce9d878","impliedFormat":1},{"version":"3fd78152a7031315478f159c6a5872c712ece6f01212c78ea82aef21cb0726e2","impliedFormat":1},{"version":"250f9a1f11580b6b8a0a86835946f048eb605b3a596196741bfe72dc8f6c69cc","impliedFormat":1},{"version":"512fc15cca3a35b8dbbf6e23fe9d07e6f87ad03c895acffd3087ce09f352aad0","impliedFormat":1},{"version":"9a0946d15a005832e432ea0cd4da71b57797efb25b755cc07f32274296d62355","impliedFormat":1},{"version":"a52ff6c0a149e9f370372fc3c715d7f2beee1f3bab7980e271a7ab7d313ec677","impliedFormat":1},{"version":"fd933f824347f9edd919618a76cdb6a0c0085c538115d9a287fa0c7f59957ab3","impliedFormat":1},{"version":"6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","impliedFormat":1},{"version":"6a1aa3e55bdc50503956c5cd09ae4cd72e3072692d742816f65c66ca14f4dfdd","impliedFormat":1},{"version":"ab75cfd9c4f93ffd601f7ca1753d6a9d953bbedfbd7a5b3f0436ac8a1de60dfa","impliedFormat":1},{"version":"f95180f03d827525ca4f990f49e17ec67198c316dd000afbe564655141f725cd","impliedFormat":1},{"version":"b73cbf0a72c8800cf8f96a9acfe94f3ad32ca71342a8908b8ae484d61113f647","impliedFormat":1},{"version":"bae6dd176832f6423966647382c0d7ba9e63f8c167522f09a982f086cd4e8b23","impliedFormat":1},{"version":"1364f64d2fb03bbb514edc42224abd576c064f89be6a990136774ecdd881a1da","impliedFormat":1},{"version":"c9958eb32126a3843deedda8c22fb97024aa5d6dd588b90af2d7f2bfac540f23","impliedFormat":1},{"version":"950fb67a59be4c2dbe69a5786292e60a5cb0e8612e0e223537784c731af55db1","impliedFormat":1},{"version":"e927c2c13c4eaf0a7f17e6022eee8519eb29ef42c4c13a31e81a611ab8c95577","impliedFormat":1},{"version":"07ca44e8d8288e69afdec7a31fa408ce6ab90d4f3d620006701d5544646da6aa","impliedFormat":1},{"version":"70246ad95ad8a22bdfe806cb5d383a26c0c6e58e7207ab9c431f1cb175aca657","impliedFormat":1},{"version":"f00f3aa5d64ff46e600648b55a79dcd1333458f7a10da2ed594d9f0a44b76d0b","impliedFormat":1},{"version":"772d8d5eb158b6c92412c03228bd9902ccb1457d7a705b8129814a5d1a6308fc","impliedFormat":1},{"version":"4e4475fba4ed93a72f167b061cd94a2e171b82695c56de9899275e880e06ba41","impliedFormat":1},{"version":"97c5f5d580ab2e4decd0a3135204050f9b97cd7908c5a8fbc041eadede79b2fa","impliedFormat":1},{"version":"c99a3a5f2215d5b9d735aa04cec6e61ed079d8c0263248e298ffe4604d4d0624","impliedFormat":1},{"version":"49b2375c586882c3ac7f57eba86680ff9742a8d8cb2fe25fe54d1b9673690d41","impliedFormat":1},{"version":"802e797bcab5663b2c9f63f51bdf67eff7c41bc64c0fd65e6da3e7941359e2f7","impliedFormat":1},{"version":"9ff1e8df66450af44161c1bfe34bc92c43074cfeec7a0a75f721830e9aabe379","impliedFormat":1},{"version":"3ecfccf916fea7c6c34394413b55eb70e817a73e39b4417d6573e523784e3f8e","impliedFormat":1},{"version":"1630192eac4188881201c64522cd3ef08209d9c4db0f9b5f0889b703dc6d936a","impliedFormat":1},{"version":"6459054aabb306821a043e02b89d54da508e3a6966601a41e71c166e4ea1474f","impliedFormat":1},{"version":"f416c9c3eee9d47ff49132c34f96b9180e50485d435d5748f0e8b72521d28d2e","impliedFormat":1},{"version":"05c97cddbaf99978f83d96de2d8af86aded9332592f08ce4a284d72d0952c391","impliedFormat":1},{"version":"14e5cdec6f8ae82dfd0694e64903a0a54abdfe37e1d966de3d4128362acbf35f","impliedFormat":1},{"version":"bbc183d2d69f4b59fd4dd8799ffdf4eb91173d1c4ad71cce91a3811c021bf80c","impliedFormat":1},{"version":"7b6ff760c8a240b40dab6e4419b989f06a5b782f4710d2967e67c695ef3e93c4","impliedFormat":1},{"version":"8dbc4134a4b3623fc476be5f36de35c40f2768e2e3d9ed437e0d5f1c4cd850f6","impliedFormat":1},{"version":"4e06330a84dec7287f7ebdd64978f41a9f70a668d3b5edc69d5d4a50b9b376bb","impliedFormat":1},{"version":"65bfa72967fbe9fc33353e1ac03f0480aa2e2ea346d61ff3ea997dfd850f641a","impliedFormat":1},{"version":"c06f0bb92d1a1a5a6c6e4b5389a5664d96d09c31673296cb7da5fe945d54d786","impliedFormat":1},{"version":"f974e4a06953682a2c15d5bd5114c0284d5abf8bc0fe4da25cb9159427b70072","impliedFormat":1},{"version":"872caaa31423f4345983d643e4649fb30f548e9883a334d6d1c5fff68ede22d4","impliedFormat":1},{"version":"94404c4a878fe291e7578a2a80264c6f18e9f1933fbb57e48f0eb368672e389c","impliedFormat":1},{"version":"5c1b7f03aa88be854bc15810bfd5bd5a1943c5a7620e1c53eddd2a013996343e","impliedFormat":1},{"version":"09dfc64fcd6a2785867f2368419859a6cc5a8d4e73cbe2538f205b1642eb0f51","impliedFormat":1},{"version":"bcf6f0a323653e72199105a9316d91463ad4744c546d1271310818b8cef7c608","impliedFormat":1},{"version":"01aa917531e116485beca44a14970834687b857757159769c16b228eb1e49c5f","impliedFormat":1},{"version":"351475f9c874c62f9b45b1f0dc7e2704e80dfd5f1af83a3a9f841f9dfe5b2912","impliedFormat":1},{"version":"ac457ad39e531b7649e7b40ee5847606eac64e236efd76c5d12db95bf4eacd17","impliedFormat":1},{"version":"187a6fdbdecb972510b7555f3caacb44b58415da8d5825d03a583c4b73fde4cf","impliedFormat":1},{"version":"d4c3250105a612202289b3a266bb7e323db144f6b9414f9dea85c531c098b811","impliedFormat":1},{"version":"95b444b8c311f2084f0fb51c616163f950fb2e35f4eaa07878f313a2d36c98a4","impliedFormat":1},{"version":"741067675daa6d4334a2dc80a4452ca3850e89d5852e330db7cb2b5f867173b1","impliedFormat":1},{"version":"f8acecec1114f11690956e007d920044799aefeb3cece9e7f4b1f8a1d542b2c9","impliedFormat":1},{"version":"178071ccd043967a58c5d1a032db0ddf9bd139e7920766b537d9783e88eb615e","impliedFormat":1},{"version":"3a17f09634c50cce884721f54fd9e7b98e03ac505889c560876291fcf8a09e90","impliedFormat":1},{"version":"32531dfbb0cdc4525296648f53b2b5c39b64282791e2a8c765712e49e6461046","impliedFormat":1},{"version":"0ce1b2237c1c3df49748d61568160d780d7b26693bd9feb3acb0744a152cd86d","impliedFormat":1},{"version":"e489985388e2c71d3542612685b4a7db326922b57ac880f299da7026a4e8a117","impliedFormat":1},{"version":"5cad4158616d7793296dd41e22e1257440910ea8d01c7b75045d4dfb20c5a41a","impliedFormat":1},{"version":"04d3aad777b6af5bd000bfc409907a159fe77e190b9d368da4ba649cdc28d39e","affectsGlobalScope":true,"impliedFormat":1},{"version":"74efc1d6523bd57eb159c18d805db4ead810626bc5bc7002a2c7f483044b2e0f","impliedFormat":1},{"version":"19252079538942a69be1645e153f7dbbc1ef56b4f983c633bf31fe26aeac32cd","impliedFormat":1},{"version":"bc11f3ac00ac060462597add171220aed628c393f2782ac75dd29ff1e0db871c","impliedFormat":1},{"version":"616775f16134fa9d01fc677ad3f76e68c051a056c22ab552c64cc281a9686790","impliedFormat":1},{"version":"65c24a8baa2cca1de069a0ba9fba82a173690f52d7e2d0f1f7542d59d5eb4db0","impliedFormat":1},{"version":"f9fe6af238339a0e5f7563acee3178f51db37f32a2e7c09f85273098cee7ec49","impliedFormat":1},{"version":"3b0b1d352b8d2e47f1c4df4fb0678702aee071155b12ef0185fce9eb4fa4af1e","impliedFormat":1},{"version":"77e71242e71ebf8528c5802993697878f0533db8f2299b4d36aa015bae08a79c","impliedFormat":1},{"version":"a344403e7a7384e0e7093942533d309194ad0a53eca2a3100c0b0ab4d3932773","impliedFormat":1},{"version":"b7fff2d004c5879cae335db8f954eb1d61242d9f2d28515e67902032723caeab","impliedFormat":1},{"version":"5f3dc10ae646f375776b4e028d2bed039a93eebbba105694d8b910feebbe8b9c","impliedFormat":1},{"version":"bb18bf4a61a17b4a6199eb3938ecfa4a59eb7c40843ad4a82b975ab6f7e3d925","impliedFormat":1},{"version":"4545c1a1ceca170d5d83452dd7c4994644c35cf676a671412601689d9a62da35","impliedFormat":1},{"version":"e9b6fc05f536dfddcdc65dbcf04e09391b1c968ab967382e48924f5cb90d88e1","impliedFormat":1},{"version":"a2d648d333cf67b9aeac5d81a1a379d563a8ffa91ddd61c6179f68de724260ff","impliedFormat":1},{"version":"2b664c3cc544d0e35276e1fb2d4989f7d4b4027ffc64da34ec83a6ccf2e5c528","impliedFormat":1},{"version":"a3f41ed1b4f2fc3049394b945a68ae4fdefd49fa1739c32f149d32c0545d67f5","impliedFormat":1},{"version":"3cd8f0464e0939b47bfccbb9bb474a6d87d57210e304029cd8eb59c63a81935d","impliedFormat":1},{"version":"47699512e6d8bebf7be488182427189f999affe3addc1c87c882d36b7f2d0b0e","impliedFormat":1},{"version":"3026abd48e5e312f2328629ede6e0f770d21c3cd32cee705c450e589d015ee09","impliedFormat":1},{"version":"8b140b398a6afbd17cc97c38aea5274b2f7f39b1ae5b62952cfe65bf493e3e75","impliedFormat":1},{"version":"7663d2c19ce5ef8288c790edba3d45af54e58c84f1b37b1249f6d49d962f3d91","impliedFormat":1},{"version":"5cce3b975cdb72b57ae7de745b3c5de5790781ee88bcb41ba142f07c0fa02e97","impliedFormat":1},{"version":"00bd6ebe607246b45296aa2b805bd6a58c859acecda154bfa91f5334d7c175c6","impliedFormat":1},{"version":"ad036a85efcd9e5b4f7dd5c1a7362c8478f9a3b6c3554654ca24a29aa850a9c5","impliedFormat":1},{"version":"fedebeae32c5cdd1a85b4e0504a01996e4a8adf3dfa72876920d3dd6e42978e7","impliedFormat":1},{"version":"0d28b974a7605c4eda20c943b3fa9ae16cb452c1666fc9b8c341b879992c7612","impliedFormat":1},{"version":"cdf21eee8007e339b1b9945abf4a7b44930b1d695cc528459e68a3adc39a622e","impliedFormat":1},{"version":"db036c56f79186da50af66511d37d9fe77fa6793381927292d17f81f787bb195","impliedFormat":1},{"version":"87ac2fb61e629e777f4d161dff534c2023ee15afd9cb3b1589b9b1f014e75c58","impliedFormat":1},{"version":"13c8b4348db91e2f7d694adc17e7438e6776bc506d5c8f5de9ad9989707fa3fe","impliedFormat":1},{"version":"3c1051617aa50b38e9efaabce25e10a5dd9b1f42e372ef0e8a674076a68742ed","impliedFormat":1},{"version":"07a3e20cdcb0f1182f452c0410606711fbea922ca76929a41aacb01104bc0d27","impliedFormat":1},{"version":"1de80059b8078ea5749941c9f863aa970b4735bdbb003be4925c853a8b6b4450","impliedFormat":1},{"version":"1d079c37fa53e3c21ed3fa214a27507bda9991f2a41458705b19ed8c2b61173d","impliedFormat":1},{"version":"4cd4b6b1279e9d744a3825cbd7757bbefe7f0708f3f1069179ad535f19e8ed2c","impliedFormat":1},{"version":"5835a6e0d7cd2738e56b671af0e561e7c1b4fb77751383672f4b009f4e161d70","impliedFormat":1},{"version":"c0eeaaa67c85c3bb6c52b629ebbfd3b2292dc67e8c0ffda2fc6cd2f78dc471e6","impliedFormat":1},{"version":"4b7f74b772140395e7af67c4841be1ab867c11b3b82a51b1aeb692822b76c872","impliedFormat":1},{"version":"27be6622e2922a1b412eb057faa854831b95db9db5035c3f6d4b677b902ab3b7","impliedFormat":1},{"version":"b95a6f019095dd1d48fd04965b50dfd63e5743a6e75478343c46d2582a5132bf","impliedFormat":99},{"version":"c2008605e78208cfa9cd70bd29856b72dda7ad89df5dc895920f8e10bcb9cd0a","impliedFormat":99},{"version":"b97cb5616d2ab82a98ec9ada7b9e9cabb1f5da880ec50ea2b8dc5baa4cbf3c16","impliedFormat":99},{"version":"d23df9ff06ae8bf1dcb7cc933e97ae7da418ac77749fecee758bb43a8d69f840","affectsGlobalScope":true,"impliedFormat":1},{"version":"040c71dde2c406f869ad2f41e8d4ce579cc60c8dbe5aa0dd8962ac943b846572","affectsGlobalScope":true,"impliedFormat":1},{"version":"3586f5ea3cc27083a17bd5c9059ede9421d587286d5a47f4341a4c2d00e4fa91","impliedFormat":1},{"version":"a6df929821e62f4719551f7955b9f42c0cd53c1370aec2dd322e24196a7dfe33","impliedFormat":1},{"version":"b789bf89eb19c777ed1e956dbad0925ca795701552d22e68fd130a032008b9f9","impliedFormat":1},"e462a655754db9df18b4a657454a7b6a88717ffded4e89403b2b3a47c6603fc3",{"version":"402e5c534fb2b85fa771170595db3ac0dd532112c8fa44fc23f233bc6967488b","impliedFormat":1},{"version":"52dcc257df5119fb66d864625112ce5033ac51a4c2afe376a0b299d2f7f76e4a","impliedFormat":1},{"version":"e5bab5f871ef708d52d47b3e5d0aa72a08ee7a152f33931d9a60809711a2a9a3","impliedFormat":1},{"version":"e16dc2a81595736024a206c7d5c8a39bfe2e6039208ef29981d0d95434ba8fcf","impliedFormat":1},{"version":"cc4a4903fb698ca1d961d4c10dce658aa3a479faf40509d526f122b044eaf6a4","impliedFormat":1},{"version":"19ee8416e6473ed6c7adb868fa796b5653cf0fa2a337658e677eaa0d134388c3","impliedFormat":1},{"version":"1328ab4e442614b28cdb3d4b414cf68325c0da0dca07287a338d0654b7a00261","impliedFormat":1},{"version":"a039dc21f045919f3cbee2ec13812cc6cc3eebc99dae4be00973230f468d19a6","impliedFormat":1},{"version":"3fbe57af01460e49dcd29df55d6931e1672bc6f1be0fb073d11410bc16f9037d","impliedFormat":1},{"version":"f760be449e8562ec5c09bb5187e8e1eabf3c113c0c58cddda53ef8c69f3e2131","impliedFormat":1},{"version":"44325ed13294fce6ab825b82947bbeed2611db7dad9d9135260192f375e5a189","impliedFormat":1},{"version":"e392e8fb5b514eafc585601c1d781485aa6dd6a320e75daf1064a4c6918a1b45","impliedFormat":1},{"version":"46e4a36e8ddbdfb4e7330e11c81c970dc8b218611df9183d39c41c5f8c653b55","impliedFormat":1},{"version":"370bde134aa8c2abc926d0e99d3a4d5d5dba65c6ee65459137e4f02670cbf841","impliedFormat":1},{"version":"6332f565867cf4a740a70e30f31cefba37ef7cebcf74f22eab8d744fde6d193e","impliedFormat":1},{"version":"2977b7884aedc895a1d0c9c210c7cf3272c29d6959a08a6fa3ff71e0aff08175","impliedFormat":1},{"version":"17f2922d41ddd032830a91371c948cd9ce903b35c95adca72271a54584f19b0b","impliedFormat":1},{"version":"3eed76ede2a1a14d7c9bb0a642041282dcc264811139d3dd275c9fe14efc9840","impliedFormat":1},{"version":"354a7f8e1287d9d6b7561bc97fdd8cbc2f7c1dd79e4cb37b942e8a5cfaff1085","impliedFormat":1},{"version":"8d369483f0c2b9ee388129cfdb6a43bc8112b377e86a41884bd06e19ce04f4c1","impliedFormat":99},{"version":"b558c9a18ea4e6e4157124465c3ef1063e64640da139e67be5edb22f534f2f08","impliedFormat":1},{"version":"01374379f82be05d25c08d2f30779fa4a4c41895a18b93b33f14aeef51768692","impliedFormat":1},{"version":"b0dee183d4e65cf938242efaf3d833c6b645afb35039d058496965014f158141","impliedFormat":1},{"version":"c0bbbf84d3fbd85dd60d040c81e8964cc00e38124a52e9c5dcdedf45fea3f213","impliedFormat":1},"5cb1f6233b663d15718ee15cbd8fa6fc86dae904eb314c51bf8c07df380399c1","45e7ea5c3ce16965b6c5b1985ec30ba25757ffef079cc7dc5393ed81558db241","325a0ad16812f06c25165da33a05623e9744bfc212ecab54abb41072817900d1","4157eace53495541355dde73617ffbc78431a83f78dd25027b4dabab779ac7b9","89f77d8cf1e5a32271edc16f71a42de9895464d4118a2d39fd91051a0d5c030b","8cac3be7fee26041174e8cf6846ed54707880256949d0b05b899692a9b40b9e3","54447c7f44dce922e3d3fdf2ea274907d171d04a32633ab9ae146d633b179e0a","8b35a29875189c425721ffa013c663577db62fe5a0408c3066b869ff8e8a0a3d"],"root":[391,[416,418],[420,423]],"options":{"allowJs":true,"declaration":true,"declarationMap":true,"esModuleInterop":true,"jsx":1,"module":99,"noUncheckedIndexedAccess":true,"skipLibCheck":true,"strict":true,"target":9},"referencedMap":[[423,1],[421,2],[422,3],[391,4],[417,5],[418,6],[420,7],[416,8],[344,9],[122,10],[123,10],[124,11],[76,12],[125,13],[126,14],[127,15],[71,9],[74,16],[72,9],[73,9],[128,17],[129,18],[130,19],[131,20],[132,21],[133,22],[134,22],[135,23],[136,24],[137,25],[138,26],[77,9],[75,9],[139,27],[140,28],[141,29],[175,30],[142,31],[143,9],[144,32],[145,33],[146,34],[147,35],[148,36],[149,37],[150,38],[151,39],[152,40],[153,40],[154,41],[155,9],[156,42],[157,43],[159,44],[158,45],[160,46],[161,47],[162,48],[163,49],[164,50],[165,51],[166,52],[167,53],[168,54],[169,55],[170,56],[171,57],[172,58],[78,9],[79,9],[80,9],[118,59],[119,9],[120,9],[121,46],[173,60],[174,61],[63,9],[180,62],[181,63],[179,64],[177,65],[178,66],[61,9],[64,67],[268,64],[62,9],[70,68],[347,69],[351,70],[353,71],[201,72],[215,73],[318,74],[247,9],[321,75],[283,76],[291,77],[319,78],[202,79],[246,9],[248,80],[320,81],[222,82],[203,83],[227,82],[216,82],[186,82],[274,84],[275,85],[191,9],[271,86],[276,87],[362,88],[269,87],[363,89],[253,9],[272,90],[375,91],[374,92],[278,87],[373,9],[371,9],[372,93],[273,64],[260,94],[261,95],[270,96],[286,97],[287,98],[277,99],[255,100],[256,101],[366,102],[369,103],[234,104],[233,105],[232,106],[378,64],[231,107],[207,9],[381,9],[384,9],[383,64],[385,108],[182,9],[312,9],[214,109],[184,110],[335,9],[336,9],[338,9],[341,111],[337,9],[339,112],[340,112],[200,9],[213,9],[346,113],[354,114],[358,115],[196,116],[263,117],[262,9],[254,100],[282,118],[280,119],[279,9],[281,9],[285,120],[258,121],[195,122],[220,123],[309,124],[187,125],[194,126],[183,74],[323,127],[333,128],[322,9],[332,129],[221,9],[205,130],[300,131],[299,9],[306,132],[308,133],[301,134],[305,135],[307,132],[304,134],[303,132],[302,134],[243,136],[228,136],[294,137],[229,137],[189,138],[188,9],[298,139],[297,140],[296,141],[295,142],[190,143],[267,144],[284,145],[266,146],[290,147],[292,148],[289,146],[223,143],[176,9],[310,149],[249,150],[331,151],[252,152],[326,153],[193,9],[327,154],[329,155],[330,156],[313,9],[325,125],[225,157],[311,158],[334,159],[197,9],[199,9],[204,160],[293,161],[192,162],[198,9],[251,163],[250,164],[206,165],[259,166],[257,167],[208,168],[210,169],[382,9],[209,170],[211,171],[349,9],[348,9],[350,9],[380,9],[212,172],[265,64],[69,9],[288,173],[235,9],[245,174],[224,9],[356,64],[365,175],[242,64],[360,87],[241,176],[343,177],[240,175],[185,9],[367,178],[238,64],[239,64],[230,9],[244,9],[237,179],[236,180],[226,181],[219,99],[328,9],[218,182],[217,9],[352,9],[264,64],[345,183],[60,9],[68,184],[65,64],[66,9],[67,9],[324,185],[317,186],[316,9],[315,187],[314,9],[355,188],[357,189],[359,190],[361,191],[364,192],[390,193],[368,193],[389,194],[370,195],[376,196],[377,197],[379,198],[386,199],[388,9],[387,200],[342,201],[408,202],[406,203],[407,204],[395,205],[396,203],[403,206],[394,207],[399,208],[409,9],[400,209],[405,210],[411,211],[410,212],[393,213],[401,214],[402,215],[397,216],[404,202],[398,217],[392,9],[414,218],[413,9],[412,9],[415,219],[58,9],[59,9],[10,9],[11,9],[13,9],[12,9],[2,9],[14,9],[15,9],[16,9],[17,9],[18,9],[19,9],[20,9],[21,9],[3,9],[22,9],[23,9],[4,9],[24,9],[28,9],[25,9],[26,9],[27,9],[29,9],[30,9],[31,9],[5,9],[32,9],[33,9],[34,9],[35,9],[6,9],[39,9],[36,9],[37,9],[38,9],[40,9],[7,9],[41,9],[46,9],[47,9],[42,9],[43,9],[44,9],[45,9],[8,9],[51,9],[48,9],[49,9],[50,9],[52,9],[9,9],[53,9],[54,9],[55,9],[57,9],[56,9],[1,9],[96,220],[106,221],[95,220],[116,222],[87,223],[86,224],[115,200],[109,225],[114,226],[89,227],[103,228],[88,229],[112,230],[84,231],[83,200],[113,232],[85,233],[90,234],[91,9],[94,234],[81,9],[117,235],[107,236],[98,237],[99,238],[101,239],[97,240],[100,241],[110,200],[92,242],[93,243],[102,244],[82,245],[105,236],[104,234],[108,9],[111,246],[419,64]],"affectedFilesPendingEmit":[[423,49],[421,49],[422,49],[417,49],[418,49],[420,49],[416,49]],"version":"5.9.3"} \ No newline at end of file diff --git a/config/eslint-config/index.js b/config/eslint-config/index.js new file mode 100644 index 0000000..d1481ac --- /dev/null +++ b/config/eslint-config/index.js @@ -0,0 +1,27 @@ +/** Base ESLint config shared across the monorepo. */ +module.exports = { + root: true, + env: { es2022: true, node: true }, + parser: "@typescript-eslint/parser", + parserOptions: { ecmaVersion: "latest", sourceType: "module" }, + plugins: ["@typescript-eslint"], + extends: [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "prettier", + ], + ignorePatterns: [ + "node_modules/", + "dist/", + ".next/", + ".turbo/", + "coverage/", + ], + rules: { + "@typescript-eslint/no-unused-vars": [ + "warn", + { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, + ], + "@typescript-eslint/no-explicit-any": "warn", + }, +}; diff --git a/config/eslint-config/next.js b/config/eslint-config/next.js new file mode 100644 index 0000000..960fe5c --- /dev/null +++ b/config/eslint-config/next.js @@ -0,0 +1,8 @@ +/** ESLint config for Next.js apps. next/core-web-vitals already pulls in + * react + react-hooks rules, so this extends the base config only — + * layering react-library.js on top double-registers the react-hooks + * plugin and breaks lint with a "Plugin conflicted" error. */ +module.exports = { + extends: ["./index.js", "next/core-web-vitals"], + env: { browser: true }, +}; diff --git a/config/eslint-config/package.json b/config/eslint-config/package.json new file mode 100644 index 0000000..9632130 --- /dev/null +++ b/config/eslint-config/package.json @@ -0,0 +1,15 @@ +{ + "name": "@ai-rxos/eslint-config", + "version": "0.1.0", + "private": true, + "license": "MIT", + "main": "index.js", + "files": ["index.js", "next.js", "react-library.js"], + "dependencies": { + "@typescript-eslint/eslint-plugin": "^8.19.0", + "@typescript-eslint/parser": "^8.19.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-react": "^7.37.2", + "eslint-plugin-react-hooks": "^5.1.0" + } +} diff --git a/config/eslint-config/react-library.js b/config/eslint-config/react-library.js new file mode 100644 index 0000000..00f4776 --- /dev/null +++ b/config/eslint-config/react-library.js @@ -0,0 +1,6 @@ +/** ESLint config for React component packages. */ +module.exports = { + extends: ["./index.js", "plugin:react/recommended", "plugin:react-hooks/recommended"], + settings: { react: { version: "detect" } }, + env: { browser: true }, +}; diff --git a/config/typescript-config/base.json b/config/typescript-config/base.json new file mode 100644 index 0000000..f474197 --- /dev/null +++ b/config/typescript-config/base.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022"], + "module": "ESNext", + "moduleResolution": "Bundler", + "moduleDetection": "force", + "strict": true, + "noUncheckedIndexedAccess": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "isolatedModules": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true + }, + "exclude": ["node_modules", "dist", ".next", ".turbo"] +} diff --git a/config/typescript-config/nextjs.json b/config/typescript-config/nextjs.json new file mode 100644 index 0000000..6e8a05a --- /dev/null +++ b/config/typescript-config/nextjs.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./base.json", + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["dom", "dom.iterable", "ES2022"], + "jsx": "preserve", + "noEmit": true, + "allowJs": true, + "incremental": true, + "plugins": [{ "name": "next" }] + } +} diff --git a/config/typescript-config/package.json b/config/typescript-config/package.json new file mode 100644 index 0000000..3aee256 --- /dev/null +++ b/config/typescript-config/package.json @@ -0,0 +1,7 @@ +{ + "name": "@ai-rxos/typescript-config", + "version": "0.1.0", + "private": true, + "license": "MIT", + "files": ["base.json", "nextjs.json", "react-library.json"] +} diff --git a/config/typescript-config/react-library.json b/config/typescript-config/react-library.json new file mode 100644 index 0000000..c1bfb8e --- /dev/null +++ b/config/typescript-config/react-library.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./base.json", + "compilerOptions": { + "lib": ["dom", "dom.iterable", "ES2022"], + "jsx": "react-jsx", + "declaration": true + } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ec3d91d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,226 @@ +name: ai-rxos + +x-py-service: &py-service + env_file: .env + networks: [ai-rxos] + restart: unless-stopped + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + +networks: + ai-rxos: + driver: bridge + +volumes: + postgres-data: + neo4j-data: + redis-data: + opensearch-data: + +services: + # ── Data stores ────────────────────────────────────────────────────── + postgres: + image: pgvector/pgvector:pg16 + environment: + POSTGRES_DB: ${POSTGRES_DB:-ai_rxos} + POSTGRES_USER: ${POSTGRES_USER:-ai_rxos} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-changeme} + ports: ["5432:5432"] + volumes: + - postgres-data:/var/lib/postgresql/data + - ./infra/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql:ro + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-ai_rxos}"] + interval: 5s + timeout: 5s + retries: 10 + networks: [ai-rxos] + restart: unless-stopped + + neo4j: + image: neo4j:5.26-community + environment: + NEO4J_AUTH: ${NEO4J_USER:-neo4j}/${NEO4J_PASSWORD:-changeme_neo4j} + NEO4J_server_memory_pagecache_size: 512M + NEO4J_server_memory_heap_max__size: 1G + ports: + - "7474:7474" # browser + - "7687:7687" # bolt + volumes: + - neo4j-data:/data + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://localhost:7474 || exit 1"] + interval: 10s + timeout: 5s + retries: 10 + networks: [ai-rxos] + restart: unless-stopped + + redis: + image: redis:7-alpine + ports: ["6379:6379"] + volumes: + - redis-data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 5s + retries: 10 + networks: [ai-rxos] + restart: unless-stopped + + opensearch: + image: opensearchproject/opensearch:2.19.1 + environment: + discovery.type: single-node + DISABLE_SECURITY_PLUGIN: "false" + OPENSEARCH_INITIAL_ADMIN_PASSWORD: ${OPENSEARCH_PASSWORD:-AiRxOS#Search9K} + OPENSEARCH_JAVA_OPTS: "-Xms512m -Xmx512m" + ports: + - "9200:9200" + volumes: + - opensearch-data:/usr/share/opensearch/data + healthcheck: + test: ["CMD-SHELL", "curl -sku admin:${OPENSEARCH_PASSWORD:-AiRxOS#Search9K} https://localhost:9200/_cluster/health || exit 1"] + interval: 10s + timeout: 5s + retries: 15 + networks: [ai-rxos] + restart: unless-stopped + + # ── Go services ────────────────────────────────────────────────────── + auth: + build: { context: ., dockerfile: services/auth/Dockerfile } + env_file: .env + environment: + PORT: "8081" + ports: ["8081:8081"] + depends_on: + postgres: { condition: service_healthy } + redis: { condition: service_healthy } + networks: [ai-rxos] + restart: unless-stopped + + search: + build: { context: ., dockerfile: services/search/Dockerfile } + env_file: .env + environment: + PORT: "8084" + ports: ["8084:8084"] + depends_on: + postgres: { condition: service_healthy } + opensearch: { condition: service_healthy } + networks: [ai-rxos] + restart: unless-stopped + + # BetterAuth adapter scaffold (see services/auth-adapter/README.md). + # Additive: `auth` above is unchanged and still owns /api/v1/auth/*. + # Not in api-gateway's depends_on/proxy — not routed to yet. + auth-adapter: + build: { context: ., dockerfile: services/auth-adapter/Dockerfile } + env_file: .env + environment: + PORT: "8089" + BETTER_AUTH_URL: "http://localhost:8089" + ports: ["8089:8089"] + depends_on: + postgres: { condition: service_healthy } + networks: [ai-rxos] + restart: unless-stopped + + api-gateway: + build: { context: ., dockerfile: apps/api-gateway/Dockerfile } + env_file: .env + environment: + PORT: "8080" + ports: ["8080:8080"] + depends_on: [auth, search, literature, kg, agents, workflows, reports, docking, ai-services, knowledge-service] + networks: [ai-rxos] + restart: unless-stopped + + # ── Python services ────────────────────────────────────────────────── + literature: + <<: *py-service + build: { context: ., dockerfile: services/literature/Dockerfile } + environment: + PORT: "8082" + ports: ["8082:8082"] + + kg: + <<: *py-service + build: { context: ., dockerfile: services/kg/Dockerfile } + environment: + PORT: "8083" + ports: ["8083:8083"] + depends_on: + neo4j: { condition: service_healthy } + + agents: + <<: *py-service + build: { context: ., dockerfile: services/agents/Dockerfile } + environment: + PORT: "8085" + ports: ["8085:8085"] + + workflows: + <<: *py-service + build: { context: ., dockerfile: services/workflows/Dockerfile } + environment: + PORT: "8086" + ports: ["8086:8086"] + + reports: + <<: *py-service + build: { context: ., dockerfile: services/reports/Dockerfile } + environment: + PORT: "8087" + ports: ["8087:8087"] + + docking: + <<: *py-service + build: { context: ., dockerfile: services/docking/Dockerfile } + environment: + PORT: "8088" + ports: ["8088:8088"] + + ai-services: + <<: *py-service + build: { context: ., dockerfile: apps/ai-services/Dockerfile } + environment: + PORT: "8090" + ports: ["8090:8090"] + + knowledge-service: + <<: *py-service + build: { context: ., dockerfile: apps/knowledge-service/Dockerfile } + environment: + PORT: "8091" + ports: ["8091:8091"] + depends_on: + neo4j: { condition: service_healthy } + + # ── Frontends ──────────────────────────────────────────────────────── + web: + build: { context: ., dockerfile: apps/web/Dockerfile } + env_file: .env + environment: + PORT: "3000" + NEXT_PUBLIC_API_BASE_URL: http://localhost:8080 + ports: ["3000:3000"] + depends_on: [api-gateway] + networks: [ai-rxos] + restart: unless-stopped + + admin: + build: { context: ., dockerfile: apps/admin/Dockerfile } + env_file: .env + environment: + PORT: "3001" + NEXT_PUBLIC_API_BASE_URL: http://localhost:8080 + ports: ["3001:3001"] + depends_on: [api-gateway] + networks: [ai-rxos] + restart: unless-stopped diff --git a/infra/README.md b/infra/README.md new file mode 100644 index 0000000..090888e --- /dev/null +++ b/infra/README.md @@ -0,0 +1,31 @@ +# infra + +- `k8s/` — raw cluster-bootstrap manifests (namespaces, baseline network + policies). Apply once per cluster, before any Helm release: + `kubectl apply -f infra/k8s/` +- `helm/ai-rxos/` — the umbrella Helm chart that deploys all 13 application + services plus (optionally, per environment) in-cluster PostgreSQL+pgvector, + Neo4j, Redis, and OpenSearch. +- `postgres/init.sql` — first-boot init script for the docker-compose + Postgres container (enables `pgvector`/`uuid-ossp`); Helm's PostgreSQL + dependency handles this via its own image (`pgvector/pgvector:pg16`). + +## Deploying with Helm + +```bash +helm dependency update infra/helm/ai-rxos + +# local/dev cluster (kind, minikube, k3d) — in-cluster dependencies, no ingress +helm install ai-rxos infra/helm/ai-rxos \ + -f infra/helm/ai-rxos/values.yaml \ + -f infra/helm/ai-rxos/values-dev.yaml \ + --create-namespace -n ai-rxos-development + +# production — managed AWS services, external secrets, ingress+TLS +helm install ai-rxos infra/helm/ai-rxos \ + -f infra/helm/ai-rxos/values.yaml \ + -f infra/helm/ai-rxos/values-prod.yaml \ + -n ai-rxos-production +``` + +Update in place with `helm upgrade` using the same `-f` flags. diff --git a/infra/helm/ai-rxos/.helmignore b/infra/helm/ai-rxos/.helmignore new file mode 100644 index 0000000..2f223ec --- /dev/null +++ b/infra/helm/ai-rxos/.helmignore @@ -0,0 +1,7 @@ +# Patterns to ignore when packaging this chart (does NOT apply to charts/ +# dependency archives or Chart.lock — those must ship with the chart). +.git/ +.gitignore +*.orig +*.bak +.DS_Store diff --git a/infra/helm/ai-rxos/Chart.lock b/infra/helm/ai-rxos/Chart.lock new file mode 100644 index 0000000..147468a --- /dev/null +++ b/infra/helm/ai-rxos/Chart.lock @@ -0,0 +1,15 @@ +dependencies: +- name: postgresql + repository: https://charts.bitnami.com/bitnami + version: 16.2.4 +- name: redis + repository: https://charts.bitnami.com/bitnami + version: 20.2.1 +- name: neo4j + repository: https://helm.neo4j.com/neo4j + version: 5.26.28 +- name: opensearch + repository: https://opensearch-project.github.io/helm-charts + version: 2.24.0 +digest: sha256:ed4aeb675649c8cebc78c58a54f9bf02c0ce0ff5ccd91c6f6d44c8ef4519931c +generated: "2026-07-15T21:15:18.410173+05:30" diff --git a/infra/helm/ai-rxos/Chart.yaml b/infra/helm/ai-rxos/Chart.yaml new file mode 100644 index 0000000..a99d5ca --- /dev/null +++ b/infra/helm/ai-rxos/Chart.yaml @@ -0,0 +1,35 @@ +apiVersion: v2 +name: ai-rxos +description: >- + AI-RxOS umbrella Helm chart — deploys all 13 application services + (web, admin, api-gateway, ai-services, knowledge-service, auth, literature, + kg, search, agents, workflows, reports, docking) plus optional in-cluster + PostgreSQL (pgvector), Neo4j, Redis, and OpenSearch dependencies for + dev/staging. Production points config at managed equivalents instead + (see values-prod.yaml). +type: application +version: 0.1.0 +appVersion: "0.1.0" +home: https://github.com/OpenHealthAgents/AI-RxOS +sources: + - https://github.com/OpenHealthAgents/AI-RxOS +maintainers: + - name: AI-RxOS Platform Team + +dependencies: + - name: postgresql + version: "16.2.4" + repository: https://charts.bitnami.com/bitnami + condition: postgresql.enabled + - name: redis + version: "20.2.1" + repository: https://charts.bitnami.com/bitnami + condition: redis.enabled + - name: neo4j + version: "5.26.28" + repository: https://helm.neo4j.com/neo4j + condition: neo4j.enabled + - name: opensearch + version: "2.24.0" + repository: https://opensearch-project.github.io/helm-charts + condition: opensearch.enabled diff --git a/infra/helm/ai-rxos/templates/NOTES.txt b/infra/helm/ai-rxos/templates/NOTES.txt new file mode 100644 index 0000000..c0afc9e --- /dev/null +++ b/infra/helm/ai-rxos/templates/NOTES.txt @@ -0,0 +1,18 @@ +AI-RxOS has been deployed to namespace {{ include "ai-rxos.namespace" . }}. + +Check rollout status: + kubectl get pods -n {{ include "ai-rxos.namespace" . }} -w + +{{- if .Values.ingress.enabled }} + +Once the ingress controller assigns an address, the platform will be reachable at: + Web: https://{{ .Values.ingress.hosts.web }} + Admin: https://{{ .Values.ingress.hosts.admin }} + API: https://{{ .Values.ingress.hosts.api }} +{{- else }} + +Ingress is disabled — use `kubectl port-forward` to reach services, e.g.: + kubectl port-forward -n {{ include "ai-rxos.namespace" . }} svc/{{ include "ai-rxos.fullname" . }}-api-gateway 8080:8080 +{{- end }} + +This release manages: {{ range $name, $svc := .Values.services }}{{ if $svc.enabled }}{{ $name }} {{ end }}{{ end }} diff --git a/infra/helm/ai-rxos/templates/_helpers.tpl b/infra/helm/ai-rxos/templates/_helpers.tpl new file mode 100644 index 0000000..50af534 --- /dev/null +++ b/infra/helm/ai-rxos/templates/_helpers.tpl @@ -0,0 +1,77 @@ +{{/* Chart name, truncated for k8s name limits. */}} +{{- define "ai-rxos.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* Fully qualified app name: - unless fullnameOverride is set. */}} +{{- define "ai-rxos.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{- define "ai-rxos.namespace" -}} +{{- default .Release.Namespace .Values.namespaceOverride -}} +{{- end -}} + +{{- define "ai-rxos.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* Common labels applied to every resource. */}} +{{- define "ai-rxos.labels" -}} +helm.sh/chart: {{ include "ai-rxos.chart" . }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +app.kubernetes.io/part-of: ai-rxos +{{ include "ai-rxos.selectorLabels" . }} +{{- end -}} + +{{/* Selector labels for a specific service, called with (dict "root" $ "service" $svcName). */}} +{{- define "ai-rxos.selectorLabels" -}} +app.kubernetes.io/name: {{ include "ai-rxos.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} + +{{- define "ai-rxos.serviceLabels" -}} +{{ include "ai-rxos.labels" .root }} +app.kubernetes.io/component: {{ .service }} +{{- end -}} + +{{- define "ai-rxos.serviceSelectorLabels" -}} +{{ include "ai-rxos.selectorLabels" .root }} +app.kubernetes.io/component: {{ .service }} +{{- end -}} + +{{- define "ai-rxos.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} +{{- default (include "ai-rxos.fullname" .) .Values.serviceAccount.name -}} +{{- else -}} +{{- default "default" .Values.serviceAccount.name -}} +{{- end -}} +{{- end -}} + +{{/* Name of the Secret to mount — externalSecret.name in prod, or the chart-managed one. */}} +{{- define "ai-rxos.secretName" -}} +{{- if .Values.externalSecret.enabled -}} +{{- .Values.externalSecret.name -}} +{{- else -}} +{{- printf "%s-secrets" (include "ai-rxos.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* Full image reference for a service entry, e.g. (dict "root" $ "svc" $svc). */}} +{{- define "ai-rxos.image" -}} +{{- $registry := .root.Values.global.imageRegistry -}} +{{- if $registry -}} +{{- printf "%s/%s:%s" $registry .svc.image.repository .svc.image.tag -}} +{{- else -}} +{{- printf "%s:%s" .svc.image.repository .svc.image.tag -}} +{{- end -}} +{{- end -}} diff --git a/infra/helm/ai-rxos/templates/configmap.yaml b/infra/helm/ai-rxos/templates/configmap.yaml new file mode 100644 index 0000000..003dd1c --- /dev/null +++ b/infra/helm/ai-rxos/templates/configmap.yaml @@ -0,0 +1,28 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "ai-rxos.fullname" . }}-config + namespace: {{ include "ai-rxos.namespace" . }} + labels: + {{- include "ai-rxos.labels" . | nindent 4 }} +data: + ENVIRONMENT: {{ .Values.global.environment | quote }} + POSTGRES_HOST: {{ .Values.config.postgresHost | quote }} + POSTGRES_PORT: {{ .Values.config.postgresPort | quote }} + POSTGRES_DB: {{ .Values.config.postgresDb | quote }} + POSTGRES_USER: {{ .Values.config.postgresUser | quote }} + NEO4J_URI: {{ .Values.config.neo4jUri | quote }} + NEO4J_USER: {{ .Values.config.neo4jUser | quote }} + REDIS_URL: {{ .Values.config.redisUrl | quote }} + OPENSEARCH_URL: {{ .Values.config.opensearchUrl | quote }} + OPENSEARCH_USER: {{ .Values.config.opensearchUser | quote }} + CORS_ALLOW_ORIGIN: {{ .Values.config.corsAllowOrigin | quote }} + SEARCH_RETRIEVAL_PROVIDER: {{ .Values.config.searchRetrievalProvider | quote }} + LLM_WIKI_URL: {{ .Values.config.llmWikiUrl | quote }} + GOOGLE_OKF_URL: {{ .Values.config.googleOkfUrl | quote }} + {{- $fullname := include "ai-rxos.fullname" . }} + {{- range $name, $svc := .Values.services }} + {{- if $svc.enabled }} + {{ upper (replace "-" "_" $name) }}_SERVICE_URL: {{ printf "http://%s-%s:%v" $fullname $name $svc.port | quote }} + {{- end }} + {{- end }} diff --git a/infra/helm/ai-rxos/templates/deployment.yaml b/infra/helm/ai-rxos/templates/deployment.yaml new file mode 100644 index 0000000..b6fc880 --- /dev/null +++ b/infra/helm/ai-rxos/templates/deployment.yaml @@ -0,0 +1,92 @@ +{{- $root := . }} +{{- range $name, $svc := .Values.services }} +{{- if $svc.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "ai-rxos.fullname" $root }}-{{ $name }} + namespace: {{ include "ai-rxos.namespace" $root }} + labels: + {{- include "ai-rxos.serviceLabels" (dict "root" $root "service" $name) | nindent 4 }} +spec: + {{- if not $svc.autoscaling.enabled }} + replicas: {{ $svc.replicas }} + {{- end }} + selector: + matchLabels: + {{- include "ai-rxos.serviceSelectorLabels" (dict "root" $root "service" $name) | nindent 6 }} + template: + metadata: + labels: + {{- include "ai-rxos.serviceLabels" (dict "root" $root "service" $name) | nindent 8 }} + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: {{ $svc.port | quote }} + prometheus.io/path: "/metrics" + spec: + serviceAccountName: {{ include "ai-rxos.serviceAccountName" $root }} + securityContext: + {{- toYaml $root.Values.podSecurityContext | nindent 8 }} + {{- if and $svc.gpu $svc.gpu.enabled }} + nodeSelector: + gpu: "true" + tolerations: + - key: "nvidia.com/gpu" + operator: "Exists" + effect: "NoSchedule" + {{- end }} + containers: + - name: {{ $name }} + securityContext: + {{- toYaml $root.Values.securityContext | nindent 12 }} + image: {{ include "ai-rxos.image" (dict "root" $root "svc" $svc) }} + imagePullPolicy: {{ $root.Values.global.imagePullPolicy }} + ports: + - name: http + containerPort: {{ $svc.port }} + protocol: TCP + envFrom: + - configMapRef: + name: {{ include "ai-rxos.fullname" $root }}-config + - secretRef: + name: {{ include "ai-rxos.secretName" $root }} + env: + - name: PORT + value: {{ $svc.port | quote }} + {{- range $key, $value := $svc.env }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + {{- if $svc.healthPath }} + livenessProbe: + httpGet: + path: {{ $svc.healthPath }} + port: http + initialDelaySeconds: 10 + periodSeconds: 15 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: {{ $svc.healthPath }} + port: http + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 3 + failureThreshold: 3 + {{- end }} + resources: + requests: + {{- toYaml $svc.resources.requests | nindent 14 }} + limits: + {{- toYaml $svc.resources.limits | nindent 14 }} + {{- if and $svc.gpu $svc.gpu.enabled }} + nvidia.com/gpu: "1" + {{- end }} + {{- with $root.Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} +--- +{{- end }} +{{- end }} diff --git a/infra/helm/ai-rxos/templates/external-secret.yaml b/infra/helm/ai-rxos/templates/external-secret.yaml new file mode 100644 index 0000000..8e0f97d --- /dev/null +++ b/infra/helm/ai-rxos/templates/external-secret.yaml @@ -0,0 +1,50 @@ +{{- /* +External Secrets Operator scaffold. Renders only when +externalSecret.enabled=true (values-prod.yaml), producing the same +Secret (name + keys) that templates/secret.yaml would otherwise create +from plaintext values — so ai-rxos.secretName and every Deployment's +envFrom keep working unchanged either way. + +Requires the External Secrets Operator (https://external-secrets.io) and +a SecretStore/ClusterSecretStore named externalSecret.secretStoreRef.name +to already exist in-cluster; this template does not create either. Each +remoteRef.key below assumes the backing store holds one entry per field +under an "ai-rxos/" prefix — adjust to match your provider's actual +layout (AWS Secrets Manager, Vault, etc) before relying on this. +*/}} +{{- if .Values.externalSecret.enabled }} +apiVersion: external-secrets.io/v1 +kind: ExternalSecret +metadata: + name: {{ include "ai-rxos.fullname" . }}-external-secret + namespace: {{ include "ai-rxos.namespace" . }} + labels: + {{- include "ai-rxos.labels" . | nindent 4 }} +spec: + refreshInterval: {{ .Values.externalSecret.refreshInterval | default "1h" }} + secretStoreRef: + name: {{ .Values.externalSecret.secretStoreRef.name }} + kind: {{ .Values.externalSecret.secretStoreRef.kind | default "ClusterSecretStore" }} + target: + name: {{ .Values.externalSecret.name }} + creationPolicy: Owner + data: + - secretKey: POSTGRES_PASSWORD + remoteRef: { key: ai-rxos/postgres-password } + - secretKey: DATABASE_URL + remoteRef: { key: ai-rxos/database-url } + - secretKey: NEO4J_PASSWORD + remoteRef: { key: ai-rxos/neo4j-password } + - secretKey: REDIS_PASSWORD + remoteRef: { key: ai-rxos/redis-password } + - secretKey: OPENSEARCH_PASSWORD + remoteRef: { key: ai-rxos/opensearch-password } + - secretKey: JWT_SECRET + remoteRef: { key: ai-rxos/jwt-secret } + - secretKey: BETTER_AUTH_SECRET + remoteRef: { key: ai-rxos/better-auth-secret } + - secretKey: LLM_WIKI_API_KEY + remoteRef: { key: ai-rxos/llm-wiki-api-key } + - secretKey: GOOGLE_OKF_API_KEY + remoteRef: { key: ai-rxos/google-okf-api-key } +{{- end }} diff --git a/infra/helm/ai-rxos/templates/hpa.yaml b/infra/helm/ai-rxos/templates/hpa.yaml new file mode 100644 index 0000000..97184d1 --- /dev/null +++ b/infra/helm/ai-rxos/templates/hpa.yaml @@ -0,0 +1,30 @@ +{{- $root := . }} +{{- range $name, $svc := .Values.services }} +{{- if and $svc.enabled $svc.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "ai-rxos.fullname" $root }}-{{ $name }} + namespace: {{ include "ai-rxos.namespace" $root }} + labels: + {{- include "ai-rxos.serviceLabels" (dict "root" $root "service" $name) | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "ai-rxos.fullname" $root }}-{{ $name }} + minReplicas: {{ $svc.autoscaling.minReplicas }} + maxReplicas: {{ $svc.autoscaling.maxReplicas }} + behavior: + scaleDown: + stabilizationWindowSeconds: 300 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ $svc.autoscaling.targetCPUUtilizationPercentage }} +--- +{{- end }} +{{- end }} diff --git a/infra/helm/ai-rxos/templates/ingress.yaml b/infra/helm/ai-rxos/templates/ingress.yaml new file mode 100644 index 0000000..a869f84 --- /dev/null +++ b/infra/helm/ai-rxos/templates/ingress.yaml @@ -0,0 +1,37 @@ +{{- if .Values.ingress.enabled }} +{{- $root := . }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "ai-rxos.fullname" . }} + namespace: {{ include "ai-rxos.namespace" . }} + labels: + {{- include "ai-rxos.labels" . | nindent 4 }} + annotations: + {{- toYaml .Values.ingress.annotations | nindent 4 }} +spec: + ingressClassName: {{ .Values.ingress.className }} + {{- if .Values.ingress.tls.enabled }} + tls: + - secretName: {{ .Values.ingress.tls.secretName }} + hosts: + {{- range $key, $host := .Values.ingress.hosts }} + - {{ $host }} + {{- end }} + {{- end }} + rules: + {{- range $name, $svc := .Values.services }} + {{- if and $svc.enabled $svc.ingress }} + - host: {{ index $root.Values.ingress.hosts $svc.ingress.host }} + http: + paths: + - path: {{ $svc.ingress.path }} + pathType: Prefix + backend: + service: + name: {{ include "ai-rxos.fullname" $root }}-{{ $name }} + port: + number: {{ $svc.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/infra/helm/ai-rxos/templates/secret.yaml b/infra/helm/ai-rxos/templates/secret.yaml new file mode 100644 index 0000000..2a3486e --- /dev/null +++ b/infra/helm/ai-rxos/templates/secret.yaml @@ -0,0 +1,20 @@ +{{- if not .Values.externalSecret.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "ai-rxos.fullname" . }}-secrets + namespace: {{ include "ai-rxos.namespace" . }} + labels: + {{- include "ai-rxos.labels" . | nindent 4 }} +type: Opaque +stringData: + POSTGRES_PASSWORD: {{ .Values.secrets.postgresPassword | quote }} + DATABASE_URL: {{ printf "postgresql://%s:%s@%s:%v/%s" .Values.config.postgresUser .Values.secrets.postgresPassword .Values.config.postgresHost .Values.config.postgresPort .Values.config.postgresDb | quote }} + NEO4J_PASSWORD: {{ .Values.secrets.neo4jPassword | quote }} + REDIS_PASSWORD: {{ .Values.secrets.redisPassword | quote }} + OPENSEARCH_PASSWORD: {{ .Values.secrets.opensearchPassword | quote }} + JWT_SECRET: {{ .Values.secrets.jwtSecret | quote }} + BETTER_AUTH_SECRET: {{ .Values.secrets.betterAuthSecret | quote }} + LLM_WIKI_API_KEY: {{ .Values.secrets.llmWikiApiKey | quote }} + GOOGLE_OKF_API_KEY: {{ .Values.secrets.googleOkfApiKey | quote }} +{{- end }} diff --git a/infra/helm/ai-rxos/templates/service.yaml b/infra/helm/ai-rxos/templates/service.yaml new file mode 100644 index 0000000..702c112 --- /dev/null +++ b/infra/helm/ai-rxos/templates/service.yaml @@ -0,0 +1,22 @@ +{{- $root := . }} +{{- range $name, $svc := .Values.services }} +{{- if $svc.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "ai-rxos.fullname" $root }}-{{ $name }} + namespace: {{ include "ai-rxos.namespace" $root }} + labels: + {{- include "ai-rxos.serviceLabels" (dict "root" $root "service" $name) | nindent 4 }} +spec: + type: ClusterIP + ports: + - name: http + port: {{ $svc.port }} + targetPort: http + protocol: TCP + selector: + {{- include "ai-rxos.serviceSelectorLabels" (dict "root" $root "service" $name) | nindent 4 }} +--- +{{- end }} +{{- end }} diff --git a/infra/helm/ai-rxos/templates/serviceaccount.yaml b/infra/helm/ai-rxos/templates/serviceaccount.yaml new file mode 100644 index 0000000..deed669 --- /dev/null +++ b/infra/helm/ai-rxos/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "ai-rxos.serviceAccountName" . }} + namespace: {{ include "ai-rxos.namespace" . }} + labels: + {{- include "ai-rxos.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/infra/helm/ai-rxos/values-dev.yaml b/infra/helm/ai-rxos/values-dev.yaml new file mode 100644 index 0000000..7654f9e --- /dev/null +++ b/infra/helm/ai-rxos/values-dev.yaml @@ -0,0 +1,20 @@ +## Dev overlay: helm install ai-rxos infra/helm/ai-rxos -f values.yaml -f values-dev.yaml +global: + environment: development + imageRegistry: "" # use locally-built images (docker compose / kind load) + imagePullPolicy: IfNotPresent + +ingress: + enabled: false # use kubectl port-forward locally instead + +postgresql: + enabled: true +redis: + enabled: true +neo4j: + enabled: true +opensearch: + enabled: true + +externalSecret: + enabled: false diff --git a/infra/helm/ai-rxos/values-prod.yaml b/infra/helm/ai-rxos/values-prod.yaml new file mode 100644 index 0000000..cfbe784 --- /dev/null +++ b/infra/helm/ai-rxos/values-prod.yaml @@ -0,0 +1,53 @@ +## Prod overlay: helm install ai-rxos infra/helm/ai-rxos -f values.yaml -f values-prod.yaml +## Disables in-cluster stateful dependencies in favor of managed services +## (see architecture/07-deployment-architecture.md), and requires secrets to +## be pre-provisioned out-of-band rather than templated from values. +global: + environment: production + imageRegistry: "ghcr.io/openhealthagents" + imagePullPolicy: IfNotPresent + +ingress: + enabled: true + tls: + enabled: true + hosts: + web: app.ai-rxos.com + admin: admin.ai-rxos.com + api: api.ai-rxos.com + +# Managed AWS services instead of in-cluster StatefulSets: +postgresql: + enabled: false +redis: + enabled: false +neo4j: + enabled: false +opensearch: + enabled: false + +config: + postgresHost: "ai-rxos-prod.cluster-xxxx.us-east-1.rds.amazonaws.com" + postgresPort: "5432" + postgresDb: ai_rxos + postgresUser: ai_rxos + neo4jUri: "bolt+s://ai-rxos-prod.databases.neo4j.io:7687" + neo4jUser: neo4j + redisUrl: "rediss://ai-rxos-prod.xxxxx.cache.amazonaws.com:6379/0" + opensearchUrl: "https://vpc-ai-rxos-search-xxxx.us-east-1.es.amazonaws.com" + opensearchUser: admin + corsAllowOrigin: "https://app.ai-rxos.com" + +# Secrets are provisioned by External Secrets Operator from AWS Secrets +# Manager in prod — the chart never templates real credentials for this env. +externalSecret: + enabled: true + name: ai-rxos-secrets + +services: + web: { autoscaling: { minReplicas: 3, maxReplicas: 12 } } + admin: { autoscaling: { minReplicas: 2, maxReplicas: 6 } } + api-gateway: { autoscaling: { minReplicas: 5, maxReplicas: 20 } } + docking: + gpu: { enabled: true } + autoscaling: { minReplicas: 2, maxReplicas: 20 } diff --git a/infra/helm/ai-rxos/values.yaml b/infra/helm/ai-rxos/values.yaml new file mode 100644 index 0000000..274bab5 --- /dev/null +++ b/infra/helm/ai-rxos/values.yaml @@ -0,0 +1,319 @@ +## Default values for ai-rxos. See values-dev.yaml / values-prod.yaml for +## environment overlays (helm install -f values.yaml -f values-.yaml). + +global: + environment: development + imageRegistry: "ghcr.io/openhealthagents" + imagePullPolicy: IfNotPresent + imagePullSecrets: [] + +namespaceOverride: "" +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + create: true + name: "" + annotations: {} + +podSecurityContext: + runAsNonRoot: true + fsGroup: 1000 + +securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + capabilities: + drop: ["ALL"] + +ingress: + enabled: true + className: nginx + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + nginx.ingress.kubernetes.io/proxy-body-size: 25m + tls: + enabled: false + secretName: ai-rxos-tls + hosts: + web: app.ai-rxos.local + admin: admin.ai-rxos.local + api: api.ai-rxos.local + +# Dev/staging: spin these up in-cluster via the chart dependencies. +# Prod: set each to `enabled: false` and point `config.*` at managed +# equivalents (RDS, ElastiCache, Neo4j Aura/causal cluster, AWS OpenSearch). +## Encryption at rest (self-hosted/dev clusters only): leave storageClass +## empty to use the cluster default, or point it at a StorageClass backed +## by an encrypted CSI driver/volume type (e.g. "gp3-encrypted" on EBS, +## "encrypted-rwo" on GKE). This chart cannot encrypt volumes itself — it +## only passes the StorageClass through to each subchart. Prod +## (values-prod.yaml) disables these subcharts entirely and relies on the +## managed AWS services' own at-rest encryption instead, which this +## setting does not affect. +postgresql: + enabled: true + auth: + username: ai_rxos + password: changeme + database: ai_rxos + image: + repository: pgvector/pgvector + tag: pg16 + primary: + persistence: + size: 20Gi + storageClass: "" + +redis: + enabled: true + architecture: standalone + auth: + enabled: false + master: + persistence: + size: 5Gi + storageClass: "" + +neo4j: + enabled: true + neo4j: + name: ai-rxos + password: changeme_neo4j + volumes: + data: + mode: dynamic + dynamic: + # For at-rest encryption, point this at an encrypted StorageClass + # instead (see comment above postgresql:). + storageClassName: standard + requests: + storage: 50Gi + +opensearch: + enabled: true + singleNode: true + replicas: 1 + persistence: + size: 20Gi + storageClass: "" + +# Non-secret runtime config, injected into every service as a ConfigMap. +# Hostnames match the in-cluster dependency release names by default; +# override in values-prod.yaml with managed-service endpoints. +config: + postgresHost: ai-rxos-postgresql + postgresPort: "5432" + postgresDb: ai_rxos + postgresUser: ai_rxos + neo4jUri: "bolt://ai-rxos-neo4j:7687" + neo4jUser: neo4j + redisUrl: "redis://ai-rxos-redis-master:6379/0" + opensearchUrl: "http://ai-rxos-opensearch-cluster-master:9200" + opensearchUser: admin + corsAllowOrigin: "https://app.ai-rxos.local" + # services/search retrieval provider selection (see + # services/search/internal/search/provider.go). pgvector is the default + # and only implemented backend; llm_wiki/google_okf are placeholders. + searchRetrievalProvider: "pgvector" + llmWikiUrl: "" + googleOkfUrl: "" + +# Secrets: for dev this seeds a Kubernetes Secret directly from values +# (fine for local clusters, NOT for prod). In prod, set +# externalSecret.enabled=true and pre-provision `externalSecret.name` +# via e.g. External Secrets Operator / Sealed Secrets / Vault. +externalSecret: + enabled: false + name: ai-rxos-secrets + # Used only when enabled=true (see templates/external-secret.yaml). + secretStoreRef: + name: ai-rxos-secret-store + kind: ClusterSecretStore + refreshInterval: 1h + +secrets: + postgresPassword: changeme + neo4jPassword: changeme_neo4j + redisPassword: "" + opensearchPassword: AiRxOS#Search9K + jwtSecret: change_this_dev_secret_before_deploying + # services/auth-adapter (BetterAuth scaffold) — see + # services/auth-adapter/README.md. + betterAuthSecret: change_this_dev_secret_before_deploying + # services/search placeholder retrieval providers — empty until real + # credentials exist (see services/search/README.md). + llmWikiApiKey: "" + googleOkfApiKey: "" + +# One entry per deployable unit. `ingressPath` is only set for +# externally-routed services (web, admin, api-gateway); everything else is +# ClusterIP-only, reached through api-gateway. +services: + web: + enabled: true + image: { repository: web, tag: latest } + port: 3000 + replicas: 2 + healthPath: /api/health + ingress: { host: web, path: / } + resources: + requests: { cpu: 100m, memory: 256Mi } + limits: { cpu: 500m, memory: 512Mi } + autoscaling: { enabled: true, minReplicas: 2, maxReplicas: 6, targetCPUUtilizationPercentage: 70 } + env: + NEXT_PUBLIC_API_BASE_URL: "https://api.ai-rxos.local" + + admin: + enabled: true + image: { repository: admin, tag: latest } + port: 3001 + replicas: 2 + healthPath: /api/health + ingress: { host: admin, path: / } + resources: + requests: { cpu: 100m, memory: 256Mi } + limits: { cpu: 500m, memory: 512Mi } + autoscaling: { enabled: true, minReplicas: 2, maxReplicas: 4, targetCPUUtilizationPercentage: 70 } + env: + NEXT_PUBLIC_API_BASE_URL: "https://api.ai-rxos.local" + + api-gateway: + enabled: true + image: { repository: api-gateway, tag: latest } + port: 8080 + replicas: 3 + healthPath: /healthz + ingress: { host: api, path: / } + resources: + requests: { cpu: 250m, memory: 256Mi } + limits: { cpu: 1000m, memory: 512Mi } + autoscaling: { enabled: true, minReplicas: 3, maxReplicas: 10, targetCPUUtilizationPercentage: 70 } + env: + CORS_ALLOW_ORIGIN: "https://app.ai-rxos.local" + + auth: + enabled: true + image: { repository: auth, tag: latest } + port: 8081 + replicas: 3 + healthPath: /healthz + resources: + requests: { cpu: 250m, memory: 256Mi } + limits: { cpu: 1000m, memory: 512Mi } + autoscaling: { enabled: true, minReplicas: 3, maxReplicas: 10, targetCPUUtilizationPercentage: 70 } + + # BetterAuth adapter scaffold (see services/auth-adapter/README.md). + # Additive: services/auth above is unchanged and still owns + # /api/v1/auth/*. Not on any ingress path yet. + auth-adapter: + enabled: true + image: { repository: auth-adapter, tag: latest } + port: 8089 + replicas: 1 + healthPath: /healthz + resources: + requests: { cpu: 100m, memory: 256Mi } + limits: { cpu: 500m, memory: 512Mi } + autoscaling: { enabled: false } + + literature: + enabled: true + image: { repository: literature, tag: latest } + port: 8082 + replicas: 2 + healthPath: /healthz + resources: + requests: { cpu: 250m, memory: 512Mi } + limits: { cpu: 1000m, memory: 1Gi } + autoscaling: { enabled: true, minReplicas: 2, maxReplicas: 8, targetCPUUtilizationPercentage: 70 } + + kg: + enabled: true + image: { repository: kg, tag: latest } + port: 8083 + replicas: 2 + healthPath: /healthz + resources: + requests: { cpu: 250m, memory: 512Mi } + limits: { cpu: 1000m, memory: 1Gi } + autoscaling: { enabled: true, minReplicas: 2, maxReplicas: 6, targetCPUUtilizationPercentage: 70 } + + search: + enabled: true + image: { repository: search, tag: latest } + port: 8084 + replicas: 2 + healthPath: /healthz + resources: + requests: { cpu: 250m, memory: 256Mi } + limits: { cpu: 1000m, memory: 512Mi } + autoscaling: { enabled: true, minReplicas: 2, maxReplicas: 8, targetCPUUtilizationPercentage: 70 } + + agents: + enabled: true + image: { repository: agents, tag: latest } + port: 8085 + replicas: 2 + healthPath: /healthz + resources: + requests: { cpu: 250m, memory: 512Mi } + limits: { cpu: 1000m, memory: 1Gi } + autoscaling: { enabled: true, minReplicas: 2, maxReplicas: 8, targetCPUUtilizationPercentage: 70 } + + workflows: + enabled: true + image: { repository: workflows, tag: latest } + port: 8086 + replicas: 2 + healthPath: /healthz + resources: + requests: { cpu: 250m, memory: 256Mi } + limits: { cpu: 1000m, memory: 512Mi } + autoscaling: { enabled: true, minReplicas: 2, maxReplicas: 6, targetCPUUtilizationPercentage: 70 } + + reports: + enabled: true + image: { repository: reports, tag: latest } + port: 8087 + replicas: 2 + healthPath: /healthz + resources: + requests: { cpu: 250m, memory: 256Mi } + limits: { cpu: 1000m, memory: 512Mi } + autoscaling: { enabled: true, minReplicas: 2, maxReplicas: 4, targetCPUUtilizationPercentage: 70 } + + docking: + enabled: true + image: { repository: docking, tag: latest } + port: 8088 + replicas: 2 + healthPath: /healthz + gpu: + enabled: false # flip true + set nodeSelector/tolerations for GPU node pools in prod + resources: + requests: { cpu: 500m, memory: 1Gi } + limits: { cpu: 2000m, memory: 4Gi } + autoscaling: { enabled: true, minReplicas: 2, maxReplicas: 6, targetCPUUtilizationPercentage: 70 } + + ai-services: + enabled: true + image: { repository: ai-services, tag: latest } + port: 8090 + replicas: 2 + healthPath: /healthz + resources: + requests: { cpu: 250m, memory: 512Mi } + limits: { cpu: 1000m, memory: 1Gi } + autoscaling: { enabled: true, minReplicas: 2, maxReplicas: 8, targetCPUUtilizationPercentage: 70 } + + knowledge-service: + enabled: true + image: { repository: knowledge-service, tag: latest } + port: 8091 + replicas: 2 + healthPath: /healthz + resources: + requests: { cpu: 250m, memory: 256Mi } + limits: { cpu: 1000m, memory: 512Mi } + autoscaling: { enabled: true, minReplicas: 2, maxReplicas: 6, targetCPUUtilizationPercentage: 70 } diff --git a/infra/k8s/namespaces.yaml b/infra/k8s/namespaces.yaml new file mode 100644 index 0000000..196804d --- /dev/null +++ b/infra/k8s/namespaces.yaml @@ -0,0 +1,34 @@ +# Raw namespace manifests — apply once per cluster, ahead of any Helm release. +# See architecture/07-deployment-architecture.md "Kubernetes Namespaces". +apiVersion: v1 +kind: Namespace +metadata: + name: ai-rxos-production + labels: { environment: production } +--- +apiVersion: v1 +kind: Namespace +metadata: + name: ai-rxos-staging + labels: { environment: staging } +--- +apiVersion: v1 +kind: Namespace +metadata: + name: ai-rxos-development + labels: { environment: development } +--- +apiVersion: v1 +kind: Namespace +metadata: + name: monitoring +--- +apiVersion: v1 +kind: Namespace +metadata: + name: infrastructure +--- +apiVersion: v1 +kind: Namespace +metadata: + name: gpu-workloads diff --git a/infra/k8s/network-policies.yaml b/infra/k8s/network-policies.yaml new file mode 100644 index 0000000..fb2da7f --- /dev/null +++ b/infra/k8s/network-policies.yaml @@ -0,0 +1,41 @@ +# Default-deny per app namespace, with DNS carved out. Mirrors +# architecture/07-deployment-architecture.md "Network Policies"; extend with +# explicit allow rules per service as they're added. +# Plain manifests — not Helm-templated, applied directly with kubectl. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: deny-all + namespace: ai-rxos-production +spec: + podSelector: {} + policyTypes: [Ingress, Egress] +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-dns + namespace: ai-rxos-production +spec: + podSelector: {} + policyTypes: [Egress] + egress: + - to: + - namespaceSelector: + matchLabels: { kubernetes.io/metadata.name: kube-system } + ports: + - { protocol: UDP, port: 53 } + - { protocol: TCP, port: 53 } +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-intra-namespace + namespace: ai-rxos-production +spec: + podSelector: {} + policyTypes: [Ingress] + ingress: + - from: + - namespaceSelector: + matchLabels: { kubernetes.io/metadata.name: ai-rxos-production } diff --git a/infra/postgres/init.sql b/infra/postgres/init.sql new file mode 100644 index 0000000..eea58cb --- /dev/null +++ b/infra/postgres/init.sql @@ -0,0 +1,3 @@ +-- Runs once on first postgres container boot (docker-entrypoint-initdb.d). +CREATE EXTENSION IF NOT EXISTS vector; +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; diff --git a/package.json b/package.json new file mode 100644 index 0000000..510a6ad --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "ai-rxos", + "version": "0.1.0", + "private": true, + "description": "AI-RxOS monorepo — AI-native drug discovery operating system", + "packageManager": "pnpm@9.15.0", + "engines": { + "node": ">=20.0.0", + "pnpm": ">=9.0.0" + }, + "scripts": { + "build": "turbo run build", + "dev": "turbo run dev --parallel", + "lint": "turbo run lint", + "test": "turbo run test", + "typecheck": "turbo run typecheck", + "clean": "turbo run clean && rimraf node_modules", + "format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md,yml,yaml}\" --ignore-path .gitignore", + "format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,md,yml,yaml}\" --ignore-path .gitignore", + "compose:up": "docker compose up --build -d", + "compose:down": "docker compose down", + "compose:logs": "docker compose logs -f" + }, + "devDependencies": { + "prettier": "^3.3.3", + "rimraf": "^6.0.1", + "turbo": "^2.3.3", + "typescript": "^5.7.2" + } +} diff --git a/packages/audit-log/.eslintrc.js b/packages/audit-log/.eslintrc.js new file mode 100644 index 0000000..75a60d0 --- /dev/null +++ b/packages/audit-log/.eslintrc.js @@ -0,0 +1 @@ +module.exports = { root: true, extends: ["@ai-rxos/eslint-config"] }; diff --git a/packages/audit-log/package.json b/packages/audit-log/package.json new file mode 100644 index 0000000..31b3630 --- /dev/null +++ b/packages/audit-log/package.json @@ -0,0 +1,33 @@ +{ + "name": "@ai-rxos/audit-log", + "version": "0.1.0", + "private": true, + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.js" + } + }, + "scripts": { + "build": "tsup src/index.ts --format esm,cjs --dts", + "dev": "tsup src/index.ts --format esm,cjs --dts --watch", + "lint": "eslint src --max-warnings 0", + "typecheck": "tsc --noEmit", + "clean": "rimraf dist .turbo" + }, + "dependencies": { + "zod": "^3.24.1" + }, + "devDependencies": { + "@ai-rxos/eslint-config": "workspace:*", + "@ai-rxos/typescript-config": "workspace:*", + "eslint": "^8.57.1", + "rimraf": "^6.0.1", + "tsup": "^8.3.5", + "typescript": "^5.7.2" + } +} diff --git a/packages/audit-log/src/index.ts b/packages/audit-log/src/index.ts new file mode 100644 index 0000000..e3fd7af --- /dev/null +++ b/packages/audit-log/src/index.ts @@ -0,0 +1,27 @@ +import { z } from "zod"; + +/** + * Shared audit-event contract for AI-RxOS. This package defines the event + * shape and a pluggable sink interface only — no persistence backend + * exists yet (no audit table, queue, or consumer exists anywhere in this + * repo as of this scaffold). Implement AuditLogSink against a real + * backend (e.g. a Postgres audit_log table, written from within the same + * request transaction as the action it records) before relying on this + * for anything. + */ + +export const AuditEventSchema = z.object({ + id: z.string().uuid(), + tenantId: z.string().uuid().optional(), + actorId: z.string().uuid().optional(), + action: z.string(), + resourceType: z.string(), + resourceId: z.string().optional(), + metadata: z.record(z.string(), z.unknown()).default({}), + createdAt: z.string().datetime(), +}); +export type AuditEvent = z.infer; + +export interface AuditLogSink { + record(event: AuditEvent): Promise; +} diff --git a/packages/audit-log/tsconfig.json b/packages/audit-log/tsconfig.json new file mode 100644 index 0000000..932c2c0 --- /dev/null +++ b/packages/audit-log/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@ai-rxos/typescript-config/base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/packages/sdk/.eslintrc.js b/packages/sdk/.eslintrc.js new file mode 100644 index 0000000..75a60d0 --- /dev/null +++ b/packages/sdk/.eslintrc.js @@ -0,0 +1 @@ +module.exports = { root: true, extends: ["@ai-rxos/eslint-config"] }; diff --git a/packages/sdk/package.json b/packages/sdk/package.json new file mode 100644 index 0000000..9112377 --- /dev/null +++ b/packages/sdk/package.json @@ -0,0 +1,33 @@ +{ + "name": "@ai-rxos/sdk", + "version": "0.1.0", + "private": true, + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.js" + } + }, + "scripts": { + "build": "tsup src/index.ts --format esm,cjs --dts", + "dev": "tsup src/index.ts --format esm,cjs --dts --watch", + "lint": "eslint src --max-warnings 0", + "typecheck": "tsc --noEmit", + "clean": "rimraf dist .turbo" + }, + "dependencies": { + "@ai-rxos/types": "workspace:*" + }, + "devDependencies": { + "@ai-rxos/eslint-config": "workspace:*", + "@ai-rxos/typescript-config": "workspace:*", + "eslint": "^8.57.1", + "rimraf": "^6.0.1", + "tsup": "^8.3.5", + "typescript": "^5.7.2" + } +} diff --git a/packages/sdk/src/client.ts b/packages/sdk/src/client.ts new file mode 100644 index 0000000..851ff7c --- /dev/null +++ b/packages/sdk/src/client.ts @@ -0,0 +1,54 @@ +export interface AiRxOsClientOptions { + baseUrl: string; + apiKey?: string; + fetchImpl?: typeof fetch; +} + +export class ApiRequestError extends Error { + constructor( + public status: number, + public code: string, + message: string, + ) { + super(message); + this.name = "ApiRequestError"; + } +} + +export class AiRxOsClient { + private readonly baseUrl: string; + private readonly apiKey?: string; + private readonly fetchImpl: typeof fetch; + + constructor(options: AiRxOsClientOptions) { + this.baseUrl = options.baseUrl.replace(/\/$/, ""); + this.apiKey = options.apiKey; + this.fetchImpl = options.fetchImpl ?? fetch; + } + + async request(path: string, init: RequestInit = {}): Promise { + const headers = new Headers(init.headers); + headers.set("Content-Type", "application/json"); + if (this.apiKey) headers.set("Authorization", `Bearer ${this.apiKey}`); + + const res = await this.fetchImpl(`${this.baseUrl}${path}`, { ...init, headers }); + if (!res.ok) { + const body = await res.json().catch(() => ({})); + throw new ApiRequestError( + res.status, + body.code ?? "unknown_error", + body.message ?? res.statusText, + ); + } + if (res.status === 204) return undefined as T; + return (await res.json()) as T; + } + + get(path: string) { + return this.request(path, { method: "GET" }); + } + + post(path: string, body?: unknown) { + return this.request(path, { method: "POST", body: body ? JSON.stringify(body) : undefined }); + } +} diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts new file mode 100644 index 0000000..33a87f1 --- /dev/null +++ b/packages/sdk/src/index.ts @@ -0,0 +1,2 @@ +export * from "./client"; +export * from "./resources"; diff --git a/packages/sdk/src/resources.ts b/packages/sdk/src/resources.ts new file mode 100644 index 0000000..148941c --- /dev/null +++ b/packages/sdk/src/resources.ts @@ -0,0 +1,40 @@ +import type { + AgentTask, + DockingResult, + Molecule, + Paginated, + Paper, + Report, + SearchResult, +} from "@ai-rxos/types"; +import type { AiRxOsClient } from "./client"; + +/** Thin typed wrappers over api-gateway routes. See architecture/03-api-contracts.md. */ + +export const search = (client: AiRxOsClient) => ({ + query: (q: string, limit = 20) => + client.get>(`/api/v1/search?q=${encodeURIComponent(q)}&limit=${limit}`), +}); + +export const literature = (client: AiRxOsClient) => ({ + list: (page = 1) => client.get>(`/api/v1/papers?page=${page}`), + get: (id: string) => client.get(`/api/v1/papers/${id}`), +}); + +export const molecules = (client: AiRxOsClient) => ({ + list: (page = 1) => client.get>(`/api/v1/molecules?page=${page}`), + dock: (moleculeId: string, targetId: string) => + client.post(`/api/v1/docking`, { moleculeId, targetId }), +}); + +export const agents = (client: AiRxOsClient) => ({ + run: (agentType: string, input: Record) => + client.post(`/api/v1/agents/run`, { agentType, input }), + get: (id: string) => client.get(`/api/v1/agents/tasks/${id}`), +}); + +export const reports = (client: AiRxOsClient) => ({ + list: (page = 1) => client.get>(`/api/v1/reports?page=${page}`), + generate: (title: string, type: Report["type"]) => + client.post(`/api/v1/reports`, { title, type }), +}); diff --git a/packages/sdk/tsconfig.json b/packages/sdk/tsconfig.json new file mode 100644 index 0000000..8880f35 --- /dev/null +++ b/packages/sdk/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@ai-rxos/typescript-config/base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "lib": ["ES2022", "DOM"] + }, + "include": ["src"] +} diff --git a/packages/tenancy/.eslintrc.js b/packages/tenancy/.eslintrc.js new file mode 100644 index 0000000..75a60d0 --- /dev/null +++ b/packages/tenancy/.eslintrc.js @@ -0,0 +1 @@ +module.exports = { root: true, extends: ["@ai-rxos/eslint-config"] }; diff --git a/packages/tenancy/package.json b/packages/tenancy/package.json new file mode 100644 index 0000000..1cba03c --- /dev/null +++ b/packages/tenancy/package.json @@ -0,0 +1,33 @@ +{ + "name": "@ai-rxos/tenancy", + "version": "0.1.0", + "private": true, + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.js" + } + }, + "scripts": { + "build": "tsup src/index.ts --format esm,cjs --dts", + "dev": "tsup src/index.ts --format esm,cjs --dts --watch", + "lint": "eslint src --max-warnings 0", + "typecheck": "tsc --noEmit", + "clean": "rimraf dist .turbo" + }, + "dependencies": { + "zod": "^3.24.1" + }, + "devDependencies": { + "@ai-rxos/eslint-config": "workspace:*", + "@ai-rxos/typescript-config": "workspace:*", + "eslint": "^8.57.1", + "rimraf": "^6.0.1", + "tsup": "^8.3.5", + "typescript": "^5.7.2" + } +} diff --git a/packages/tenancy/src/index.ts b/packages/tenancy/src/index.ts new file mode 100644 index 0000000..906decc --- /dev/null +++ b/packages/tenancy/src/index.ts @@ -0,0 +1,30 @@ +import { z } from "zod"; + +/** + * Shared multi-tenancy conventions for AI-RxOS. This package only defines + * the contract (naming + types) — it does not enforce anything itself. + * + * The Postgres side of this contract lives in each Go service's schema + * migration (services/auth/internal/store/postgres.go, + * services/search/internal/search/pgvector.go): both enable Row Level + * Security with a policy that reads TENANT_SESSION_VARIABLE via + * Postgres's current_setting(). No caller sets that session variable yet + * (see README.md "Row Level Security" for what's still required); until + * one does, the RLS policies fail open and behave exactly as before. + */ + +/** Postgres session variable RLS policies key on, set per-request via `SET LOCAL = ''`. */ +export const TENANT_SESSION_VARIABLE = "app.tenant_id"; + +/** HTTP header a gateway/service should use to propagate the resolved tenant downstream. Not yet emitted or read anywhere. */ +export const TENANT_ID_HEADER = "X-Organization-Id"; + +/** JWT claim name a tenant-aware token issuer should use for the tenant id. Not yet emitted by services/auth or services/auth-adapter. */ +export const TENANT_ID_CLAIM = "organizationId"; + +export const TenantIdSchema = z.string().uuid(); +export type TenantId = z.infer; + +export interface TenantContext { + tenantId: TenantId; +} diff --git a/packages/tenancy/tsconfig.json b/packages/tenancy/tsconfig.json new file mode 100644 index 0000000..932c2c0 --- /dev/null +++ b/packages/tenancy/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@ai-rxos/typescript-config/base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/packages/types/.eslintrc.js b/packages/types/.eslintrc.js new file mode 100644 index 0000000..75a60d0 --- /dev/null +++ b/packages/types/.eslintrc.js @@ -0,0 +1 @@ +module.exports = { root: true, extends: ["@ai-rxos/eslint-config"] }; diff --git a/packages/types/package.json b/packages/types/package.json new file mode 100644 index 0000000..f4907c4 --- /dev/null +++ b/packages/types/package.json @@ -0,0 +1,33 @@ +{ + "name": "@ai-rxos/types", + "version": "0.1.0", + "private": true, + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.js" + } + }, + "scripts": { + "build": "tsup src/index.ts --format esm,cjs --dts", + "dev": "tsup src/index.ts --format esm,cjs --dts --watch", + "lint": "eslint src --max-warnings 0", + "typecheck": "tsc --noEmit", + "clean": "rimraf dist .turbo" + }, + "dependencies": { + "zod": "^3.24.1" + }, + "devDependencies": { + "@ai-rxos/eslint-config": "workspace:*", + "@ai-rxos/typescript-config": "workspace:*", + "eslint": "^8.57.1", + "rimraf": "^6.0.1", + "tsup": "^8.3.5", + "typescript": "^5.7.2" + } +} diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts new file mode 100644 index 0000000..8c8a74e --- /dev/null +++ b/packages/types/src/index.ts @@ -0,0 +1,99 @@ +import { z } from "zod"; + +/** Shared domain types for the AI-RxOS platform. Mirrors architecture/03-api-contracts.md. */ + +export const UserSchema = z.object({ + id: z.string().uuid(), + email: z.string().email(), + displayName: z.string(), + organizationId: z.string().uuid(), + roles: z.array(z.string()), + createdAt: z.string().datetime(), +}); +export type User = z.infer; + +export const OrganizationSchema = z.object({ + id: z.string().uuid(), + name: z.string(), + slug: z.string(), + plan: z.enum(["free", "team", "enterprise"]), +}); +export type Organization = z.infer; + +export const PaperSchema = z.object({ + id: z.string().uuid(), + title: z.string(), + abstract: z.string().optional(), + source: z.enum(["pubmed", "biorxiv", "medrxiv", "patent", "conference"]), + doi: z.string().optional(), + publishedAt: z.string().datetime().optional(), + citationCount: z.number().int().nonnegative().default(0), +}); +export type Paper = z.infer; + +export const GraphEntitySchema = z.object({ + id: z.string(), + label: z.string(), + type: z.enum(["gene", "protein", "disease", "drug", "pathway", "compound"]), + properties: z.record(z.string(), z.unknown()).default({}), +}); +export type GraphEntity = z.infer; + +export const MoleculeSchema = z.object({ + id: z.string().uuid(), + smiles: z.string(), + name: z.string().optional(), + molecularWeight: z.number().optional(), + logP: z.number().optional(), +}); +export type Molecule = z.infer; + +export const DockingResultSchema = z.object({ + id: z.string().uuid(), + moleculeId: z.string().uuid(), + targetId: z.string(), + bindingAffinity: z.number(), + pose: z.string().optional(), + status: z.enum(["queued", "running", "completed", "failed"]), +}); +export type DockingResult = z.infer; + +export const AgentTaskSchema = z.object({ + id: z.string().uuid(), + agentType: z.string(), + input: z.record(z.string(), z.unknown()), + status: z.enum(["pending", "running", "succeeded", "failed"]), + result: z.record(z.string(), z.unknown()).optional(), +}); +export type AgentTask = z.infer; + +export const SearchResultSchema = z.object({ + id: z.string(), + score: z.number(), + source: z.enum(["opensearch", "pgvector", "graph"]), + title: z.string(), + snippet: z.string().optional(), +}); +export type SearchResult = z.infer; + +export const ReportSchema = z.object({ + id: z.string().uuid(), + title: z.string(), + type: z.enum(["scientific", "competitive", "due-diligence", "executive"]), + status: z.enum(["draft", "generating", "ready", "failed"]), + createdAt: z.string().datetime(), +}); +export type Report = z.infer; + +export interface Paginated { + items: T[]; + total: number; + page: number; + pageSize: number; +} + +export interface ApiError { + code: string; + message: string; + details?: Record; +} diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json new file mode 100644 index 0000000..932c2c0 --- /dev/null +++ b/packages/types/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@ai-rxos/typescript-config/base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/packages/ui/.eslintrc.js b/packages/ui/.eslintrc.js new file mode 100644 index 0000000..5ade715 --- /dev/null +++ b/packages/ui/.eslintrc.js @@ -0,0 +1 @@ +module.exports = { root: true, extends: ["@ai-rxos/eslint-config/react-library.js"] }; diff --git a/packages/ui/package.json b/packages/ui/package.json new file mode 100644 index 0000000..a9b035c --- /dev/null +++ b/packages/ui/package.json @@ -0,0 +1,39 @@ +{ + "name": "@ai-rxos/ui", + "version": "0.1.0", + "private": true, + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.js" + }, + "./styles.css": "./dist/styles.css" + }, + "scripts": { + "build": "tsup src/index.ts --format esm,cjs --dts --external react", + "dev": "tsup src/index.ts --format esm,cjs --dts --external react --watch", + "lint": "eslint src --max-warnings 0", + "typecheck": "tsc --noEmit", + "clean": "rimraf dist .turbo" + }, + "peerDependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@ai-rxos/eslint-config": "workspace:*", + "@ai-rxos/typescript-config": "workspace:*", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "eslint": "^8.57.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "rimraf": "^6.0.1", + "tsup": "^8.3.5", + "typescript": "^5.7.2" + } +} diff --git a/packages/ui/src/Badge.tsx b/packages/ui/src/Badge.tsx new file mode 100644 index 0000000..6f9ceee --- /dev/null +++ b/packages/ui/src/Badge.tsx @@ -0,0 +1,18 @@ +import * as React from "react"; + +export interface BadgeProps extends React.HTMLAttributes { + tone?: "neutral" | "success" | "warning" | "danger" | "info"; +} + +const TONE_CLASS: Record, string> = { + neutral: "rxos-badge-neutral", + success: "rxos-badge-success", + warning: "rxos-badge-warning", + danger: "rxos-badge-danger", + info: "rxos-badge-info", +}; + +export function Badge({ tone = "neutral", className, ...props }: BadgeProps) { + const classes = ["rxos-badge", TONE_CLASS[tone], className].filter(Boolean).join(" "); + return ; +} diff --git a/packages/ui/src/Button.tsx b/packages/ui/src/Button.tsx new file mode 100644 index 0000000..927b2e5 --- /dev/null +++ b/packages/ui/src/Button.tsx @@ -0,0 +1,29 @@ +import * as React from "react"; + +export interface ButtonProps extends React.ButtonHTMLAttributes { + variant?: "primary" | "secondary" | "ghost" | "danger"; + size?: "sm" | "md" | "lg"; +} + +const VARIANT_CLASS: Record, string> = { + primary: "rxos-btn-primary", + secondary: "rxos-btn-secondary", + ghost: "rxos-btn-ghost", + danger: "rxos-btn-danger", +}; + +const SIZE_CLASS: Record, string> = { + sm: "rxos-btn-sm", + md: "rxos-btn-md", + lg: "rxos-btn-lg", +}; + +export const Button = React.forwardRef( + ({ variant = "primary", size = "md", className, ...props }, ref) => { + const classes = ["rxos-btn", VARIANT_CLASS[variant], SIZE_CLASS[size], className] + .filter(Boolean) + .join(" "); + return