diff --git a/.cixignore b/.cixignore new file mode 100644 index 0000000..746c048 --- /dev/null +++ b/.cixignore @@ -0,0 +1,27 @@ +# Files / dirs the cix indexer skips for this repo. +# Pattern syntax mirrors .gitignore. + +# Frontend toolchain noise — don't index 50k+ files of node_modules / build output +server/dashboard/node_modules/ +server/internal/httpapi/dashboard/dist/ + +# Build artefacts +server/dist/ +cli/build/ +cli/dist/ + +# Generated code (regenerate with `npm run gen:api`) +server/dashboard/src/api/generated.ts + +# Embedded vendored bundles already covered by the linked sources elsewhere +server/internal/httpapi/docs/swagger-ui/ + +# Local data / runtime state +data/ +.local-data/ +*.db +*.db-wal +*.db-shm + +# Legacy archived tree (see commit 063a14e) +legacy/python-api/ diff --git a/.env.example b/.env.example index b7b3e54..ba37d24 100644 --- a/.env.example +++ b/.env.example @@ -1,12 +1,46 @@ +# cix-server environment template. Copy to .env and fill in real values. + +# ── Auth ────────────────────────────────────────────────────────────────── +# Header API key for direct CLI / CI traffic. Generate with: +# openssl rand -hex 32 | sed 's/^/cix_/' CIX_API_KEY=cix_ + +# First-boot admin seed (REQUIRED when the DB has no users yet — the server +# refuses to start otherwise). The user is flagged must_change_password=true, +# so the temporary password only works for the first login. Generate with: +# openssl rand -base64 18 | tr -d '/+=' | cut -c1-20 +CIX_BOOTSTRAP_ADMIN_EMAIL=admin@example.com +CIX_BOOTSTRAP_ADMIN_PASSWORD=change-me-on-first-login + +# ── Networking + storage ────────────────────────────────────────────────── CIX_PORT=21847 -CIX_EMBEDDING_MODEL=awhiteside/CodeRankEmbed-Q8_0-GGUF -CIX_MAX_FILE_SIZE=524288 -CIX_EXCLUDED_DIRS=node_modules,.git,.venv,__pycache__,dist,build,.next,.cache,.DS_Store CIX_CHROMA_PERSIST_DIR=~/.cix/data/chroma CIX_SQLITE_PATH=~/.cix/data/sqlite/projects.db CIX_GGUF_CACHE_DIR=~/.cix/data/models + +# ── Indexing ────────────────────────────────────────────────────────────── +CIX_EMBEDDING_MODEL=awhiteside/CodeRankEmbed-Q8_0-GGUF +CIX_MAX_FILE_SIZE=524288 +CIX_EXCLUDED_DIRS=node_modules,.git,.venv,__pycache__,dist,build,.next,.cache,.DS_Store + +# ── llama-server sidecar ────────────────────────────────────────────────── CIX_LLAMA_BIN_DIR=/app +# 99 = offload all layers (CUDA / Metal). 0 = CPU only. CIX_N_GPU_LAYERS=0 CIX_LLAMA_STARTUP_TIMEOUT=60 CIX_EMBEDDINGS_ENABLED=true + +# ── PR-E runtime tunables (also editable from /dashboard/server) ────────── +# 0 = auto. Threads → runtime.NumCPU()/2; batch → match n_ctx. +CIX_LLAMA_THREADS=0 +CIX_LLAMA_BATCH=0 +# Embedding queue parallelism. 5 is the new default — pipelines host-side +# prep with device inference. Drop to 1 if you observe contention. +CIX_MAX_EMBEDDING_CONCURRENCY=5 +CIX_EMBEDDING_QUEUE_TIMEOUT=300 + +# ── Optional: bootstrap the GGUF cache from a host-side file ────────────── +# Paired with the bind-mount example in docker-compose.{yml,cuda.yml}. +# Uncomment + set after wiring the bind, then drop both once the cache +# is seeded. +# CIX_BOOTSTRAP_GGUF_PATH=/bootstrap/model.gguf diff --git a/.github/workflows/release-server.yml b/.github/workflows/release-server.yml index 8f1c7fd..f0c7c2f 100644 --- a/.github/workflows/release-server.yml +++ b/.github/workflows/release-server.yml @@ -84,6 +84,11 @@ jobs: provenance: mode=max sbom: true build-args: VERSION=${{ steps.ver.outputs.version }} + # `openapi=doc` mounts the repo-root doc/ folder so the dashboard + # build stage can `COPY --from=openapi openapi.yaml` without us + # widening the primary build context (which is `server/`). + build-contexts: | + openapi=doc tags: ${{ steps.tags.outputs.tags }} docker-cuda: @@ -139,6 +144,11 @@ jobs: provenance: mode=max sbom: true build-args: VERSION=${{ steps.ver.outputs.version }} + # `openapi=doc` mounts the repo-root doc/ folder so the dashboard + # build stage can `COPY --from=openapi openapi.yaml` without us + # widening the primary build context (which is `server/`). + build-contexts: | + openapi=doc tags: ${{ steps.tags.outputs.tags }} release: diff --git a/.gitignore b/.gitignore index e3ef4cf..94f02a6 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,11 @@ __pycache__/ *$py.class *.egg-info/ *.egg -dist/ +# Note: the bare `dist/` rule was scoped to /dist/ (root only) so the +# server/internal/httpapi/dashboard/dist/.gitkeep negation below +# actually works — Git can't re-include a file if a parent directory +# is broadly excluded. +/dist/ build/ .eggs/ *.whl @@ -44,11 +48,25 @@ cli/dist/ server/dist/ server/exec.log +# Dashboard build output — produced by `make dashboard-build`. +# A committed `.gitkeep` keeps dist/ non-empty so `//go:embed all:dist` works +# on a fresh clone (the embed.FS needs at least one entry). The real +# index.html + assets/ tree is produced by `make dashboard-build` and never +# tracked. The handler in dashboard.go returns an inline "please build" +# placeholder when index.html is absent. +server/dashboard/node_modules/ +server/internal/httpapi/dashboard/dist/* +!server/internal/httpapi/dashboard/dist/.gitkeep + # uv .python-version -# Local docs +# Local docs (top-level docs/ is a notebook directory per CLAUDE.md; +# tracked project documentation lives in doc/). Exception below for the +# embedded Swagger UI bundle inside the Go server package. docs/ +!server/internal/httpapi/docs/ +!server/internal/httpapi/docs/** # Claude Code .claude/ diff --git a/README.md b/README.md index f8021bf..ddbb1da 100644 --- a/README.md +++ b/README.md @@ -667,8 +667,11 @@ idle draw. Embedding calls do not spike VRAM the way fp16 PyTorch attention used to — sequence length and batch size only change latency, not peak memory. `MAX_CHUNK_TOKENS` still caps the length of each code chunk (1 token ≈ 4 chars) -and must stay ≤ `n_ctx` (8192). `MAX_EMBEDDING_CONCURRENCY` should stay at `1` -for single-GPU setups — llama.cpp serialises through one context. +and must stay ≤ `n_ctx` (8192). `MAX_EMBEDDING_CONCURRENCY` defaults to `5` — +the indexing queue ships chunks in parallel; the llama-server sidecar still +serialises requests through one context, but pipelining host-side prep with +device inference at this depth saturates the GPU without measurable latency +cost. Drop to `1` only if you observe contention. See [`doc/vram-profiling.md`](doc/vram-profiling.md) for methodology and numbers. diff --git a/doc/SECURITY_DEPLOYMENT.md b/doc/SECURITY_DEPLOYMENT.md new file mode 100644 index 0000000..555070a --- /dev/null +++ b/doc/SECURITY_DEPLOYMENT.md @@ -0,0 +1,160 @@ +# Security & deployment notes + +This document captures the operational requirements that the cix-server +codebase assumes but does not enforce on its own. Read it before exposing +the dashboard to users beyond a single trusted operator. + +## Trusted-proxy posture for `X-Forwarded-For` + +The server reads `X-Forwarded-For` (first hop) when present and uses the +result for two things: + +1. **Audit metadata** — stored as `sessions.last_seen_ip` and + `api_keys.last_used_ip`. +2. **Per-IP login rate limit key** — see "Login brute-force resistance" + below. The per-(IP, email) key still binds independently of the IP + source, so password guessing against a known account is rate-limited + regardless; only the global per-IP sweep cap depends on the header + being trustworthy. + +This makes the trusted-proxy posture **load-bearing for security**, not +just for audit honesty. Two safe deployments: + +- **Reverse proxy in front** (Cloudflare / Caddy / nginx / Traefik / ALB): + configure the proxy to *replace* the inbound `X-Forwarded-For` with the + real client IP, not append to it. Drop `X-Real-IP` if you don't need + it. This is the recommended posture for any internet-exposed + deployment. +- **Direct exposure on a trusted network** (LAN / VPN only): nothing + forwards `X-Forwarded-For` for you, so an attacker who can reach the + port can also forge the header. The per-(IP, email) cap still slows + password guessing, but the global per-IP cap is bypassable. Acceptable + on a trusted network, never on the open internet. + +Example for nginx: + +```nginx +location / { + proxy_set_header X-Forwarded-For $remote_addr; # replace, not append + proxy_set_header Host $host; + proxy_pass http://cix-server:21847; +} +``` + +## TLS + +The session cookie's `Secure` attribute is set automatically when the +request arrives over TLS (`r.TLS != nil`). For any deployment beyond +`localhost`, terminate TLS in front of the server and ensure the server +sees TLS-marked requests so the cookie is not sent in cleartext. + +If you front the server with a TLS-terminating proxy that downgrades to +plain HTTP for the upstream hop, the auto-detection will return false and +`Secure` will be omitted. Two fixes: + +- Terminate TLS directly in cix-server (drop the proxy). +- Or configure the proxy to make the upstream hop look TLS-marked — the + details vary; consult the proxy docs. + +## Login brute-force resistance + +POST `/api/v1/auth/login` is rate-limited in process (`internal/httpapi/loginlimiter.go`): + +- **5 failed attempts per (IP, email) per 15 minutes** — slows guessing + against a known account. Cleared on a successful login so a user who + fat-fingers their password a few times is not stuck. +- **60 attempts per IP per minute** — slows horizontal sweeps across many + emails from a single source. Not cleared on a successful login. + +This is a single-process limiter; multi-replica deployments do not share +state. If you scale out, put a shared throttle (Redis, your reverse proxy) +in front of `/api/v1/auth/login` or accept that the per-replica caps are +the floor. + +## Request body size limits + +A request-body middleware rejects oversize payloads up-front: + +- **Default cap: 1 MiB** for every endpoint. +- **Indexing cap: 64 MiB** for `POST /api/v1/projects/{path}/index/files`, + which legitimately receives JSON-encoded source from a batch of files. + At default config (batch=20, max-file=512 KiB) a real payload is ~11 MiB; + the cap also covers operator-tuned worst case (batch=50 × max-file=1 MiB + ≈ 55 MiB) with headroom. + +The cap fires on `Content-Length` (clean 413) and on chunked-transfer +overflow (the JSON decoder fails and the handler returns 422). If your +indexer batches need more than 64 MiB, raise `indexingMaxBodyBytes` in +`internal/httpapi/middleware.go` rather than asking operators to disable +the cap. + +## Bootstrap admin + +On a fresh database the server reads `CIX_BOOTSTRAP_ADMIN_EMAIL` and +`CIX_BOOTSTRAP_ADMIN_PASSWORD` and creates the first admin row, marked +`must_change_password=1` so the operator must change the password on +first login. + +- Both env vars must be set together; setting only one is a fatal + startup error. +- Once the users table is non-empty, the env vars are ignored. Rotating + the bootstrap password by editing the env has no effect on a running + installation — go through the dashboard or directly through SQLite. +- The bootstrap path is **not transactional**. If two server instances + start simultaneously against the same fresh database, one of them will + fail with a UNIQUE-constraint error from the duplicate email. This is + intentional (better to fail loud than silently create two admins) but + operationally surprising under HA-style deployments — start a single + instance first, then scale out. + +## Password policy + +The server enforces only `len(password) >= 8`. There is no complexity +rule, no breached-password dictionary check, no rotation prompt. + +For internet-exposed deployments, choose admin passwords accordingly: a +20+ character random passphrase from a password manager beats anything +the server could enforce. The rate limiter above caps the damage of +weak passwords at ~480 guesses per (IP, email) per day. + +## No self-service password reset + +A user who forgets their password cannot reset it themselves. Recovery +options (in order of preference): + +1. Another admin issues `POST /api/v1/admin/users` with a new initial + password and `must_change_password=1`, then disables the old account. +2. Direct SQLite access to clear `users.disabled_at` and reset + `users.password_hash` (use bcrypt cost 12). + +Plan for this when designating admins — keep at least two so an admin +reset never requires DB-level intervention. + +## API key scoping + +API keys inherit the full permissions of their owning user. A viewer's +key can do anything a viewer can; an admin's key can do anything an +admin can. There is no read-only scoping, no per-project scoping, no +expiry. + +For automated callers (CI, scripts) that only need to read, create a +dedicated viewer user and issue keys from that account. Rotate keys via +`DELETE /api/v1/api-keys/{id}` rather than reusing them. + +## What the server does NOT do + +If your threat model needs any of these, build them in front of cix-server +or accept the risk: + +- **CSRF tokens.** Protection relies on the cookie's `SameSite=Strict` + + `HttpOnly` attributes, which modern browsers honour. There is no + separate token to validate. +- **CORS.** No `Access-Control-Allow-*` headers are emitted; same-origin + is the assumption. +- **WAF / IDS.** No IP allowlisting, no anomaly detection. Use your + reverse proxy or a host-level firewall. +- **Multi-tenant project ownership.** All authenticated users see all + projects. Destructive mutations (PATCH/DELETE) are admin- + only; create/list/search are open to any authenticated user. If you + need true tenant separation, run separate cix-server instances per + tenant. diff --git a/doc/openapi.yaml b/doc/openapi.yaml new file mode 100644 index 0000000..9ce80ed --- /dev/null +++ b/doc/openapi.yaml @@ -0,0 +1,2258 @@ +openapi: 3.0.3 +info: + title: cix-server API + version: v1 + description: | + HTTP API for the `cix-server` semantic code-index daemon. + + The wire format is byte-stable across server versions; the `api_version` + field in `/api/v1/status` ticks independently from `server_version` when + a backwards-incompatible change lands. + + ## Authentication + + Two parallel auth paths back the same identity model: + + - **Browsers / dashboard**: `POST /api/v1/auth/login` with email + + password issues an HttpOnly cookie `cix_session`. The cookie is sent + automatically on subsequent same-origin requests. Sessions roll + forward 14 days from the last request. + - **CLI / SDK**: `Authorization: Bearer ` where the key was + issued by `POST /api/v1/api-keys`. Keys are owner-scoped and can be + revoked individually without affecting other clients. + + Public endpoints (no auth required): `GET /health`, `GET /docs`, + `GET /openapi.yaml`, `GET /api/v1/auth/bootstrap-status`, + `POST /api/v1/auth/login`. Setting `CIX_AUTH_DISABLED=true` skips the + check on every endpoint (development only — a warning is logged). + + When the server starts with an empty users table it requires + `CIX_BOOTSTRAP_ADMIN_EMAIL` + `CIX_BOOTSTRAP_ADMIN_PASSWORD` to seed + the first admin (forced to change password on first login). A legacy + `CIX_API_KEY` set on a fresh database is imported as a single + `env-bootstrap` API key owned by that admin. + + ## Project addressing + + Project `host_path` values contain slashes that cannot be embedded in + URL segments cleanly, so project-scoped endpoints take a `{path}` URL + parameter that is the **first 16 hex chars of `SHA1(host_path)`**. + Compute it with `internal/projects.HashPath`. + + ## Streaming indexing + + `POST /api/v1/projects/{path}/index/files` honours the `Accept` header: + sending `Accept: application/x-ndjson` switches the response to a + newline-delimited stream of `IndexProgressEvent` objects, with a + `heartbeat` event every 10 seconds. Old clients that send no Accept + header (or `application/json`) get the legacy single-JSON response. + + ## Errors + + All error responses use the same body shape: `{"detail": ""}`. + `GET /health` is the only exception — the unhealthy variant additionally + carries `{"reason": "..."}`. + +servers: + # Relative URL — Swagger UI's "Try it out" sends requests to whatever + # origin served /openapi.json, so localhost:8001, localhost:21847, and a + # remote deployment all just work without CORS preflight surprises. + - url: / + description: Same-origin (auto-detected from the URL serving this spec) + +security: + - bearerAuth: [] + +tags: + - name: probe + description: Health and status probes + - name: projects + description: Project lifecycle (CRUD) + - name: search + description: Symbol, definition, reference, file, and semantic search + - name: indexing + description: Three-phase indexing protocol + - name: docs + description: API documentation + - name: auth + description: Sessions, login/logout, password change, current user + - name: admin + description: Admin-only user management + - name: api-keys + description: Issue and revoke owner-scoped API keys for CLI/SDK use + +paths: + /health: + get: + operationId: getHealth + tags: [probe] + summary: Liveness probe (public) + description: | + Returns `{"status":"ok"}` when the server can reach SQLite within 1 + second. Returns 503 with `{"status":"unhealthy","reason":"..."}` when + the DB ping fails. Public — no `Authorization` header required. + security: [] + responses: + "200": + description: Server is healthy + content: + application/json: + schema: + $ref: "#/components/schemas/HealthResponse" + "503": + description: Server is unhealthy (e.g. DB unreachable) + content: + application/json: + schema: + $ref: "#/components/schemas/HealthResponse" + + /api/v1/status: + get: + operationId: getStatus + tags: [probe] + summary: Server / sidecar status (authenticated) + description: | + Returns server metadata: version, configured embedding model, whether + the llama-server sidecar is reachable (`model_loaded`), number of + registered projects, and number of currently-running indexing jobs. + responses: + "200": + description: Status payload + content: + application/json: + schema: + $ref: "#/components/schemas/StatusResponse" + "401": + $ref: "#/components/responses/Unauthorized" + + /api/v1/auth/bootstrap-status: + get: + operationId: getBootstrapStatus + tags: [auth] + summary: Whether the dashboard needs first-run bootstrap (public) + description: | + Returns `{"needs_bootstrap": true}` when the users table is empty. + The dashboard renders an explanatory screen in that case ("ask the + operator to deploy with CIX_BOOTSTRAP_ADMIN_* env vars and + restart"). No authentication required. + security: [] + responses: + "200": + description: Bootstrap status + content: + application/json: + schema: + $ref: "#/components/schemas/BootstrapStatusResponse" + + /api/v1/auth/login: + post: + operationId: login + tags: [auth] + summary: Exchange email + password for a session cookie (public) + description: | + On success, sets `Set-Cookie: cix_session=; HttpOnly; + SameSite=Strict; Path=/`. Returns the user payload plus a + `must_change_password` flag — when true, the dashboard forces a + change-password screen before any other action. + security: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/LoginRequest" + responses: + "200": + description: Logged in + headers: + Set-Cookie: + schema: + type: string + description: Session cookie (HttpOnly, SameSite=Strict) + content: + application/json: + schema: + $ref: "#/components/schemas/LoginResponse" + "401": + $ref: "#/components/responses/Unauthorized" + "422": + $ref: "#/components/responses/Unprocessable" + + /api/v1/auth/logout: + post: + operationId: logout + tags: [auth] + summary: End the current session + description: | + Deletes the server-side session row and instructs the browser to + clear `cix_session` via `Set-Cookie: cix_session=; Max-Age=0`. + responses: + "204": + description: Session ended + "401": + $ref: "#/components/responses/Unauthorized" + + /api/v1/auth/me: + get: + operationId: getMe + tags: [auth] + summary: Current authenticated user + description: | + Returns the user attached to the active session OR API key. + Includes `must_change_password` so the dashboard knows whether to + gate further navigation behind a change-password screen. + responses: + "200": + description: Current user + content: + application/json: + schema: + $ref: "#/components/schemas/MeResponse" + "401": + $ref: "#/components/responses/Unauthorized" + + /api/v1/auth/change-password: + post: + operationId: changePassword + tags: [auth] + summary: Change the current user's password + description: | + Verifies `current_password`, updates to `new_password`, clears + `must_change_password`, and revokes every other session of this + user (the cookie carrying the change-password request is kept + alive). Does NOT touch API keys — those are revoked individually. + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/ChangePasswordRequest" + responses: + "204": + description: Password updated + "401": + $ref: "#/components/responses/Unauthorized" + "422": + $ref: "#/components/responses/Unprocessable" + + /api/v1/auth/sessions: + get: + operationId: listMySessions + tags: [auth] + summary: Active sessions of the current user + description: | + Returns every non-expired session of the caller, newest-first, with + `last_seen_at`, `last_seen_ip`, and `last_seen_ua` so the user can + spot unfamiliar logins. The `is_current` flag marks the session + carrying this request. + responses: + "200": + description: Session list + content: + application/json: + schema: + $ref: "#/components/schemas/SessionListResponse" + "401": + $ref: "#/components/responses/Unauthorized" + + /api/v1/auth/sessions/{id}: + delete: + operationId: deleteMySession + tags: [auth] + summary: End one of my sessions (sign out a single device) + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + "204": + description: Session ended + "401": + $ref: "#/components/responses/Unauthorized" + "404": + $ref: "#/components/responses/NotFound" + + /api/v1/admin/users: + get: + operationId: listUsers + tags: [admin] + summary: List all users (admin only) + responses: + "200": + description: User list + content: + application/json: + schema: + $ref: "#/components/schemas/UserListResponse" + "401": + $ref: "#/components/responses/Unauthorized" + "403": + $ref: "#/components/responses/Forbidden" + post: + operationId: createUser + tags: [admin] + summary: Invite a new user (admin only) + description: | + The admin sets an `initial_password` and shares it out-of-band. + The new user is flagged `must_change_password=true` and will be + forced to change it on first login. + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/CreateUserRequest" + responses: + "201": + description: Created + content: + application/json: + schema: + $ref: "#/components/schemas/User" + "401": + $ref: "#/components/responses/Unauthorized" + "403": + $ref: "#/components/responses/Forbidden" + "409": + $ref: "#/components/responses/Conflict" + "422": + $ref: "#/components/responses/Unprocessable" + + /api/v1/admin/users/{id}: + parameters: + - name: id + in: path + required: true + schema: + type: string + patch: + operationId: updateUser + tags: [admin] + summary: Change role or disabled flag (admin only) + description: | + Refuses to demote or disable the last enabled admin in the system. + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/UpdateUserRequest" + responses: + "200": + description: Updated + content: + application/json: + schema: + $ref: "#/components/schemas/User" + "401": + $ref: "#/components/responses/Unauthorized" + "403": + $ref: "#/components/responses/Forbidden" + "404": + $ref: "#/components/responses/NotFound" + "422": + $ref: "#/components/responses/Unprocessable" + delete: + operationId: deleteUser + tags: [admin] + summary: Delete a user (admin only) + description: | + Cascades to the user's sessions and API keys. Refuses to delete + the last enabled admin. + responses: + "204": + description: Deleted + "401": + $ref: "#/components/responses/Unauthorized" + "403": + $ref: "#/components/responses/Forbidden" + "404": + $ref: "#/components/responses/NotFound" + + /api/v1/admin/runtime-config: + get: + operationId: getRuntimeConfig + tags: [admin] + summary: Read effective runtime config (admin only) + description: | + Returns the resolved runtime config (DB row → env → recommended) the + sidecar is currently configured against, plus a `source` map labelling + each field's origin so the dashboard can render the "DB" / "Env" / + "Recommended" pill next to every value. + responses: + "200": + description: Effective runtime config + content: + application/json: + schema: + $ref: "#/components/schemas/RuntimeConfig" + "401": + $ref: "#/components/responses/Unauthorized" + "403": + $ref: "#/components/responses/Forbidden" + put: + operationId: putRuntimeConfig + tags: [admin] + summary: Save runtime config overrides (admin only) + description: | + Replaces the runtime_settings row. Fields omitted from the request + clear their override (= fall back to env / recommended on next Get). + Does NOT restart the sidecar — the dashboard issues a separate + POST /admin/sidecar/restart after a successful PUT. + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/RuntimeConfigUpdate" + responses: + "200": + description: Updated config + content: + application/json: + schema: + $ref: "#/components/schemas/RuntimeConfig" + "401": + $ref: "#/components/responses/Unauthorized" + "403": + $ref: "#/components/responses/Forbidden" + "422": + $ref: "#/components/responses/Unprocessable" + + /api/v1/admin/sidecar/restart: + post: + operationId: restartSidecar + tags: [admin] + summary: Restart the llama-server sidecar (admin only) + description: | + Drains the embedding queue (30s timeout), terminates the current + child process, and respawns with the latest runtime config. + Returns 202 immediately; poll GET /admin/sidecar/status to observe + the running → restarting → running transition. In-flight indexing + batches at the moment of restart will fail with ErrSupervisor and + must be re-driven by the operator (`cix reindex `). + responses: + "202": + description: Restart accepted + content: + application/json: + schema: + $ref: "#/components/schemas/RestartAccepted" + "401": + $ref: "#/components/responses/Unauthorized" + "403": + $ref: "#/components/responses/Forbidden" + "503": + description: Embeddings disabled at boot — restart is a no-op + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + /api/v1/admin/sidecar/status: + get: + operationId: getSidecarStatus + tags: [admin] + summary: Sidecar process status (admin only) + responses: + "200": + description: Status snapshot + content: + application/json: + schema: + $ref: "#/components/schemas/SidecarStatus" + "401": + $ref: "#/components/responses/Unauthorized" + "403": + $ref: "#/components/responses/Forbidden" + + /api/v1/admin/models: + get: + operationId: listModels + tags: [admin] + summary: List GGUF model files cached on disk (admin only) + description: | + Walks `CIX_GGUF_CACHE_DIR` and returns one entry per .gguf file. Used + by the dashboard's embedding-model picker so admins don't have to + type HF repo IDs by hand. Empty list when the cache is empty — + dashboard falls back to a free-text path input in that case. + responses: + "200": + description: Cached model list + content: + application/json: + schema: + $ref: "#/components/schemas/ModelList" + "401": + $ref: "#/components/responses/Unauthorized" + "403": + $ref: "#/components/responses/Forbidden" + + /api/v1/api-keys: + get: + operationId: listApiKeys + tags: [api-keys] + summary: List my API keys (or all keys if admin) + parameters: + - name: owner + in: query + required: false + description: | + `all` — admin-only, returns every key in the system. + Anything else (or unset) returns the caller's keys. + schema: + type: string + enum: [all] + responses: + "200": + description: Key list + content: + application/json: + schema: + $ref: "#/components/schemas/ApiKeyListResponse" + "401": + $ref: "#/components/responses/Unauthorized" + "403": + $ref: "#/components/responses/Forbidden" + post: + operationId: createApiKey + tags: [api-keys] + summary: Issue a new API key + description: | + Returns the **plaintext** `full_key` exactly once in the response. + The dashboard shows it with a copy button and a "this is the only + time you will see this value" warning. Subsequent reads only + return the `prefix`. + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/CreateApiKeyRequest" + responses: + "201": + description: Key created + content: + application/json: + schema: + $ref: "#/components/schemas/ApiKeyCreated" + "401": + $ref: "#/components/responses/Unauthorized" + "422": + $ref: "#/components/responses/Unprocessable" + + /api/v1/api-keys/{id}: + delete: + operationId: revokeApiKey + tags: [api-keys] + summary: Revoke an API key + description: | + The owner can revoke their own keys. An admin can revoke any + key. Revoking is permanent — the row stays in the table marked + with `revoked_at` so the audit trail is preserved, but + subsequent Bearer auth attempts with the value fail with 401. + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + "204": + description: Revoked + "401": + $ref: "#/components/responses/Unauthorized" + "403": + $ref: "#/components/responses/Forbidden" + "404": + $ref: "#/components/responses/NotFound" + + /api/v1/projects: + post: + operationId: createProject + tags: [projects] + summary: Register a new project + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/CreateProjectRequest" + responses: + "201": + description: Project created + content: + application/json: + schema: + $ref: "#/components/schemas/Project" + "401": + $ref: "#/components/responses/Unauthorized" + "409": + description: A project with this `host_path` already exists + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "422": + $ref: "#/components/responses/Unprocessable" + "500": + $ref: "#/components/responses/InternalError" + get: + operationId: listProjects + tags: [projects] + summary: List all registered projects + responses: + "200": + description: Project list + content: + application/json: + schema: + $ref: "#/components/schemas/ProjectListResponse" + "401": + $ref: "#/components/responses/Unauthorized" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/projects/{path}: + parameters: + - $ref: "#/components/parameters/ProjectHash" + get: + operationId: getProject + tags: [projects] + summary: Get one project by hash + responses: + "200": + description: Project + content: + application/json: + schema: + $ref: "#/components/schemas/Project" + "401": + $ref: "#/components/responses/Unauthorized" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalError" + patch: + operationId: updateProject + tags: [projects] + summary: Patch project settings (admin only) + description: | + Admin-only. Settings changes can shrink the indexing surface + (exclude_patterns, max_file_size); viewers must not be able to + silently de-index a project. + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/UpdateProjectRequest" + responses: + "200": + description: Updated project + content: + application/json: + schema: + $ref: "#/components/schemas/Project" + "401": + $ref: "#/components/responses/Unauthorized" + "403": + $ref: "#/components/responses/Forbidden" + "404": + $ref: "#/components/responses/NotFound" + "422": + $ref: "#/components/responses/Unprocessable" + "500": + $ref: "#/components/responses/InternalError" + delete: + operationId: deleteProject + tags: [projects] + summary: Delete a project and all its indexed data (admin only) + description: | + Admin-only. Drops the project plus its symbols, refs, file hashes, + and embeddings. Destructive enough that viewer-scoped sessions and + API keys must not reach it. + responses: + "204": + description: Deleted (no body) + "401": + $ref: "#/components/responses/Unauthorized" + "403": + $ref: "#/components/responses/Forbidden" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/projects/{path}/summary: + parameters: + - $ref: "#/components/parameters/ProjectHash" + get: + operationId: getProjectSummary + tags: [projects] + summary: Project overview (top dirs, recent symbols, totals) + responses: + "200": + description: Summary + content: + application/json: + schema: + $ref: "#/components/schemas/ProjectSummary" + "401": + $ref: "#/components/responses/Unauthorized" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/projects/{path}/search: + parameters: + - $ref: "#/components/parameters/ProjectHash" + post: + operationId: semanticSearch + tags: [search] + summary: Semantic (vector) search + description: | + Embeds the query and runs an approximate nearest-neighbour search + against the project's chromem-go collection. Results are + post-filtered by `min_score`, `paths` (whitelist, prefix-OR-substring + match), `excludes` (blacklist, same matching), and `languages` — + then merged into per-file groups and ranked by best match score. + + `min_score` semantics: + - omitted → server default `0.4` (calibrated for CodeRankEmbed-Q8) + - explicit `0` → return everything above HNSW floor + - explicit positive → that floor + + `query_time_ms` is rounded to 1 decimal place. + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/SemanticSearchRequest" + responses: + "200": + description: Per-file grouped semantic results + content: + application/json: + schema: + $ref: "#/components/schemas/SemanticSearchResponse" + "401": + $ref: "#/components/responses/Unauthorized" + "404": + $ref: "#/components/responses/NotFound" + "422": + $ref: "#/components/responses/Unprocessable" + "503": + description: | + Embeddings disabled, vector store missing, or sidecar busy + (Retry-After header set). + headers: + Retry-After: + schema: { type: integer } + description: Seconds to wait when the embedder is busy + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/projects/{path}/search/symbols: + parameters: + - $ref: "#/components/parameters/ProjectHash" + post: + operationId: searchSymbols + tags: [search] + summary: Symbol search by name (prefix/substring) + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/SymbolSearchRequest" + responses: + "200": + description: Matching symbols + content: + application/json: + schema: + $ref: "#/components/schemas/SymbolSearchResponse" + "401": + $ref: "#/components/responses/Unauthorized" + "404": + $ref: "#/components/responses/NotFound" + "422": + $ref: "#/components/responses/Unprocessable" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/projects/{path}/search/definitions: + parameters: + - $ref: "#/components/parameters/ProjectHash" + post: + operationId: searchDefinitions + tags: [search] + summary: Go-to-definition by symbol name + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/DefinitionRequest" + responses: + "200": + description: Matching definitions + content: + application/json: + schema: + $ref: "#/components/schemas/DefinitionResponse" + "401": + $ref: "#/components/responses/Unauthorized" + "404": + $ref: "#/components/responses/NotFound" + "422": + $ref: "#/components/responses/Unprocessable" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/projects/{path}/search/references: + parameters: + - $ref: "#/components/parameters/ProjectHash" + post: + operationId: searchReferences + tags: [search] + summary: Find references to a symbol + description: | + Returns the locations where `symbol` appears as an identifier token. + `content` is intentionally empty and `end_line == start_line` — + populating snippets here would require a full file re-read per + result; clients should follow up with semantic search or read the + file directly. + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/ReferenceRequest" + responses: + "200": + description: References + content: + application/json: + schema: + $ref: "#/components/schemas/ReferenceResponse" + "401": + $ref: "#/components/responses/Unauthorized" + "404": + $ref: "#/components/responses/NotFound" + "422": + $ref: "#/components/responses/Unprocessable" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/projects/{path}/search/files: + parameters: + - $ref: "#/components/parameters/ProjectHash" + post: + operationId: searchFiles + tags: [search] + summary: File-path substring search + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/FileSearchRequest" + responses: + "200": + description: Matching file paths + content: + application/json: + schema: + $ref: "#/components/schemas/FileSearchResponse" + "401": + $ref: "#/components/responses/Unauthorized" + "404": + $ref: "#/components/responses/NotFound" + "422": + $ref: "#/components/responses/Unprocessable" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/projects/{path}/index/begin: + parameters: + - $ref: "#/components/parameters/ProjectHash" + post: + operationId: indexBegin + tags: [indexing] + summary: Open an indexing session + description: | + Returns a `run_id` and the map of currently-stored content hashes + (`stored_hashes`) so the client can compute a delta. Pass `full:true` + to wipe project state before indexing. Body is optional. + requestBody: + required: false + content: + application/json: + schema: + $ref: "#/components/schemas/IndexBeginRequest" + responses: + "200": + description: Session opened + content: + application/json: + schema: + $ref: "#/components/schemas/IndexBeginResponse" + "401": + $ref: "#/components/responses/Unauthorized" + "404": + $ref: "#/components/responses/NotFound" + "409": + description: An indexing session is already active for this project + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "422": + $ref: "#/components/responses/Unprocessable" + "503": + $ref: "#/components/responses/IndexerUnavailable" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/projects/{path}/index/files: + parameters: + - $ref: "#/components/parameters/ProjectHash" + post: + operationId: indexFiles + tags: [indexing] + summary: Submit a batch of files (max 50) + description: | + Sends a batch of file payloads belonging to the open `run_id`. + Maximum 50 files per call. + + **Streaming negotiation**: send `Accept: application/x-ndjson` to + receive a newline-delimited stream of `IndexProgressEvent` objects + (one event per line, plus a `heartbeat` every 10 s). Without that + header (or with `Accept: application/json`) the response is the + legacy single-JSON `IndexFilesResponse` returned after the batch + completes. + parameters: + - in: header + name: Accept + required: false + schema: + type: string + enum: [application/json, application/x-ndjson] + description: | + `application/x-ndjson` switches to a streamed response + (one `IndexProgressEvent` per line). Default: `application/json`. + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/IndexFilesRequest" + responses: + "200": + description: | + Either a single JSON summary (default) or a stream of NDJSON + events (when the client opted in via `Accept: application/x-ndjson`). + content: + application/json: + schema: + $ref: "#/components/schemas/IndexFilesResponse" + application/x-ndjson: + schema: + $ref: "#/components/schemas/IndexProgressEvent" + examples: + stream: + summary: NDJSON stream (one event per line) + value: | + {"event":"file_started","run_id":"r-1","path":"main.go","file_index":1,"batch_size":20} + {"event":"file_chunked","path":"main.go","chunks":12} + {"event":"file_embedded","path":"main.go","chunks":12,"embed_ms":540} + {"event":"file_done","path":"main.go","chunks":12} + {"event":"heartbeat","ts":"2026-04-27T17:25:00Z"} + {"event":"batch_done","files_accepted":20,"chunks_created":347,"files_processed_total":300} + "401": + $ref: "#/components/responses/Unauthorized" + "404": + description: Project not found OR run_id not found / project mismatch + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "422": + $ref: "#/components/responses/Unprocessable" + "503": + description: | + Indexer disabled, or GPU sidecar busy. When busy, `Retry-After` + header is set with a suggested wait (seconds). + headers: + Retry-After: + schema: { type: integer } + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/projects/{path}/index/finish: + parameters: + - $ref: "#/components/parameters/ProjectHash" + post: + operationId: indexFinish + tags: [indexing] + summary: Commit the indexing session + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/IndexFinishRequest" + responses: + "200": + description: Session committed + content: + application/json: + schema: + $ref: "#/components/schemas/IndexFinishResponse" + "401": + $ref: "#/components/responses/Unauthorized" + "404": + description: Project not found OR run_id unknown / project mismatch + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "422": + $ref: "#/components/responses/Unprocessable" + "503": + $ref: "#/components/responses/IndexerUnavailable" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/projects/{path}/index/cancel: + parameters: + - $ref: "#/components/parameters/ProjectHash" + post: + operationId: indexCancel + tags: [indexing] + summary: Cancel any active indexing session (idempotent) + description: | + Open to any authenticated user. Returns `{"cancelled": true}` if a + session was killed, `false` if none was active. The CLI calls this + in defer-cleanup on early exit, so gating it on admin would leave + run locks hanging for viewers until the 1-hour TTL. + responses: + "200": + description: Cancellation result + content: + application/json: + schema: + $ref: "#/components/schemas/IndexCancelResponse" + "401": + $ref: "#/components/responses/Unauthorized" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/projects/{path}/index/status: + parameters: + - $ref: "#/components/parameters/ProjectHash" + get: + operationId: indexStatus + tags: [indexing] + summary: Live progress of the current run, or last completed run + description: | + When a session is active: returns mid-run progress with + `phase`, `files_discovered/processed/total`, `chunks_created`, + `elapsed_seconds`, and `run_id`. When no session is active: + falls back to the most recent `index_runs` row, returning only + `files_processed/total` and `chunks_created`. If the project + has no history at all: `{"status":"idle"}`. + responses: + "200": + description: Progress payload (or idle) + content: + application/json: + schema: + $ref: "#/components/schemas/IndexProgressResponse" + "401": + $ref: "#/components/responses/Unauthorized" + "404": + $ref: "#/components/responses/NotFound" + +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + description: "API key passed as `Authorization: Bearer `" + + parameters: + ProjectHash: + name: path + in: path + required: true + description: | + First 16 hex chars of `SHA1(host_path)`. See + `internal/projects.HashPath`. + schema: + type: string + pattern: "^[a-f0-9]{16}$" + example: "5b7d2c9e1a3f8042" + + responses: + Unauthorized: + description: Missing or invalid API key + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + NotFound: + description: Resource not found + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + Unprocessable: + description: Malformed request body or missing required fields + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + InternalError: + description: Unhandled server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + IndexerUnavailable: + description: Indexing service not configured + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + Forbidden: + description: Authenticated, but lacks the required role + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + Conflict: + description: Resource already exists (e.g. email taken) + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + schemas: + Error: + type: object + required: [detail] + properties: + detail: + type: string + + BootstrapStatusResponse: + type: object + required: [needs_bootstrap] + properties: + needs_bootstrap: + type: boolean + description: True when the users table is empty. + + User: + type: object + required: [id, email, role, must_change_password, created_at, updated_at, disabled] + properties: + id: + type: string + email: + type: string + format: email + role: + type: string + enum: [admin, viewer] + must_change_password: + type: boolean + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + disabled: + type: boolean + description: | + True when `disabled_at` is set. Disabled users cannot + authenticate via password OR API key. + disabled_at: + type: string + format: date-time + nullable: true + + UserWithStats: + allOf: + - $ref: "#/components/schemas/User" + - type: object + required: [active_sessions_count, api_keys_count] + properties: + last_login_at: + type: string + format: date-time + nullable: true + description: | + Most recent session creation timestamp (RFC3339). + Null if the user has never logged in. + active_sessions_count: + type: integer + minimum: 0 + description: Count of non-expired sessions for this user. + api_keys_count: + type: integer + minimum: 0 + description: Count of non-revoked API keys owned by this user. + + UserListResponse: + type: object + required: [users, total] + properties: + users: + type: array + items: + $ref: "#/components/schemas/UserWithStats" + total: + type: integer + + LoginRequest: + type: object + required: [email, password] + properties: + email: + type: string + format: email + password: + type: string + minLength: 1 + + LoginResponse: + type: object + required: [user] + properties: + user: + $ref: "#/components/schemas/User" + + MeResponse: + type: object + required: [user, auth_method] + properties: + user: + $ref: "#/components/schemas/User" + auth_method: + type: string + enum: [session, api_key] + description: | + Tells the dashboard whether to surface "logout" (session) or + hide it (api_key access — there's nothing to log out of). + + ChangePasswordRequest: + type: object + required: [current_password, new_password] + properties: + current_password: + type: string + minLength: 1 + new_password: + type: string + minLength: 8 + description: Minimum 8 characters. No upper bound. + + CreateUserRequest: + type: object + required: [email, initial_password, role] + properties: + email: + type: string + format: email + initial_password: + type: string + minLength: 8 + description: | + One-time password the new user must change on first login. + The admin shares this out-of-band. + role: + type: string + enum: [admin, viewer] + + UpdateUserRequest: + type: object + properties: + role: + type: string + enum: [admin, viewer] + description: | + New role for the user. Refused for the last enabled admin + when set to `viewer`. + disabled: + type: boolean + description: | + When true, the user can no longer authenticate. Refused for + the last enabled admin when set to true. + + RuntimeConfig: + type: object + required: + - embedding_model + - llama_ctx_size + - llama_n_gpu_layers + - llama_n_threads + - max_embedding_concurrency + - llama_batch_size + - source + properties: + embedding_model: + type: string + description: HF repo ID or absolute filesystem path to a .gguf file. + llama_ctx_size: + type: integer + minimum: 1 + llama_n_gpu_layers: + type: integer + description: -1 = all layers (Metal/CUDA), 0 = CPU only. + llama_n_threads: + type: integer + minimum: 0 + description: 0 = let llama-server auto-detect. + max_embedding_concurrency: + type: integer + minimum: 1 + llama_batch_size: + type: integer + minimum: 1 + source: + type: object + additionalProperties: + type: string + enum: [db, env, recommended] + description: | + Per-field origin label so the dashboard can render a "DB" / + "Env" / "Recommended" pill next to each value. Keys match the + other field names: `embedding_model`, `llama_ctx_size`, ... + recommended: + $ref: "#/components/schemas/RuntimeConfigRecommended" + updated_at: + type: string + format: date-time + nullable: true + description: When the runtime_settings row was last written, or null when only env/recommended are in effect. + updated_by: + type: string + nullable: true + description: Who issued the last PUT, captured from the active session. + + RuntimeConfigRecommended: + type: object + required: + - embedding_model + - llama_ctx_size + - llama_n_gpu_layers + - llama_n_threads + - max_embedding_concurrency + - llama_batch_size + properties: + embedding_model: { type: string } + llama_ctx_size: { type: integer } + llama_n_gpu_layers: { type: integer } + llama_n_threads: { type: integer } + max_embedding_concurrency: { type: integer } + llama_batch_size: { type: integer } + + RuntimeConfigUpdate: + type: object + description: | + All fields optional. Send a value to set/replace the override for + that field, send `""` (string fields) or `0` (numeric fields) to + CLEAR the override (next read falls back to env / recommended). + Omitted fields keep their current value. + properties: + embedding_model: + type: string + nullable: true + llama_ctx_size: + type: integer + nullable: true + llama_n_gpu_layers: + type: integer + nullable: true + llama_n_threads: + type: integer + nullable: true + max_embedding_concurrency: + type: integer + nullable: true + llama_batch_size: + type: integer + nullable: true + + SidecarStatus: + type: object + required: [state, ready, in_flight] + properties: + state: + type: string + enum: [running, starting, restarting, failed, disabled] + pid: + type: integer + minimum: 0 + description: 0 when no child process is alive (failed / disabled). + uptime_seconds: + type: integer + minimum: 0 + model: + type: string + ready: + type: boolean + last_error: + type: string + in_flight: + type: integer + minimum: 0 + description: Embedding queue depth at the moment of sampling. + restart_in_flight: + type: boolean + description: True between accept of POST /sidecar/restart and respawn completion. + + RestartAccepted: + type: object + required: [restart_id] + properties: + restart_id: + type: string + description: Opaque ID; future versions may expose per-restart progress under this id. + + ModelEntry: + type: object + required: [id, path, size_bytes] + properties: + id: + type: string + description: HF repo ID derived from the cache directory name (e.g. owner/model). + path: + type: string + description: Absolute path to the .gguf file on disk. + size_bytes: + type: integer + format: int64 + minimum: 0 + + ModelList: + type: object + required: [models, cache_dir] + properties: + models: + type: array + items: + $ref: "#/components/schemas/ModelEntry" + cache_dir: + type: string + description: The CIX_GGUF_CACHE_DIR that was scanned. Empty list with non-empty cache_dir = no .gguf files found. + + Session: + type: object + required: [id, created_at, expires_at, last_seen_at, is_current] + properties: + id: + type: string + created_at: + type: string + format: date-time + expires_at: + type: string + format: date-time + last_seen_at: + type: string + format: date-time + last_seen_ip: + type: string + nullable: true + last_seen_ua: + type: string + nullable: true + is_current: + type: boolean + description: True for the session carrying this request. + + SessionListResponse: + type: object + required: [sessions, total] + properties: + sessions: + type: array + items: + $ref: "#/components/schemas/Session" + total: + type: integer + + ApiKey: + type: object + required: [id, owner_user_id, name, prefix, created_at, revoked] + properties: + id: + type: string + owner_user_id: + type: string + name: + type: string + prefix: + type: string + description: | + Display-only prefix of the full key (e.g. `cix_a1b2c3d4`). + Long enough to recognise in lists, short enough that it + cannot reconstruct the original. + created_at: + type: string + format: date-time + last_used_at: + type: string + format: date-time + nullable: true + last_used_ip: + type: string + nullable: true + last_used_ua: + type: string + nullable: true + revoked: + type: boolean + revoked_at: + type: string + format: date-time + nullable: true + + ApiKeyListResponse: + type: object + required: [api_keys, total] + properties: + api_keys: + type: array + items: + $ref: "#/components/schemas/ApiKey" + total: + type: integer + + CreateApiKeyRequest: + type: object + required: [name] + properties: + name: + type: string + minLength: 1 + description: | + Human-friendly label shown in the dashboard. The full key + value is generated server-side and returned exactly once. + + ApiKeyCreated: + type: object + required: [api_key, full_key] + properties: + api_key: + $ref: "#/components/schemas/ApiKey" + full_key: + type: string + description: | + The plaintext key value. **Returned exactly once.** Store it + securely — there is no way to retrieve it later. + + HealthResponse: + type: object + required: [status] + properties: + status: + type: string + enum: [ok, unhealthy] + reason: + type: string + description: Set only when `status` is `unhealthy`. + + StatusResponse: + type: object + required: + - status + - backend + - server_version + - api_version + - model_loaded + - embedding_model + - projects + - active_indexing_jobs + properties: + status: + type: string + enum: [ok] + backend: + type: string + description: Backend identifier (e.g. `go`). + server_version: + type: string + api_version: + type: string + example: v1 + model_loaded: + type: boolean + description: | + Whether the llama-server sidecar reports ready within 500 ms. + False when the sidecar is starting or has crashed. + embedding_model: + type: string + description: Hugging Face model id (e.g. `awhiteside/CodeRankEmbed-Q8_0-GGUF`). + projects: + type: integer + minimum: 0 + description: Total registered projects. + active_indexing_jobs: + type: integer + minimum: 0 + description: Currently-running `index_runs` rows. + + ProjectSettings: + type: object + required: [exclude_patterns, max_file_size] + properties: + exclude_patterns: + type: array + items: { type: string } + max_file_size: + type: integer + minimum: 0 + + ProjectStats: + type: object + required: [total_files, indexed_files, total_chunks, total_symbols] + properties: + total_files: + type: integer + minimum: 0 + indexed_files: + type: integer + minimum: 0 + total_chunks: + type: integer + minimum: 0 + total_symbols: + type: integer + minimum: 0 + + Project: + type: object + required: + - path_hash + - host_path + - container_path + - languages + - settings + - stats + - status + - created_at + - updated_at + - last_indexed_at + properties: + path_hash: + type: string + pattern: "^[a-f0-9]{16}$" + description: First 16 hex chars of SHA1(host_path) — stable URL identifier. + host_path: + type: string + description: Absolute filesystem path on the operator's machine. + container_path: + type: string + description: Path inside the container (often equal to host_path). + languages: + type: array + items: { type: string } + settings: + $ref: "#/components/schemas/ProjectSettings" + stats: + $ref: "#/components/schemas/ProjectStats" + status: + type: string + enum: [created, indexing, indexed, error] + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + last_indexed_at: + type: string + format: date-time + nullable: true + indexed_with_model: + type: string + nullable: true + description: | + Embedding model identifier active when this project was last + (re)indexed. NULL on rows that pre-date drift tracking — the + dashboard treats NULL as "Unknown" rather than as drift. + sqlite_path: + type: string + nullable: true + description: Resolved SQLite database path for the active model. NULL on dashboards that don't expose storage info. + chroma_path: + type: string + nullable: true + description: Resolved chromem-go collection directory for this project. NULL when not computed. + sqlite_size_bytes: + type: integer + format: int64 + nullable: true + minimum: 0 + chroma_size_bytes: + type: integer + format: int64 + nullable: true + minimum: 0 + + ProjectListResponse: + type: object + required: [projects, total] + properties: + projects: + type: array + items: + $ref: "#/components/schemas/Project" + total: + type: integer + minimum: 0 + + CreateProjectRequest: + type: object + required: [host_path] + properties: + host_path: + type: string + + UpdateProjectRequest: + type: object + properties: + settings: + $ref: "#/components/schemas/ProjectSettings" + + DirEntry: + type: object + required: [path, file_count] + properties: + path: + type: string + file_count: + type: integer + minimum: 0 + + SymbolEntry: + type: object + required: [name, kind, file_path, language] + properties: + name: + type: string + kind: + type: string + file_path: + type: string + language: + type: string + + ProjectSummary: + type: object + required: + - path_hash + - host_path + - status + - languages + - total_files + - total_chunks + - total_symbols + - top_directories + - recent_symbols + properties: + path_hash: + type: string + pattern: "^[a-f0-9]{16}$" + description: First 16 hex chars of SHA1(host_path) — stable URL identifier. + host_path: + type: string + status: + type: string + languages: + type: array + items: { type: string } + total_files: + type: integer + minimum: 0 + total_chunks: + type: integer + minimum: 0 + total_symbols: + type: integer + minimum: 0 + top_directories: + type: array + items: + $ref: "#/components/schemas/DirEntry" + recent_symbols: + type: array + items: + $ref: "#/components/schemas/SymbolEntry" + + SymbolSearchRequest: + type: object + required: [query] + properties: + query: + type: string + minLength: 1 + kinds: + type: array + items: { type: string } + limit: + type: integer + minimum: 0 + default: 20 + + SymbolResultItem: + type: object + required: [name, kind, file_path, line, end_line, language] + properties: + name: { type: string } + kind: { type: string } + file_path: { type: string } + line: { type: integer } + end_line: { type: integer } + language: { type: string } + signature: { type: string } + parent_name: { type: string } + + SymbolSearchResponse: + type: object + required: [results, total] + properties: + results: + type: array + items: + $ref: "#/components/schemas/SymbolResultItem" + total: + type: integer + minimum: 0 + + DefinitionRequest: + type: object + required: [symbol] + properties: + symbol: + type: string + minLength: 1 + kind: + type: string + file_path: + type: string + limit: + type: integer + minimum: 0 + default: 10 + + DefinitionItem: + type: object + required: [name, kind, file_path, line, end_line, language] + properties: + name: { type: string } + kind: { type: string } + file_path: { type: string } + line: { type: integer } + end_line: { type: integer } + language: { type: string } + signature: { type: string } + parent_name: { type: string } + + DefinitionResponse: + type: object + required: [results, total] + properties: + results: + type: array + items: + $ref: "#/components/schemas/DefinitionItem" + total: + type: integer + minimum: 0 + + ReferenceRequest: + type: object + required: [symbol] + properties: + symbol: + type: string + minLength: 1 + limit: + type: integer + minimum: 0 + default: 50 + file_path: + type: string + + ReferenceItem: + type: object + required: + - file_path + - start_line + - end_line + - content + - chunk_type + - symbol_name + - language + properties: + file_path: { type: string } + start_line: { type: integer } + end_line: + type: integer + description: Always equal to `start_line` (refs table stores tokens, not ranges). + content: + type: string + description: Always empty — see endpoint description. + chunk_type: + type: string + enum: [reference] + symbol_name: { type: string } + language: { type: string } + + ReferenceResponse: + type: object + required: [results, total] + properties: + results: + type: array + items: + $ref: "#/components/schemas/ReferenceItem" + total: + type: integer + minimum: 0 + + FileSearchRequest: + type: object + required: [query] + properties: + query: + type: string + minLength: 1 + description: Substring matched against `file_path`. + limit: + type: integer + minimum: 0 + default: 20 + + FileResultItem: + type: object + required: [file_path, language] + properties: + file_path: { type: string } + language: + type: string + nullable: true + description: Detected language, or null if undetected. + + FileSearchResponse: + type: object + required: [results, total] + properties: + results: + type: array + items: + $ref: "#/components/schemas/FileResultItem" + total: + type: integer + minimum: 0 + + SemanticSearchRequest: + type: object + required: [query] + properties: + query: + type: string + minLength: 1 + limit: + type: integer + minimum: 0 + default: 10 + description: Maximum number of FILE groups (not chunks) to return. + languages: + type: array + items: { type: string } + paths: + type: array + items: { type: string } + description: Whitelist — keep only results whose path matches any prefix or substring. + excludes: + type: array + items: { type: string } + description: Blacklist — drop results whose path matches any prefix or substring. + min_score: + type: number + format: float + description: | + Minimum cosine similarity. Omit for server default (0.4 for + CodeRankEmbed-Q8). Send `0` explicitly to disable the floor. + + NestedHit: + type: object + required: [start_line, end_line, chunk_type, score] + properties: + start_line: { type: integer } + end_line: { type: integer } + symbol_name: { type: string } + chunk_type: { type: string } + score: + type: number + format: float + + FileMatch: + type: object + required: [start_line, end_line, content, score, chunk_type] + properties: + start_line: { type: integer } + end_line: { type: integer } + content: { type: string } + score: + type: number + format: float + chunk_type: { type: string } + symbol_name: { type: string } + nested_hits: + type: array + items: + $ref: "#/components/schemas/NestedHit" + + FileGroupResult: + type: object + required: [file_path, best_score, matches] + properties: + file_path: { type: string } + language: { type: string } + best_score: + type: number + format: float + matches: + type: array + items: + $ref: "#/components/schemas/FileMatch" + + SemanticSearchResponse: + type: object + required: [results, total, query_time_ms] + properties: + results: + type: array + items: + $ref: "#/components/schemas/FileGroupResult" + total: + type: integer + minimum: 0 + query_time_ms: + type: number + format: double + description: Wall-clock query latency, rounded to 1 decimal place. + + IndexBeginRequest: + type: object + properties: + full: + type: boolean + default: false + description: When true, wipes existing project state before opening the session. + + IndexBeginResponse: + type: object + required: [run_id, stored_hashes] + properties: + run_id: + type: string + stored_hashes: + type: object + additionalProperties: + type: string + description: | + Map from file path → SHA-256 of currently-stored content. Empty + when the project has never been indexed (or `full:true` was passed). + + FilePayload: + type: object + required: [path, content, content_hash, size] + properties: + path: + type: string + content: + type: string + description: UTF-8 text. Binary files should not be submitted. + content_hash: + type: string + description: SHA-256 hex digest of `content`. + language: + type: string + size: + type: integer + minimum: 0 + + IndexFilesRequest: + type: object + required: [run_id, files] + properties: + run_id: + type: string + minLength: 1 + files: + type: array + maxItems: 50 + items: + $ref: "#/components/schemas/FilePayload" + + IndexFilesResponse: + type: object + required: [files_accepted, chunks_created, files_processed_total] + properties: + files_accepted: + type: integer + minimum: 0 + chunks_created: + type: integer + minimum: 0 + files_processed_total: + type: integer + minimum: 0 + + IndexFinishRequest: + type: object + required: [run_id] + properties: + run_id: + type: string + minLength: 1 + deleted_paths: + type: array + items: { type: string } + total_files_discovered: + type: integer + minimum: 0 + + IndexFinishResponse: + type: object + required: [status, files_processed, chunks_created] + properties: + status: + type: string + enum: [completed] + files_processed: + type: integer + minimum: 0 + chunks_created: + type: integer + minimum: 0 + + IndexCancelResponse: + type: object + required: [cancelled] + properties: + cancelled: + type: boolean + + IndexProgressInfo: + type: object + description: | + Progress payload. The active-session variant carries every field; + the historical-fallback variant only carries `files_processed`, + `files_total`, and `chunks_created`. + properties: + phase: + type: string + enum: [receiving, completed] + files_discovered: + type: integer + minimum: 0 + files_processed: + type: integer + minimum: 0 + files_total: + type: integer + minimum: 0 + chunks_created: + type: integer + minimum: 0 + elapsed_seconds: + type: number + format: double + run_id: + type: string + + IndexProgressResponse: + type: object + required: [status] + properties: + status: + type: string + enum: [idle, indexing, completed, cancelled, failed, running] + description: | + `idle` — no session ever / fallback unavailable. + `indexing` — session active. + `completed`/`cancelled`/`failed`/`running` — last-run status from `index_runs`. + progress: + $ref: "#/components/schemas/IndexProgressInfo" + + IndexProgressEvent: + type: object + description: | + One event in the NDJSON stream emitted by `POST /index/files` when + the client sends `Accept: application/x-ndjson`. The `event` field + discriminates the variant; other fields are populated as relevant. + required: [event] + properties: + event: + type: string + enum: + - file_started + - file_chunked + - file_embedded + - file_done + - file_error + - heartbeat + - batch_done + - error + run_id: + type: string + path: + type: string + file_index: + type: integer + batch_size: + type: integer + chunks: + type: integer + embed_ms: + type: integer + format: int64 + ts: + type: string + format: date-time + message: + type: string + fatal: + type: boolean + files_accepted: + type: integer + chunks_created: + type: integer + files_processed_total: + type: integer diff --git a/docker-compose.cuda.yml b/docker-compose.cuda.yml index 0cc5dc9..628a25e 100644 --- a/docker-compose.cuda.yml +++ b/docker-compose.cuda.yml @@ -13,11 +13,51 @@ services: - CIX_MAX_FILE_SIZE=${CIX_MAX_FILE_SIZE:-524288} - CIX_EXCLUDED_DIRS=${CIX_EXCLUDED_DIRS:-node_modules,.git,.venv,__pycache__,dist,build,.next,.cache,.DS_Store} - CIX_N_GPU_LAYERS=99 + # GGUF cache lives on the named volume below — survives `docker compose + # down` (without -v) and is owned by the image's 1001:1001 user, so the + # cix-server process can always write to it regardless of host + # bind-mount permissions. - CIX_GGUF_CACHE_DIR=/data/models + - CIX_LLAMA_BIN_DIR=/app - CIX_LLAMA_STARTUP_TIMEOUT=120 + - CIX_EMBEDDINGS_ENABLED=${CIX_EMBEDDINGS_ENABLED:-true} + # ── First-boot admin seed (required when the DB has no users yet) ── + # cix-server refuses to start when the users table is empty AND these + # are unset. Set BOTH in your .env, log in once, change the password + # immediately (the user is flagged must_change_password=true). + - CIX_BOOTSTRAP_ADMIN_EMAIL=${CIX_BOOTSTRAP_ADMIN_EMAIL:-} + - CIX_BOOTSTRAP_ADMIN_PASSWORD=${CIX_BOOTSTRAP_ADMIN_PASSWORD:-} + # ── PR-E runtime tunables (all DB-overridable from /dashboard/server) ── + # 0 = auto. Threads → runtime.NumCPU()/2; batch → match n_ctx. + - CIX_LLAMA_THREADS=${CIX_LLAMA_THREADS:-0} + - CIX_LLAMA_BATCH=${CIX_LLAMA_BATCH:-0} + # Embedding queue parallelism. Default 5 (was 1) — pipelines host-side + # prep with device inference. Drop to 1 if you observe contention. + - CIX_MAX_EMBEDDING_CONCURRENCY=${CIX_MAX_EMBEDDING_CONCURRENCY:-5} + - CIX_EMBEDDING_QUEUE_TIMEOUT=${CIX_EMBEDDING_QUEUE_TIMEOUT:-300} + # Optional: skip the first-boot HF download by pointing at a GGUF + # file the operator already has on disk. cix copies it into the + # cix-models named volume once (atomic .partial → rename) and never + # touches the source again. Subsequent boots find the file in cache + # and ignore the env. See volumes block below for an example bind. + - CIX_BOOTSTRAP_GGUF_PATH=${CIX_BOOTSTRAP_GGUF_PATH:-} - NVIDIA_VISIBLE_DEVICES=all volumes: + # Operator-managed bind for sqlite + chroma so backups and inspection + # are one `cd` away on the host. Make sure the directory is owned by + # 1001:1001 OR use `user: "0:0"` — see CLAUDE.md. - ${HOME}/.cix/data:/data + # Docker-managed named volume layered ON TOP of /data/models. This + # isolates the GGUF cache from host-side bind permission issues and + # guarantees the model is downloaded exactly once across container + # recreates (`docker compose up --force-recreate`, image bumps, etc.). + - cix-models:/data/models + # Optional bootstrap: bind a host-side .gguf read-only into + # /bootstrap/model.gguf and set CIX_BOOTSTRAP_GGUF_PATH=/bootstrap/model.gguf + # in your .env. cix imports it into the cix-models cache on first boot, + # then ignores both the env and the bind. After verifying the cache is + # seeded, the bind can be removed entirely. + # - /srv/hf-cache/coderankembed-q8_0.gguf:/bootstrap/model.gguf:ro deploy: resources: limits: @@ -29,8 +69,13 @@ services: count: 1 capabilities: [gpu] healthcheck: - test: ["/cix-server", "-healthcheck"] + test: ["CMD", "/cix-server", "-healthcheck"] interval: 30s timeout: 10s start_period: 120s retries: 3 + +volumes: + cix-models: + # GGUF model cache. Persisted by Docker; only `docker compose down -v` + # (or explicit `docker volume rm _cix-models`) wipes it. diff --git a/docker-compose.yml b/docker-compose.yml index e1e2633..f20a807 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,12 +12,50 @@ services: - CIX_SQLITE_PATH=/data/sqlite/projects.db - CIX_MAX_FILE_SIZE=${CIX_MAX_FILE_SIZE:-524288} - CIX_EXCLUDED_DIRS=${CIX_EXCLUDED_DIRS:-node_modules,.git,.venv,__pycache__,dist,build,.next,.cache,.DS_Store} + # GGUF cache lives on the named volume below — survives `docker compose + # down` (without -v) and is owned by the image's 1001:1001 user, so the + # cix-server process can always write to it regardless of host + # bind-mount permissions. - CIX_GGUF_CACHE_DIR=/data/models - CIX_LLAMA_BIN_DIR=/app - CIX_LLAMA_STARTUP_TIMEOUT=120 - CIX_EMBEDDINGS_ENABLED=${CIX_EMBEDDINGS_ENABLED:-true} + # ── First-boot admin seed (required when the DB has no users yet) ── + # cix-server refuses to start when the users table is empty AND these + # are unset. Set BOTH in your .env, log in once, change the password + # immediately (the user is flagged must_change_password=true). + - CIX_BOOTSTRAP_ADMIN_EMAIL=${CIX_BOOTSTRAP_ADMIN_EMAIL:-} + - CIX_BOOTSTRAP_ADMIN_PASSWORD=${CIX_BOOTSTRAP_ADMIN_PASSWORD:-} + # ── PR-E runtime tunables (all DB-overridable from /dashboard/server) ── + # 0 = auto. Threads → runtime.NumCPU()/2; batch → match n_ctx. + - CIX_LLAMA_THREADS=${CIX_LLAMA_THREADS:-0} + - CIX_LLAMA_BATCH=${CIX_LLAMA_BATCH:-0} + # Embedding queue parallelism. Default 5 (was 1) — pipelines host-side + # prep with device inference. Drop to 1 if you observe contention. + - CIX_MAX_EMBEDDING_CONCURRENCY=${CIX_MAX_EMBEDDING_CONCURRENCY:-5} + - CIX_EMBEDDING_QUEUE_TIMEOUT=${CIX_EMBEDDING_QUEUE_TIMEOUT:-300} + # Optional: skip the first-boot HF download by pointing at a GGUF + # file the operator already has on disk. cix copies it into the + # cix-models named volume once (atomic .partial → rename) and never + # touches the source again. Subsequent boots find the file in cache + # and ignore the env. See volumes block below for an example bind. + - CIX_BOOTSTRAP_GGUF_PATH=${CIX_BOOTSTRAP_GGUF_PATH:-} volumes: + # Operator-managed bind for sqlite + chroma so backups and inspection + # are one `cd` away on the host. Make sure the directory is owned by + # 1001:1001 OR use `user: "0:0"` — see CLAUDE.md. - ${HOME}/.cix/data:/data + # Docker-managed named volume layered ON TOP of /data/models. This + # isolates the GGUF cache from host-side bind permission issues and + # guarantees the model is downloaded exactly once across container + # recreates (`docker compose up --force-recreate`, image bumps, etc.). + - cix-models:/data/models + # Optional bootstrap: bind a host-side .gguf read-only into + # /bootstrap/model.gguf and set CIX_BOOTSTRAP_GGUF_PATH=/bootstrap/model.gguf + # in your .env. cix imports it into the cix-models cache on first boot, + # then ignores both the env and the bind. After verifying the cache is + # seeded, the bind can be removed entirely. + # - /Users/me/.cache/huggingface/hub/.../coderankembed-q8_0.gguf:/bootstrap/model.gguf:ro deploy: resources: limits: @@ -26,8 +64,13 @@ services: reservations: memory: 1G healthcheck: - test: ["/cix-server", "-healthcheck"] + test: ["CMD", "/cix-server", "-healthcheck"] interval: 30s timeout: 10s start_period: 120s retries: 3 + +volumes: + cix-models: + # GGUF model cache. Persisted by Docker; only `docker compose down -v` + # (or explicit `docker volume rm cix_cix-models`) wipes it. diff --git a/server/Dockerfile b/server/Dockerfile index bfcb57a..989864f 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -1,9 +1,33 @@ # syntax=docker/dockerfile:1.7 # Phase 1 CPU-only multi-stage Dockerfile. # Embeddings (CUDA / llama-server sidecar) land in Phase 3. +# +# The build context is `server/` but the OpenAPI spec lives at +# `/doc/openapi.yaml`. We expose it via a named build context so +# the dashboard stage can `npm run gen:api` against the canonical spec: +# +# docker buildx build \ +# --build-context openapi=../doc \ +# -f server/Dockerfile server/ ARG VERSION=0.0.0-dev +# ── Stage 1: build the React dashboard ───────────────────────────────────── +# Output lands inside the Go embed.FS root (internal/httpapi/dashboard/dist) +# so the Go build picks it up automatically. +FROM node:20-alpine AS dashboard +WORKDIR /src +RUN apk upgrade --no-cache +# Install deps first for warm cache. +COPY dashboard/package.json dashboard/package-lock.json* ./dashboard/ +RUN cd dashboard && npm ci --no-audit --no-fund +# Bring in the spec from the named context, then the rest of the dashboard. +COPY --from=openapi openapi.yaml /spec/openapi.yaml +COPY dashboard/ ./dashboard/ +RUN cd dashboard && \ + npx openapi-typescript /spec/openapi.yaml -o src/api/generated.ts && \ + npm run build + FROM golang:1.25-alpine AS builder ARG VERSION # Patch Alpine base packages before building to avoid CVEs in the builder layer. @@ -15,6 +39,8 @@ COPY go.mod go.sum ./ RUN go mod download COPY . . +# Drop the built dashboard into the embed root so `go build` includes it. +COPY --from=dashboard /src/internal/httpapi/dashboard/dist/ ./internal/httpapi/dashboard/dist/ RUN CGO_ENABLED=0 GOOS=linux go build \ -trimpath \ -ldflags "-s -w -X main.version=${VERSION}" \ @@ -29,5 +55,13 @@ COPY --from=builder /out/cix-server /cix-server ENV CIX_PORT=8001 EXPOSE 8001 +# Container data layout — pin paths under /data so the named volume in +# portainer-stack.yml / docker-compose receives both SQLite and chromem-go +# state. The Go default ($HOME/.cix/data) targets local `make run` and is +# inappropriate inside distroless where there is no usable HOME. +ENV CIX_SQLITE_PATH=/data/sqlite/projects.db +ENV CIX_CHROMA_PERSIST_DIR=/data/chroma +VOLUME ["/data"] + USER nonroot:nonroot ENTRYPOINT ["/cix-server"] diff --git a/server/Dockerfile.cuda b/server/Dockerfile.cuda index 7e80042..eb1a692 100644 --- a/server/Dockerfile.cuda +++ b/server/Dockerfile.cuda @@ -1,7 +1,8 @@ # syntax=docker/dockerfile:1.7 # Phase 7 CUDA Dockerfile — distroless runtime, glibc + CUDA libs only. # -# Stage 1: compile Go binary (pure static, no CGO) +# Stage 0: build the React dashboard (node:20-alpine) +# Stage 1: compile Go binary (pure static, no CGO) — embeds the dashboard # Stage 2: pull llama.cpp CUDA binaries # Stage 3: install CUDA shared libs in nvidia/cuda → extract individual .so # Stage 4: distroless/cc runtime — no shell, no apt, no tar/dpkg @@ -12,11 +13,30 @@ # Build: # docker buildx build --builder cix-builder --platform linux/amd64 \ # --pull --build-arg VERSION=$(git describe --tags --always) \ +# --build-context openapi=../doc \ # -f server/Dockerfile.cuda -t dvcdsys/code-index:go-cu128 \ # --push server/ +# +# `--build-context openapi=../doc` mounts the repo-root doc/ folder so the +# dashboard stage can read openapi.yaml without us widening the build +# context (which would balloon image-build time). ARG VERSION=0.0.0-dev +# ── Stage 0: build the React dashboard ───────────────────────────────────── +FROM --platform=linux/amd64 node:20-alpine AS dashboard +WORKDIR /src +RUN apk upgrade --no-cache +# Install deps first for warm cache. +COPY dashboard/package.json dashboard/package-lock.json* ./dashboard/ +RUN cd dashboard && npm ci --no-audit --no-fund +# Pull in the spec from the named context, then the rest of the dashboard. +COPY --from=openapi openapi.yaml /spec/openapi.yaml +COPY dashboard/ ./dashboard/ +RUN cd dashboard && \ + npx openapi-typescript /spec/openapi.yaml -o src/api/generated.ts && \ + npm run build + # ── Stage 1: compile the Go binary ───────────────────────────────────────── FROM --platform=linux/amd64 golang:1.25-alpine AS builder ARG VERSION @@ -27,6 +47,8 @@ COPY go.mod go.sum ./ RUN go mod download COPY . . +# Drop the built dashboard into the embed root so `go build` includes it. +COPY --from=dashboard /src/internal/httpapi/dashboard/dist/ ./internal/httpapi/dashboard/dist/ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \ -trimpath \ -ldflags "-s -w -X main.version=${VERSION}" \ diff --git a/server/Makefile b/server/Makefile index 085a96f..22f3f0e 100644 --- a/server/Makefile +++ b/server/Makefile @@ -35,11 +35,23 @@ BUILDER ?= cix-builder SCOUT_TAG ?= scout-$(shell date +%Y%m%d-%H%M) .PHONY: help build test test-gate fetch-llama bundle run docker-build-cuda \ - docker-build-cuda-dev scout-cuda scout-cpu promote-cuda clean + docker-build-cuda-dev scout-cuda scout-cpu promote-cuda clean \ + openapi-gen openapi-check swagger-ui-fetch \ + dashboard-deps dashboard-gen-types dashboard-build dashboard-dev dashboard-clean + +# Pin the Swagger UI bundle version. Bumping this is a deliberate commit: +# update the version, run `make swagger-ui-fetch`, eyeball the diff, ship. +SWAGGER_UI_VERSION ?= 5.18.2 + +# Dashboard frontend — Vite project under server/dashboard/. Output lands in +# internal/httpapi/dashboard/dist/ and is picked up automatically by the Go +# embed.FS in dashboard/embed.go. +DASHBOARD_DIR := $(ROOT)/dashboard +DASHBOARD_OUT := $(ROOT)/internal/httpapi/dashboard/dist help: @echo "Targets:" - @echo " build — go build cmd/cix-server into dist/$(BUNDLE_NAME)/cix-server" + @echo " build — dashboard-build + go build cmd/cix-server into dist/$(BUNDLE_NAME)/cix-server" @echo " test — go test ./... (no llama-server required)" @echo " fetch-llama — download + SHA256-verify llama.cpp $(LLAMA_VERSION) for $(OS)-$(ARCH)" @echo " bundle — build + fetch-llama, assemble dist/$(BUNDLE_NAME)/ tree" @@ -50,9 +62,19 @@ help: @echo " scout-cuda — build CUDA image via native x86 builder → push : → docker scout cves" @echo " scout-cpu — build CPU image locally → docker scout cves (no push)" @echo " promote-cuda — retag SCOUT_TAG as go-cu128+cu128 without rebuild (imagetools)" + @echo " openapi-gen — regenerate internal/httpapi/openapi/openapi.gen.go from doc/openapi.yaml" + @echo " openapi-check — fail if openapi.gen.go is out of sync with doc/openapi.yaml (CI)" + @echo " swagger-ui-fetch — refresh internal/httpapi/docs/swagger-ui/ from jsdelivr (SWAGGER_UI_VERSION=$(SWAGGER_UI_VERSION))" + @echo " dashboard-deps — npm ci inside server/dashboard/" + @echo " dashboard-gen-types — regenerate src/api/generated.ts from doc/openapi.yaml" + @echo " dashboard-build — build the React dashboard into $(DASHBOARD_OUT)" + @echo " dashboard-dev — run vite dev server on :5173 (proxies /api to :21847)" + @echo " dashboard-clean — remove built dashboard artefacts (keeps PLACEHOLDER.html)" @echo " clean — remove dist/" -build: +# `go build` embeds internal/httpapi/dashboard/dist/ via go:embed — the +# dashboard must be built first so the binary picks up fresh assets. +build: dashboard-build mkdir -p $(BUNDLE_DIR) $(GO) build $(GOFLAGS) -o $(BUNDLE_DIR)/cix-server ./cmd/cix-server @@ -96,11 +118,18 @@ run: bundle "$(BUNDLE_DIR)/cix-server" test-gate: - @# The gate depends on a local GGUF and a fetched llama-server. The dev - @# fallback inside config.Validate reads bench/results/reference_gguf_path.txt - @# when CIX_GGUF_PATH is unset, so this target Just Works on the dev - @# machine that produced the reference vectors. + @# The gate depends on a local GGUF and a fetched llama-server. PR-E + @# removed the implicit config.Validate fallback that used to read + @# bench/results/reference_gguf_path.txt — the dashboard now requires + @# operators to pick a model source explicitly, so we do the same here: + @# read the file (if it exists) and stamp CIX_GGUF_PATH for the test + @# run only. + @if [ ! -f bench/results/reference_gguf_path.txt ]; then \ + echo "ERROR: bench/results/reference_gguf_path.txt missing — run bench/emit_reference_embeddings.py first."; \ + exit 1; \ + fi CIX_LLAMA_BIN_DIR=$(LLAMA_DIR) \ + CIX_GGUF_PATH="$$(cat bench/results/reference_gguf_path.txt)" \ $(GO) test -tags=embed_gate -v -run TestEmbeddingParity ./internal/embeddings/... docker-build-cuda: @@ -111,6 +140,7 @@ docker-build-cuda: --provenance=mode=max \ --sbom=true \ --build-arg VERSION=$(VERSION) \ + --build-context openapi=$(ROOT)/../doc \ -f $(ROOT)/Dockerfile.cuda \ -t $(IMAGE_REPO):$(IMAGE_TAG) \ --push \ @@ -133,6 +163,7 @@ docker-build-cuda-dev: --provenance=mode=max \ --sbom=true \ --build-arg VERSION=$(VERSION)-dev-$(GIT_SHA) \ + --build-context openapi=$(ROOT)/../doc \ -f $(ROOT)/Dockerfile.cuda \ -t $(IMAGE_REPO):$(DEV_TAG) \ --push \ @@ -159,6 +190,7 @@ scout-cuda: --provenance=mode=max \ --sbom=true \ --build-arg VERSION=$(VERSION) \ + --build-context openapi=$(ROOT)/../doc \ -f $(ROOT)/Dockerfile.cuda \ -t $(IMAGE_REPO):$(SCOUT_TAG) \ --push \ @@ -181,6 +213,7 @@ scout-cpu: docker buildx build \ --load \ --build-arg VERSION=$(VERSION) \ + --build-context openapi=$(ROOT)/../doc \ -t $(IMAGE_REPO):scout-cpu-tmp \ -f $(ROOT)/Dockerfile \ $(ROOT) @@ -190,3 +223,69 @@ scout-cpu: clean: rm -rf $(DIST_DIR) + +# openapi-gen — regenerate the chi server interface + types from doc/openapi.yaml. +# The spec is the source of truth; generated code is committed to git so consumers +# can `go install` without needing the oapi-codegen tool installed. +openapi-gen: + $(GO) generate ./internal/httpapi/openapi/... + +# openapi-check — used in CI to ensure the committed .gen.go is in sync with +# doc/openapi.yaml. Regenerates and fails if `git diff` is non-empty. +openapi-check: openapi-gen + @if ! git diff --quiet internal/httpapi/openapi/openapi.gen.go; then \ + echo "ERROR: internal/httpapi/openapi/openapi.gen.go is out of sync with doc/openapi.yaml"; \ + echo " Run 'make openapi-gen' and commit the result."; \ + git --no-pager diff internal/httpapi/openapi/openapi.gen.go | head -40; \ + exit 1; \ + fi + @echo "openapi.gen.go is in sync with doc/openapi.yaml" + +# swagger-ui-fetch — refresh the embedded Swagger UI bundle from jsdelivr. +# Run when bumping SWAGGER_UI_VERSION; commit the diff in the same PR as the +# version bump so reviewers can see what changed in the bundle. +SWAGGER_UI_DIR := $(ROOT)/internal/httpapi/docs/swagger-ui +SWAGGER_UI_BASE = https://cdn.jsdelivr.net/npm/swagger-ui-dist@$(SWAGGER_UI_VERSION) +swagger-ui-fetch: + @echo "→ Fetching Swagger UI $(SWAGGER_UI_VERSION) into $(SWAGGER_UI_DIR)" + @for f in swagger-ui.css swagger-ui-bundle.js swagger-ui-standalone-preset.js favicon-32x32.png favicon-16x16.png; do \ + echo " $$f"; \ + curl -fsSL -o "$(SWAGGER_UI_DIR)/$$f" "$(SWAGGER_UI_BASE)/$$f" || { echo "FAIL: $$f"; exit 1; }; \ + done + @echo "✓ Swagger UI bundle refreshed. index.html is hand-maintained — review the diff before committing." + +# Dashboard frontend — Vite + React project under server/dashboard/. +# +# Output lives at $(DASHBOARD_OUT) (= internal/httpapi/dashboard/dist) which +# is referenced by `//go:embed dist` in dashboard/embed.go. After +# `dashboard-build` finishes, a regular `go build` picks up the bundle +# automatically — no extra wiring. + +dashboard-deps: + cd $(DASHBOARD_DIR) && npm ci + +dashboard-gen-types: + cd $(DASHBOARD_DIR) && npm run gen:api + +# `dashboard-deps` runs `npm ci` which is strict about lockfile match. For +# day-to-day rebuilds where node_modules already exists this is wasteful; +# fall through to a no-op when node_modules is present and the lockfile +# hasn't changed since the last install. +dashboard-build: + @if [ ! -d "$(DASHBOARD_DIR)/node_modules" ]; then \ + echo "→ npm ci (no node_modules/)"; \ + cd $(DASHBOARD_DIR) && npm ci; \ + fi + cd $(DASHBOARD_DIR) && npm run gen:api && npm run build + +dashboard-dev: dashboard-gen-types + cd $(DASHBOARD_DIR) && npm run dev + +dashboard-clean: + @# Wipe everything in dist/ except the committed .gitkeep so that + @# `//go:embed all:dist` still has at least one entry afterwards + @# (otherwise `go build` would fail with "embed: no matching files"). + @if [ -d "$(DASHBOARD_OUT)" ]; then \ + find $(DASHBOARD_OUT) -mindepth 1 ! -name .gitkeep -exec rm -rf {} +; \ + echo "→ Cleaned $(DASHBOARD_OUT) (kept .gitkeep)"; \ + fi diff --git a/server/README.md b/server/README.md index 2344931..521eeb5 100644 --- a/server/README.md +++ b/server/README.md @@ -49,7 +49,7 @@ All are optional; defaults match `api/app/config.py` except `CIX_PORT`. | `CIX_SQLITE_PATH` | `/data/sqlite/projects.db` | Suffixed with model-safe name on open | | `CIX_MAX_FILE_SIZE` | `524288` | | | `CIX_EXCLUDED_DIRS` | see config.go | Comma-separated | -| `CIX_MAX_EMBEDDING_CONCURRENCY` | `1` | | +| `CIX_MAX_EMBEDDING_CONCURRENCY` | `5` | Embedding queue parallelism. Recommended for both CPU and single-GPU setups. | | `CIX_EMBEDDING_QUEUE_TIMEOUT` | `300` | Seconds | | `CIX_MAX_CHUNK_TOKENS` | `1500` | | diff --git a/server/cmd/cix-server/bootstrap.go b/server/cmd/cix-server/bootstrap.go new file mode 100644 index 0000000..00bf7dd --- /dev/null +++ b/server/cmd/cix-server/bootstrap.go @@ -0,0 +1,100 @@ +package main + +import ( + "context" + "errors" + "fmt" + "log/slog" + + "github.com/dvcdsys/code-index/server/internal/apikeys" + "github.com/dvcdsys/code-index/server/internal/config" + "github.com/dvcdsys/code-index/server/internal/users" +) + +// bootstrapAuth seeds the dashboard auth model on a cold start. +// +// Decision matrix (when CIX_AUTH_DISABLED is FALSE): +// +// users.Count == 0: +// EMAIL + PASSWORD both set → create admin (must_change_password=1) +// exactly one of the two set → fatal: tell the operator which is missing +// neither set → fatal: refuse to start with open auth +// users.Count > 0: +// EMAIL or PASSWORD set → log warning, ignore env (DB wins) +// neither set → no-op +// +// CIX_API_KEY is imported as a one-time legacy api_key on a fresh DB so +// existing CLI clients keep working through the upgrade. After the +// import the env var becomes a no-op (the dashboard takes over key +// management). +func bootstrapAuth(ctx context.Context, cfg *config.Config, logger *slog.Logger, usr *users.Service, ak *apikeys.Service) error { + count, err := usr.Count(ctx) + if err != nil { + return fmt.Errorf("count users: %w", err) + } + + hasEmail := cfg.BootstrapAdminEmail != "" + hasPassword := cfg.BootstrapAdminPassword != "" + + // Catch the half-configured case BEFORE the populated-DB branch — a + // stale env var on an already-bootstrapped deployment is still worth + // logging, but starting up with only one of the two on a fresh DB is + // a real misconfiguration we should refuse loud. + if count == 0 && hasEmail != hasPassword { + missing := "CIX_BOOTSTRAP_ADMIN_PASSWORD" + if !hasEmail { + missing = "CIX_BOOTSTRAP_ADMIN_EMAIL" + } + return fmt.Errorf("incomplete bootstrap configuration: %s is set but %s is empty.\n"+ + " Both env vars are required together to seed the first admin. Example:\n"+ + " CIX_BOOTSTRAP_ADMIN_EMAIL=admin@example.com \\\n"+ + " CIX_BOOTSTRAP_ADMIN_PASSWORD='change-me-on-first-login' \\\n"+ + " ./cix-server", oppositeOf(missing), missing) + } + + switch { + case count == 0 && hasEmail && hasPassword: + u, err := usr.Create(ctx, cfg.BootstrapAdminEmail, cfg.BootstrapAdminPassword, users.RoleAdmin, true) + if err != nil { + if errors.Is(err, users.ErrEmailTaken) { + return fmt.Errorf("bootstrap admin email already taken in fresh DB — refusing to continue") + } + return fmt.Errorf("create bootstrap admin: %w", err) + } + logger.Warn("bootstrap admin created from CIX_BOOTSTRAP_ADMIN_EMAIL + CIX_BOOTSTRAP_ADMIN_PASSWORD; user will be forced to change password on first login", + "email", u.Email, "user_id", u.ID) + + if cfg.APIKey != "" { + if _, err := ak.ImportLegacy(ctx, u.ID, "env-bootstrap", cfg.APIKey); err != nil { + logger.Error("could not import CIX_API_KEY as legacy api_key — CLI clients using that key will fail until you create one via the dashboard", "err", err) + } else { + logger.Warn("CIX_API_KEY imported as 'env-bootstrap' api_key for the bootstrap admin — rotate it via the dashboard at your earliest convenience") + } + } + + case count == 0: + return fmt.Errorf("no users in database and the bootstrap admin env vars are not set: refuse to start.\n" + + " Set BOTH CIX_BOOTSTRAP_ADMIN_EMAIL and CIX_BOOTSTRAP_ADMIN_PASSWORD to seed the first admin\n" + + " (you will be forced to change the password on first login). Example:\n" + + " CIX_BOOTSTRAP_ADMIN_EMAIL=admin@example.com \\\n" + + " CIX_BOOTSTRAP_ADMIN_PASSWORD='change-me-on-first-login' \\\n" + + " ./cix-server\n" + + " Or set CIX_AUTH_DISABLED=true for local dev (every endpoint becomes public)") + + default: + if hasEmail || hasPassword { + logger.Info("CIX_BOOTSTRAP_ADMIN_* ignored — database already has users (DB wins over env)") + } + } + + return nil +} + +// oppositeOf returns the env-var name that goes with the supplied missing one. +// Tiny helper to keep the half-configured error message readable. +func oppositeOf(missing string) string { + if missing == "CIX_BOOTSTRAP_ADMIN_EMAIL" { + return "CIX_BOOTSTRAP_ADMIN_PASSWORD" + } + return "CIX_BOOTSTRAP_ADMIN_EMAIL" +} diff --git a/server/cmd/cix-server/bootstrap_test.go b/server/cmd/cix-server/bootstrap_test.go new file mode 100644 index 0000000..03f71e2 --- /dev/null +++ b/server/cmd/cix-server/bootstrap_test.go @@ -0,0 +1,148 @@ +package main + +import ( + "context" + "io" + "log/slog" + "strings" + "testing" + + "github.com/dvcdsys/code-index/server/internal/apikeys" + "github.com/dvcdsys/code-index/server/internal/config" + "github.com/dvcdsys/code-index/server/internal/db" + "github.com/dvcdsys/code-index/server/internal/users" +) + +// silentLogger discards every log line — bootstrapAuth's warnings should +// not pollute test output. +func silentLogger() *slog.Logger { + return slog.New(slog.NewTextHandler(io.Discard, nil)) +} + +func newServices(t *testing.T) (*users.Service, *apikeys.Service) { + t.Helper() + d, err := db.Open(":memory:") + if err != nil { + t.Fatalf("open db: %v", err) + } + t.Cleanup(func() { _ = d.Close() }) + return users.New(d), apikeys.New(d) +} + +func TestBootstrap_FreshDB_WithEnv_CreatesAdmin(t *testing.T) { + usrSvc, akSvc := newServices(t) + cfg := &config.Config{ + BootstrapAdminEmail: "admin@example.com", + BootstrapAdminPassword: "initialpass", + } + if err := bootstrapAuth(context.Background(), cfg, silentLogger(), usrSvc, akSvc); err != nil { + t.Fatalf("bootstrapAuth: %v", err) + } + u, err := usrSvc.GetByEmail(context.Background(), "admin@example.com") + if err != nil { + t.Fatalf("admin not seeded: %v", err) + } + if u.Role != users.RoleAdmin { + t.Errorf("seeded user has role %q, want admin", u.Role) + } + if !u.MustChangePassword { + t.Errorf("seeded admin must be flagged must_change_password") + } +} + +func TestBootstrap_FreshDB_NoEnv_Fatal(t *testing.T) { + usrSvc, akSvc := newServices(t) + cfg := &config.Config{} // empty bootstrap fields + err := bootstrapAuth(context.Background(), cfg, silentLogger(), usrSvc, akSvc) + if err == nil { + t.Fatal("expected fatal error on empty DB without bootstrap env") + } + if !strings.Contains(err.Error(), "no users in database") { + t.Errorf("error message lacks expected text: %v", err) + } + // Sanity: the message must mention BOTH env var names, otherwise the + // operator has to read the source to figure out what to set. + for _, want := range []string{"CIX_BOOTSTRAP_ADMIN_EMAIL", "CIX_BOOTSTRAP_ADMIN_PASSWORD"} { + if !strings.Contains(err.Error(), want) { + t.Errorf("error must name %s, got: %v", want, err) + } + } +} + +// TestBootstrap_FreshDB_PartialEnv_Fatal — half-set env vars are a real +// configuration mistake (typo, deleted line in compose, etc.). Refuse to +// start AND name the missing var so the operator doesn't have to guess. +func TestBootstrap_FreshDB_PartialEnv_Fatal(t *testing.T) { + cases := []struct { + name string + cfg *config.Config + missing string + }{ + { + name: "email only", + cfg: &config.Config{BootstrapAdminEmail: "admin@example.com"}, + missing: "CIX_BOOTSTRAP_ADMIN_PASSWORD", + }, + { + name: "password only", + cfg: &config.Config{BootstrapAdminPassword: "initialpw"}, + missing: "CIX_BOOTSTRAP_ADMIN_EMAIL", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + usrSvc, akSvc := newServices(t) + err := bootstrapAuth(context.Background(), tc.cfg, silentLogger(), usrSvc, akSvc) + if err == nil { + t.Fatal("expected error on half-set bootstrap env") + } + if !strings.Contains(err.Error(), "incomplete bootstrap configuration") { + t.Errorf("error must say 'incomplete bootstrap configuration', got: %v", err) + } + if !strings.Contains(err.Error(), tc.missing) { + t.Errorf("error must name the missing var %s, got: %v", tc.missing, err) + } + }) + } +} + +func TestBootstrap_PopulatedDB_IgnoresEnv(t *testing.T) { + usrSvc, akSvc := newServices(t) + if _, err := usrSvc.Create(context.Background(), "preexisting@example.com", "preexisting1", users.RoleAdmin, false); err != nil { + t.Fatalf("preseed: %v", err) + } + cfg := &config.Config{ + BootstrapAdminEmail: "different@example.com", + BootstrapAdminPassword: "differentpw", + } + if err := bootstrapAuth(context.Background(), cfg, silentLogger(), usrSvc, akSvc); err != nil { + t.Fatalf("bootstrapAuth: %v", err) + } + // "different@example.com" should NOT have been created. + if _, err := usrSvc.GetByEmail(context.Background(), "different@example.com"); err == nil { + t.Errorf("env-supplied email was seeded despite DB having existing users") + } +} + +func TestBootstrap_LegacyAPIKey_Imported(t *testing.T) { + usrSvc, akSvc := newServices(t) + cfg := &config.Config{ + BootstrapAdminEmail: "admin@example.com", + BootstrapAdminPassword: "initialpw", + APIKey: "legacy-cix-api-key-from-env", + } + if err := bootstrapAuth(context.Background(), cfg, silentLogger(), usrSvc, akSvc); err != nil { + t.Fatalf("bootstrapAuth: %v", err) + } + u, _ := usrSvc.GetByEmail(context.Background(), "admin@example.com") + keys, err := akSvc.ListForOwner(context.Background(), u.ID) + if err != nil { + t.Fatalf("ListForOwner: %v", err) + } + if len(keys) != 1 { + t.Fatalf("len(keys) = %d, want 1 (env-bootstrap)", len(keys)) + } + if keys[0].Name != "env-bootstrap" { + t.Errorf("key name = %q, want 'env-bootstrap'", keys[0].Name) + } +} diff --git a/server/cmd/cix-server/main.go b/server/cmd/cix-server/main.go index 263e5c6..1ea3c81 100644 --- a/server/cmd/cix-server/main.go +++ b/server/cmd/cix-server/main.go @@ -15,12 +15,16 @@ import ( "syscall" "time" + "github.com/dvcdsys/code-index/server/internal/apikeys" "github.com/dvcdsys/code-index/server/internal/chunker" "github.com/dvcdsys/code-index/server/internal/config" "github.com/dvcdsys/code-index/server/internal/db" "github.com/dvcdsys/code-index/server/internal/embeddings" "github.com/dvcdsys/code-index/server/internal/httpapi" "github.com/dvcdsys/code-index/server/internal/indexer" + "github.com/dvcdsys/code-index/server/internal/runtimecfg" + "github.com/dvcdsys/code-index/server/internal/sessions" + "github.com/dvcdsys/code-index/server/internal/users" "github.com/dvcdsys/code-index/server/internal/vectorstore" ) @@ -70,8 +74,12 @@ func run() error { return fmt.Errorf("validate config: %w", err) } - if cfg.APIKey == "" { - logger.Warn("CIX_API_KEY is empty — authenticated endpoints are reachable without auth (dev mode)") + // CIX_AUTH_DISABLED=true skips ALL auth — log loudly so the warning + // shows up in container logs / Portainer. Bootstrap (further below) + // also enforces "no users + no bootstrap env = fatal", so this branch + // is the only path to a legitimately auth-free deployment. + if cfg.AuthDisabled { + logger.Warn("auth disabled (CIX_AUTH_DISABLED=true) — every endpoint is reachable without authentication") } chunker.Configure(cfg.Languages) @@ -89,6 +97,34 @@ func run() error { } }() + // PR-E — overlay dashboard-saved runtime overrides onto the env-loaded + // config before any code path reads its fields. The DB row may not + // exist yet (fresh install); resolution falls through to env / recommended + // in that case so behaviour matches pre-PR-E exactly. + rcfg := runtimecfg.New(database, cfg) + snap, err := rcfg.Get(context.Background()) + if err != nil { + return fmt.Errorf("load runtime_settings: %w", err) + } + snap.ApplyTo(cfg) + logger.Info("runtime config resolved", + "embedding_model", cfg.EmbeddingModel, + "llama_ctx", cfg.LlamaCtxSize, + "n_gpu_layers", cfg.LlamaNGpuLayers, + "n_threads", cfg.LlamaNThreads, + "max_concurrency", cfg.MaxEmbeddingConcurrency, + "batch", cfg.LlamaBatchSize, + "sources", snap.Source, + ) + // DynamicSQLitePath embeds ModelSafeName(); if the dashboard switched the + // model, the storage path resolved a moment ago is for the OLD model. The + // already-opened DB is still correct (it's the OLD model's state) but the + // chroma vectorstore opened below needs to honour the NEW model. Recompute + // dbPath only matters if we want to re-open under the new model — for PR-E + // we deliberately keep the old DB so historical projects keep their + // indexed_with_model and the dashboard can show the drift. Sidecar + + // vectorstore use the new model. + // Embeddings service. When disabled we still build the value so router // wiring stays consistent — Service methods return ErrDisabled in that case. // Startup is bounded by a context derived from LlamaStartupSec plus a grace @@ -133,6 +169,9 @@ func run() error { idx := indexer.New(database, vs, embedSvc, logger) idx.SetEmbedIncludePath(cfg.EmbedIncludePath) + // PR-E — record the active embedding model on every indexed project so the + // dashboard can highlight stale vectors when the runtime model changes. + idx.SetEmbeddingModel(cfg.EmbeddingModel) if cfg.EmbedIncludePath { logger.Info("embedding format: path-aware preamble enabled (CIX_EMBED_INCLUDE_PATH=true) — full reindex required if upgrading") } @@ -140,6 +179,17 @@ func run() error { // leak for up to 1h past shutdown. m8 fix. defer idx.Shutdown() + // Dashboard auth services. Built once and shared with the router. + usrSvc := users.New(database) + sessSvc := sessions.New(database) + akSvc := apikeys.New(database) + + if !cfg.AuthDisabled { + if err := bootstrapAuth(context.Background(), cfg, logger, usrSvc, akSvc); err != nil { + return fmt.Errorf("bootstrap auth: %w", err) + } + } + handler := httpapi.NewRouter(httpapi.Deps{ DB: database, ServerVersion: version, @@ -147,10 +197,14 @@ func run() error { Backend: backend, EmbeddingModel: cfg.EmbeddingModel, Logger: logger, - APIKey: cfg.APIKey, + AuthDisabled: cfg.AuthDisabled, + Users: usrSvc, + Sessions: sessSvc, + APIKeys: akSvc, EmbeddingSvc: embedSvc, VectorStore: vs, Indexer: idx, + RuntimeCfg: rcfg, }) srv := &http.Server{ @@ -168,6 +222,25 @@ func run() error { "version", version, "embedding_model", cfg.EmbeddingModel, ) + // Public surface — emit as one structured line so JSON-aware log + // shippers (Loki, GCP Cloud Logging) keep them grouped, plus a + // plaintext banner to stderr for humans tailing the terminal. + // `localhost` is good enough here: the server binds 0.0.0.0 by + // default, but the operator is almost always reading this on the + // same host they're about to click on. + base := fmt.Sprintf("http://localhost:%d", cfg.Port) + logger.Info("server ready", + "dashboard", base+"/dashboard", + "api_docs", base+"/docs", + "openapi_spec", base+"/openapi.json", + "health", base+"/health", + ) + fmt.Fprintln(os.Stderr, "") + fmt.Fprintln(os.Stderr, " cix-server is ready 🟢") + fmt.Fprintln(os.Stderr, " Dashboard: "+base+"/dashboard") + fmt.Fprintln(os.Stderr, " API docs: "+base+"/docs") + fmt.Fprintln(os.Stderr, " OpenAPI spec: "+base+"/openapi.json") + fmt.Fprintln(os.Stderr, "") if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { serverErr <- err } diff --git a/server/dashboard/.gitignore b/server/dashboard/.gitignore new file mode 100644 index 0000000..fd16421 --- /dev/null +++ b/server/dashboard/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +.vite/ +.cache/ +*.log + +# Type-gen output is reproducible — never commit +src/api/generated.ts diff --git a/server/dashboard/README.md b/server/dashboard/README.md new file mode 100644 index 0000000..da33ac3 --- /dev/null +++ b/server/dashboard/README.md @@ -0,0 +1,172 @@ +# cix-dashboard + +The embedded operator dashboard for `cix-server`. Vite + React + TypeScript + +Tailwind + shadcn/ui + TanStack Query, served by the Go binary at +`/dashboard` via `embed.FS`. + +## Local development + +```bash +# one-time +cd server/dashboard +npm ci + +# regenerate API types when doc/openapi.yaml changes +npm run gen:api + +# vite dev server on http://localhost:5173 (proxies /api → :21847) +npm run dev + +# production build → ../internal/httpapi/dashboard/dist +npm run build +``` + +The repo Makefile wraps the same targets: + +```bash +cd server +make dashboard-dev # vite dev server with type-gen +make dashboard-build # production build +make build # rebuild Go binary with the latest dashboard embedded +``` + +## How to add a new feature module + +A "feature" is a self-contained folder under `src/modules//` that +exports a single `Module` constant. The sidebar and the router both read +from `src/modules/registry.ts` — adding a feature is **create folder, +register, done**. + +1. **Create the module folder**: + + ``` + src/modules/projects/ + index.ts # exports the Module + ProjectsPage.tsx # the entry component + hooks.ts # TanStack Query hooks (useProjects(), …) + components/ # local components, never imported from outside + ``` + +2. **Define the module**: + + ```ts + // src/modules/projects/index.ts + import { Folder } from 'lucide-react'; + import type { Module } from '../types'; + import ProjectsPage from './ProjectsPage'; + + export const ProjectsModule: Module = { + id: 'projects', + label: 'Projects', + icon: Folder, + path: '/projects', + element: ProjectsPage, + // requiredRole: 'admin', // omit for everyone, 'admin' to gate + weight: 10, // lower numbers come first in the sidebar + }; + ``` + +3. **Register it**: + + ```ts + // src/modules/registry.ts + import { ProjectsModule } from './projects'; + + export const MODULES: Module[] = [HomeModule, ProjectsModule, …] + .sort((a, b) => (a.weight ?? 100) - (b.weight ?? 100)); + ``` + + That's it. The sidebar renders the new entry, role-filtered by the + current user; the router mounts `` + so the module owns its sub-tree. + +4. **If the module needs new endpoints** — add them to `doc/openapi.yaml` + first, then run: + ```bash + cd server && make openapi-gen # Go server stub + cd dashboard && npm run gen:api # TS types in src/api/generated.ts + ``` + Both generators are idempotent and CI-checked, so a forgotten + regeneration fails fast. + +## Conventions + +- **Data fetching**: every API call goes through TanStack Query + (`useQuery` / `useMutation`). Never raw `useEffect + fetch` — that + loses retry, cache, and dedupe for free. +- **API client**: import `api` from `@/api/client`. Returns parsed JSON + on success, throws an `ApiError` (with `.status` and `.detail`) on any + non-2xx. The provider in `app/providers.tsx` already disables retries + on 401/403. +- **UI primitives**: import from `@/ui/*` (button, card, input, dialog, + alert, sonner). All wrap shadcn-style Tailwind primitives. Add new + ones via `npx shadcn add ` when needed. +- **Icons**: `lucide-react` only. Named imports — no default re-exports + so the bundle tree-shakes. +- **Styling**: Tailwind tokens only (`bg-background`, `text-muted-foreground`, + …). Never inline `style={{ color: '#abc' }}` — colour drift is the + reason we have a token system. +- **Class strings**: use `cn()` from `@/lib/cn` for conditional classes. + It de-duplicates conflicting Tailwind classes. +- **Dates**: format via helpers in `@/lib/formatDate`. Don't sprinkle + `new Date(x).toLocaleDateString()` across the codebase. +- **Auth state**: `useAuth()` from `@/auth/useAuth`. Don't read the + `/auth/me` query directly — the hook is the public surface. + +## Architecture at a glance + +``` +src/ + main.tsx boot React + Router + providers + index.css Tailwind + shadcn CSS variables (light/dark tokens) + api/ + client.ts fetch wrapper, ApiError, cookie-based auth + types.ts stable re-exports of generated schemas + generated.ts ← gitignored; produced by `npm run gen:api` + app/ + App.tsx auth-gate + module routing + Shell.tsx sidebar + main content layout + Sidebar.tsx renders modules from the registry, role-filtered + providers.tsx QueryClient + AuthProvider + Toaster + auth/ + AuthProvider.tsx bootstrap-status + /auth/me + login/logout mutations + useAuth.ts consumer hook + LoginPage.tsx full-page (no Shell) + ChangePasswordPage.tsx forced password change + BootstrapNeededPage.tsx shown when `needs_bootstrap === true` + modules/ + types.ts Module interface + registry.ts array of all registered modules, sorted by weight + home/ PR-B placeholder home; replace/augment in PR-C + ui/ shadcn primitives — never edit unless adding a new one + lib/ + cn.ts `cn()` className helper + formatDate.ts date / relative-time helpers +``` + +## Embedding into the Go binary + +The Vite build writes its output to +`server/internal/httpapi/dashboard/dist/`, which is referenced by +`//go:embed all:dist` in `dashboard/embed.go`. After `make dashboard-build` +finishes, a regular `go build` picks the bundle up automatically. + +A committed `dist/.gitkeep` keeps the embed.FS non-empty on a fresh clone +so `go build` works without the npm toolchain. The handler in +`dashboard.go` serves an inline "please run `make dashboard-build`" +placeholder when `dist/index.html` is missing. + +## Bundle-size budget + +`npm run build` should land below ~500 KB gzipped total. Today (PR-B): + +``` +index.html 0.5 kB │ gzip: 0.3 kB +assets/index-*.css 18 kB │ gzip: 4.3 kB +assets/index-*.js 289 kB │ gzip: 90 kB +``` + +If a future PR pushes that significantly higher, audit imports — the +usual culprits are accidentally pulling all of lucide-react instead of +named imports, or shadcn primitives that ship more Radix code than the +feature actually uses. diff --git a/server/dashboard/components.json b/server/dashboard/components.json new file mode 100644 index 0000000..3e184fd --- /dev/null +++ b/server/dashboard/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "src/index.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/ui", + "utils": "@/lib/cn", + "ui": "@/ui", + "lib": "@/lib", + "hooks": "@/lib" + }, + "iconLibrary": "lucide" +} diff --git a/server/dashboard/index.html b/server/dashboard/index.html new file mode 100644 index 0000000..b4487ed --- /dev/null +++ b/server/dashboard/index.html @@ -0,0 +1,29 @@ + + + + + + + cix dashboard + + + +
+ + + diff --git a/server/dashboard/package-lock.json b/server/dashboard/package-lock.json new file mode 100644 index 0000000..8614f1a --- /dev/null +++ b/server/dashboard/package-lock.json @@ -0,0 +1,4314 @@ +{ + "name": "cix-dashboard", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cix-dashboard", + "version": "0.1.0", + "dependencies": { + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-dropdown-menu": "^2.1.6", + "@radix-ui/react-label": "^2.1.2", + "@radix-ui/react-radio-group": "^1.3.8", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-slider": "^1.3.6", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-switch": "^1.2.6", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-tooltip": "^1.2.8", + "@tanstack/react-query": "^5.66.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.475.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.28.0", + "sonner": "^1.7.4", + "tailwind-merge": "^3.0.1" + }, + "devDependencies": { + "@types/node": "^22.13.0", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.20", + "openapi-typescript": "^7.5.2", + "postcss": "^8.5.1", + "tailwindcss": "^3.4.17", + "tailwindcss-animate": "^1.0.7", + "typescript": "^5.7.3", + "vite": "^5.4.14" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", + "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.6" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "license": "MIT" + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", + "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz", + "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", + "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz", + "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz", + "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", + "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slider": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.6.tgz", + "integrity": "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", + "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", + "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, + "node_modules/@redocly/ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js-replace": "^1.0.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@redocly/config": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.22.0.tgz", + "integrity": "sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@redocly/openapi-core": { + "version": "1.34.14", + "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.34.14.tgz", + "integrity": "sha512-y+xFx+Zz54Xhr8jUdnLENYnt7Y7GEDL6Q03ga7rTtX8DVwefX9H+hQEPgJp1nda7vdH+wJ9/HBVvyfBuW9x6rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@redocly/ajv": "8.11.2", + "@redocly/config": "0.22.0", + "colorette": "1.4.0", + "https-proxy-agent": "7.0.6", + "js-levenshtein": "1.1.6", + "js-yaml": "4.1.1", + "minimatch": "5.1.9", + "pluralize": "8.0.0", + "yaml-ast-parser": "0.0.43" + }, + "engines": { + "node": ">=18.17.0", + "npm": ">=9.5.0" + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", + "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", + "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", + "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", + "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", + "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", + "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", + "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", + "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", + "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", + "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", + "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", + "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", + "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", + "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", + "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", + "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", + "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", + "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", + "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", + "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", + "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", + "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", + "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", + "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", + "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", + "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tanstack/query-core": { + "version": "5.100.8", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.8.tgz", + "integrity": "sha512-ceYwSFOqjPwET5TA6IOYxzxlGc0ekyH/gfOtWkP0PX43rzX9bxW48Iuw8KAduKCToi4rJAQ6nRy2kAe8gszdmg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.100.8", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.8.tgz", + "integrity": "sha512-iNNEekixXU5vtAGKKZX2lx3jTooG5yNY+kv0wSgEdEYG0Mj0JM5bcuQtC35ZAP3nDopT6jciUK3xeX65U7AnfA==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.100.8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", + "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/autoprefixer": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", + "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.2", + "caniuse-lite": "^1.0.30001787", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.25", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.25.tgz", + "integrity": "sha512-QO/VHsXCQdnzADMfmkeOPvHdIAkoB7i0/rGjINPJEetLx75hNttVWGQ/jycHUDP9zZ9rupbm60WRxcwViB0MiA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001791", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", + "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/change-case": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", + "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.349", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.349.tgz", + "integrity": "sha512-QsWVGyRuY07Aqb234QytTfwd5d9AJlfNIQ5wIOl1L+PZDzI9d9+Fn0FRale/QYlFxt/bUnB0/nLd1jFPGxGK1A==", + "dev": true, + "license": "ISC" + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/index-to-position": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", + "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.475.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.475.0.tgz", + "integrity": "sha512-NJzvVu1HwFVeZ+Gwq2q00KygM1aBhy/ZrhY9FsAgJtpB+E4R7uxRk9M2iKvHa6/vNxZydIB59htha4c2vvwvVg==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/openapi-typescript": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-7.13.0.tgz", + "integrity": "sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@redocly/openapi-core": "^1.34.6", + "ansi-colors": "^4.1.3", + "change-case": "^5.4.4", + "parse-json": "^8.3.0", + "supports-color": "^10.2.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "openapi-typescript": "bin/cli.js" + }, + "peerDependencies": { + "typescript": "^5.x" + } + }, + "node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss": { + "version": "8.5.13", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.13.tgz", + "integrity": "sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-router": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz", + "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz", + "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2", + "react-router": "6.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", + "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.2", + "@rollup/rollup-android-arm64": "4.60.2", + "@rollup/rollup-darwin-arm64": "4.60.2", + "@rollup/rollup-darwin-x64": "4.60.2", + "@rollup/rollup-freebsd-arm64": "4.60.2", + "@rollup/rollup-freebsd-x64": "4.60.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", + "@rollup/rollup-linux-arm-musleabihf": "4.60.2", + "@rollup/rollup-linux-arm64-gnu": "4.60.2", + "@rollup/rollup-linux-arm64-musl": "4.60.2", + "@rollup/rollup-linux-loong64-gnu": "4.60.2", + "@rollup/rollup-linux-loong64-musl": "4.60.2", + "@rollup/rollup-linux-ppc64-gnu": "4.60.2", + "@rollup/rollup-linux-ppc64-musl": "4.60.2", + "@rollup/rollup-linux-riscv64-gnu": "4.60.2", + "@rollup/rollup-linux-riscv64-musl": "4.60.2", + "@rollup/rollup-linux-s390x-gnu": "4.60.2", + "@rollup/rollup-linux-x64-gnu": "4.60.2", + "@rollup/rollup-linux-x64-musl": "4.60.2", + "@rollup/rollup-openbsd-x64": "4.60.2", + "@rollup/rollup-openharmony-arm64": "4.60.2", + "@rollup/rollup-win32-arm64-msvc": "4.60.2", + "@rollup/rollup-win32-ia32-msvc": "4.60.2", + "@rollup/rollup-win32-x64-gnu": "4.60.2", + "@rollup/rollup-win32-x64-msvc": "4.60.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/sonner": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-1.7.4.tgz", + "integrity": "sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwind-merge": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz", + "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js-replace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uri-js-replace/-/uri-js-replace-1.0.1.tgz", + "integrity": "sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml-ast-parser": { + "version": "0.0.43", + "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz", + "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/server/dashboard/package.json b/server/dashboard/package.json new file mode 100644 index 0000000..e599c1c --- /dev/null +++ b/server/dashboard/package.json @@ -0,0 +1,50 @@ +{ + "name": "cix-dashboard", + "version": "0.1.0", + "private": true, + "type": "module", + "description": "Embedded operator dashboard for cix-server. Built with Vite, served by Go via embed.FS at /dashboard.", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview", + "gen:api": "openapi-typescript ../../doc/openapi.yaml -o src/api/generated.ts", + "typecheck": "tsc --noEmit", + "lint": "tsc --noEmit" + }, + "dependencies": { + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-dropdown-menu": "^2.1.6", + "@radix-ui/react-label": "^2.1.2", + "@radix-ui/react-radio-group": "^1.3.8", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-slider": "^1.3.6", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-switch": "^1.2.6", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-tooltip": "^1.2.8", + "@tanstack/react-query": "^5.66.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.475.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.28.0", + "sonner": "^1.7.4", + "tailwind-merge": "^3.0.1" + }, + "devDependencies": { + "@types/node": "^22.13.0", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.20", + "openapi-typescript": "^7.5.2", + "postcss": "^8.5.1", + "tailwindcss": "^3.4.17", + "tailwindcss-animate": "^1.0.7", + "typescript": "^5.7.3", + "vite": "^5.4.14" + } +} diff --git a/server/dashboard/postcss.config.js b/server/dashboard/postcss.config.js new file mode 100644 index 0000000..2aa7205 --- /dev/null +++ b/server/dashboard/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/server/dashboard/src/api/client.ts b/server/dashboard/src/api/client.ts new file mode 100644 index 0000000..8035316 --- /dev/null +++ b/server/dashboard/src/api/client.ts @@ -0,0 +1,111 @@ +// Lightweight fetch wrapper used by every TanStack Query call. +// +// Two contracts the rest of the app relies on: +// +// 1. Cookie-based auth: requests are sent with `credentials: 'same-origin'` +// so the HttpOnly `cix_session` cookie set by /api/v1/auth/login flows +// automatically. No tokens in localStorage; no Authorization header. +// +// 2. Error normalisation: any non-2xx response is translated into an +// `ApiError` whose `.detail` mirrors the FastAPI-style {"detail": "..."} +// payload our handlers always emit. Callers can `instanceof ApiError` +// and check `.status === 401` to drive auth redirects. + +const API_PREFIX = '/api/v1'; + +export class ApiError extends Error { + status: number; + detail: string; + constructor(status: number, detail: string) { + super(`HTTP ${status}: ${detail}`); + this.name = 'ApiError'; + this.status = status; + this.detail = detail; + } +} + +export interface RequestOptions { + method?: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE'; + /** Plain object — serialized as JSON. */ + body?: unknown; + /** Extra query-string params; values are stringified. */ + query?: Record; + /** Cancel signal from React Query. */ + signal?: AbortSignal; +} + +function buildUrl(path: string, query?: RequestOptions['query']): string { + // Path always starts with `/auth/...` etc. The /api/v1 prefix is added here + // so individual call sites stay short and won't drift from the OpenAPI spec. + const base = path.startsWith('/api/') ? path : `${API_PREFIX}${path}`; + if (!query) return base; + const params = new URLSearchParams(); + for (const [k, v] of Object.entries(query)) { + if (v === undefined || v === null) continue; + params.set(k, String(v)); + } + const qs = params.toString(); + return qs ? `${base}?${qs}` : base; +} + +async function readDetail(res: Response): Promise { + try { + const data = (await res.clone().json()) as { detail?: unknown }; + if (data && typeof data.detail === 'string') return data.detail; + } catch { + // fall through — non-JSON body + } + try { + const txt = await res.text(); + return txt || res.statusText || `HTTP ${res.status}`; + } catch { + return res.statusText || `HTTP ${res.status}`; + } +} + +export async function request( + path: string, + opts: RequestOptions = {} +): Promise { + const { method = 'GET', body, query, signal } = opts; + + const init: RequestInit = { + method, + credentials: 'same-origin', + headers: { Accept: 'application/json' }, + signal, + }; + + if (body !== undefined) { + init.headers = { + ...(init.headers as Record), + 'Content-Type': 'application/json', + }; + init.body = JSON.stringify(body); + } + + const res = await fetch(buildUrl(path, query), init); + + if (!res.ok) { + throw new ApiError(res.status, await readDetail(res)); + } + + // 204 No Content + empty body for DELETEs. + if (res.status === 204) return undefined as T; + const ctype = res.headers.get('content-type') || ''; + if (!ctype.includes('application/json')) return undefined as T; + return (await res.json()) as T; +} + +export const api = { + get: (path: string, opts?: Omit) => + request(path, { ...opts, method: 'GET' }), + post: (path: string, body?: unknown, opts?: Omit) => + request(path, { ...opts, method: 'POST', body }), + patch: (path: string, body?: unknown, opts?: Omit) => + request(path, { ...opts, method: 'PATCH', body }), + put: (path: string, body?: unknown, opts?: Omit) => + request(path, { ...opts, method: 'PUT', body }), + delete: (path: string, opts?: Omit) => + request(path, { ...opts, method: 'DELETE' }), +}; diff --git a/server/dashboard/src/api/types.ts b/server/dashboard/src/api/types.ts new file mode 100644 index 0000000..f79accf --- /dev/null +++ b/server/dashboard/src/api/types.ts @@ -0,0 +1,66 @@ +// Hand-written re-exports of the OpenAPI schemas the dashboard actually uses. +// +// The full generated `./generated.ts` is produced by `npm run gen:api` and is +// gitignored — this file gives us stable, named imports without leaking +// `components['schemas']['User']` syntax into every component. Add a new +// alias here when the dashboard starts consuming a new schema. + +import type { components } from './generated'; + +export type Role = 'admin' | 'viewer'; + +export type User = components['schemas']['User']; +export type UserWithStats = components['schemas']['UserWithStats']; +export type Session = components['schemas']['Session']; +export type ApiKey = components['schemas']['ApiKey']; +export type ApiKeyCreated = components['schemas']['ApiKeyCreated']; +export type ApiKeyListResponse = components['schemas']['ApiKeyListResponse']; + +export type Project = components['schemas']['Project']; +export type ProjectSummary = components['schemas']['ProjectSummary']; +export type ProjectStats = components['schemas']['ProjectStats']; +export type ProjectSettings = components['schemas']['ProjectSettings']; +export type ProjectListResponse = components['schemas']['ProjectListResponse']; +export type DirEntry = components['schemas']['DirEntry']; +export type SymbolEntry = components['schemas']['SymbolEntry']; + +export type SemanticSearchRequest = components['schemas']['SemanticSearchRequest']; +export type SemanticSearchResponse = components['schemas']['SemanticSearchResponse']; +export type FileGroupResult = components['schemas']['FileGroupResult']; +export type FileMatch = components['schemas']['FileMatch']; +export type NestedHit = components['schemas']['NestedHit']; + +export type SymbolSearchRequest = components['schemas']['SymbolSearchRequest']; +export type SymbolSearchResponse = components['schemas']['SymbolSearchResponse']; +export type SymbolResultItem = components['schemas']['SymbolResultItem']; + +export type DefinitionRequest = components['schemas']['DefinitionRequest']; +export type DefinitionResponse = components['schemas']['DefinitionResponse']; +export type DefinitionItem = components['schemas']['DefinitionItem']; + +export type ReferenceRequest = components['schemas']['ReferenceRequest']; +export type ReferenceResponse = components['schemas']['ReferenceResponse']; +export type ReferenceItem = components['schemas']['ReferenceItem']; + +export type FileSearchRequest = components['schemas']['FileSearchRequest']; +export type FileSearchResponse = components['schemas']['FileSearchResponse']; +export type FileResultItem = components['schemas']['FileResultItem']; + +export type LoginRequest = components['schemas']['LoginRequest']; +export type LoginResponse = components['schemas']['LoginResponse']; +export type MeResponse = components['schemas']['MeResponse']; +export type ChangePasswordRequest = components['schemas']['ChangePasswordRequest']; +export type CreateUserRequest = components['schemas']['CreateUserRequest']; +export type UpdateUserRequest = components['schemas']['UpdateUserRequest']; +export type UserListResponse = components['schemas']['UserListResponse']; +export type CreateApiKeyRequest = components['schemas']['CreateApiKeyRequest']; +export type SessionListResponse = components['schemas']['SessionListResponse']; +export type BootstrapStatusResponse = components['schemas']['BootstrapStatusResponse']; + +export type RuntimeConfig = components['schemas']['RuntimeConfig']; +export type RuntimeConfigUpdate = components['schemas']['RuntimeConfigUpdate']; +export type RuntimeConfigRecommended = components['schemas']['RuntimeConfigRecommended']; +export type SidecarStatus = components['schemas']['SidecarStatus']; +export type ModelEntry = components['schemas']['ModelEntry']; +export type ModelList = components['schemas']['ModelList']; +export type RestartAccepted = components['schemas']['RestartAccepted']; diff --git a/server/dashboard/src/app/App.tsx b/server/dashboard/src/app/App.tsx new file mode 100644 index 0000000..260549b --- /dev/null +++ b/server/dashboard/src/app/App.tsx @@ -0,0 +1,75 @@ +import { Loader2 } from 'lucide-react'; +import { Navigate, Route, Routes } from 'react-router-dom'; +import { useAuth } from '@/auth/useAuth'; +import BootstrapNeededPage from '@/auth/BootstrapNeededPage'; +import ChangePasswordPage from '@/auth/ChangePasswordPage'; +import LoginPage from '@/auth/LoginPage'; +import { MODULES } from '@/modules/registry'; +import { Shell } from './Shell'; + +// Top-level auth + route gate. Three states branch off here: +// - bootstrap not done → BootstrapNeededPage (no other route works) +// - logged out → LoginPage (no Shell, no nav) +// - must change password → ChangePasswordPage (no Shell, no nav) +// - logged in & happy → Shell + module routes +// +// Module routes are derived from the registry — no manual entries +// per feature. Each module owns its `path` (relative to /dashboard) and +// renders whatever it likes inside. +export default function App() { + const { loading, needsBootstrap, user, mustChangePassword } = useAuth(); + + if (loading) { + return ( +
+ + Loading… +
+ ); + } + + if (needsBootstrap) return ; + + if (!user) { + return ( + + } /> + } /> + + ); + } + + if (mustChangePassword) { + return ( + + } /> + } /> + + ); + } + + // Authenticated + ready — render every registered module under the Shell. + // A module whose role gate excludes the current user simply has no + // mounted, so a deep link to it 404s back to /. + const visible = MODULES.filter((m) => { + if (!m.requiredRole) return true; + if (m.requiredRole === 'viewer') return true; + return user.role === 'admin'; + }); + + return ( + + + {visible.map((m) => { + // Modules can own a sub-tree by defining their own routes inside + // their element; we mount with a trailing wildcard so they get + // them on `//*`. + const mountPath = m.path === '/' ? '/*' : `${m.path}/*`; + const Element = m.element; + return } />; + })} + } /> + + + ); +} diff --git a/server/dashboard/src/app/Footer.tsx b/server/dashboard/src/app/Footer.tsx new file mode 100644 index 0000000..f772808 --- /dev/null +++ b/server/dashboard/src/app/Footer.tsx @@ -0,0 +1,73 @@ +import { Link } from 'react-router-dom'; +import { useServerStatus } from '@/lib/useServerStatus'; +import { useAuth } from '@/auth/useAuth'; +import { cn } from '@/lib/cn'; + +// Footer spans the full width below the sidebar + main pane. Reads +// from the shared /status query (polled every 30 s) — server version +// on the left, llama sidecar liveness dot on the right. The "llama" +// label links to /server (admin-only page); viewers see plain text +// since the route isn't mounted for them. +export function Footer() { + const { data, isLoading } = useServerStatus(); + const { user } = useAuth(); + const version = data?.server_version ?? 'dev'; + const alive = data?.model_loaded === true; + const isAdmin = user?.role === 'admin'; + + const dotClass = isLoading + ? 'bg-muted-foreground/40' + : alive + ? 'bg-emerald-500' + : 'bg-red-500'; + const dotTitle = isLoading + ? 'Checking sidecar status…' + : alive + ? 'Sidecar is alive' + : 'Sidecar is not responding'; + + const indicator = ( + <> + + llama + + ); + + return ( + + ); +} diff --git a/server/dashboard/src/app/Shell.tsx b/server/dashboard/src/app/Shell.tsx new file mode 100644 index 0000000..f0f8955 --- /dev/null +++ b/server/dashboard/src/app/Shell.tsx @@ -0,0 +1,21 @@ +import type { ReactNode } from 'react'; +import { Sidebar } from './Sidebar'; +import { Footer } from './Footer'; + +// Three-row layout: sidebar + main on top, footer spanning the full +// width on the bottom. min-h-0 on the inner row is required so that +//
's overflow-y-auto honors the footer's height when content +// grows tall. +export function Shell({ children }: { children: ReactNode }) { + return ( +
+
+ +
+
{children}
+
+
+
+
+ ); +} diff --git a/server/dashboard/src/app/Sidebar.tsx b/server/dashboard/src/app/Sidebar.tsx new file mode 100644 index 0000000..6a1241e --- /dev/null +++ b/server/dashboard/src/app/Sidebar.tsx @@ -0,0 +1,76 @@ +import { LogOut } from 'lucide-react'; +import { NavLink } from 'react-router-dom'; +import { useAuth } from '@/auth/useAuth'; +import { Button } from '@/ui/button'; +import { cn } from '@/lib/cn'; +import { MODULES } from '@/modules/registry'; + +// Sidebar is rendered from the module registry, filtered by the current +// user's role. A module without `requiredRole` is always visible; a module +// requiring `admin` is hidden from viewers. +// +// New features show up in the sidebar automatically once registered — no +// edits to this component are needed when a module is added. +export function Sidebar() { + const { user, logout } = useAuth(); + const role = user?.role ?? 'viewer'; + + const visible = MODULES.filter((m) => { + if (!m.requiredRole) return true; + if (m.requiredRole === 'viewer') return true; + return role === 'admin'; + }); + + return ( + + ); +} diff --git a/server/dashboard/src/app/ThemeProvider.tsx b/server/dashboard/src/app/ThemeProvider.tsx new file mode 100644 index 0000000..f6e1028 --- /dev/null +++ b/server/dashboard/src/app/ThemeProvider.tsx @@ -0,0 +1,64 @@ +import { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useState, + type ReactNode, +} from 'react'; +import { + applyResolvedTheme, + readStoredTheme, + resolveTheme, + writeStoredTheme, + type ResolvedTheme, + type ThemeMode, +} from '@/lib/theme'; + +interface ThemeContextValue { + mode: ThemeMode; + resolved: ResolvedTheme; + setMode: (mode: ThemeMode) => void; +} + +const ThemeContext = createContext(null); + +export function ThemeProvider({ children }: { children: ReactNode }) { + const [mode, setModeState] = useState(() => readStoredTheme()); + const [resolved, setResolved] = useState(() => resolveTheme(mode)); + + // Apply class + persist whenever the mode changes. Includes a listener on + // `(prefers-color-scheme: dark)` for the 'system' mode so OS toggles flip + // the UI live without a reload. + useEffect(() => { + const next = resolveTheme(mode); + setResolved(next); + applyResolvedTheme(next); + + if (mode !== 'system') return; + const mql = window.matchMedia('(prefers-color-scheme: dark)'); + const handler = () => { + const r = mql.matches ? 'dark' : 'light'; + setResolved(r); + applyResolvedTheme(r); + }; + mql.addEventListener('change', handler); + return () => mql.removeEventListener('change', handler); + }, [mode]); + + const setMode = useCallback((next: ThemeMode) => { + writeStoredTheme(next); + setModeState(next); + }, []); + + const value = useMemo(() => ({ mode, resolved, setMode }), [mode, resolved, setMode]); + + return {children}; +} + +export function useTheme(): ThemeContextValue { + const ctx = useContext(ThemeContext); + if (!ctx) throw new Error('useTheme must be used inside '); + return ctx; +} diff --git a/server/dashboard/src/app/providers.tsx b/server/dashboard/src/app/providers.tsx new file mode 100644 index 0000000..ab3a324 --- /dev/null +++ b/server/dashboard/src/app/providers.tsx @@ -0,0 +1,51 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { useState, type ReactNode } from 'react'; +import { Toaster } from '@/ui/sonner'; +import { TooltipProvider } from '@/ui/tooltip'; +import { AuthProvider } from '@/auth/AuthProvider'; +import { ApiError } from '@/api/client'; +import { ThemeProvider } from './ThemeProvider'; + +// One place to wire app-level providers — order matters: +// 1. QueryClient: needed before AuthProvider, which uses useQuery for /me. +// 2. AuthProvider: hooks the whole app to the current session. +// 3. Toaster: rendered last so toasts paint above everything else. +export function AppProviders({ children }: { children: ReactNode }) { + // Lazy-init so a fast refresh doesn't lose in-flight queries. + const [client] = useState( + () => + new QueryClient({ + defaultOptions: { + queries: { + staleTime: 30_000, + gcTime: 5 * 60_000, + refetchOnWindowFocus: false, + // Default retry: 3 fast retries, but not on auth errors — those + // mean the cookie is gone, retrying just delays the redirect. + retry: (failureCount, error) => { + if (error instanceof ApiError && (error.status === 401 || error.status === 403)) { + return false; + } + return failureCount < 2; + }, + }, + mutations: { + // Mutations should never be auto-retried; the user clicked once, + // surface the failure once. + retry: false, + }, + }, + }) + ); + + return ( + + + + {children} + + + + + ); +} diff --git a/server/dashboard/src/auth/AuthProvider.tsx b/server/dashboard/src/auth/AuthProvider.tsx new file mode 100644 index 0000000..e793dfa --- /dev/null +++ b/server/dashboard/src/auth/AuthProvider.tsx @@ -0,0 +1,110 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { createContext, useCallback, useMemo, type ReactNode } from 'react'; +import { ApiError, api } from '@/api/client'; +import type { BootstrapStatusResponse, LoginRequest, LoginResponse, MeResponse, User } from '@/api/types'; + +// Shape exposed to components — kept narrow on purpose. Use `useAuth` to +// consume; `AuthProvider` is the only place that touches the underlying +// queries directly. +export interface AuthContextValue { + /** True while we're still figuring out the initial auth state. */ + loading: boolean; + /** True until the operator has done the first-run bootstrap. */ + needsBootstrap: boolean; + /** Currently authenticated user, or null when logged out. */ + user: User | null; + /** When true, the user must change their password before reaching the app. */ + mustChangePassword: boolean; + /** Performs the login flow + warms /me. Throws ApiError on failure. */ + login: (req: LoginRequest) => Promise; + /** Performs server-side logout + clears cached /me. */ + logout: () => Promise; + /** Re-fetches /me — call after the user changes their password. */ + refresh: () => Promise; +} + +export const AuthContext = createContext(null); + +// Two queries drive the auth state machine: +// +// 1. /auth/bootstrap-status — public; tells us whether *any* user exists. +// If `needs_bootstrap === true`, the dashboard renders the +// BootstrapNeededPage instead of the login form. +// 2. /auth/me — requires a session cookie. 401 means logged out; +// anything else is treated as an outage and surfaces the error. +export function AuthProvider({ children }: { children: ReactNode }) { + const qc = useQueryClient(); + + const bootstrap = useQuery({ + queryKey: ['auth', 'bootstrap-status'], + queryFn: () => api.get('/auth/bootstrap-status'), + staleTime: Infinity, + retry: false, + }); + + const me = useQuery({ + queryKey: ['auth', 'me'], + queryFn: async () => { + try { + return await api.get('/auth/me'); + } catch (err) { + if (err instanceof ApiError && err.status === 401) return null; + throw err; + } + }, + enabled: bootstrap.data?.needs_bootstrap === false, + staleTime: 60_000, + retry: false, + }); + + const loginMutation = useMutation({ + mutationFn: (req: LoginRequest) => api.post('/auth/login', req), + onSuccess: () => { + // /auth/me has a slightly richer envelope than /auth/login (carries + // auth_method); re-fetching is simpler than constructing a partial. + void qc.invalidateQueries({ queryKey: ['auth', 'me'] }); + }, + }); + + const logoutMutation = useMutation({ + mutationFn: () => api.post('/auth/logout'), + onSettled: () => { + qc.setQueryData(['auth', 'me'], null); + qc.removeQueries({ queryKey: ['auth', 'me'] }); + }, + }); + + const login = useCallback( + async (req: LoginRequest) => { + await loginMutation.mutateAsync(req); + }, + [loginMutation] + ); + + const logout = useCallback(async () => { + try { + await logoutMutation.mutateAsync(); + } catch { + // logout endpoint can fail if the cookie is already gone — that's fine + } + }, [logoutMutation]); + + const refresh = useCallback(async () => { + await qc.invalidateQueries({ queryKey: ['auth', 'me'] }); + }, [qc]); + + const value = useMemo( + () => ({ + loading: bootstrap.isLoading || (bootstrap.data?.needs_bootstrap === false && me.isLoading), + needsBootstrap: bootstrap.data?.needs_bootstrap ?? false, + user: me.data?.user ?? null, + mustChangePassword: me.data?.user?.must_change_password ?? false, + login, + logout, + refresh, + }), + [bootstrap.isLoading, bootstrap.data, me.isLoading, me.data, login, logout, refresh] + ); + + return {children}; +} diff --git a/server/dashboard/src/auth/BootstrapNeededPage.tsx b/server/dashboard/src/auth/BootstrapNeededPage.tsx new file mode 100644 index 0000000..e4a8fc3 --- /dev/null +++ b/server/dashboard/src/auth/BootstrapNeededPage.tsx @@ -0,0 +1,39 @@ +import { Alert, AlertDescription, AlertTitle } from '@/ui/alert'; + +// Shown when /auth/bootstrap-status reports `needs_bootstrap: true` — +// i.e. the database has no users and the operator hasn't supplied the +// bootstrap admin env vars. There's nothing the visitor can do from the +// browser; this page exists to explain what to set on the server. +export default function BootstrapNeededPage() { + return ( +
+
+
+
Server not configured
+
+ cix-server has no users yet. An administrator must seed the first account before the dashboard becomes available. +
+
+ + + How to bootstrap the first admin + +

+ Restart the server with both of these environment variables set: +

+
+{`CIX_BOOTSTRAP_ADMIN_EMAIL=admin@example.com \\
+CIX_BOOTSTRAP_ADMIN_PASSWORD='change-me-on-first-login' \\
+./cix-server`}
+            
+

+ On first login the admin will be required to change the + password. After that, both env vars are ignored on subsequent + starts. +

+
+
+
+
+ ); +} diff --git a/server/dashboard/src/auth/ChangePasswordPage.tsx b/server/dashboard/src/auth/ChangePasswordPage.tsx new file mode 100644 index 0000000..30e72a1 --- /dev/null +++ b/server/dashboard/src/auth/ChangePasswordPage.tsx @@ -0,0 +1,115 @@ +import { useState, type FormEvent } from 'react'; +import { ApiError, api } from '@/api/client'; +import type { ChangePasswordRequest } from '@/api/types'; +import { Alert, AlertDescription, AlertTitle } from '@/ui/alert'; +import { Button } from '@/ui/button'; +import { Input } from '@/ui/input'; +import { Label } from '@/ui/label'; +import { toast } from '@/ui/sonner'; +import { useAuth } from './useAuth'; + +// Forced password-change page — reached either right after a bootstrap +// admin first logs in, or after an admin invite. Server-side: a successful +// POST /auth/change-password ALSO revokes every other session for this +// user, so we log out and bounce back to /login on success. +export default function ChangePasswordPage() { + const { logout } = useAuth(); + const [current, setCurrent] = useState(''); + const [next, setNext] = useState(''); + const [confirm, setConfirm] = useState(''); + const [submitting, setSubmitting] = useState(false); + const [error, setError] = useState(null); + + async function onSubmit(e: FormEvent) { + e.preventDefault(); + setError(null); + if (next !== confirm) { + setError('New password and confirmation must match.'); + return; + } + if (next.length < 8) { + setError('New password must be at least 8 characters.'); + return; + } + setSubmitting(true); + try { + const req: ChangePasswordRequest = { current_password: current, new_password: next }; + await api.post('/auth/change-password', req); + toast.success('Password updated. Please sign in with your new password.'); + // Server already invalidated this session — calling logout cleans up + // the cookie + clears cached /me so App.tsx falls back to LoginPage. + await logout(); + } catch (err) { + setError(err instanceof ApiError ? err.detail : 'Unexpected error. Try again.'); + } finally { + setSubmitting(false); + } + } + + return ( +
+
+
+
Change your password
+
+ For security, you must set a new password before continuing. +
+
+ +
+
+ + setCurrent(e.target.value)} + disabled={submitting} + /> +
+ +
+ + setNext(e.target.value)} + disabled={submitting} + /> +
+ +
+ + setConfirm(e.target.value)} + disabled={submitting} + /> +
+ + {error && ( + + Could not update password + {error} + + )} + + +
+
+
+ ); +} diff --git a/server/dashboard/src/auth/LoginPage.tsx b/server/dashboard/src/auth/LoginPage.tsx new file mode 100644 index 0000000..62f1e0f --- /dev/null +++ b/server/dashboard/src/auth/LoginPage.tsx @@ -0,0 +1,87 @@ +import { useState, type FormEvent } from 'react'; +import { ApiError } from '@/api/client'; +import { Alert, AlertDescription, AlertTitle } from '@/ui/alert'; +import { Button } from '@/ui/button'; +import { Input } from '@/ui/input'; +import { Label } from '@/ui/label'; +import { useAuth } from './useAuth'; + +// Standalone full-page login form. Lives outside the Shell so the sidebar +// doesn't peek through. Successful login transitions the auth state and +// the parent App routes the user to /change-password or / accordingly. +export default function LoginPage() { + const { login } = useAuth(); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [submitting, setSubmitting] = useState(false); + const [error, setError] = useState(null); + + async function onSubmit(e: FormEvent) { + e.preventDefault(); + setError(null); + setSubmitting(true); + try { + await login({ email, password }); + } catch (err) { + const detail = + err instanceof ApiError ? err.detail : 'Could not reach server. Try again.'; + setError(detail); + } finally { + setSubmitting(false); + } + } + + return ( +
+
+
+
cix dashboard
+
Sign in to continue
+
+ +
+
+ + setEmail(e.target.value)} + disabled={submitting} + /> +
+ +
+ + setPassword(e.target.value)} + disabled={submitting} + /> +
+ + {error && ( + + Login failed + {error} + + )} + + +
+ +

+ CLI users authenticate with API keys, not this form. +

+
+
+ ); +} diff --git a/server/dashboard/src/auth/useAuth.ts b/server/dashboard/src/auth/useAuth.ts new file mode 100644 index 0000000..fe530c4 --- /dev/null +++ b/server/dashboard/src/auth/useAuth.ts @@ -0,0 +1,13 @@ +import { useContext } from 'react'; +import { AuthContext, type AuthContextValue } from './AuthProvider'; + +// Hook for components to read the current auth state. Throws when used +// outside — that's a developer mistake, not a runtime +// condition we'd want to silently fall through. +export function useAuth(): AuthContextValue { + const ctx = useContext(AuthContext); + if (!ctx) { + throw new Error('useAuth must be used within '); + } + return ctx; +} diff --git a/server/dashboard/src/index.css b/server/dashboard/src/index.css new file mode 100644 index 0000000..5703ce5 --- /dev/null +++ b/server/dashboard/src/index.css @@ -0,0 +1,63 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +/* shadcn/ui design tokens — neutral baseColor, Notion-like palette. + * Light is the default; .dark variants live alongside for the toggle that + * lands in PR-D. Keep both in sync when adjusting. */ +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 0 0% 9%; + --card: 0 0% 100%; + --card-foreground: 0 0% 9%; + --popover: 0 0% 100%; + --popover-foreground: 0 0% 9%; + --primary: 0 0% 9%; + --primary-foreground: 0 0% 98%; + --secondary: 0 0% 96%; + --secondary-foreground: 0 0% 9%; + --muted: 0 0% 96%; + --muted-foreground: 0 0% 45%; + --accent: 0 0% 96%; + --accent-foreground: 0 0% 9%; + --destructive: 0 72% 51%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 90%; + --input: 0 0% 90%; + --ring: 0 0% 9%; + --radius: 0.5rem; + } + + .dark { + --background: 0 0% 7%; + --foreground: 0 0% 95%; + --card: 0 0% 9%; + --card-foreground: 0 0% 95%; + --popover: 0 0% 9%; + --popover-foreground: 0 0% 95%; + --primary: 0 0% 95%; + --primary-foreground: 0 0% 9%; + --secondary: 0 0% 14%; + --secondary-foreground: 0 0% 95%; + --muted: 0 0% 14%; + --muted-foreground: 0 0% 60%; + --accent: 0 0% 14%; + --accent-foreground: 0 0% 95%; + --destructive: 0 62% 45%; + --destructive-foreground: 0 0% 95%; + --border: 0 0% 18%; + --input: 0 0% 18%; + --ring: 0 0% 80%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + font-feature-settings: 'rlig' 1, 'calt' 1; + } +} diff --git a/server/dashboard/src/lib/cn.ts b/server/dashboard/src/lib/cn.ts new file mode 100644 index 0000000..00a0be5 --- /dev/null +++ b/server/dashboard/src/lib/cn.ts @@ -0,0 +1,9 @@ +import { clsx, type ClassValue } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +// Standard shadcn helper — clsx for conditional classes + tailwind-merge to +// resolve conflicts (e.g. "px-2 px-4" → "px-4"). Used everywhere instead of +// raw className concatenation. +export function cn(...inputs: ClassValue[]): string { + return twMerge(clsx(inputs)); +} diff --git a/server/dashboard/src/lib/editorPreference.ts b/server/dashboard/src/lib/editorPreference.ts new file mode 100644 index 0000000..9cd5f4a --- /dev/null +++ b/server/dashboard/src/lib/editorPreference.ts @@ -0,0 +1,56 @@ +// Editor protocol preference. Persisted in localStorage; consumed by every +// "Open in editor" call site (currently search ResultSnippet). The default +// 'cursor' matches the prior hardcoded behaviour so users without a stored +// preference see no behaviour change. + +export type EditorProtocol = 'cursor' | 'vscode' | 'none'; + +export const EDITOR_STORAGE_KEY = 'cix.editor.protocol'; + +const VALID: ReadonlySet = new Set(['cursor', 'vscode', 'none']); + +export function getEditorPreference(): EditorProtocol { + if (typeof window === 'undefined') return 'cursor'; + try { + const v = window.localStorage.getItem(EDITOR_STORAGE_KEY); + if (v && VALID.has(v as EditorProtocol)) return v as EditorProtocol; + } catch { + /* localStorage may throw in privacy mode */ + } + return 'cursor'; +} + +export function setEditorPreference(p: EditorProtocol): void { + if (typeof window === 'undefined') return; + try { + window.localStorage.setItem(EDITOR_STORAGE_KEY, p); + } catch { + /* swallow — preference will reset on reload */ + } +} + +// CURSOR_FALLBACK_DELAY_MS — Cursor's URL handler usually takes ~100ms to +// pull focus on macOS. We give it 250ms before falling back to VS Code, +// which is long enough that a successful Cursor handle pre-empts the +// fallback navigation, and short enough that users without Cursor barely +// see a delay. +const CURSOR_FALLBACK_DELAY_MS = 250; + +export function openInEditor(absolutePath: string, line?: number): void { + const pref = getEditorPreference(); + if (pref === 'none') return; + + const suffix = typeof line === 'number' ? `:${line}` : ''; + const cursorURL = `cursor://file/${absolutePath}${suffix}`; + const vscodeURL = `vscode://file/${absolutePath}${suffix}`; + + if (pref === 'vscode') { + window.location.href = vscodeURL; + return; + } + // 'cursor' — try Cursor first, fall back to VS Code if no handler claims focus. + window.location.href = cursorURL; + window.setTimeout(() => { + window.location.href = vscodeURL; + }, CURSOR_FALLBACK_DELAY_MS); +} diff --git a/server/dashboard/src/lib/formatDate.ts b/server/dashboard/src/lib/formatDate.ts new file mode 100644 index 0000000..2c13d85 --- /dev/null +++ b/server/dashboard/src/lib/formatDate.ts @@ -0,0 +1,54 @@ +// Centralised date formatters so the whole UI stays consistent. +// +// All cix-server APIs emit RFC3339 strings (Go time.Time JSON default) or +// `null` for never-touched fields like `last_used_at`. Helpers here accept +// `string | null | undefined` to keep call sites concise. + +const DATE_FMT = new Intl.DateTimeFormat(undefined, { + year: 'numeric', + month: 'short', + day: 'numeric', +}); + +const TIME_FMT = new Intl.DateTimeFormat(undefined, { + hour: '2-digit', + minute: '2-digit', +}); + +export function formatDate(iso: string | null | undefined): string { + if (!iso) return '—'; + const d = new Date(iso); + if (Number.isNaN(d.getTime())) return iso; + return DATE_FMT.format(d); +} + +export function formatDateTime(iso: string | null | undefined): string { + if (!iso) return '—'; + const d = new Date(iso); + if (Number.isNaN(d.getTime())) return iso; + return `${DATE_FMT.format(d)}, ${TIME_FMT.format(d)}`; +} + +const RTF = new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' }); + +const STEPS: Array<[Intl.RelativeTimeFormatUnit, number]> = [ + ['year', 60 * 60 * 24 * 365], + ['month', 60 * 60 * 24 * 30], + ['day', 60 * 60 * 24], + ['hour', 60 * 60], + ['minute', 60], + ['second', 1], +]; + +export function formatRelative(iso: string | null | undefined): string { + if (!iso) return 'never'; + const then = new Date(iso).getTime(); + if (Number.isNaN(then)) return iso; + const seconds = Math.round((then - Date.now()) / 1000); + for (const [unit, secs] of STEPS) { + if (Math.abs(seconds) >= secs || unit === 'second') { + return RTF.format(Math.round(seconds / secs), unit); + } + } + return 'just now'; +} diff --git a/server/dashboard/src/lib/theme.ts b/server/dashboard/src/lib/theme.ts new file mode 100644 index 0000000..bb4cff9 --- /dev/null +++ b/server/dashboard/src/lib/theme.ts @@ -0,0 +1,46 @@ +// Theme storage + system-preference resolution. Mirrors the inline script +// in index.html — keep the storage key + values in sync with that script +// (otherwise the anti-flash hint and the React state diverge on first paint). + +export type ThemeMode = 'light' | 'dark' | 'system'; +export type ResolvedTheme = 'light' | 'dark'; + +export const THEME_STORAGE_KEY = 'cix.theme'; + +const VALID_MODES: ReadonlySet = new Set(['light', 'dark', 'system']); + +export function readStoredTheme(): ThemeMode { + if (typeof window === 'undefined') return 'system'; + try { + const v = window.localStorage.getItem(THEME_STORAGE_KEY); + if (v && VALID_MODES.has(v as ThemeMode)) return v as ThemeMode; + } catch { + /* localStorage may throw in privacy mode */ + } + return 'system'; +} + +export function writeStoredTheme(mode: ThemeMode): void { + if (typeof window === 'undefined') return; + try { + window.localStorage.setItem(THEME_STORAGE_KEY, mode); + } catch { + /* swallow — theme will reset on reload */ + } +} + +export function resolveSystemTheme(): ResolvedTheme { + if (typeof window === 'undefined' || !window.matchMedia) return 'light'; + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; +} + +export function resolveTheme(mode: ThemeMode): ResolvedTheme { + return mode === 'system' ? resolveSystemTheme() : mode; +} + +export function applyResolvedTheme(resolved: ResolvedTheme): void { + if (typeof document === 'undefined') return; + const root = document.documentElement; + if (resolved === 'dark') root.classList.add('dark'); + else root.classList.remove('dark'); +} diff --git a/server/dashboard/src/lib/useServerStatus.ts b/server/dashboard/src/lib/useServerStatus.ts new file mode 100644 index 0000000..6a900bd --- /dev/null +++ b/server/dashboard/src/lib/useServerStatus.ts @@ -0,0 +1,32 @@ +import { useQuery } from '@tanstack/react-query'; +import { api } from '@/api/client'; + +interface StatusPayload { + server_version: string; + embedding_model: string; + model_loaded: boolean; +} + +// useServerStatus polls /api/v1/status every 30 seconds. The footer +// reads server_version + model_loaded; the Projects drift indicator +// reads embedding_model. /status is auth-only (not admin-only) so +// viewers also see the footer indicator. model_loaded is set by an +// active Ready(ctx) ping, so it tracks actual sidecar liveness. +// +// queryKey is kept as ['runtime-model'] because server/hooks.ts +// invalidates that key after a sidecar restart to refresh drift +// immediately. +export function useServerStatus() { + return useQuery({ + queryKey: ['runtime-model'], + queryFn: ({ signal }) => api.get('/status', { signal }), + refetchInterval: 30_000, + refetchIntervalInBackground: false, + staleTime: 30_000, + }); +} + +export function useRuntimeModel() { + const { data } = useServerStatus(); + return data?.embedding_model ?? ''; +} diff --git a/server/dashboard/src/main.tsx b/server/dashboard/src/main.tsx new file mode 100644 index 0000000..f42e509 --- /dev/null +++ b/server/dashboard/src/main.tsx @@ -0,0 +1,22 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; +import App from './app/App.tsx'; +import { AppProviders } from './app/providers.tsx'; +import './index.css'; + +const root = document.getElementById('root'); +if (!root) throw new Error('cix-dashboard: #root not found in index.html'); + +// React Router lives at /dashboard so all in-app paths are relative to that +// prefix. The Go server returns the same index.html for any /dashboard/* +// URL so a deep refresh still boots the SPA, then BrowserRouter takes over. +createRoot(root).render( + + + + + + + +); diff --git a/server/dashboard/src/modules/api-keys/ApiKeysPage.tsx b/server/dashboard/src/modules/api-keys/ApiKeysPage.tsx new file mode 100644 index 0000000..c75a52f --- /dev/null +++ b/server/dashboard/src/modules/api-keys/ApiKeysPage.tsx @@ -0,0 +1,101 @@ +import { useMemo, useState } from 'react'; +import { AlertCircle, KeyRound } from 'lucide-react'; +import { ApiError } from '@/api/client'; +import { Alert, AlertDescription, AlertTitle } from '@/ui/alert'; +import { Skeleton } from '@/ui/skeleton'; +import { Tabs, TabsList, TabsTrigger } from '@/ui/tabs'; +import { useAuth } from '@/auth/useAuth'; +import { ApiKeyTable } from './components/ApiKeyTable'; +import { CreateApiKeyDialog } from './components/CreateApiKeyDialog'; +import { useAllApiKeys, useMyApiKeys } from './hooks'; + +type Mode = 'mine' | 'all'; + +export default function ApiKeysPage() { + const { user } = useAuth(); + const isAdmin = user?.role === 'admin'; + const [mode, setMode] = useState('mine'); + + const mine = useMyApiKeys(); + // Only fetch the All bucket when admin actively switches to it — avoids + // a wasted request and a redundant refetch on viewers (server would 403). + const all = useAllApiKeys(isAdmin && mode === 'all'); + + const active = mode === 'all' && isAdmin ? all : mine; + const keys = active.data?.api_keys ?? []; + + const ownerEmailLookup = useMemo(() => { + // The Owner column would ideally show emails, but the api-keys endpoint + // returns owner_user_id only. Resolving emails would need /admin/users — + // skipping that JOIN here keeps this page lean. Render a short id slice + // until a follow-up adds the lookup. Self-key is highlighted via + // canRevoke ownership so the audit trail still works. + return (id: string) => + id === user?.id ? user.email : undefined; + }, [user?.id, user?.email]); + + return ( +
+
+
+

API keys

+

+ Bearer tokens for CLI / SDK access. Created here, revoked here. +

+
+ +
+ + {isAdmin ? ( + setMode(v as Mode)}> + + My keys + All keys (admin) + + + ) : null} + + {active.isLoading ? ( +
+ {Array.from({ length: 4 }).map((_, i) => ( + + ))} +
+ ) : active.error ? ( + + + Failed to load API keys + + {active.error instanceof ApiError ? active.error.detail : String(active.error)} + + + ) : keys.length === 0 ? ( + + ) : ( + isAdmin || k.owner_user_id === user?.id} + /> + )} +
+ ); +} + +function EmptyState({ mode, isAdmin }: { mode: Mode; isAdmin: boolean }) { + return ( +
+ +
+

+ {mode === 'all' && isAdmin ? 'No API keys exist yet' : 'You have no API keys yet'} +

+

+ Create one to authenticate the cix{' '} + CLI from a workstation. +

+
+
+ ); +} diff --git a/server/dashboard/src/modules/api-keys/components/ApiKeyTable.tsx b/server/dashboard/src/modules/api-keys/components/ApiKeyTable.tsx new file mode 100644 index 0000000..482e6a9 --- /dev/null +++ b/server/dashboard/src/modules/api-keys/components/ApiKeyTable.tsx @@ -0,0 +1,87 @@ +import type { ApiKey } from '@/api/types'; +import { Badge } from '@/ui/badge'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/ui/table'; +import { cn } from '@/lib/cn'; +import { formatDateTime, formatRelative } from '@/lib/formatDate'; +import { RevokeApiKeyDialog } from './RevokeApiKeyDialog'; + +interface Props { + keys: ApiKey[]; + /** Owner column appears in admin "All keys" mode. */ + showOwner?: boolean; + /** Maps owner_user_id → email for the Owner column. Omit when showOwner is false. */ + ownerEmail?: (id: string) => string | undefined; + /** Whether the current viewer can revoke a row. Server enforces too. */ + canRevoke: (key: ApiKey) => boolean; +} + +export function ApiKeyTable({ keys, showOwner = false, ownerEmail, canRevoke }: Props) { + return ( +
+ + + + Name + Prefix + {showOwner ? Owner : null} + Created + Last used + Last IP + Actions + + + + {keys.map((k) => { + const revoked = Boolean(k.revoked); + return ( + + +
+ {k.name} + {revoked ? revoked : null} +
+
+ {k.prefix}… + {showOwner ? ( + + {ownerEmail?.(k.owner_user_id) ?? k.owner_user_id.slice(0, 8)} + + ) : null} + + {formatRelative(k.created_at)} + + + {formatRelative(k.last_used_at)} + + + {k.last_used_ip ?? '—'} + + + {revoked || !canRevoke(k) ? null : ( + + )} + +
+ ); + })} +
+
+
+ ); +} diff --git a/server/dashboard/src/modules/api-keys/components/CreateApiKeyDialog.tsx b/server/dashboard/src/modules/api-keys/components/CreateApiKeyDialog.tsx new file mode 100644 index 0000000..4b56de6 --- /dev/null +++ b/server/dashboard/src/modules/api-keys/components/CreateApiKeyDialog.tsx @@ -0,0 +1,242 @@ +import { useEffect, useRef, useState } from 'react'; +import { Check, Copy, Loader2, KeyRound, AlertTriangle } from 'lucide-react'; +import { toast } from 'sonner'; +import { ApiError } from '@/api/client'; +import { Alert, AlertDescription, AlertTitle } from '@/ui/alert'; +import { Button } from '@/ui/button'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/ui/dialog'; +import { Input } from '@/ui/input'; +import { Label } from '@/ui/label'; +import { useCreateApiKey } from '../hooks'; + +// Last-resort copy when the async Clipboard API isn't available — happens +// on plain HTTP deploys (non-localhost) and inside some embedded webviews. +// document.execCommand('copy') is deprecated but universally implemented as +// of 2026; keeping it as a fallback turns "no way to copy" into "always works". +function legacyCopy(text: string): boolean { + if (typeof document === 'undefined') return false; + const ta = document.createElement('textarea'); + ta.value = text; + ta.setAttribute('readonly', ''); + ta.style.position = 'fixed'; + ta.style.opacity = '0'; + document.body.appendChild(ta); + ta.focus(); + ta.select(); + let ok = false; + try { + ok = document.execCommand('copy'); + } catch { + ok = false; + } + document.body.removeChild(ta); + return ok; +} + +// Two-stage dialog: collect a name, then reveal the freshly minted key once. +// Once revealed, the dialog refuses outside-click and Escape — accidental +// dismissal would lose the unrecoverable secret. Only the explicit "I've +// saved it" / X button can close it. +export function CreateApiKeyDialog() { + const [open, setOpen] = useState(false); + const [name, setName] = useState(''); + const [revealed, setRevealed] = useState(null); + const [copied, setCopied] = useState(false); + const inputRef = useRef(null); + const create = useCreateApiKey(); + + // Auto-select the revealed key as soon as it appears so users can ⌘C + // immediately if the Copy button doesn't work in their context. + useEffect(() => { + if (revealed && inputRef.current) { + inputRef.current.focus(); + inputRef.current.select(); + } + }, [revealed]); + + function reset() { + setName(''); + setRevealed(null); + setCopied(false); + create.reset(); + } + + async function onCreate() { + const trimmed = name.trim(); + if (!trimmed) return; + try { + const out = await create.mutateAsync({ name: trimmed }); + setRevealed(out.full_key); + } catch (err) { + const detail = err instanceof ApiError ? err.detail : String(err); + toast.error('Failed to create API key', { description: detail }); + } + } + + async function copyToClipboard() { + if (!revealed) return; + // navigator.clipboard requires a secure context (HTTPS or localhost). On + // bare-IP / HTTP deploys it throws — fall back to document.execCommand + // through a transient textarea so users still get one-click copy. + try { + if (window.isSecureContext && navigator.clipboard?.writeText) { + await navigator.clipboard.writeText(revealed); + } else { + if (!legacyCopy(revealed)) throw new Error('legacy copy failed'); + } + setCopied(true); + window.setTimeout(() => setCopied(false), 2000); + } catch { + toast.error('Could not copy automatically.', { + description: 'Click the field, ⌘A / Ctrl-A to select, then copy.', + }); + } + } + + return ( + { + // Once a key is revealed, only the explicit "I've saved it" button + // (or close-X) may dismiss the dialog. Outside-click and Escape are + // blocked at the DialogContent layer below; we still gate state-resets + // here so a sibling dismiss path can't wipe the key silently. + if (!next && revealed) return; + setOpen(next); + if (!next) reset(); + }} + > + + + + { + if (revealed) e.preventDefault(); + }} + onEscapeKeyDown={(e) => { + if (revealed) e.preventDefault(); + }} + onInteractOutside={(e) => { + if (revealed) e.preventDefault(); + }} + > + {revealed ? ( + <> + + API key created + + Copy and store this key now. You will not be able to view it again. + + + + + Save it before closing + + The full value is shown exactly once. We store only a SHA-256 + hash; if you lose it you must revoke and create a new key. + + +
+ +
+ {/* readonly Input + select-all-on-focus is the most reliable + fallback when navigator.clipboard is unavailable (e.g. HTTP + deploys without a secure context). User can always ⌘A → ⌘C. */} + e.currentTarget.select()} + onClick={(e) => e.currentTarget.select()} + /> + +
+

+ Click the field to select all, then ⌘C / Ctrl-C if the Copy + button is blocked by your browser. +

+
+ + + + + ) : ( + <> + + Create API key + + Generate a long-lived bearer token for CLI / SDK use. The full key + is shown once and never stored in plaintext. + + +
+ + setName(e.target.value)} + placeholder="e.g. ci-bot, laptop, jenkins" + autoFocus + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault(); + void onCreate(); + } + }} + /> +
+ + + + + + )} +
+
+ ); +} diff --git a/server/dashboard/src/modules/api-keys/components/RevokeApiKeyDialog.tsx b/server/dashboard/src/modules/api-keys/components/RevokeApiKeyDialog.tsx new file mode 100644 index 0000000..db4b5e2 --- /dev/null +++ b/server/dashboard/src/modules/api-keys/components/RevokeApiKeyDialog.tsx @@ -0,0 +1,73 @@ +import { useState } from 'react'; +import { Loader2, Trash2 } from 'lucide-react'; +import { toast } from 'sonner'; +import { ApiError } from '@/api/client'; +import { Button } from '@/ui/button'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/ui/dialog'; +import { useRevokeApiKey } from '../hooks'; + +export function RevokeApiKeyDialog({ + id, + name, + prefix, +}: { + id: string; + name: string; + prefix: string; +}) { + const [open, setOpen] = useState(false); + const revoke = useRevokeApiKey(); + + async function onConfirm() { + try { + await revoke.mutateAsync(id); + toast.success('API key revoked', { description: name }); + setOpen(false); + } catch (err) { + const detail = err instanceof ApiError ? err.detail : String(err); + toast.error('Failed to revoke key', { description: detail }); + } + } + + return ( + + + + + + + Revoke this API key? + + Any client using {prefix}… ({name}) + will start receiving 401 immediately. The audit row stays in the + database but the key cannot authenticate again. + + + + + + + + + ); +} diff --git a/server/dashboard/src/modules/api-keys/hooks.ts b/server/dashboard/src/modules/api-keys/hooks.ts new file mode 100644 index 0000000..02e0e96 --- /dev/null +++ b/server/dashboard/src/modules/api-keys/hooks.ts @@ -0,0 +1,53 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { api } from '@/api/client'; +import type { + ApiKeyCreated, + ApiKeyListResponse, + CreateApiKeyRequest, +} from '@/api/types'; + +// Owned-keys vs admin-only "all keys" view share an endpoint differentiated +// by the `?owner=all` query param. Two cache buckets so an admin toggling +// between the views doesn't see a stale slice from the other. +export const apiKeyKeys = { + mine: ['apikeys', 'mine'] as const, + all: ['apikeys', 'all'] as const, +}; + +export function useMyApiKeys() { + return useQuery({ + queryKey: apiKeyKeys.mine, + queryFn: ({ signal }) => api.get('/api-keys', { signal }), + }); +} + +export function useAllApiKeys(enabled: boolean) { + return useQuery({ + queryKey: apiKeyKeys.all, + queryFn: ({ signal }) => + api.get('/api-keys', { signal, query: { owner: 'all' } }), + enabled, + }); +} + +export function useCreateApiKey() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: (body: CreateApiKeyRequest) => api.post('/api-keys', body), + onSuccess: () => { + qc.invalidateQueries({ queryKey: apiKeyKeys.mine }); + qc.invalidateQueries({ queryKey: apiKeyKeys.all }); + }, + }); +} + +export function useRevokeApiKey() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: (id: string) => api.delete(`/api-keys/${id}`), + onSuccess: () => { + qc.invalidateQueries({ queryKey: apiKeyKeys.mine }); + qc.invalidateQueries({ queryKey: apiKeyKeys.all }); + }, + }); +} diff --git a/server/dashboard/src/modules/api-keys/index.ts b/server/dashboard/src/modules/api-keys/index.ts new file mode 100644 index 0000000..a83d94e --- /dev/null +++ b/server/dashboard/src/modules/api-keys/index.ts @@ -0,0 +1,12 @@ +import { KeyRound } from 'lucide-react'; +import type { Module } from '../types'; +import ApiKeysPage from './ApiKeysPage'; + +export const ApiKeysModule: Module = { + id: 'api-keys', + label: 'API Keys', + icon: KeyRound, + path: '/api-keys', + element: ApiKeysPage, + weight: 30, +}; diff --git a/server/dashboard/src/modules/home/HomePage.tsx b/server/dashboard/src/modules/home/HomePage.tsx new file mode 100644 index 0000000..e8511dd --- /dev/null +++ b/server/dashboard/src/modules/home/HomePage.tsx @@ -0,0 +1,120 @@ +import { NavLink } from 'react-router-dom'; +import { useAuth } from '@/auth/useAuth'; +import { useServerStatus } from '@/lib/useServerStatus'; +import { Card, CardDescription, CardHeader, CardTitle } from '@/ui/card'; +import { cn } from '@/lib/cn'; +import { MODULES } from '../registry'; + +// One-line pitch per module — kept here (not on the Module type) so the +// sidebar stays terse and only the landing page carries the prose. +const DESCRIPTIONS: Record = { + projects: + 'Browse indexed repositories, inspect stats, copy reindex commands, and watch for stale-model drift.', + search: + 'Five modes — semantic, symbols, references, definitions, files — across every project from one bar.', + 'api-keys': + 'Mint long-lived API keys for CLI / CI use, scope them to a role, revoke at any time.', + users: 'Invite teammates, set roles, reset passwords, and audit access.', + settings: 'Personal preferences — theme, default editor, change password.', + server: + 'Tune the embedding model and llama-server runtime, restart the sidecar without dropping into SSH.', +}; + +export default function HomePage() { + const { user } = useAuth(); + const role = user?.role ?? 'viewer'; + const { data: status } = useServerStatus(); + + const cards = MODULES.filter((m) => m.id !== 'home').filter((m) => { + if (!m.requiredRole) return true; + if (m.requiredRole === 'viewer') return true; + return role === 'admin'; + }); + + return ( +
+
+

+ Welcome back{user?.email ? `, ${user.email}` : ''} +

+

+ The cix dashboard — semantic code search, project management, and runtime control. +

+
+ + {status && ( +
+ + + +
+ )} + +
+

Modules

+
+ {cards.map((m) => { + const Icon = m.icon; + return ( + + + +
+ + {m.label} +
+ {DESCRIPTIONS[m.id] ?? ''} +
+
+
+ ); + })} +
+
+ +

+ Prefer the terminal? cix --help{' '} + does everything the dashboard does, plus reindex, watch, and bulk operations. +

+
+ ); +} + +function StatusStat({ + label, + value, + mono, + tone, +}: { + label: string; + value: string; + mono?: boolean; + tone?: 'ok' | 'warn'; +}) { + return ( +
+
{label}
+
+ {value} +
+
+ ); +} diff --git a/server/dashboard/src/modules/home/index.ts b/server/dashboard/src/modules/home/index.ts new file mode 100644 index 0000000..1b94f96 --- /dev/null +++ b/server/dashboard/src/modules/home/index.ts @@ -0,0 +1,15 @@ +import { Home } from 'lucide-react'; +import type { Module } from '../types'; +import HomePage from './HomePage'; + +// Landing page for /dashboard/. Renders a status strip + cards for every +// module the user can see, driven by the registry — new features show up +// here automatically. +export const HomeModule: Module = { + id: 'home', + label: 'Home', + icon: Home, + path: '/', + element: HomePage, + weight: 0, +}; diff --git a/server/dashboard/src/modules/projects/ProjectDetailPage.tsx b/server/dashboard/src/modules/projects/ProjectDetailPage.tsx new file mode 100644 index 0000000..6a475f1 --- /dev/null +++ b/server/dashboard/src/modules/projects/ProjectDetailPage.tsx @@ -0,0 +1,229 @@ +import { AlertCircle, AlertTriangle, ArrowLeft, Search } from 'lucide-react'; +import { Link, useParams } from 'react-router-dom'; +import { ApiError } from '@/api/client'; +import type { Project } from '@/api/types'; +import { useAuth } from '@/auth/useAuth'; +import { Alert, AlertDescription, AlertTitle } from '@/ui/alert'; +import { Badge } from '@/ui/badge'; +import { Button } from '@/ui/button'; +import { Card, CardContent } from '@/ui/card'; +import { Skeleton } from '@/ui/skeleton'; +import { formatDateTime, formatRelative } from '@/lib/formatDate'; +import { useRuntimeModel } from '@/lib/useServerStatus'; +import { DeleteProjectDialog } from './components/DeleteProjectDialog'; +import { ProjectInfoCard } from './components/ProjectInfoCard'; +import { useProject, useProjectSummary } from './hooks'; + +const STATUS_VARIANT: Record = { + created: 'outline', + indexing: 'secondary', + indexed: 'default', + error: 'destructive', +}; + +export function ProjectDetailPage() { + const { id } = useParams<{ id: string }>(); + const { user } = useAuth(); + const isAdmin = user?.role === 'admin'; + const project = useProject(id); + const summary = useProjectSummary(id); + const currentModel = useRuntimeModel(); + + if (project.isLoading) return ; + if (project.error || !project.data) { + return ( + + + Project not found + + {project.error instanceof ApiError ? project.error.detail : 'Unknown error'} + +
+ +
+
+ ); + } + + const p = project.data; + const s = summary.data; + const drift = !!p.indexed_with_model && !!currentModel && p.indexed_with_model !== currentModel; + + return ( +
+
+ +
+ +
+
+ + {p.status} + + {p.languages.slice(0, 6).map((l) => ( + + {l} + + ))} +
+

+ {p.host_path} +

+
+ Hash: {p.path_hash} + Created {formatRelative(p.created_at)} + + {p.last_indexed_at + ? `Indexed ${formatRelative(p.last_indexed_at)} (${formatDateTime(p.last_indexed_at)})` + : 'Never indexed'} + +
+
+ + {isAdmin ? ( + + ) : null} +
+
+ + {drift ? ( + + + Indexed with a stale embedding model + +
+ This project's vectors were produced under{' '} + {p.indexed_with_model}, + but the sidecar is currently running{' '} + {currentModel}. + Search results may be incorrect or empty until the project is reindexed. +
+
+ Reindex from your terminal:{' '} + cix reindex {p.host_path} +
+
+
+ ) : null} + +
+ + + + +
+ + + +
+
+

Top directories

+ {summary.isLoading ? ( + + ) : !s || s.top_directories.length === 0 ? ( +

No directories yet.

+ ) : ( + + + {s.top_directories.map((d) => ( +
+ {d.path} + + {d.file_count.toLocaleString()} {d.file_count === 1 ? 'file' : 'files'} + +
+ ))} +
+
+ )} +
+ +
+

Recent symbols

+ {summary.isLoading ? ( + + ) : !s || s.recent_symbols.length === 0 ? ( +

No symbols indexed yet.

+ ) : ( + + + {s.recent_symbols.slice(0, 12).map((sym, i) => ( +
+ + {sym.kind} + +
+
{sym.name}
+
+ {sym.file_path} +
+
+ {sym.language} +
+ ))} +
+
+ )} +
+
+ + + Reindexing + + Indexing reads files from the local filesystem and is driven by the CLI. Run{' '} + cix reindex for a one-shot + rescan, or keep cix watch{' '} + running for automatic updates on file change. + + +
+ ); +} + +function StatCard({ label, value, sub }: { label: string; value: number; sub?: string }) { + return ( + + +
{label}
+
{value.toLocaleString()}
+ {sub ?
{sub}
: null} +
+
+ ); +} + +function DetailSkeleton() { + return ( +
+ + +
+ {Array.from({ length: 4 }).map((_, i) => ( + + ))} +
+
+ + +
+
+ ); +} diff --git a/server/dashboard/src/modules/projects/ProjectsListPage.tsx b/server/dashboard/src/modules/projects/ProjectsListPage.tsx new file mode 100644 index 0000000..97bfc5d --- /dev/null +++ b/server/dashboard/src/modules/projects/ProjectsListPage.tsx @@ -0,0 +1,61 @@ +import { AlertCircle, FolderPlus } from 'lucide-react'; +import { Alert, AlertDescription, AlertTitle } from '@/ui/alert'; +import { Skeleton } from '@/ui/skeleton'; +import { ApiError } from '@/api/client'; +import { ProjectCard } from './components/ProjectCard'; +import { useProjects } from './hooks'; + +export function ProjectsListPage() { + const { data, error, isLoading } = useProjects(); + + return ( +
+
+

Projects

+

+ {data ? `${data.total} indexed ${data.total === 1 ? 'project' : 'projects'}` : ' '} +

+
+ + {isLoading ? ( +
+ {Array.from({ length: 6 }).map((_, i) => ( + + ))} +
+ ) : error ? ( + + + Failed to load projects + + {error instanceof ApiError ? error.detail : String(error)} + + + ) : !data || data.projects.length === 0 ? ( + + ) : ( +
+ {data.projects.map((p) => ( + + ))} +
+ )} +
+ ); +} + +function EmptyState() { + return ( +
+ +
+

No projects yet

+

+ Register a project from the CLI with{' '} + cix init <path>. + A GitHub source will land here in a future PR. +

+
+
+ ); +} diff --git a/server/dashboard/src/modules/projects/ProjectsPage.tsx b/server/dashboard/src/modules/projects/ProjectsPage.tsx new file mode 100644 index 0000000..77017f5 --- /dev/null +++ b/server/dashboard/src/modules/projects/ProjectsPage.tsx @@ -0,0 +1,12 @@ +import { Route, Routes } from 'react-router-dom'; +import { ProjectsListPage } from './ProjectsListPage'; +import { ProjectDetailPage } from './ProjectDetailPage'; + +export default function ProjectsPage() { + return ( + + } /> + } /> + + ); +} diff --git a/server/dashboard/src/modules/projects/components/DeleteProjectDialog.tsx b/server/dashboard/src/modules/projects/components/DeleteProjectDialog.tsx new file mode 100644 index 0000000..4ec7305 --- /dev/null +++ b/server/dashboard/src/modules/projects/components/DeleteProjectDialog.tsx @@ -0,0 +1,72 @@ +import { useState } from 'react'; +import { Loader2, Trash2 } from 'lucide-react'; +import { useNavigate } from 'react-router-dom'; +import { toast } from 'sonner'; +import { ApiError } from '@/api/client'; +import { Button } from '@/ui/button'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/ui/dialog'; +import { useDeleteProject } from '../hooks'; + +export function DeleteProjectDialog({ + hash, + hostPath, + redirectAfter = false, +}: { + hash: string; + hostPath: string; + redirectAfter?: boolean; +}) { + const [open, setOpen] = useState(false); + const del = useDeleteProject(); + const navigate = useNavigate(); + + async function onConfirm() { + try { + await del.mutateAsync(hash); + toast.success('Project deleted', { description: hostPath }); + setOpen(false); + if (redirectAfter) navigate('/projects'); + } catch (err) { + const detail = err instanceof ApiError ? err.detail : String(err); + toast.error('Failed to delete project', { description: detail }); + } + } + + return ( + + + + + + + Delete this project? + + This removes the project record and all indexed chunks, symbols, and references for{' '} + {hostPath}. The files on disk are + not touched. This cannot be undone. + + + + + + + + + ); +} diff --git a/server/dashboard/src/modules/projects/components/ProjectCard.tsx b/server/dashboard/src/modules/projects/components/ProjectCard.tsx new file mode 100644 index 0000000..4b17b5e --- /dev/null +++ b/server/dashboard/src/modules/projects/components/ProjectCard.tsx @@ -0,0 +1,90 @@ +import { Link } from 'react-router-dom'; +import { AlertTriangle, ChevronRight, Database, FileText } from 'lucide-react'; +import type { Project } from '@/api/types'; +import { Badge } from '@/ui/badge'; +import { Card, CardContent } from '@/ui/card'; +import { formatRelative } from '@/lib/formatDate'; +import { useRuntimeModel } from '@/lib/useServerStatus'; + +function basename(p: string): string { + const parts = p.replace(/\/+$/, '').split('/'); + return parts[parts.length - 1] || p; +} + +const STATUS_VARIANT: Record = { + created: 'outline', + indexing: 'secondary', + indexed: 'default', + error: 'destructive', +}; + +export function ProjectCard({ project }: { project: Project }) { + const currentModel = useRuntimeModel(); + // Drift = the project was indexed under a different model than the one + // the sidecar is running right now. NULL indexed_with_model is a legacy + // row from before drift tracking landed — not drift, just unknown. + const drift = + !!project.indexed_with_model && + !!currentModel && + project.indexed_with_model !== currentModel; + + return ( + + + +
+
+
+ {basename(project.host_path)} +
+
+ {project.host_path} +
+
+ +
+
+ + {project.status} + + {drift ? ( + + + Stale model + + ) : null} + {project.languages.slice(0, 4).map((l) => ( + + {l} + + ))} + {project.languages.length > 4 ? ( + +{project.languages.length - 4} + ) : null} +
+
+ + + {project.stats.indexed_files.toLocaleString()} files + + + + {project.stats.total_symbols.toLocaleString()} symbols + + + {project.last_indexed_at + ? `Indexed ${formatRelative(project.last_indexed_at)}` + : 'Never indexed'} + +
+
+
+ + ); +} diff --git a/server/dashboard/src/modules/projects/components/ProjectInfoCard.tsx b/server/dashboard/src/modules/projects/components/ProjectInfoCard.tsx new file mode 100644 index 0000000..84f4ec5 --- /dev/null +++ b/server/dashboard/src/modules/projects/components/ProjectInfoCard.tsx @@ -0,0 +1,90 @@ +import type { Project } from '@/api/types'; +import { Card, CardContent, CardHeader, CardTitle } from '@/ui/card'; +import { formatDateTime, formatRelative } from '@/lib/formatDate'; + +function formatBytes(bytes?: number | null): string { + if (!bytes || bytes <= 0) return '—'; + const units = ['B', 'KB', 'MB', 'GB', 'TB']; + let i = 0; + let v = bytes; + while (v >= 1024 && i < units.length - 1) { + v /= 1024; + i++; + } + return `${v.toFixed(v < 10 ? 1 : 0)} ${units[i]}`; +} + +// ProjectInfoCard surfaces metadata that would otherwise require SSH'ing +// onto the box: which embedding model produced the vectors, where the +// SQLite + chromem-go state for this project lives, on-disk sizes. +// Storage fields are nullable — embeddings-disabled servers don't have +// resolvable paths and we want to render gracefully rather than show "0 B". +export function ProjectInfoCard({ project }: { project: Project }) { + return ( + + + Storage & index info + + +
+ + {project.indexed_with_model ? ( + {project.indexed_with_model} + ) : ( + Unknown (indexed before drift tracking landed) + )} + + + {project.last_indexed_at ? ( + <> + {formatRelative(project.last_indexed_at)}{' '} + ({formatDateTime(project.last_indexed_at)}) + + ) : ( + Never + )} + + +
+ {project.sqlite_path ? ( + {project.sqlite_path} + ) : ( + Not available + )} + {project.sqlite_size_bytes ? ( +
{formatBytes(project.sqlite_size_bytes)}
+ ) : null} +
+
+ +
+ {project.chroma_path ? ( + {project.chroma_path} + ) : ( + Not available + )} + {project.chroma_size_bytes ? ( +
{formatBytes(project.chroma_size_bytes)}
+ ) : null} +
+
+ + {project.stats.total_chunks.toLocaleString()} + + + {project.stats.total_symbols.toLocaleString()} + +
+
+
+ ); +} + +function Row({ label, children }: { label: string; children: React.ReactNode }) { + return ( + <> +
{label}
+
{children}
+ + ); +} diff --git a/server/dashboard/src/modules/projects/hooks.ts b/server/dashboard/src/modules/projects/hooks.ts new file mode 100644 index 0000000..10a0ca1 --- /dev/null +++ b/server/dashboard/src/modules/projects/hooks.ts @@ -0,0 +1,50 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { api } from '@/api/client'; +import type { + Project, + ProjectListResponse, + ProjectSummary, +} from '@/api/types'; + +export const projectKeys = { + all: ['projects'] as const, + detail: (hash: string) => ['projects', hash] as const, + summary: (hash: string) => ['projects', hash, 'summary'] as const, +}; + +export function useProjects() { + return useQuery({ + queryKey: projectKeys.all, + queryFn: ({ signal }) => api.get('/projects', { signal }), + }); +} + +export function useProject(hash: string | undefined) { + return useQuery({ + queryKey: hash ? projectKeys.detail(hash) : ['projects', 'unknown'], + queryFn: ({ signal }) => api.get(`/projects/${hash}`, { signal }), + enabled: Boolean(hash), + }); +} + +export function useProjectSummary(hash: string | undefined) { + return useQuery({ + queryKey: hash ? projectKeys.summary(hash) : ['projects', 'unknown', 'summary'], + queryFn: ({ signal }) => api.get(`/projects/${hash}/summary`, { signal }), + enabled: Boolean(hash), + }); +} + +export function useDeleteProject() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: (hash: string) => api.delete(`/projects/${hash}`), + onSuccess: () => qc.invalidateQueries({ queryKey: projectKeys.all }), + }); +} + +// NOTE: a "Reindex" button is intentionally absent. The server's three-phase +// indexing protocol (begin → files → finish) requires a producer with filesystem +// access to upload file contents. That is the CLI's job (`cix reindex` / +// `cix watch`). The browser cannot drive this — it has no local filesystem. +// The detail page surfaces this expectation in copy. diff --git a/server/dashboard/src/modules/projects/index.ts b/server/dashboard/src/modules/projects/index.ts new file mode 100644 index 0000000..afd42a3 --- /dev/null +++ b/server/dashboard/src/modules/projects/index.ts @@ -0,0 +1,12 @@ +import { Folder } from 'lucide-react'; +import type { Module } from '../types'; +import ProjectsPage from './ProjectsPage'; + +export const ProjectsModule: Module = { + id: 'projects', + label: 'Projects', + icon: Folder, + path: '/projects', + element: ProjectsPage, + weight: 10, +}; diff --git a/server/dashboard/src/modules/registry.ts b/server/dashboard/src/modules/registry.ts new file mode 100644 index 0000000..0f6b6fe --- /dev/null +++ b/server/dashboard/src/modules/registry.ts @@ -0,0 +1,21 @@ +import { ApiKeysModule } from './api-keys'; +import { HomeModule } from './home'; +import { ProjectsModule } from './projects'; +import { SearchModule } from './search'; +import { ServerModule } from './server'; +import { SettingsModule } from './settings'; +import { UsersModule } from './users'; +import type { Module } from './types'; + +// Static registry of every dashboard feature. Order in the sidebar is +// determined by `weight` (default 100). PR-D adds API Keys, Users, Settings. +// PR-E adds Server (admin-only runtime config + sidecar lifecycle). +export const MODULES: Module[] = [ + HomeModule, + ProjectsModule, + SearchModule, + ApiKeysModule, + UsersModule, + SettingsModule, + ServerModule, +].sort((a, b) => (a.weight ?? 100) - (b.weight ?? 100)); diff --git a/server/dashboard/src/modules/search/SearchPage.tsx b/server/dashboard/src/modules/search/SearchPage.tsx new file mode 100644 index 0000000..5299448 --- /dev/null +++ b/server/dashboard/src/modules/search/SearchPage.tsx @@ -0,0 +1,450 @@ +import { useEffect, useMemo, useState } from 'react'; +import { useSearchParams } from 'react-router-dom'; +import { AlertCircle, FileQuestion, Search as SearchIcon } from 'lucide-react'; +import { ApiError } from '@/api/client'; +import { Alert, AlertDescription, AlertTitle } from '@/ui/alert'; +import { Badge } from '@/ui/badge'; +import { Card, CardContent } from '@/ui/card'; +import { Skeleton } from '@/ui/skeleton'; +import { Tabs, TabsList, TabsTrigger } from '@/ui/tabs'; +import { SearchInput } from './components/SearchInput'; +import { + LimitInput, + LanguagesInput, + MinScoreSlider, + ModeSpecificHelp, + ProjectPicker, +} from './components/Filters'; +import { ResultFileCard } from './components/ResultFileCard'; +import { OpenInEditorButton } from './components/ResultSnippet'; +import { + SEARCH_MODES, + type SearchMode, + useDefinitions, + useFileSearch, + useReferences, + useSemanticSearch, + useSymbolSearch, +} from './hooks'; + +const MODE_IDS = SEARCH_MODES.map((m) => m.id) as readonly SearchMode[]; + +function isMode(value: string | null): value is SearchMode { + return value !== null && (MODE_IDS as readonly string[]).includes(value); +} + +export default function SearchPage() { + const [params, setParams] = useSearchParams(); + const mode = isMode(params.get('mode')) ? (params.get('mode') as SearchMode) : 'semantic'; + const projectHash = params.get('project') ?? undefined; + const queryParam = params.get('q') ?? ''; + const [draft, setDraft] = useState(queryParam); + + // Debounce: input → URL after 250ms idle. Enter on the form bypasses this + // via `commitQuery(draft)`. + useEffect(() => { + const id = setTimeout(() => { + if (draft === queryParam) return; + const next = new URLSearchParams(params); + if (draft.trim()) next.set('q', draft); + else next.delete('q'); + setParams(next, { replace: true }); + }, 250); + return () => clearTimeout(id); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [draft]); + + // Sync local draft if URL changes externally (e.g. user pastes a link). + useEffect(() => { + setDraft(queryParam); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [queryParam]); + + function commitQuery(value: string) { + const v = value.trim(); + const next = new URLSearchParams(params); + if (v) next.set('q', v); + else next.delete('q'); + setParams(next, { replace: true }); + } + + function setMode(next: SearchMode) { + const p = new URLSearchParams(params); + p.set('mode', next); + setParams(p, { replace: true }); + } + function setProject(hash: string) { + const p = new URLSearchParams(params); + p.set('project', hash); + setParams(p, { replace: true }); + } + + return ( +
+
+

Search

+

+ Semantic, symbol, and path search across your indexed projects. +

+
+ + + + + setMode(v as SearchMode)}> + + {SEARCH_MODES.map((m) => ( + + {m.label} + + ))} + + + + + + +
+ + +
+
+ ); +} + +function placeholderFor(mode: SearchMode): string { + switch (mode) { + case 'semantic': + return 'e.g. "JWT validation middleware" or "retry with backoff"'; + case 'symbols': + return 'Symbol name (substring)'; + case 'definitions': + case 'references': + return 'Exact symbol name'; + case 'files': + return 'File path substring'; + } +} + +function ModeFilters({ + mode, + params, + setParams, +}: { + mode: SearchMode; + params: URLSearchParams; + setParams: ReturnType[1]; +}) { + const limit = Number(params.get('limit') ?? defaultLimit(mode)); + const minScore = Number(params.get('min_score') ?? '0.4'); + const langs = params.get('langs') ?? ''; + + function update(key: string, value: string | undefined) { + const p = new URLSearchParams(params); + if (value === undefined || value === '') p.delete(key); + else p.set(key, value); + setParams(p, { replace: true }); + } + + return ( +
+

Filters

+ update('limit', String(v))} /> + {mode === 'semantic' ? ( + <> + update('min_score', v.toFixed(2))} /> + update('langs', v)} /> + + ) : null} +
+ ); +} + +function defaultLimit(mode: SearchMode): number { + switch (mode) { + case 'references': + return 50; + case 'symbols': + case 'files': + return 20; + default: + return 10; + } +} + +function ResultsArea({ + mode, + projectHash, + query, + params, +}: { + mode: SearchMode; + projectHash: string | undefined; + query: string; + params: URLSearchParams; +}) { + if (!projectHash) { + return ; + } + if (query.trim().length < 2) { + return ; + } + switch (mode) { + case 'semantic': + return ; + case 'symbols': + return ; + case 'definitions': + return ; + case 'references': + return ; + case 'files': + return ; + } +} + +type ResultProps = { + projectHash: string; + query: string; + params: URLSearchParams; +}; + +function SemanticResults({ projectHash, query, params }: ResultProps) { + const limit = Number(params.get('limit') ?? '10'); + const minScore = Number(params.get('min_score') ?? '0.4'); + const langs = (params.get('langs') ?? '').split(',').map((s) => s.trim()).filter(Boolean); + const body = useMemo( + () => ({ + query, + limit, + min_score: minScore, + languages: langs.length > 0 ? langs : undefined, + }), + [query, limit, minScore, langs.join(',')], + ); + const q = useSemanticSearch(projectHash, body); + + if (q.isLoading) return ; + if (q.error) return ; + if (!q.data || q.data.results.length === 0) return ; + + return ( +
+ + {q.data.results.map((g) => ( + + ))} +
+ ); +} + +function SymbolResults({ projectHash, query, params }: ResultProps) { + const limit = Number(params.get('limit') ?? '20'); + const body = useMemo(() => ({ query, limit }), [query, limit]); + const q = useSymbolSearch(projectHash, body); + + if (q.isLoading) return ; + if (q.error) return ; + if (!q.data || q.data.results.length === 0) return ; + + return ( +
+ + + + {q.data.results.map((s, i) => ( +
+ + {s.kind} + +
+
{s.name}
+
+ {s.file_path}:{s.line} +
+
+ {s.language} + +
+ ))} +
+
+
+ ); +} + +function DefinitionResults({ projectHash, query, params }: ResultProps) { + const limit = Number(params.get('limit') ?? '10'); + const body = useMemo(() => ({ symbol: query, limit }), [query, limit]); + const q = useDefinitions(projectHash, body); + + if (q.isLoading) return ; + if (q.error) return ; + if (!q.data || q.data.results.length === 0) return ; + + return ( +
+ + + + {q.data.results.map((d, i) => ( +
+ + {d.kind} + +
+
{d.name}
+
+ {d.file_path}:{d.line} +
+ {d.signature ? ( +
+ {d.signature} +
+ ) : null} +
+ {d.language} + +
+ ))} +
+
+
+ ); +} + +function ReferenceResults({ projectHash, query, params }: ResultProps) { + const limit = Number(params.get('limit') ?? '50'); + const body = useMemo(() => ({ symbol: query, limit }), [query, limit]); + const q = useReferences(projectHash, body); + + if (q.isLoading) return ; + if (q.error) return ; + if (!q.data || q.data.results.length === 0) return ; + + return ( +
+ + + + {q.data.results.map((r, i) => ( +
+ + L{r.start_line} + + + {r.file_path} + + {r.language} + +
+ ))} +
+
+
+ ); +} + +function FileResults({ projectHash, query, params }: ResultProps) { + const limit = Number(params.get('limit') ?? '20'); + const body = useMemo(() => ({ query, limit }), [query, limit]); + const q = useFileSearch(projectHash, body); + + if (q.isLoading) return ; + if (q.error) return ; + if (!q.data || q.data.results.length === 0) return ; + + return ( +
+ + + + {q.data.results.map((f, i) => ( +
+ + {f.file_path} + + {f.language ? ( + {f.language} + ) : null} + +
+ ))} +
+
+
+ ); +} + +function ResultsMeta({ total, timeMs }: { total: number; timeMs?: number }) { + return ( +
+ {total} {total === 1 ? 'result' : 'results'} + {typeof timeMs === 'number' ? ` · ${timeMs.toFixed(1)} ms` : ''} +
+ ); +} + +function ResultsSkeleton() { + return ( +
+ {Array.from({ length: 4 }).map((_, i) => ( + + ))} +
+ ); +} + +function ResultsError({ error }: { error: unknown }) { + return ( + + + Search failed + + {error instanceof ApiError ? error.detail : String(error)} + + + ); +} + +function NoResultsCard() { + return ( + + + +

No matches

+

+ Try a different query, lower the minimum score, or pick a different mode. +

+
+
+ ); +} + +function PromptCard({ + icon: Icon, + title, +}: { + icon: typeof SearchIcon; + title: string; +}) { + return ( + + + +

{title}

+
+
+ ); +} diff --git a/server/dashboard/src/modules/search/components/Filters.tsx b/server/dashboard/src/modules/search/components/Filters.tsx new file mode 100644 index 0000000..45182fe --- /dev/null +++ b/server/dashboard/src/modules/search/components/Filters.tsx @@ -0,0 +1,142 @@ +import { Loader2 } from 'lucide-react'; +import { useProjects } from '@/modules/projects/hooks'; +import { Label } from '@/ui/label'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/ui/select'; +import { Slider } from '@/ui/slider'; +import { Input } from '@/ui/input'; +import type { SearchMode } from '../hooks'; + +export function ProjectPicker({ + value, + onChange, +}: { + value: string | undefined; + onChange: (hash: string) => void; +}) { + const { data, isLoading } = useProjects(); + const projects = data?.projects ?? []; + + return ( +
+ + +
+ ); +} + +export function MinScoreSlider({ + value, + onChange, +}: { + value: number; + onChange: (v: number) => void; +}) { + return ( +
+
+ + {value.toFixed(2)} +
+ onChange(v)} + /> +
+ ); +} + +export function LimitInput({ + value, + onChange, + max = 100, +}: { + value: number; + onChange: (v: number) => void; + max?: number; +}) { + return ( +
+ + { + const n = Number(e.target.value); + if (Number.isFinite(n)) onChange(Math.max(1, Math.min(max, Math.round(n)))); + }} + className="h-9" + /> +
+ ); +} + +export function LanguagesInput({ + value, + onChange, +}: { + value: string; + onChange: (v: string) => void; +}) { + return ( +
+ + onChange(e.target.value)} + className="h-9" + /> +

Comma-separated. Leave empty for all.

+
+ ); +} + +export function ModeSpecificHelp({ mode }: { mode: SearchMode }) { + const messages: Record = { + semantic: 'Ask in natural language ("JWT validation", "retry with exponential backoff").', + symbols: 'Substring match against symbol names.', + definitions: 'Exact symbol name. Optional kind/file filters.', + references: 'Exact symbol name. Returns every callsite.', + files: 'Substring match against file paths.', + }; + return

{messages[mode]}

; +} diff --git a/server/dashboard/src/modules/search/components/ResultFileCard.tsx b/server/dashboard/src/modules/search/components/ResultFileCard.tsx new file mode 100644 index 0000000..ee84095 --- /dev/null +++ b/server/dashboard/src/modules/search/components/ResultFileCard.tsx @@ -0,0 +1,65 @@ +import { useState } from 'react'; +import { ChevronDown, ChevronRight } from 'lucide-react'; +import type { FileGroupResult } from '@/api/types'; +import { Badge } from '@/ui/badge'; +import { Card, CardContent } from '@/ui/card'; +import { cn } from '@/lib/cn'; +import { OpenInEditorButton, ResultSnippet } from './ResultSnippet'; + +export function ResultFileCard({ + group, + defaultOpen = true, +}: { + group: FileGroupResult; + defaultOpen?: boolean; +}) { + const [open, setOpen] = useState(defaultOpen); + const matchCount = group.matches.length; + + return ( + + + + {open ? ( +
+ {group.matches.map((m, i) => ( + + ))} +
+ ) : null} +
+
+ ); +} diff --git a/server/dashboard/src/modules/search/components/ResultSnippet.tsx b/server/dashboard/src/modules/search/components/ResultSnippet.tsx new file mode 100644 index 0000000..33f38fb --- /dev/null +++ b/server/dashboard/src/modules/search/components/ResultSnippet.tsx @@ -0,0 +1,96 @@ +import { ExternalLink } from 'lucide-react'; +import type { FileMatch, NestedHit } from '@/api/types'; +import { Badge } from '@/ui/badge'; +import { Button } from '@/ui/button'; +import { cn } from '@/lib/cn'; +import { openInEditor } from '@/lib/editorPreference'; + +export function ResultSnippet({ + filePath, + match, +}: { + filePath: string; + match: FileMatch; +}) { + const lines = match.content.split('\n'); + return ( +
+
+ + L{match.start_line} + {match.end_line !== match.start_line ? `–${match.end_line}` : ''} + + {match.symbol_name ? ( + {match.symbol_name} + ) : null} + + {match.chunk_type} + + + {match.score.toFixed(2)} + + +
+
+        
+          {lines.map((line, i) => (
+            
+ + {match.start_line + i} + + {line || ' '} +
+ ))} +
+
+ {match.nested_hits && match.nested_hits.length > 0 ? ( + + ) : null} +
+ ); +} + +function NestedHitsList({ hits }: { hits: NestedHit[] }) { + return ( +
+
Also matches:
+
    + {hits.map((h, i) => ( +
  • + + L{h.start_line} + {h.end_line !== h.start_line ? `–${h.end_line}` : ''} + + {h.symbol_name ? {h.symbol_name} : null} + ({h.chunk_type}) + {h.score.toFixed(2)} +
  • + ))} +
+
+ ); +} + +export function OpenInEditorButton({ + path, + line, + className, +}: { + path: string; + line?: number; + className?: string; +}) { + return ( + + ); +} diff --git a/server/dashboard/src/modules/search/components/SearchInput.tsx b/server/dashboard/src/modules/search/components/SearchInput.tsx new file mode 100644 index 0000000..1d94774 --- /dev/null +++ b/server/dashboard/src/modules/search/components/SearchInput.tsx @@ -0,0 +1,60 @@ +import { useEffect, useRef } from 'react'; +import { Search as SearchIcon } from 'lucide-react'; +import { Input } from '@/ui/input'; +import { cn } from '@/lib/cn'; + +export function SearchInput({ + value, + onChange, + onSubmit, + placeholder = 'Search…', + className, +}: { + value: string; + onChange: (v: string) => void; + /** Fired on Enter — bypasses debounce and commits immediately. */ + onSubmit?: (v: string) => void; + placeholder?: string; + className?: string; +}) { + const ref = useRef(null); + + // ⌘K / Ctrl+K focuses the search input from anywhere on the page. + useEffect(() => { + function onKey(e: KeyboardEvent) { + const isMac = navigator.platform.toLowerCase().includes('mac'); + const cmd = isMac ? e.metaKey : e.ctrlKey; + if (cmd && e.key === 'k') { + e.preventDefault(); + ref.current?.focus(); + ref.current?.select(); + } + } + window.addEventListener('keydown', onKey); + return () => window.removeEventListener('keydown', onKey); + }, []); + + return ( +
{ + e.preventDefault(); + onSubmit?.(value); + }} + > + + onChange(e.target.value)} + className="h-10 pl-9 pr-12" + /> + + ⌘K + + + ); +} diff --git a/server/dashboard/src/modules/search/hooks.ts b/server/dashboard/src/modules/search/hooks.ts new file mode 100644 index 0000000..351c0aa --- /dev/null +++ b/server/dashboard/src/modules/search/hooks.ts @@ -0,0 +1,90 @@ +import { useQuery } from '@tanstack/react-query'; +import { api } from '@/api/client'; +import type { + DefinitionRequest, + DefinitionResponse, + FileSearchRequest, + FileSearchResponse, + ReferenceRequest, + ReferenceResponse, + SemanticSearchRequest, + SemanticSearchResponse, + SymbolSearchRequest, + SymbolSearchResponse, +} from '@/api/types'; + +export type SearchMode = 'semantic' | 'symbols' | 'definitions' | 'references' | 'files'; + +export const SEARCH_MODES: { id: SearchMode; label: string; description: string }[] = [ + { id: 'semantic', label: 'Semantic', description: 'Vector search: ask in natural language.' }, + { id: 'symbols', label: 'Symbols', description: 'Find symbols by name (substring match).' }, + { id: 'definitions', label: 'Definitions', description: 'Where is this symbol defined?' }, + { id: 'references', label: 'References', description: 'Where is this symbol used?' }, + { id: 'files', label: 'Files', description: 'Find files by path substring.' }, +]; + +const baseKey = ['search'] as const; + +function searchEnabled(query: string) { + return query.trim().length >= 2; +} + +export function useSemanticSearch( + projectHash: string | undefined, + body: SemanticSearchRequest +) { + return useQuery({ + queryKey: [...baseKey, 'semantic', projectHash, body], + queryFn: ({ signal }) => + api.post(`/projects/${projectHash}/search`, body, { signal }), + enabled: Boolean(projectHash) && searchEnabled(body.query), + }); +} + +export function useSymbolSearch( + projectHash: string | undefined, + body: SymbolSearchRequest +) { + return useQuery({ + queryKey: [...baseKey, 'symbols', projectHash, body], + queryFn: ({ signal }) => + api.post(`/projects/${projectHash}/search/symbols`, body, { signal }), + enabled: Boolean(projectHash) && searchEnabled(body.query), + }); +} + +export function useDefinitions( + projectHash: string | undefined, + body: DefinitionRequest +) { + return useQuery({ + queryKey: [...baseKey, 'definitions', projectHash, body], + queryFn: ({ signal }) => + api.post(`/projects/${projectHash}/search/definitions`, body, { signal }), + enabled: Boolean(projectHash) && searchEnabled(body.symbol), + }); +} + +export function useReferences( + projectHash: string | undefined, + body: ReferenceRequest +) { + return useQuery({ + queryKey: [...baseKey, 'references', projectHash, body], + queryFn: ({ signal }) => + api.post(`/projects/${projectHash}/search/references`, body, { signal }), + enabled: Boolean(projectHash) && searchEnabled(body.symbol), + }); +} + +export function useFileSearch( + projectHash: string | undefined, + body: FileSearchRequest +) { + return useQuery({ + queryKey: [...baseKey, 'files', projectHash, body], + queryFn: ({ signal }) => + api.post(`/projects/${projectHash}/search/files`, body, { signal }), + enabled: Boolean(projectHash) && searchEnabled(body.query), + }); +} diff --git a/server/dashboard/src/modules/search/index.ts b/server/dashboard/src/modules/search/index.ts new file mode 100644 index 0000000..c305c29 --- /dev/null +++ b/server/dashboard/src/modules/search/index.ts @@ -0,0 +1,12 @@ +import { Search } from 'lucide-react'; +import type { Module } from '../types'; +import SearchPage from './SearchPage'; + +export const SearchModule: Module = { + id: 'search', + label: 'Search', + icon: Search, + path: '/search', + element: SearchPage, + weight: 20, +}; diff --git a/server/dashboard/src/modules/server/ServerPage.tsx b/server/dashboard/src/modules/server/ServerPage.tsx new file mode 100644 index 0000000..8481ae7 --- /dev/null +++ b/server/dashboard/src/modules/server/ServerPage.tsx @@ -0,0 +1,196 @@ +import { useEffect, useMemo, useState } from 'react'; +import { AlertCircle, Loader2, Save } from 'lucide-react'; +import { toast } from 'sonner'; +import { ApiError } from '@/api/client'; +import type { RuntimeConfig, RuntimeConfigUpdate } from '@/api/types'; +import { Alert, AlertDescription, AlertTitle } from '@/ui/alert'; +import { Button } from '@/ui/button'; +import { Skeleton } from '@/ui/skeleton'; +import { useRestartSidecar, useRuntimeConfig, useSidecarStatus, useUpdateRuntimeConfig } from './hooks'; +import { EmbeddingModelSection } from './sections/EmbeddingModelSection'; +import { RuntimeParamsSection } from './sections/RuntimeParamsSection'; +import { SidecarSection } from './sections/SidecarSection'; +import { AdvancedSection } from './sections/AdvancedSection'; +import { SaveAndRestartDialog } from './components/SaveAndRestartDialog'; + +interface Draft { + embedding_model: string; + llama_ctx_size: number; + llama_n_gpu_layers: number; + llama_n_threads: number; + max_embedding_concurrency: number; + llama_batch_size: number; +} + +function configToDraft(c: RuntimeConfig): Draft { + return { + embedding_model: c.embedding_model, + llama_ctx_size: c.llama_ctx_size, + llama_n_gpu_layers: c.llama_n_gpu_layers, + llama_n_threads: c.llama_n_threads, + max_embedding_concurrency: c.max_embedding_concurrency, + llama_batch_size: c.llama_batch_size, + }; +} + +// diffPatch produces (a) the partial PUT body containing only changed +// fields and (b) the human-readable changes list the confirm dialog renders. +function diffPatch(c: RuntimeConfig, d: Draft): { patch: RuntimeConfigUpdate; changes: Array<{ field: string; from: string; to: string }> } { + const patch: RuntimeConfigUpdate = {}; + const changes: Array<{ field: string; from: string; to: string }> = []; + if (d.embedding_model !== c.embedding_model) { + patch.embedding_model = d.embedding_model; + changes.push({ field: 'embedding_model', from: c.embedding_model, to: d.embedding_model }); + } + for (const k of [ + 'llama_ctx_size', + 'llama_n_gpu_layers', + 'llama_n_threads', + 'max_embedding_concurrency', + 'llama_batch_size', + ] as const) { + if (d[k] !== c[k]) { + patch[k] = d[k]; + changes.push({ field: k, from: String(c[k]), to: String(d[k]) }); + } + } + return { patch, changes }; +} + +export default function ServerPage() { + const cfg = useRuntimeConfig(); + const status = useSidecarStatus(); + const update = useUpdateRuntimeConfig(); + const restart = useRestartSidecar(); + + const [draft, setDraft] = useState(null); + const [confirmOpen, setConfirmOpen] = useState(false); + + // Initialise / reset draft whenever the server-side config changes from + // under us (initial fetch, optimistic refresh after save). + useEffect(() => { + if (cfg.data) setDraft(configToDraft(cfg.data)); + }, [cfg.data]); + + const dirty = useMemo(() => { + if (!cfg.data || !draft) return false; + return diffPatch(cfg.data, draft).changes.length > 0; + }, [cfg.data, draft]); + + if (cfg.isLoading || !draft) { + return ( +
+
+

Server

+

Embedding model, indexing parameters, sidecar lifecycle.

+
+ + +
+ ); + } + + if (cfg.error || !cfg.data) { + return ( + + + Could not load runtime config + {cfg.error instanceof ApiError ? cfg.error.detail : String(cfg.error)} + + ); + } + + const disabled = status.data?.state === 'disabled'; + const { changes } = diffPatch(cfg.data, draft); + + async function onConfirm() { + if (!cfg.data || !draft) return; + const { patch } = diffPatch(cfg.data, draft); + try { + // Step 1 — write overrides to DB. The mutation also refreshes the + // cache so the form's "DB" pills appear before the restart fires. + if (Object.keys(patch).length > 0) { + await update.mutateAsync(patch); + } + // Step 2 — kick a sidecar restart so the new model / flags load. + await restart.mutateAsync(); + setConfirmOpen(false); + toast.success('Configuration saved', { + description: 'Sidecar is restarting — watch the Sidecar card for status.', + }); + } catch (e) { + const detail = e instanceof ApiError ? e.detail : String(e); + toast.error('Save & Restart failed', { description: detail }); + } + } + + const isPending = update.isPending || restart.isPending; + + return ( +
+
+
+

Server

+

+ Embedding model, indexing parameters, sidecar lifecycle. Saved + overrides land in the database and are reapplied on the next + sidecar restart — env vars stay as bootstrap defaults. +

+
+ +
+ + {disabled ? ( + + + Embeddings disabled at boot + + The server was started with CIX_EMBEDDINGS_ENABLED=false. + Restart the server with the env var set to true to + enable runtime config + the sidecar. + + + ) : null} + + setDraft({ ...draft, embedding_model: v })} + /> + + setDraft({ ...draft, llama_ctx_size: n })} + onDraftGpuLayers={(n) => setDraft({ ...draft, llama_n_gpu_layers: n })} + onDraftThreads={(n) => setDraft({ ...draft, llama_n_threads: n })} + /> + + + + setDraft({ ...draft, max_embedding_concurrency: n })} + onDraftBatch={(n) => setDraft({ ...draft, llama_batch_size: n })} + /> + + (!isPending ? setConfirmOpen(next) : null)} + onConfirm={onConfirm} + isPending={isPending} + changes={changes} + /> +
+ ); +} diff --git a/server/dashboard/src/modules/server/components/SaveAndRestartDialog.tsx b/server/dashboard/src/modules/server/components/SaveAndRestartDialog.tsx new file mode 100644 index 0000000..59ddc65 --- /dev/null +++ b/server/dashboard/src/modules/server/components/SaveAndRestartDialog.tsx @@ -0,0 +1,72 @@ +import { Loader2 } from 'lucide-react'; +import { Button } from '@/ui/button'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/ui/dialog'; + +interface Props { + open: boolean; + onOpenChange: (next: boolean) => void; + onConfirm: () => void; + isPending: boolean; + // Per-field summary the dialog renders so the admin sees exactly what + // they're about to apply before the sidecar gets killed. + changes: Array<{ field: string; from: string; to: string }>; +} + +// SaveAndRestartDialog confirms a runtime-config save that will trigger an +// embedding-sidecar restart. Active indexing batches will fail mid-call — +// not a quiet operation — so we always require explicit confirm before the +// PUT + restart cascade fires. +export function SaveAndRestartDialog({ + open, + onOpenChange, + onConfirm, + isPending, + changes, +}: Props) { + return ( + + + + Apply config and restart sidecar? + + Saving will write the overrides to the database, then drain the + embedding queue (up to 30s) and restart llama-server. Indexing + batches in flight at restart time will fail — re-run{' '} + cix reindex{' '} + for affected projects. + + + {changes.length > 0 ? ( +
    + {changes.map((c) => ( +
  • + {c.field}: + {c.from} + + {c.to} +
  • + ))} +
+ ) : ( +

No field changes — restart only.

+ )} + + + + +
+
+ ); +} diff --git a/server/dashboard/src/modules/server/components/SidecarStateBadge.tsx b/server/dashboard/src/modules/server/components/SidecarStateBadge.tsx new file mode 100644 index 0000000..f2c33fc --- /dev/null +++ b/server/dashboard/src/modules/server/components/SidecarStateBadge.tsx @@ -0,0 +1,18 @@ +import { Badge } from '@/ui/badge'; + +const VARIANT: Record = { + running: 'default', + starting: 'secondary', + restarting: 'secondary', + failed: 'destructive', + disabled: 'outline', +}; + +export function SidecarStateBadge({ state }: { state?: string }) { + if (!state) return null; + return ( + + {state} + + ); +} diff --git a/server/dashboard/src/modules/server/components/SourcePill.tsx b/server/dashboard/src/modules/server/components/SourcePill.tsx new file mode 100644 index 0000000..3df85ed --- /dev/null +++ b/server/dashboard/src/modules/server/components/SourcePill.tsx @@ -0,0 +1,27 @@ +import { Badge } from '@/ui/badge'; + +const VARIANT: Record = { + db: 'default', + env: 'secondary', + recommended: 'outline', +}; + +const LABEL: Record = { + db: 'DB', + env: 'Env', + recommended: 'Default', +}; + +// SourcePill renders a tiny "DB" / "Env" / "Default" tag next to a runtime +// config field, telling the admin where the currently-effective value came +// from. "DB" means the dashboard saved an override; "Env" means the operator +// set CIX_* at boot; "Default" means we're falling through to the hardcoded +// recommended value. +export function SourcePill({ source }: { source?: string }) { + if (!source) return null; + return ( + + {LABEL[source] ?? source} + + ); +} diff --git a/server/dashboard/src/modules/server/hooks.ts b/server/dashboard/src/modules/server/hooks.ts new file mode 100644 index 0000000..fb1e715 --- /dev/null +++ b/server/dashboard/src/modules/server/hooks.ts @@ -0,0 +1,79 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { api } from '@/api/client'; +import type { + ModelList, + RestartAccepted, + RuntimeConfig, + RuntimeConfigUpdate, + SidecarStatus, +} from '@/api/types'; + +export const serverKeys = { + runtimeConfig: ['server', 'runtime-config'] as const, + sidecarStatus: ['server', 'sidecar-status'] as const, + models: ['server', 'models'] as const, +}; + +export function useRuntimeConfig() { + return useQuery({ + queryKey: serverKeys.runtimeConfig, + queryFn: ({ signal }) => api.get('/admin/runtime-config', { signal }), + }); +} + +export function useUpdateRuntimeConfig() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: (patch: RuntimeConfigUpdate) => + api.put('/admin/runtime-config', patch), + onSuccess: (data) => { + // Replace the cached value so the form switches to "DB"-sourced + // pills before the dashboard issues the restart call. + qc.setQueryData(serverKeys.runtimeConfig, data); + }, + }); +} + +export function useSidecarStatus() { + return useQuery({ + queryKey: serverKeys.sidecarStatus, + queryFn: ({ signal }) => api.get('/admin/sidecar/status', { signal }), + // Poll every second whenever a restart is in flight; otherwise back off + // to 5s — the status almost never changes outside of admin actions and + // we don't want to thrash on idle dashboards. + refetchInterval: (q) => { + const data = q.state.data as SidecarStatus | undefined; + if (!data) return 2_000; + if (data.restart_in_flight || data.state === 'starting' || data.state === 'restarting') { + return 1_000; + } + return 5_000; + }, + refetchIntervalInBackground: false, + }); +} + +export function useRestartSidecar() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: () => api.post('/admin/sidecar/restart'), + onSettled: () => { + // Force a status refetch immediately so the UI flips to "restarting" + // without waiting for the next poll tick. Also invalidate the cached + // runtime model — drift indicators on Projects depend on it being + // current after a model swap. + qc.invalidateQueries({ queryKey: serverKeys.sidecarStatus }); + qc.invalidateQueries({ queryKey: ['runtime-model'] }); + }, + }); +} + +export function useGGUFModels() { + return useQuery({ + queryKey: serverKeys.models, + queryFn: ({ signal }) => api.get('/admin/models', { signal }), + // Cache aggressively: GGUFs only change when the operator runs + // `cix init` or manually drops a file in the cache. + staleTime: 60_000, + }); +} diff --git a/server/dashboard/src/modules/server/index.ts b/server/dashboard/src/modules/server/index.ts new file mode 100644 index 0000000..1c9da80 --- /dev/null +++ b/server/dashboard/src/modules/server/index.ts @@ -0,0 +1,13 @@ +import { ServerCog } from 'lucide-react'; +import type { Module } from '../types'; +import ServerPage from './ServerPage'; + +export const ServerModule: Module = { + id: 'server', + label: 'Server', + icon: ServerCog, + path: '/server', + element: ServerPage, + requiredRole: 'admin', + weight: 60, +}; diff --git a/server/dashboard/src/modules/server/sections/AdvancedSection.tsx b/server/dashboard/src/modules/server/sections/AdvancedSection.tsx new file mode 100644 index 0000000..1854ea7 --- /dev/null +++ b/server/dashboard/src/modules/server/sections/AdvancedSection.tsx @@ -0,0 +1,97 @@ +import { useId } from 'react'; +import type { RuntimeConfig } from '@/api/types'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/ui/card'; +import { Input } from '@/ui/input'; +import { Label } from '@/ui/label'; +import { SourcePill } from '../components/SourcePill'; + +interface Props { + config?: RuntimeConfig; + draftConcurrency: number; + draftBatch: number; + onDraftConcurrency: (n: number) => void; + onDraftBatch: (n: number) => void; +} + +// AdvancedSection: throughput-tuning fields most operators won't touch. +// Wrapped in a native
so the form stays light by default — Radix +// Collapsible isn't installed and the plain HTML element is good enough. +export function AdvancedSection({ + config, + draftConcurrency, + draftBatch, + onDraftConcurrency, + onDraftBatch, +}: Props) { + const concId = useId(); + const batchId = useId(); + const rec = config?.recommended; + const src = config?.source; + + return ( + + + Advanced + Throughput tuning. Leave at recommended unless you have a specific reason. + + +
+ + Show advanced tunables + +
+
+
+ + +
+ { + const n = parseInt(e.target.value, 10); + onDraftConcurrency(Number.isFinite(n) ? n : 0); + }} + className="max-w-xs" + /> +

+ Concurrent /v1/embeddings calls allowed against the sidecar. 1 = strictly sequential. + Recommended: {rec?.max_embedding_concurrency ?? 1}. +

+
+ +
+
+ + +
+ { + const n = parseInt(e.target.value, 10); + onDraftBatch(Number.isFinite(n) ? n : 0); + }} + className="max-w-xs" + /> +

+ Logical batch passed to llama-server (-b). 0 = match context window. + Recommended: {rec?.llama_batch_size ?? 'ctx'}. +

+
+
+
+
+
+ ); +} diff --git a/server/dashboard/src/modules/server/sections/EmbeddingModelSection.tsx b/server/dashboard/src/modules/server/sections/EmbeddingModelSection.tsx new file mode 100644 index 0000000..8d6cb18 --- /dev/null +++ b/server/dashboard/src/modules/server/sections/EmbeddingModelSection.tsx @@ -0,0 +1,210 @@ +import { useEffect, useId, useState } from 'react'; +import { AlertTriangle } from 'lucide-react'; +import type { ModelEntry, RuntimeConfig } from '@/api/types'; +import { Alert, AlertDescription, AlertTitle } from '@/ui/alert'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/ui/card'; +import { Input } from '@/ui/input'; +import { Label } from '@/ui/label'; +import { RadioGroup, RadioGroupItem } from '@/ui/radio-group'; +import { Skeleton } from '@/ui/skeleton'; +import { useGGUFModels } from '../hooks'; +import { SourcePill } from '../components/SourcePill'; + +interface Props { + config?: RuntimeConfig; + draftModel: string; + onDraftChange: (next: string) => void; +} + +type Mode = 'repo' | 'path'; + +function isAbsPath(v: string): boolean { + // POSIX-only check is enough — the server is Linux/macOS for the + // foreseeable future. Windows path support would need an additional + // drive-letter test (`/^[a-zA-Z]:[\\/]/.test(v)`). + return v.startsWith('/'); +} + +function formatSize(bytes: number): string { + if (!Number.isFinite(bytes) || bytes <= 0) return '—'; + const units = ['B', 'KB', 'MB', 'GB']; + let i = 0; + let v = bytes; + while (v >= 1024 && i < units.length - 1) { + v /= 1024; + i++; + } + return `${v.toFixed(v < 10 ? 1 : 0)} ${units[i]}`; +} + +// EmbeddingModelSection lets the admin pick exactly one model source: +// 1. "HuggingFace repo" — selects from cix's own GGUF cache (or types +// a repo ID for first-use download). Active = repo mode. +// 2. "Local file path" — points at an absolute .gguf path on the host. +// Active = path mode. +// +// The two inputs are mutually exclusive. Switching modes resets the draft +// to a sensible default for the new mode (recommended repo / empty path) +// so the parent component never holds a half-typed cross-mode value. +export function EmbeddingModelSection({ config, draftModel, onDraftChange }: Props) { + const repoSelectId = useId(); + const repoInputId = useId(); + const pathInputId = useId(); + + const [mode, setMode] = useState(() => (isAbsPath(draftModel) ? 'path' : 'repo')); + + // Sync mode if the draft is changed from the outside (initial fetch, + // optimistic refresh after save). Without this the radio would lie + // after the parent updates draftModel from a server-reload. + useEffect(() => { + setMode(isAbsPath(draftModel) ? 'path' : 'repo'); + }, [draftModel]); + + const models = useGGUFModels(); + const cached: ModelEntry[] = models.data?.models ?? []; + const cacheDir = models.data?.cache_dir ?? ''; + const matched = cached.find((m) => m.id === draftModel); + + function switchTo(next: Mode) { + if (next === mode) return; + setMode(next); + if (next === 'repo') { + // Switching out of path → restore a sensible repo default so the + // form doesn't show an absolute path under the disabled path input. + onDraftChange(config?.recommended?.embedding_model ?? ''); + } else { + // Switching into path → clear the field so the user types a fresh + // absolute path. Empty string is invalid, save button stays disabled + // until they enter something. + onDraftChange(''); + } + } + + return ( + + + + Embedding model + + + + Pick one source. Saving triggers a sidecar restart so the new + weights load before any further embedding. + + + + switchTo(v as Mode)} + className="grid grid-cols-1 gap-2 sm:grid-cols-2" + > + + + + + {/* Repo mode: dropdown when cache has entries, free-text input either way. */} +
+ {models.isLoading ? ( + + ) : cached.length > 0 ? ( +
+ + + {cacheDir ? ( +

+ Scanned {cacheDir} +

+ ) : null} +
+ ) : ( + + + No cached repos + + {cacheDir + ? <>Nothing under {cacheDir}. Type a repo ID below — first save will download to cache. + : <>No cache directory reported. Type a repo ID below or switch to "Local file path" if the model lives outside cix.} + + + )} + +
+ + onDraftChange(e.target.value)} + placeholder="awhiteside/CodeRankEmbed-Q8_0-GGUF" + disabled={mode !== 'repo'} + className="font-mono text-xs" + /> + {config?.recommended ? ( +

+ Recommended: {config.recommended.embedding_model} +

+ ) : null} +
+
+ + {/* Path mode: single absolute-path input. */} +
+ + onDraftChange(e.target.value)} + placeholder="/Users/me/.cache/huggingface/hub/.../coderankembed-q8_0.gguf" + disabled={mode !== 'path'} + className="font-mono text-xs" + /> +

+ File must be readable by the cix-server process. The path is used as-is — cix will not copy it into its cache. +

+
+
+
+ ); +} + +function ModeOption({ value, label, hint }: { value: Mode; label: string; hint: string }) { + const id = useId(); + return ( + + ); +} diff --git a/server/dashboard/src/modules/server/sections/RuntimeParamsSection.tsx b/server/dashboard/src/modules/server/sections/RuntimeParamsSection.tsx new file mode 100644 index 0000000..ac8fb5e --- /dev/null +++ b/server/dashboard/src/modules/server/sections/RuntimeParamsSection.tsx @@ -0,0 +1,115 @@ +import { useId } from 'react'; +import type { RuntimeConfig } from '@/api/types'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/ui/card'; +import { Input } from '@/ui/input'; +import { Label } from '@/ui/label'; +import { SourcePill } from '../components/SourcePill'; + +interface NumberFieldProps { + field: string; + label: string; + hint: string; + value: number; + recommended?: number; + source?: string; + onChange: (next: number) => void; + min?: number; +} + +function NumberField({ field, label, hint, value, recommended, source, onChange, min = 0 }: NumberFieldProps) { + const id = useId(); + return ( +
+
+ + +
+ { + const n = parseInt(e.target.value, 10); + onChange(Number.isFinite(n) ? n : 0); + }} + className="max-w-xs" + /> +

+ {hint} + {recommended !== undefined ? <> Recommended: {recommended}. : null} +

+
+ ); +} + +interface Props { + config?: RuntimeConfig; + draftCtx: number; + draftGpuLayers: number; + draftThreads: number; + onDraftCtx: (n: number) => void; + onDraftGpuLayers: (n: number) => void; + onDraftThreads: (n: number) => void; +} + +// RuntimeParamsSection: ctx, n_gpu_layers, n_threads form. n_gpu_layers +// allows -1 (Metal/CUDA all-layers sentinel) so we deliberately do NOT +// clamp to >= 0 in the input. +export function RuntimeParamsSection({ + config, + draftCtx, + draftGpuLayers, + draftThreads, + onDraftCtx, + onDraftGpuLayers, + onDraftThreads, +}: Props) { + const rec = config?.recommended; + const src = config?.source; + return ( + + + Runtime parameters + + Tunables passed to llama-server on every (re)start. Leaving a field + at zero falls back to env / recommended on the next save. + + + + + + + + + ); +} diff --git a/server/dashboard/src/modules/server/sections/SidecarSection.tsx b/server/dashboard/src/modules/server/sections/SidecarSection.tsx new file mode 100644 index 0000000..499b33d --- /dev/null +++ b/server/dashboard/src/modules/server/sections/SidecarSection.tsx @@ -0,0 +1,133 @@ +import { useState } from 'react'; +import { Loader2, RefreshCw } from 'lucide-react'; +import { toast } from 'sonner'; +import { ApiError } from '@/api/client'; +import { Alert, AlertDescription, AlertTitle } from '@/ui/alert'; +import { Button } from '@/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/ui/card'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/ui/dialog'; +import { useRestartSidecar, useSidecarStatus } from '../hooks'; +import { SidecarStateBadge } from '../components/SidecarStateBadge'; + +function formatUptime(seconds?: number): string { + if (!seconds || seconds <= 0) return '—'; + const h = Math.floor(seconds / 3600); + const m = Math.floor((seconds % 3600) / 60); + const s = seconds % 60; + if (h > 0) return `${h}h ${m}m`; + if (m > 0) return `${m}m ${s}s`; + return `${s}s`; +} + +// SidecarSection renders the live llama-server status (PID, uptime, ready, +// model, last error) plus a "Restart sidecar" button. Used when an admin +// changes a runtime-config field that needs the new weights / flags to +// take effect, OR for opportunistic recovery when the sidecar got stuck. +export function SidecarSection() { + const status = useSidecarStatus(); + const restart = useRestartSidecar(); + const [confirm, setConfirm] = useState(false); + + const data = status.data; + const restarting = data?.restart_in_flight || data?.state === 'restarting'; + + return ( + + + + Sidecar + + + + The llama-server child process embedding chunks for this index. + Restart re-spawns with the latest runtime config. + + + + {data?.last_error ? ( + + Sidecar reported an error + {data.last_error} + + ) : null} + +
+ + + + +
+ +
+ +
+
+ + (!restart.isPending ? setConfirm(next) : null)}> + + + Restart llama-server? + + Drains the embedding queue (up to 30s) and respawns. Active + indexing batches will fail mid-call and need to be re-driven by + the operator (cix reindex). + + + + + + + + +
+ ); +} + +function Field({ label, value, mono }: { label: string; value: string; mono?: boolean }) { + return ( +
+
{label}
+
+ {value} +
+
+ ); +} diff --git a/server/dashboard/src/modules/settings/SettingsPage.tsx b/server/dashboard/src/modules/settings/SettingsPage.tsx new file mode 100644 index 0000000..3092764 --- /dev/null +++ b/server/dashboard/src/modules/settings/SettingsPage.tsx @@ -0,0 +1,22 @@ +import { EditorSection } from './sections/EditorSection'; +import { ProfileSection } from './sections/ProfileSection'; +import { SessionsSection } from './sections/SessionsSection'; +import { ThemeSection } from './sections/ThemeSection'; + +export default function SettingsPage() { + return ( +
+
+

Settings

+

+ Account, sessions, and personal UI preferences. +

+
+ + + + + +
+ ); +} diff --git a/server/dashboard/src/modules/settings/components/ChangePasswordForm.tsx b/server/dashboard/src/modules/settings/components/ChangePasswordForm.tsx new file mode 100644 index 0000000..5be0e5b --- /dev/null +++ b/server/dashboard/src/modules/settings/components/ChangePasswordForm.tsx @@ -0,0 +1,102 @@ +import { useState, type FormEvent } from 'react'; +import { Loader2 } from 'lucide-react'; +import { toast } from 'sonner'; +import { ApiError } from '@/api/client'; +import { Alert, AlertDescription, AlertTitle } from '@/ui/alert'; +import { Button } from '@/ui/button'; +import { Input } from '@/ui/input'; +import { Label } from '@/ui/label'; +import { useAuth } from '@/auth/useAuth'; +import { useChangePassword } from '../hooks'; + +// Settings-page password change. Server invalidates sibling sessions and +// keeps the current cookie alive — but to make the cookie consistent with +// the new password we still log out + bounce to /login afterwards. +export function ChangePasswordForm() { + const { logout } = useAuth(); + const change = useChangePassword(); + const [current, setCurrent] = useState(''); + const [next, setNext] = useState(''); + const [confirm, setConfirm] = useState(''); + const [error, setError] = useState(null); + + async function onSubmit(e: FormEvent) { + e.preventDefault(); + setError(null); + if (next !== confirm) { + setError('New password and confirmation must match.'); + return; + } + if (next.length < 8) { + setError('New password must be at least 8 characters.'); + return; + } + try { + await change.mutateAsync({ current_password: current, new_password: next }); + toast.success('Password updated', { + description: 'Please sign in again with your new password.', + }); + await logout(); + } catch (err) { + const detail = err instanceof ApiError ? err.detail : 'Unexpected error. Try again.'; + setError(detail); + } + } + + return ( +
+
+
+ + setCurrent(e.target.value)} + disabled={change.isPending} + /> +
+
+ + setNext(e.target.value)} + disabled={change.isPending} + /> +
+
+ + setConfirm(e.target.value)} + disabled={change.isPending} + /> +
+
+ {error ? ( + + Could not update password + {error} + + ) : null} +
+ +
+
+ ); +} diff --git a/server/dashboard/src/modules/settings/components/SessionRow.tsx b/server/dashboard/src/modules/settings/components/SessionRow.tsx new file mode 100644 index 0000000..eadf6ca --- /dev/null +++ b/server/dashboard/src/modules/settings/components/SessionRow.tsx @@ -0,0 +1,88 @@ +import { Loader2, LogOut } from 'lucide-react'; +import { toast } from 'sonner'; +import { ApiError } from '@/api/client'; +import type { Session } from '@/api/types'; +import { Badge } from '@/ui/badge'; +import { Button } from '@/ui/button'; +import { TableCell, TableRow } from '@/ui/table'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@/ui/tooltip'; +import { formatDateTime, formatRelative } from '@/lib/formatDate'; +import { useDeleteSession } from '../hooks'; + +export function SessionRow({ session }: { session: Session }) { + const del = useDeleteSession(); + const ua = session.last_seen_ua ?? '—'; + + async function onSignOut() { + try { + await del.mutateAsync(session.id); + toast.success('Session ended'); + } catch (err) { + const detail = err instanceof ApiError ? err.detail : String(err); + toast.error('Could not end session', { description: detail }); + } + } + + return ( + + + {formatRelative(session.created_at)} + + + {formatRelative(session.last_seen_at)} + + + {session.last_seen_ip ?? '—'} + + + + + {ua} + + {ua} + + + + {session.is_current ? current : null} + + + {session.is_current ? ( + + + + + + + + This is your current session. Use the sidebar Sign out to end it. + + + ) : ( + + )} + + + ); +} diff --git a/server/dashboard/src/modules/settings/hooks.ts b/server/dashboard/src/modules/settings/hooks.ts new file mode 100644 index 0000000..54dcdce --- /dev/null +++ b/server/dashboard/src/modules/settings/hooks.ts @@ -0,0 +1,32 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { api } from '@/api/client'; +import type { ChangePasswordRequest, SessionListResponse } from '@/api/types'; + +export const settingsKeys = { + sessions: ['auth', 'sessions'] as const, +}; + +export function useMySessions() { + return useQuery({ + queryKey: settingsKeys.sessions, + queryFn: ({ signal }) => api.get('/auth/sessions', { signal }), + }); +} + +export function useDeleteSession() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: (id: string) => api.delete(`/auth/sessions/${id}`), + onSuccess: () => qc.invalidateQueries({ queryKey: settingsKeys.sessions }), + }); +} + +// Change-password — no useQueryClient invalidation; the caller is expected +// to logout immediately afterwards (server already revoked sibling sessions +// for us, only the current one survives, and we want a fresh login anyway). +export function useChangePassword() { + return useMutation({ + mutationFn: (body: ChangePasswordRequest) => + api.post('/auth/change-password', body), + }); +} diff --git a/server/dashboard/src/modules/settings/index.ts b/server/dashboard/src/modules/settings/index.ts new file mode 100644 index 0000000..66d77ee --- /dev/null +++ b/server/dashboard/src/modules/settings/index.ts @@ -0,0 +1,12 @@ +import { Settings as SettingsIcon } from 'lucide-react'; +import type { Module } from '../types'; +import SettingsPage from './SettingsPage'; + +export const SettingsModule: Module = { + id: 'settings', + label: 'Settings', + icon: SettingsIcon, + path: '/settings', + element: SettingsPage, + weight: 50, +}; diff --git a/server/dashboard/src/modules/settings/sections/EditorSection.tsx b/server/dashboard/src/modules/settings/sections/EditorSection.tsx new file mode 100644 index 0000000..0f1122b --- /dev/null +++ b/server/dashboard/src/modules/settings/sections/EditorSection.tsx @@ -0,0 +1,71 @@ +import { useState } from 'react'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/ui/card'; +import { Label } from '@/ui/label'; +import { RadioGroup, RadioGroupItem } from '@/ui/radio-group'; +import { + getEditorPreference, + setEditorPreference, + type EditorProtocol, +} from '@/lib/editorPreference'; + +const OPTIONS: ReadonlyArray<{ + value: EditorProtocol; + label: string; + hint: string; +}> = [ + { + value: 'cursor', + label: 'Cursor (default)', + hint: 'Opens cursor:// — falls back to VS Code if Cursor is not installed.', + }, + { + value: 'vscode', + label: 'VS Code', + hint: 'Opens vscode:// directly.', + }, + { + value: 'none', + label: 'Disabled', + hint: 'The Open in editor button does nothing.', + }, +]; + +export function EditorSection() { + const [pref, setPref] = useState(() => getEditorPreference()); + + function onChange(next: EditorProtocol) { + setPref(next); + setEditorPreference(next); + } + + return ( + + + Open in editor + + What happens when you click the Open icon next to a search result. + Stored locally — applies to this browser only. + + + + onChange(v as EditorProtocol)} + className="space-y-3" + > + {OPTIONS.map((o) => ( +
+ +
+ +

{o.hint}

+
+
+ ))} +
+
+
+ ); +} diff --git a/server/dashboard/src/modules/settings/sections/ProfileSection.tsx b/server/dashboard/src/modules/settings/sections/ProfileSection.tsx new file mode 100644 index 0000000..d23e989 --- /dev/null +++ b/server/dashboard/src/modules/settings/sections/ProfileSection.tsx @@ -0,0 +1,30 @@ +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/ui/card'; +import { useAuth } from '@/auth/useAuth'; +import { ChangePasswordForm } from '../components/ChangePasswordForm'; + +export function ProfileSection() { + const { user } = useAuth(); + return ( + + + Profile + Account email + password. + + +
+ + Email + + {user?.email ?? '—'} + + Role: {user?.role ?? 'unknown'} + +
+
+

Change password

+ +
+
+
+ ); +} diff --git a/server/dashboard/src/modules/settings/sections/SessionsSection.tsx b/server/dashboard/src/modules/settings/sections/SessionsSection.tsx new file mode 100644 index 0000000..e453cbf --- /dev/null +++ b/server/dashboard/src/modules/settings/sections/SessionsSection.tsx @@ -0,0 +1,69 @@ +import { AlertCircle } from 'lucide-react'; +import { ApiError } from '@/api/client'; +import { Alert, AlertDescription, AlertTitle } from '@/ui/alert'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/ui/card'; +import { Skeleton } from '@/ui/skeleton'; +import { + Table, + TableBody, + TableHead, + TableHeader, + TableRow, +} from '@/ui/table'; +import { SessionRow } from '../components/SessionRow'; +import { useMySessions } from '../hooks'; + +export function SessionsSection() { + const { data, error, isLoading } = useMySessions(); + + return ( + + + Active sessions + + Browsers signed in to your account. Sign out of any session you + don’t recognise. + + + + {isLoading ? ( +
+ {Array.from({ length: 2 }).map((_, i) => ( + + ))} +
+ ) : error ? ( + + + Failed to load sessions + + {error instanceof ApiError ? error.detail : String(error)} + + + ) : !data || data.sessions.length === 0 ? ( +

No active sessions.

+ ) : ( +
+ + + + Started + Last seen + IP + User agent + + Actions + + + + {data.sessions.map((s) => ( + + ))} + +
+
+ )} +
+
+ ); +} diff --git a/server/dashboard/src/modules/settings/sections/ThemeSection.tsx b/server/dashboard/src/modules/settings/sections/ThemeSection.tsx new file mode 100644 index 0000000..6b8da72 --- /dev/null +++ b/server/dashboard/src/modules/settings/sections/ThemeSection.tsx @@ -0,0 +1,50 @@ +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/ui/card'; +import { Label } from '@/ui/label'; +import { RadioGroup, RadioGroupItem } from '@/ui/radio-group'; +import { useTheme } from '@/app/ThemeProvider'; +import type { ThemeMode } from '@/lib/theme'; + +const OPTIONS: ReadonlyArray<{ value: ThemeMode; label: string; hint: string }> = [ + { value: 'light', label: 'Light', hint: 'Always use the light theme.' }, + { value: 'dark', label: 'Dark', hint: 'Always use the dark theme.' }, + { + value: 'system', + label: 'System', + hint: 'Follow the OS-level prefers-color-scheme setting.', + }, +]; + +export function ThemeSection() { + const { mode, resolved, setMode } = useTheme(); + + return ( + + + Theme + + Currently rendering in {resolved}{' '} + mode. Stored locally — applies to this browser only. + + + + setMode(v as ThemeMode)} + className="space-y-3" + > + {OPTIONS.map((o) => ( +
+ +
+ +

{o.hint}

+
+
+ ))} +
+
+
+ ); +} diff --git a/server/dashboard/src/modules/types.ts b/server/dashboard/src/modules/types.ts new file mode 100644 index 0000000..03c3143 --- /dev/null +++ b/server/dashboard/src/modules/types.ts @@ -0,0 +1,25 @@ +import type { ComponentType, SVGProps } from 'react'; +import type { Role } from '@/api/types'; + +// A `Module` is a self-contained dashboard feature: its own folder under +// `src/modules//` with an `index.ts` that exports a `Module` constant. +// +// Sidebar + router both consume the registry — adding a new feature is +// "create folder, register the module" and nothing else. See +// `dashboard/README.md` for the full style guide. +export interface Module { + /** Stable kebab-case identifier — also used as the React key in the sidebar. */ + id: string; + /** Human-facing label shown in the sidebar. */ + label: string; + /** Sidebar icon — must accept `className`. lucide-react icons satisfy this. */ + icon: ComponentType>; + /** Path relative to the `/dashboard` basename (must start with `/`). */ + path: string; + /** Top-level page rendered for this module. Owns its own internal routes. */ + element: ComponentType; + /** Minimum role required to *see* this module in the sidebar. Default: viewer. */ + requiredRole?: Role; + /** Sort order in the sidebar — lower comes first. Default: 100. */ + weight?: number; +} diff --git a/server/dashboard/src/modules/users/UsersPage.tsx b/server/dashboard/src/modules/users/UsersPage.tsx new file mode 100644 index 0000000..0a2a573 --- /dev/null +++ b/server/dashboard/src/modules/users/UsersPage.tsx @@ -0,0 +1,60 @@ +import { AlertCircle, Users as UsersIcon } from 'lucide-react'; +import { ApiError } from '@/api/client'; +import { Alert, AlertDescription, AlertTitle } from '@/ui/alert'; +import { Skeleton } from '@/ui/skeleton'; +import { useAuth } from '@/auth/useAuth'; +import { InviteUserDialog } from './components/InviteUserDialog'; +import { UsersTable } from './components/UsersTable'; +import { useUsers } from './hooks'; + +export default function UsersPage() { + const { user } = useAuth(); + const { data, error, isLoading } = useUsers(); + + return ( +
+
+
+

Users

+

+ {data ? `${data.total} ${data.total === 1 ? 'user' : 'users'}` : ' '} +

+
+ +
+ + {isLoading ? ( +
+ {Array.from({ length: 4 }).map((_, i) => ( + + ))} +
+ ) : error ? ( + + + Failed to load users + + {error instanceof ApiError ? error.detail : String(error)} + + + ) : !data || data.users.length === 0 ? ( + + ) : ( + + )} +
+ ); +} + +function EmptyState() { + return ( +
+ +

No users yet

+

+ Invite the first teammate to share dashboard access. Bootstrap admin + always counts — if you can read this, your account is in the list. +

+
+ ); +} diff --git a/server/dashboard/src/modules/users/components/DeleteUserDialog.tsx b/server/dashboard/src/modules/users/components/DeleteUserDialog.tsx new file mode 100644 index 0000000..8057a72 --- /dev/null +++ b/server/dashboard/src/modules/users/components/DeleteUserDialog.tsx @@ -0,0 +1,104 @@ +import { useState } from 'react'; +import { Loader2, Trash2 } from 'lucide-react'; +import { toast } from 'sonner'; +import { ApiError } from '@/api/client'; +import { Button } from '@/ui/button'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/ui/dialog'; +import { Input } from '@/ui/input'; +import { Label } from '@/ui/label'; +import { useDeleteUser } from '../hooks'; + +// Two-factor confirm: typing the email avoids accidental destructive clicks +// in a list of similar-looking rows. Server cascade-deletes sessions + keys. +export function DeleteUserDialog({ + userId, + email, +}: { + userId: string; + email: string; +}) { + const [open, setOpen] = useState(false); + const [typed, setTyped] = useState(''); + const del = useDeleteUser(); + + function reset() { + setTyped(''); + del.reset(); + } + + async function onConfirm() { + if (typed.trim() !== email) return; + try { + await del.mutateAsync(userId); + toast.success('User deleted', { description: email }); + setOpen(false); + reset(); + } catch (err) { + const detail = err instanceof ApiError ? err.detail : String(err); + toast.error('Failed to delete user', { description: detail }); + } + } + + return ( + { + setOpen(next); + if (!next) reset(); + }} + > + + + + + + Delete user? + + Permanently removes {email}{' '} + and cascades all their sessions and API keys. This cannot be undone. + + +
+ + setTyped(e.target.value)} + placeholder={email} + autoComplete="off" + /> +
+ + + + +
+
+ ); +} diff --git a/server/dashboard/src/modules/users/components/DisableUserButton.tsx b/server/dashboard/src/modules/users/components/DisableUserButton.tsx new file mode 100644 index 0000000..a512faf --- /dev/null +++ b/server/dashboard/src/modules/users/components/DisableUserButton.tsx @@ -0,0 +1,48 @@ +import { Loader2, ShieldOff, ShieldCheck } from 'lucide-react'; +import { toast } from 'sonner'; +import { ApiError } from '@/api/client'; +import { Button } from '@/ui/button'; +import { useUpdateUser } from '../hooks'; + +// One-click toggle. Server's last-admin guard kicks in for the disable path. +export function DisableUserButton({ + userId, + disabled, +}: { + userId: string; + /** Current disabled state on the user record. */ + disabled: boolean; +}) { + const update = useUpdateUser(); + + async function onToggle() { + try { + await update.mutateAsync({ id: userId, body: { disabled: !disabled } }); + toast.success(disabled ? 'User re-enabled' : 'User disabled'); + } catch (err) { + const detail = err instanceof ApiError ? err.detail : String(err); + toast.error(disabled ? 'Could not enable user' : 'Could not disable user', { + description: detail, + }); + } + } + + return ( + + ); +} diff --git a/server/dashboard/src/modules/users/components/InviteUserDialog.tsx b/server/dashboard/src/modules/users/components/InviteUserDialog.tsx new file mode 100644 index 0000000..458df94 --- /dev/null +++ b/server/dashboard/src/modules/users/components/InviteUserDialog.tsx @@ -0,0 +1,151 @@ +import { useState } from 'react'; +import { Loader2, UserPlus } from 'lucide-react'; +import { toast } from 'sonner'; +import { ApiError } from '@/api/client'; +import { Alert, AlertDescription } from '@/ui/alert'; +import { Button } from '@/ui/button'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/ui/dialog'; +import { Input } from '@/ui/input'; +import { Label } from '@/ui/label'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/ui/select'; +import type { Role } from '@/api/types'; +import { useCreateUser } from '../hooks'; + +// Invite-only flow: admin sets the initial password and shares it out-of-band. +// The new user is forced to change it on first login (server sets +// must_change_password=true). Field minimums (≥8 chars) mirror the server. +export function InviteUserDialog() { + const [open, setOpen] = useState(false); + const [email, setEmail] = useState(''); + const [role, setRole] = useState('viewer'); + const [pw, setPw] = useState(''); + const create = useCreateUser(); + + function reset() { + setEmail(''); + setRole('viewer'); + setPw(''); + create.reset(); + } + + async function onSubmit() { + const trimmedEmail = email.trim(); + if (!trimmedEmail || pw.length < 8) return; + try { + await create.mutateAsync({ + email: trimmedEmail, + role, + initial_password: pw, + }); + toast.success('User created', { + description: `Share the initial password with ${trimmedEmail}. They will be required to change it on first login.`, + }); + setOpen(false); + reset(); + } catch (err) { + const detail = err instanceof ApiError ? err.detail : String(err); + toast.error('Failed to invite user', { description: detail }); + } + } + + return ( + { + setOpen(next); + if (!next) reset(); + }} + > + + + + + + Invite user + + Creates a user with an initial password you set. The user is forced + to change it on first login. + + + +
+
+ + setEmail(e.target.value)} + placeholder="user@example.com" + /> +
+
+ + +
+
+ + setPw(e.target.value)} + placeholder="At least 8 characters" + /> +

+ Shown once. Share via your team’s preferred secure channel. +

+
+ + + The user’s account will be flagged{' '} + must_change_password; + they cannot use the system until they pick a new password. + + +
+ + + + + +
+
+ ); +} diff --git a/server/dashboard/src/modules/users/components/UserRoleSelect.tsx b/server/dashboard/src/modules/users/components/UserRoleSelect.tsx new file mode 100644 index 0000000..18de764 --- /dev/null +++ b/server/dashboard/src/modules/users/components/UserRoleSelect.tsx @@ -0,0 +1,52 @@ +import { toast } from 'sonner'; +import { ApiError } from '@/api/client'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/ui/select'; +import type { Role } from '@/api/types'; +import { useUpdateUser } from '../hooks'; + +// Inline role-edit. The server enforces the last-admin guard — if it returns +// 409 we just surface the toast and the next refetch resets the value. +export function UserRoleSelect({ + userId, + role, + disabled = false, +}: { + userId: string; + role: Role; + disabled?: boolean; +}) { + const update = useUpdateUser(); + + async function onChange(next: Role) { + if (next === role) return; + try { + await update.mutateAsync({ id: userId, body: { role: next } }); + toast.success('Role updated', { description: `Now ${next}` }); + } catch (err) { + const detail = err instanceof ApiError ? err.detail : String(err); + toast.error('Could not update role', { description: detail }); + } + } + + return ( + + ); +} diff --git a/server/dashboard/src/modules/users/components/UsersTable.tsx b/server/dashboard/src/modules/users/components/UsersTable.tsx new file mode 100644 index 0000000..592ef00 --- /dev/null +++ b/server/dashboard/src/modules/users/components/UsersTable.tsx @@ -0,0 +1,95 @@ +import type { UserWithStats } from '@/api/types'; +import { Badge } from '@/ui/badge'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/ui/table'; +import { cn } from '@/lib/cn'; +import { formatDateTime, formatRelative } from '@/lib/formatDate'; +import { DeleteUserDialog } from './DeleteUserDialog'; +import { DisableUserButton } from './DisableUserButton'; +import { UserRoleSelect } from './UserRoleSelect'; + +export function UsersTable({ + users, + currentUserId, +}: { + users: UserWithStats[]; + currentUserId: string | undefined; +}) { + return ( +
+ + + + Email + Role + Created + Last login + Sessions + API keys + Status + Actions + + + + {users.map((u) => { + const isSelf = u.id === currentUserId; + return ( + + +
+ {u.email} + {isSelf ? You : null} +
+
+ + + + + {formatRelative(u.created_at)} + + + {formatRelative(u.last_login_at)} + + + {u.active_sessions_count} + + + {u.api_keys_count} + + + {u.disabled ? ( + disabled + ) : ( + active + )} + + +
+ {isSelf ? null : ( + <> + + + + )} +
+
+
+ ); + })} +
+
+
+ ); +} diff --git a/server/dashboard/src/modules/users/hooks.ts b/server/dashboard/src/modules/users/hooks.ts new file mode 100644 index 0000000..d8baab6 --- /dev/null +++ b/server/dashboard/src/modules/users/hooks.ts @@ -0,0 +1,44 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { api } from '@/api/client'; +import type { + CreateUserRequest, + UpdateUserRequest, + User, + UserListResponse, +} from '@/api/types'; + +export const userKeys = { + all: ['users'] as const, +}; + +export function useUsers() { + return useQuery({ + queryKey: userKeys.all, + queryFn: ({ signal }) => api.get('/admin/users', { signal }), + }); +} + +export function useCreateUser() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: (body: CreateUserRequest) => api.post('/admin/users', body), + onSuccess: () => qc.invalidateQueries({ queryKey: userKeys.all }), + }); +} + +export function useUpdateUser() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: ({ id, body }: { id: string; body: UpdateUserRequest }) => + api.patch(`/admin/users/${id}`, body), + onSuccess: () => qc.invalidateQueries({ queryKey: userKeys.all }), + }); +} + +export function useDeleteUser() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: (id: string) => api.delete(`/admin/users/${id}`), + onSuccess: () => qc.invalidateQueries({ queryKey: userKeys.all }), + }); +} diff --git a/server/dashboard/src/modules/users/index.ts b/server/dashboard/src/modules/users/index.ts new file mode 100644 index 0000000..4352e82 --- /dev/null +++ b/server/dashboard/src/modules/users/index.ts @@ -0,0 +1,13 @@ +import { Users } from 'lucide-react'; +import type { Module } from '../types'; +import UsersPage from './UsersPage'; + +export const UsersModule: Module = { + id: 'users', + label: 'Users', + icon: Users, + path: '/users', + element: UsersPage, + requiredRole: 'admin', + weight: 40, +}; diff --git a/server/dashboard/src/ui/alert.tsx b/server/dashboard/src/ui/alert.tsx new file mode 100644 index 0000000..2f7b5c8 --- /dev/null +++ b/server/dashboard/src/ui/alert.tsx @@ -0,0 +1,52 @@ +import { cva, type VariantProps } from 'class-variance-authority'; +import { forwardRef, type HTMLAttributes } from 'react'; +import { cn } from '@/lib/cn'; + +const alertVariants = cva( + 'relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7', + { + variants: { + variant: { + default: 'bg-background text-foreground', + destructive: + 'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive', + }, + }, + defaultVariants: { variant: 'default' }, + } +); + +export const Alert = forwardRef< + HTMLDivElement, + HTMLAttributes & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)); +Alert.displayName = 'Alert'; + +export const AlertTitle = forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +); +AlertTitle.displayName = 'AlertTitle'; + +export const AlertDescription = forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +); +AlertDescription.displayName = 'AlertDescription'; diff --git a/server/dashboard/src/ui/badge.tsx b/server/dashboard/src/ui/badge.tsx new file mode 100644 index 0000000..d7fbe4b --- /dev/null +++ b/server/dashboard/src/ui/badge.tsx @@ -0,0 +1,36 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/cn" + +const badgeVariants = cva( + "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ) +} + +export { Badge, badgeVariants } diff --git a/server/dashboard/src/ui/button.tsx b/server/dashboard/src/ui/button.tsx new file mode 100644 index 0000000..577eb63 --- /dev/null +++ b/server/dashboard/src/ui/button.tsx @@ -0,0 +1,55 @@ +import { Slot } from '@radix-ui/react-slot'; +import { cva, type VariantProps } from 'class-variance-authority'; +import { forwardRef, type ButtonHTMLAttributes } from 'react'; +import { cn } from '@/lib/cn'; + +const buttonVariants = cva( + 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', + { + variants: { + variant: { + default: 'bg-primary text-primary-foreground hover:bg-primary/90', + destructive: + 'bg-destructive text-destructive-foreground hover:bg-destructive/90', + outline: + 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', + secondary: + 'bg-secondary text-secondary-foreground hover:bg-secondary/80', + ghost: 'hover:bg-accent hover:text-accent-foreground', + link: 'text-primary underline-offset-4 hover:underline', + }, + size: { + default: 'h-9 px-4 py-2', + sm: 'h-8 rounded-md px-3 text-xs', + lg: 'h-10 rounded-md px-6', + icon: 'h-9 w-9', + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + } +); + +export interface ButtonProps + extends ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +export const Button = forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : 'button'; + return ( + + ); + } +); +Button.displayName = 'Button'; + +export { buttonVariants }; diff --git a/server/dashboard/src/ui/card.tsx b/server/dashboard/src/ui/card.tsx new file mode 100644 index 0000000..a03423f --- /dev/null +++ b/server/dashboard/src/ui/card.tsx @@ -0,0 +1,52 @@ +import { forwardRef, type HTMLAttributes } from 'react'; +import { cn } from '@/lib/cn'; + +export const Card = forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +); +Card.displayName = 'Card'; + +export const CardHeader = forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +); +CardHeader.displayName = 'CardHeader'; + +export const CardTitle = forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +); +CardTitle.displayName = 'CardTitle'; + +export const CardDescription = forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +); +CardDescription.displayName = 'CardDescription'; + +export const CardContent = forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +); +CardContent.displayName = 'CardContent'; + +export const CardFooter = forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +); +CardFooter.displayName = 'CardFooter'; diff --git a/server/dashboard/src/ui/dialog.tsx b/server/dashboard/src/ui/dialog.tsx new file mode 100644 index 0000000..b4e6616 --- /dev/null +++ b/server/dashboard/src/ui/dialog.tsx @@ -0,0 +1,93 @@ +import * as DialogPrimitive from '@radix-ui/react-dialog'; +import { X } from 'lucide-react'; +import { + forwardRef, + type ComponentPropsWithoutRef, + type ElementRef, + type HTMLAttributes, +} from 'react'; +import { cn } from '@/lib/cn'; + +export const Dialog = DialogPrimitive.Root; +export const DialogTrigger = DialogPrimitive.Trigger; +export const DialogPortal = DialogPrimitive.Portal; +export const DialogClose = DialogPrimitive.Close; + +export const DialogOverlay = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; + +export const DialogContent = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)); +DialogContent.displayName = DialogPrimitive.Content.displayName; + +export const DialogHeader = ({ className, ...props }: HTMLAttributes) => ( +
+); +DialogHeader.displayName = 'DialogHeader'; + +export const DialogFooter = ({ className, ...props }: HTMLAttributes) => ( +
+); +DialogFooter.displayName = 'DialogFooter'; + +export const DialogTitle = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogTitle.displayName = DialogPrimitive.Title.displayName; + +export const DialogDescription = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogDescription.displayName = DialogPrimitive.Description.displayName; diff --git a/server/dashboard/src/ui/input.tsx b/server/dashboard/src/ui/input.tsx new file mode 100644 index 0000000..05b78c3 --- /dev/null +++ b/server/dashboard/src/ui/input.tsx @@ -0,0 +1,17 @@ +import { forwardRef, type InputHTMLAttributes } from 'react'; +import { cn } from '@/lib/cn'; + +export const Input = forwardRef>( + ({ className, type, ...props }, ref) => ( + + ) +); +Input.displayName = 'Input'; diff --git a/server/dashboard/src/ui/label.tsx b/server/dashboard/src/ui/label.tsx new file mode 100644 index 0000000..2a98d66 --- /dev/null +++ b/server/dashboard/src/ui/label.tsx @@ -0,0 +1,18 @@ +import * as LabelPrimitive from '@radix-ui/react-label'; +import { forwardRef, type ComponentPropsWithoutRef, type ElementRef } from 'react'; +import { cn } from '@/lib/cn'; + +export const Label = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +Label.displayName = LabelPrimitive.Root.displayName; diff --git a/server/dashboard/src/ui/radio-group.tsx b/server/dashboard/src/ui/radio-group.tsx new file mode 100644 index 0000000..571a487 --- /dev/null +++ b/server/dashboard/src/ui/radio-group.tsx @@ -0,0 +1,42 @@ +import * as React from "react" +import * as RadioGroupPrimitive from "@radix-ui/react-radio-group" +import { Circle } from "lucide-react" + +import { cn } from "@/lib/cn" + +const RadioGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + return ( + + ) +}) +RadioGroup.displayName = RadioGroupPrimitive.Root.displayName + +const RadioGroupItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + return ( + + + + + + ) +}) +RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName + +export { RadioGroup, RadioGroupItem } diff --git a/server/dashboard/src/ui/scroll-area.tsx b/server/dashboard/src/ui/scroll-area.tsx new file mode 100644 index 0000000..f099c02 --- /dev/null +++ b/server/dashboard/src/ui/scroll-area.tsx @@ -0,0 +1,46 @@ +import * as React from "react" +import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" + +import { cn } from "@/lib/cn" + +const ScrollArea = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + {children} + + + + +)) +ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName + +const ScrollBar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, orientation = "vertical", ...props }, ref) => ( + + + +)) +ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName + +export { ScrollArea, ScrollBar } diff --git a/server/dashboard/src/ui/select.tsx b/server/dashboard/src/ui/select.tsx new file mode 100644 index 0000000..1f7778c --- /dev/null +++ b/server/dashboard/src/ui/select.tsx @@ -0,0 +1,158 @@ +import * as React from "react" +import * as SelectPrimitive from "@radix-ui/react-select" +import { Check, ChevronDown, ChevronUp } from "lucide-react" + +import { cn } from "@/lib/cn" + +const Select = SelectPrimitive.Root + +const SelectGroup = SelectPrimitive.Group + +const SelectValue = SelectPrimitive.Value + +const SelectTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + span]:line-clamp-1", + className + )} + {...props} + > + {children} + + + + +)) +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName + +const SelectScrollUpButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName + +const SelectScrollDownButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollDownButton.displayName = + SelectPrimitive.ScrollDownButton.displayName + +const SelectContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = "popper", ...props }, ref) => ( + + + + + {children} + + + + +)) +SelectContent.displayName = SelectPrimitive.Content.displayName + +const SelectLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectLabel.displayName = SelectPrimitive.Label.displayName + +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + + {children} + +)) +SelectItem.displayName = SelectPrimitive.Item.displayName + +const SelectSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectSeparator.displayName = SelectPrimitive.Separator.displayName + +export { + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator, + SelectScrollUpButton, + SelectScrollDownButton, +} diff --git a/server/dashboard/src/ui/skeleton.tsx b/server/dashboard/src/ui/skeleton.tsx new file mode 100644 index 0000000..67dc9fc --- /dev/null +++ b/server/dashboard/src/ui/skeleton.tsx @@ -0,0 +1,15 @@ +import { cn } from "@/lib/cn" + +function Skeleton({ + className, + ...props +}: React.HTMLAttributes) { + return ( +
+ ) +} + +export { Skeleton } diff --git a/server/dashboard/src/ui/slider.tsx b/server/dashboard/src/ui/slider.tsx new file mode 100644 index 0000000..ed0d360 --- /dev/null +++ b/server/dashboard/src/ui/slider.tsx @@ -0,0 +1,28 @@ +"use client" + +import * as React from "react" +import * as SliderPrimitive from "@radix-ui/react-slider" + +import { cn } from "@/lib/cn" + +const Slider = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + + +)) +Slider.displayName = SliderPrimitive.Root.displayName + +export { Slider } diff --git a/server/dashboard/src/ui/sonner.tsx b/server/dashboard/src/ui/sonner.tsx new file mode 100644 index 0000000..6dc40e9 --- /dev/null +++ b/server/dashboard/src/ui/sonner.tsx @@ -0,0 +1,20 @@ +import { Toaster as SonnerToaster } from 'sonner'; + +// Thin wrapper so the rest of the app imports `Toaster` from `@/ui/sonner` +// instead of pulling sonner directly. Keeps swap potential trivial. +export function Toaster() { + return ( + + ); +} + +export { toast } from 'sonner'; diff --git a/server/dashboard/src/ui/switch.tsx b/server/dashboard/src/ui/switch.tsx new file mode 100644 index 0000000..ad88818 --- /dev/null +++ b/server/dashboard/src/ui/switch.tsx @@ -0,0 +1,29 @@ +"use client" + +import * as React from "react" +import * as SwitchPrimitives from "@radix-ui/react-switch" + +import { cn } from "@/lib/cn" + +const Switch = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +Switch.displayName = SwitchPrimitives.Root.displayName + +export { Switch } diff --git a/server/dashboard/src/ui/table.tsx b/server/dashboard/src/ui/table.tsx new file mode 100644 index 0000000..3699604 --- /dev/null +++ b/server/dashboard/src/ui/table.tsx @@ -0,0 +1,117 @@ +import * as React from "react" + +import { cn } from "@/lib/cn" + +const Table = React.forwardRef< + HTMLTableElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+ + +)) +Table.displayName = "Table" + +const TableHeader = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableHeader.displayName = "TableHeader" + +const TableBody = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableBody.displayName = "TableBody" + +const TableFooter = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + tr]:last:border-b-0", + className + )} + {...props} + /> +)) +TableFooter.displayName = "TableFooter" + +const TableRow = React.forwardRef< + HTMLTableRowElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableRow.displayName = "TableRow" + +const TableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( + \n"},cC.thead_close=function(){return"\n"},cC.tbody_open=function(){return"\n"},cC.tbody_close=function(){return"\n"},cC.tr_open=function(){return""},cC.tr_close=function(){return"\n"},cC.th_open=function(s,o){var i=s[o];return""},cC.th_close=function(){return""},cC.td_open=function(s,o){var i=s[o];return""},cC.td_close=function(){return""},cC.strong_open=function(){return""},cC.strong_close=function(){return""},cC.em_open=function(){return""},cC.em_close=function(){return""},cC.del_open=function(){return""},cC.del_close=function(){return""},cC.ins_open=function(){return""},cC.ins_close=function(){return""},cC.mark_open=function(){return""},cC.mark_close=function(){return""},cC.sub=function(s,o){return""+escapeHtml(s[o].content)+""},cC.sup=function(s,o){return""+escapeHtml(s[o].content)+""},cC.hardbreak=function(s,o,i){return i.xhtmlOut?"
\n":"
\n"},cC.softbreak=function(s,o,i){return i.breaks?i.xhtmlOut?"
\n":"
\n":"\n"},cC.text=function(s,o){return escapeHtml(s[o].content)},cC.htmlblock=function(s,o){return s[o].content},cC.htmltag=function(s,o){return s[o].content},cC.abbr_open=function(s,o){return''},cC.abbr_close=function(){return""},cC.footnote_ref=function(s,o){var i=Number(s[o].id+1).toString(),u="fnref"+i;return s[o].subId>0&&(u+=":"+s[o].subId),'['+i+"]"},cC.footnote_block_open=function(s,o,i){return(i.xhtmlOut?'
\n':'
\n')+'
\n
    \n'},cC.footnote_block_close=function(){return"
\n
\n"},cC.footnote_open=function(s,o){return'
  • '},cC.footnote_close=function(){return"
  • \n"},cC.footnote_anchor=function(s,o){var i="fnref"+Number(s[o].id+1).toString();return s[o].subId>0&&(i+=":"+s[o].subId),' '},cC.dl_open=function(){return"
    \n"},cC.dt_open=function(){return"
    "},cC.dd_open=function(){return"
    "},cC.dl_close=function(){return"
    \n"},cC.dt_close=function(){return"\n"},cC.dd_close=function(){return"\n"};var uC=cC.getBreak=function getBreak(s,o){return(o=nextToken(s,o))1)break;if(41===i&&--u<0)break;o++}return w!==o&&(_=unescapeMd(s.src.slice(w,o)),!!s.parser.validateLink(_)&&(s.linkContent=_,s.pos=o,!0))}function parseLinkTitle(s,o){var i,u=o,_=s.posMax,w=s.src.charCodeAt(o);if(34!==w&&39!==w&&40!==w)return!1;for(o++,40===w&&(w=41);o<_;){if((i=s.src.charCodeAt(o))===w)return s.pos=o+1,s.linkContent=unescapeMd(s.src.slice(u+1,o)),!0;92===i&&o+1<_?o+=2:o++}return!1}function normalizeReference(s){return s.trim().replace(/\s+/g," ").toUpperCase()}function parseReference(s,o,i,u){var _,w,x,C,j,L,B,$,V;if(91!==s.charCodeAt(0))return-1;if(-1===s.indexOf("]:"))return-1;if((w=parseLinkLabel(_=new StateInline(s,o,i,u,[]),0))<0||58!==s.charCodeAt(w+1))return-1;for(C=_.posMax,x=w+2;x=s.length)&&!yC.test(s[o])}function replaceAt(s,o,i){return s.substr(0,o)+i+s.substr(o+1)}var vC=[["block",function block(s){s.inlineMode?s.tokens.push({type:"inline",content:s.src.replace(/\n/g," ").trim(),level:0,lines:[0,1],children:[]}):s.block.parse(s.src,s.options,s.env,s.tokens)}],["abbr",function abbr(s){var o,i,u,_,w=s.tokens;if(!s.inlineMode)for(o=1,i=w.length-1;o0?x[o].count:1,u=0;u<_;u++)s.tokens.push({type:"footnote_anchor",id:o,subId:u,level:B});w&&s.tokens.push(w),s.tokens.push({type:"footnote_close",level:--B})}s.tokens.push({type:"footnote_block_close",level:--B})}}],["abbr2",function abbr2(s){var o,i,u,_,w,x,C,j,L,B,$,V,U=s.tokens;if(s.env.abbreviations)for(s.env.abbrRegExp||(V="(^|["+pC.split("").map(regEscape).join("")+"])("+Object.keys(s.env.abbreviations).map((function(s){return s.substr(1)})).sort((function(s,o){return o.length-s.length})).map(regEscape).join("|")+")($|["+pC.split("").map(regEscape).join("")+"])",s.env.abbrRegExp=new RegExp(V,"g")),B=s.env.abbrRegExp,i=0,u=U.length;i=0;o--)if("text"===(w=_[o]).type){for(j=0,x=w.content,B.lastIndex=0,L=w.level,C=[];$=B.exec(x);)B.lastIndex>j&&C.push({type:"text",content:x.slice(j,$.index+$[1].length),level:L}),C.push({type:"abbr_open",title:s.env.abbreviations[":"+$[2]],level:L++}),C.push({type:"text",content:$[2],level:L}),C.push({type:"abbr_close",level:--L}),j=B.lastIndex-$[3].length;C.length&&(j=0;w--)if("inline"===s.tokens[w].type)for(o=(_=s.tokens[w].children).length-1;o>=0;o--)"text"===(i=_[o]).type&&(u=replaceScopedAbbr(u=i.content),hC.test(u)&&(u=u.replace(/\+-/g,"±").replace(/\.{2,}/g,"…").replace(/([?!])…/g,"$1..").replace(/([?!]){4,}/g,"$1$1$1").replace(/,{2,}/g,",").replace(/(^|[^-])---([^-]|$)/gm,"$1—$2").replace(/(^|\s)--(\s|$)/gm,"$1–$2").replace(/(^|[^-\s])--([^-\s]|$)/gm,"$1–$2")),i.content=u)}],["smartquotes",function smartquotes(s){var o,i,u,_,w,x,C,j,L,B,$,V,U,z,Y,Z,ee;if(s.options.typographer)for(ee=[],Y=s.tokens.length-1;Y>=0;Y--)if("inline"===s.tokens[Y].type)for(Z=s.tokens[Y].children,ee.length=0,o=0;o=0&&!(ee[U].level<=C);U--);ee.length=U+1,w=0,x=(u=i.content).length;e:for(;w=0&&(B=ee[U],!(ee[U].level=(_=s.eMarks[o])||42!==(i=s.src.charCodeAt(u++))&&45!==i&&43!==i||u<_&&32!==s.src.charCodeAt(u)?-1:u}function skipOrderedListMarker(s,o){var i,u=s.bMarks[o]+s.tShift[o],_=s.eMarks[o];if(u+1>=_)return-1;if((i=s.src.charCodeAt(u++))<48||i>57)return-1;for(;;){if(u>=_)return-1;if(!((i=s.src.charCodeAt(u++))>=48&&i<=57)){if(41===i||46===i)break;return-1}}return u<_&&32!==s.src.charCodeAt(u)?-1:u}Core.prototype.process=function(s){var o,i,u;for(o=0,i=(u=this.ruler.getRules("")).length;o=this.eMarks[s]},StateBlock.prototype.skipEmptyLines=function skipEmptyLines(s){for(var o=this.lineMax;si;)if(o!==this.src.charCodeAt(--s))return s+1;return s},StateBlock.prototype.getLines=function getLines(s,o,i,u){var _,w,x,C,j,L=s;if(s>=o)return"";if(L+1===o)return w=this.bMarks[L]+Math.min(this.tShift[L],i),x=u?this.eMarks[L]+1:this.eMarks[L],this.src.slice(w,x);for(C=new Array(o-s),_=0;Li&&(j=i),j<0&&(j=0),w=this.bMarks[L]+j,x=L+1]/,EC=/^<\/([a-zA-Z]{1,15})[\s>]/;function index_browser_getLine(s,o){var i=s.bMarks[o]+s.blkIndent,u=s.eMarks[o];return s.src.substr(i,u-i)}function skipMarker(s,o){var i,u,_=s.bMarks[o]+s.tShift[o],w=s.eMarks[o];return _>=w||126!==(u=s.src.charCodeAt(_++))&&58!==u||_===(i=s.skipSpaces(_))||i>=w?-1:i}var wC=[["code",function code(s,o,i){var u,_;if(s.tShift[o]-s.blkIndent<4)return!1;for(_=u=o+1;u=4))break;_=++u}return s.line=u,s.tokens.push({type:"code",content:s.getLines(o,_,4+s.blkIndent,!0),block:!0,lines:[o,s.line],level:s.level}),!0}],["fences",function fences(s,o,i,u){var _,w,x,C,j,L=!1,B=s.bMarks[o]+s.tShift[o],$=s.eMarks[o];if(B+3>$)return!1;if(126!==(_=s.src.charCodeAt(B))&&96!==_)return!1;if(j=B,(w=(B=s.skipChars(B,_))-j)<3)return!1;if((x=s.src.slice(B,$).trim()).indexOf("`")>=0)return!1;if(u)return!0;for(C=o;!(++C>=i)&&!((B=j=s.bMarks[C]+s.tShift[C])<($=s.eMarks[C])&&s.tShift[C]=4||(B=s.skipChars(B,_))-jZ)return!1;if(62!==s.src.charCodeAt(Y++))return!1;if(s.level>=s.options.maxNesting)return!1;if(u)return!0;for(32===s.src.charCodeAt(Y)&&Y++,j=s.blkIndent,s.blkIndent=0,C=[s.bMarks[o]],s.bMarks[o]=Y,w=(Y=Y=Z,x=[s.tShift[o]],s.tShift[o]=Y-s.bMarks[o],$=s.parser.ruler.getRules("blockquote"),_=o+1;_=(Z=s.eMarks[_]));_++)if(62!==s.src.charCodeAt(Y++)){if(w)break;for(z=!1,V=0,U=$.length;V=Z,x.push(s.tShift[_]),s.tShift[_]=Y-s.bMarks[_];for(L=s.parentType,s.parentType="blockquote",s.tokens.push({type:"blockquote_open",lines:B=[o,0],level:s.level++}),s.parser.tokenize(s,o,_),s.tokens.push({type:"blockquote_close",level:--s.level}),s.parentType=L,B[1]=s.line,V=0;Vj)return!1;if(42!==(_=s.src.charCodeAt(C++))&&45!==_&&95!==_)return!1;for(w=1;C=0)Y=!0;else{if(!(($=skipBulletListMarker(s,o))>=0))return!1;Y=!1}if(s.level>=s.options.maxNesting)return!1;if(z=s.src.charCodeAt($-1),u)return!0;for(ee=s.tokens.length,Y?(B=s.bMarks[o]+s.tShift[o],U=Number(s.src.substr(B,$-B-1)),s.tokens.push({type:"ordered_list_open",order:U,lines:ae=[o,0],level:s.level++})):s.tokens.push({type:"bullet_list_open",lines:ae=[o,0],level:s.level++}),_=o,ie=!1,ce=s.parser.ruler.getRules("list");!(!(_=s.eMarks[_]?1:Z-$)>4&&(V=1),V<1&&(V=1),w=$-s.bMarks[_]+V,s.tokens.push({type:"list_item_open",lines:le=[o,0],level:s.level++}),C=s.blkIndent,j=s.tight,x=s.tShift[o],L=s.parentType,s.tShift[o]=Z-s.bMarks[o],s.blkIndent=w,s.tight=!0,s.parentType="list",s.parser.tokenize(s,o,i,!0),s.tight&&!ie||(ye=!1),ie=s.line-o>1&&s.isEmpty(s.line-1),s.blkIndent=C,s.tShift[o]=x,s.tight=j,s.parentType=L,s.tokens.push({type:"list_item_close",level:--s.level}),_=o=s.line,le[1]=_,Z=s.bMarks[o],_>=i)||s.isEmpty(_)||s.tShift[_]B)return!1;if(91!==s.src.charCodeAt(L))return!1;if(94!==s.src.charCodeAt(L+1))return!1;if(s.level>=s.options.maxNesting)return!1;for(C=L+2;C=B||58!==s.src.charCodeAt(++C))&&(u||(C++,s.env.footnotes||(s.env.footnotes={}),s.env.footnotes.refs||(s.env.footnotes.refs={}),j=s.src.slice(L+2,C-2),s.env.footnotes.refs[":"+j]=-1,s.tokens.push({type:"footnote_reference_open",label:j,level:s.level++}),_=s.bMarks[o],w=s.tShift[o],x=s.parentType,s.tShift[o]=s.skipSpaces(C)-C,s.bMarks[o]=C,s.blkIndent+=4,s.parentType="footnote",s.tShift[o]=j)return!1;if(35!==(_=s.src.charCodeAt(C))||C>=j)return!1;for(w=1,_=s.src.charCodeAt(++C);35===_&&C6||CC&&32===s.src.charCodeAt(x-1)&&(j=x),s.line=o+1,s.tokens.push({type:"heading_open",hLevel:w,lines:[o,s.line],level:s.level}),C=i)&&(!(s.tShift[x]3)&&(!((_=s.bMarks[x]+s.tShift[x])>=(w=s.eMarks[x]))&&((45===(u=s.src.charCodeAt(_))||61===u)&&(_=s.skipChars(_,u),!((_=s.skipSpaces(_))3||C+2>=j)return!1;if(60!==s.src.charCodeAt(C))return!1;if(33===(_=s.src.charCodeAt(C+1))||63===_){if(u)return!0}else{if(47!==_&&!function isLetter$1(s){var o=32|s;return o>=97&&o<=122}(_))return!1;if(47===_){if(!(w=s.src.slice(C,j).match(EC)))return!1}else if(!(w=s.src.slice(C,j).match(_C)))return!1;if(!0!==bC[w[1].toLowerCase()])return!1;if(u)return!0}for(x=o+1;xi)return!1;if(j=o+1,s.tShift[j]=s.eMarks[j])return!1;if(124!==(_=s.src.charCodeAt(x))&&45!==_&&58!==_)return!1;if(w=index_browser_getLine(s,o+1),!/^[-:| ]+$/.test(w))return!1;if((L=w.split("|"))<=2)return!1;for($=[],C=0;C=0;if(B=o+1,s.isEmpty(B)&&++B>i)return!1;if(s.tShift[B]=s.options.maxNesting)return!1;L=s.tokens.length,s.tokens.push({type:"dl_open",lines:j=[o,0],level:s.level++}),x=o,w=B;e:for(;;){for(ee=!0,Z=!1,s.tokens.push({type:"dt_open",lines:[x,x],level:s.level++}),s.tokens.push({type:"inline",content:s.getLines(x,x+1,s.blkIndent,!1).trim(),level:s.level+1,lines:[x,x],children:[]}),s.tokens.push({type:"dt_close",level:--s.level});;){if(s.tokens.push({type:"dd_open",lines:C=[B,0],level:s.level++}),Y=s.tight,V=s.ddIndent,$=s.blkIndent,z=s.tShift[w],U=s.parentType,s.blkIndent=s.ddIndent=s.tShift[w]+2,s.tShift[w]=_-s.bMarks[w],s.tight=!0,s.parentType="deflist",s.parser.tokenize(s,w,i,!0),s.tight&&!Z||(ee=!1),Z=s.line-w>1&&s.isEmpty(s.line-1),s.tShift[w]=z,s.tight=Y,s.parentType=U,s.blkIndent=$,s.ddIndent=V,s.tokens.push({type:"dd_close",level:--s.level}),C[1]=B=s.line,B>=i)break e;if(s.tShift[B]=i)break;if(x=B,s.isEmpty(x))break;if(s.tShift[x]=i)break;if(s.isEmpty(w)&&w++,w>=i)break;if(s.tShift[w]3)){for(_=!1,w=0,x=C.length;w=i))&&!(s.tShift[x]=0&&(s=s.replace(SC,(function(o,i){var u;return 10===s.charCodeAt(i)?(w=i+1,x=0,o):(u=" ".slice((i-w-x)%4),x=i-w+1,u)}))),_=new StateBlock(s,this,o,i,u),this.tokenize(_,_.line,_.lineMax)};for(var CC=[],OC=0;OC<256;OC++)CC.push(0);function isAlphaNum(s){return s>=48&&s<=57||s>=65&&s<=90||s>=97&&s<=122}function scanDelims(s,o){var i,u,_,w=o,x=!0,C=!0,j=s.posMax,L=s.src.charCodeAt(o);for(i=o>0?s.src.charCodeAt(o-1):-1;w=j&&(x=!1),(_=w-o)>=4?x=C=!1:(32!==(u=w?@[]^_`{|}~-".split("").forEach((function(s){CC[s.charCodeAt(0)]=1}));var AC=/\\([ \\!"#$%&'()*+,.\/:;<=>?@[\]^_`{|}~-])/g;var jC=/\\([ \\!"#$%&'()*+,.\/:;<=>?@[\]^_`{|}~-])/g;var IC=["coap","doi","javascript","aaa","aaas","about","acap","cap","cid","crid","data","dav","dict","dns","file","ftp","geo","go","gopher","h323","http","https","iax","icap","im","imap","info","ipp","iris","iris.beep","iris.xpc","iris.xpcs","iris.lwz","ldap","mailto","mid","msrp","msrps","mtqp","mupdate","news","nfs","ni","nih","nntp","opaquelocktoken","pop","pres","rtsp","service","session","shttp","sieve","sip","sips","sms","snmp","soap.beep","soap.beeps","tag","tel","telnet","tftp","thismessage","tn3270","tip","tv","urn","vemmi","ws","wss","xcon","xcon-userid","xmlrpc.beep","xmlrpc.beeps","xmpp","z39.50r","z39.50s","adiumxtra","afp","afs","aim","apt","attachment","aw","beshare","bitcoin","bolo","callto","chrome","chrome-extension","com-eventbrite-attendee","content","cvs","dlna-playsingle","dlna-playcontainer","dtn","dvb","ed2k","facetime","feed","finger","fish","gg","git","gizmoproject","gtalk","hcp","icon","ipn","irc","irc6","ircs","itms","jar","jms","keyparc","lastfm","ldaps","magnet","maps","market","message","mms","ms-help","msnim","mumble","mvn","notes","oid","palm","paparazzi","platform","proxy","psyc","query","res","resource","rmi","rsync","rtmp","secondlife","sftp","sgn","skype","smb","soldat","spotify","ssh","steam","svn","teamspeak","things","udp","unreal","ut2004","ventrilo","view-source","webcal","wtai","wyciwyg","xfire","xri","ymsgr"],PC=/^<([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)>/,MC=/^<([a-zA-Z.\-]{1,25}):([^<>\x00-\x20]*)>/;function replace$1(s,o){return s=s.source,o=o||"",function self(i,u){return i?(u=u.source||u,s=s.replace(i,u),self):new RegExp(s,o)}}var TC=replace$1(/(?:unquoted|single_quoted|double_quoted)/)("unquoted",/[^"'=<>`\x00-\x20]+/)("single_quoted",/'[^']*'/)("double_quoted",/"[^"]*"/)(),NC=replace$1(/(?:\s+attr_name(?:\s*=\s*attr_value)?)/)("attr_name",/[a-zA-Z_:][a-zA-Z0-9:._-]*/)("attr_value",TC)(),RC=replace$1(/<[A-Za-z][A-Za-z0-9]*attribute*\s*\/?>/)("attribute",NC)(),DC=replace$1(/^(?:open_tag|close_tag|comment|processing|declaration|cdata)/)("open_tag",RC)("close_tag",/<\/[A-Za-z][A-Za-z0-9]*\s*>/)("comment",/|/)("processing",/<[?].*?[?]>/)("declaration",/]*>/)("cdata",//)();var LC=/^&#((?:x[a-f0-9]{1,8}|[0-9]{1,8}));/i,BC=/^&([a-z][a-z0-9]{1,31});/i;var FC=[["text",function index_browser_text(s,o){for(var i=s.pos;i=0&&32===s.pending.charCodeAt(i))if(i>=1&&32===s.pending.charCodeAt(i-1)){for(var w=i-2;w>=0;w--)if(32!==s.pending.charCodeAt(w)){s.pending=s.pending.substring(0,w+1);break}s.push({type:"hardbreak",level:s.level})}else s.pending=s.pending.slice(0,-1),s.push({type:"softbreak",level:s.level});else s.push({type:"softbreak",level:s.level});for(_++;_=C)return!1;if(126!==s.src.charCodeAt(j+1))return!1;if(s.level>=s.options.maxNesting)return!1;if(w=j>0?s.src.charCodeAt(j-1):-1,x=s.src.charCodeAt(j+2),126===w)return!1;if(126===x)return!1;if(32===x||10===x)return!1;for(u=j+2;uj+3)return s.pos+=u-j,o||(s.pending+=s.src.slice(j,u)),!0;for(s.pos=j+2,_=1;s.pos+1=C)return!1;if(43!==s.src.charCodeAt(j+1))return!1;if(s.level>=s.options.maxNesting)return!1;if(w=j>0?s.src.charCodeAt(j-1):-1,x=s.src.charCodeAt(j+2),43===w)return!1;if(43===x)return!1;if(32===x||10===x)return!1;for(u=j+2;u=C)return!1;if(61!==s.src.charCodeAt(j+1))return!1;if(s.level>=s.options.maxNesting)return!1;if(w=j>0?s.src.charCodeAt(j-1):-1,x=s.src.charCodeAt(j+2),61===w)return!1;if(61===x)return!1;if(32===x||10===x)return!1;for(u=j+2;u=s.options.maxNesting)return!1;for(s.pos=B+i,C=[i];s.pos=_)return!1;if(s.level>=s.options.maxNesting)return!1;for(s.pos=w+1;s.pos<_;){if(126===s.src.charCodeAt(s.pos)){i=!0;break}s.parser.skipToken(s)}return i&&w+1!==s.pos?(u=s.src.slice(w+1,s.pos)).match(/(^|[^\\])(\\\\)*\s/)?(s.pos=w,!1):(s.posMax=s.pos,s.pos=w+1,o||s.push({type:"sub",level:s.level,content:u.replace(AC,"$1")}),s.pos=s.posMax+1,s.posMax=_,!0):(s.pos=w,!1)}],["sup",function sup(s,o){var i,u,_=s.posMax,w=s.pos;if(94!==s.src.charCodeAt(w))return!1;if(o)return!1;if(w+2>=_)return!1;if(s.level>=s.options.maxNesting)return!1;for(s.pos=w+1;s.pos<_;){if(94===s.src.charCodeAt(s.pos)){i=!0;break}s.parser.skipToken(s)}return i&&w+1!==s.pos?(u=s.src.slice(w+1,s.pos)).match(/(^|[^\\])(\\\\)*\s/)?(s.pos=w,!1):(s.posMax=s.pos,s.pos=w+1,o||s.push({type:"sup",level:s.level,content:u.replace(jC,"$1")}),s.pos=s.posMax+1,s.posMax=_,!0):(s.pos=w,!1)}],["links",function links(s,o){var i,u,_,w,x,C,j,L,B=!1,$=s.pos,V=s.posMax,U=s.pos,z=s.src.charCodeAt(U);if(33===z&&(B=!0,z=s.src.charCodeAt(++U)),91!==z)return!1;if(s.level>=s.options.maxNesting)return!1;if(i=U+1,(u=parseLinkLabel(s,U))<0)return!1;if((C=u+1)=V)return!1;for(U=C,parseLinkDestination(s,C)?(w=s.linkContent,C=s.pos):w="",U=C;C=V||41!==s.src.charCodeAt(C))return s.pos=$,!1;C++}else{if(s.linkLevel>0)return!1;for(;C=0?_=s.src.slice(U,C++):C=U-1),_||(void 0===_&&(C=u+1),_=s.src.slice(i,u)),!(j=s.env.references[normalizeReference(_)]))return s.pos=$,!1;w=j.href,x=j.title}return o||(s.pos=i,s.posMax=u,B?s.push({type:"image",src:w,title:x,alt:s.src.substr(i,u-i),level:s.level}):(s.push({type:"link_open",href:w,title:x,level:s.level++}),s.linkLevel++,s.parser.tokenize(s),s.linkLevel--,s.push({type:"link_close",level:--s.level}))),s.pos=C,s.posMax=V,!0}],["footnote_inline",function footnote_inline(s,o){var i,u,_,w,x=s.posMax,C=s.pos;return!(C+2>=x)&&(94===s.src.charCodeAt(C)&&(91===s.src.charCodeAt(C+1)&&(!(s.level>=s.options.maxNesting)&&(i=C+2,!((u=parseLinkLabel(s,C+1))<0)&&(o||(s.env.footnotes||(s.env.footnotes={}),s.env.footnotes.list||(s.env.footnotes.list=[]),_=s.env.footnotes.list.length,s.pos=i,s.posMax=u,s.push({type:"footnote_ref",id:_,level:s.level}),s.linkLevel++,w=s.tokens.length,s.parser.tokenize(s),s.env.footnotes.list[_]={tokens:s.tokens.splice(w)},s.linkLevel--),s.pos=u+1,s.posMax=x,!0)))))}],["footnote_ref",function footnote_ref(s,o){var i,u,_,w,x=s.posMax,C=s.pos;if(C+3>x)return!1;if(!s.env.footnotes||!s.env.footnotes.refs)return!1;if(91!==s.src.charCodeAt(C))return!1;if(94!==s.src.charCodeAt(C+1))return!1;if(s.level>=s.options.maxNesting)return!1;for(u=C+2;u=x)&&(u++,i=s.src.slice(C+2,u-1),void 0!==s.env.footnotes.refs[":"+i]&&(o||(s.env.footnotes.list||(s.env.footnotes.list=[]),s.env.footnotes.refs[":"+i]<0?(_=s.env.footnotes.list.length,s.env.footnotes.list[_]={label:i,count:0},s.env.footnotes.refs[":"+i]=_):_=s.env.footnotes.refs[":"+i],w=s.env.footnotes.list[_].count,s.env.footnotes.list[_].count++,s.push({type:"footnote_ref",id:_,subId:w,level:s.level})),s.pos=u,s.posMax=x,!0)))}],["autolink",function autolink(s,o){var i,u,_,w,x,C=s.pos;return 60===s.src.charCodeAt(C)&&(!((i=s.src.slice(C)).indexOf(">")<0)&&((u=i.match(MC))?!(IC.indexOf(u[1].toLowerCase())<0)&&(x=normalizeLink(w=u[0].slice(1,-1)),!!s.parser.validateLink(w)&&(o||(s.push({type:"link_open",href:x,level:s.level}),s.push({type:"text",content:w,level:s.level+1}),s.push({type:"link_close",level:s.level})),s.pos+=u[0].length,!0)):!!(_=i.match(PC))&&(x=normalizeLink("mailto:"+(w=_[0].slice(1,-1))),!!s.parser.validateLink(x)&&(o||(s.push({type:"link_open",href:x,level:s.level}),s.push({type:"text",content:w,level:s.level+1}),s.push({type:"link_close",level:s.level})),s.pos+=_[0].length,!0))))}],["htmltag",function htmltag(s,o){var i,u,_,w=s.pos;return!!s.options.html&&(_=s.posMax,!(60!==s.src.charCodeAt(w)||w+2>=_)&&(!(33!==(i=s.src.charCodeAt(w+1))&&63!==i&&47!==i&&!function isLetter$2(s){var o=32|s;return o>=97&&o<=122}(i))&&(!!(u=s.src.slice(w).match(DC))&&(o||s.push({type:"htmltag",content:s.src.slice(w,w+u[0].length),level:s.level}),s.pos+=u[0].length,!0))))}],["entity",function entity(s,o){var i,u,_=s.pos,w=s.posMax;if(38!==s.src.charCodeAt(_))return!1;if(_+10)s.pos=i;else{for(o=0;o<_;o++)if(u[o](s,!0))return void s.cacheSet(w,s.pos);s.pos++,s.cacheSet(w,s.pos)}},ParserInline.prototype.tokenize=function(s){for(var o,i,u=this.ruler.getRules(""),_=u.length,w=s.posMax;s.pos=w)break}else s.pending+=s.src[s.pos++]}s.pending&&s.pushPending()},ParserInline.prototype.parse=function(s,o,i,u){var _=new StateInline(s,this,o,i,u);this.tokenize(_)};var qC={default:{options:{html:!1,xhtmlOut:!1,breaks:!1,langPrefix:"language-",linkTarget:"",typographer:!1,quotes:"“”‘’",highlight:null,maxNesting:20},components:{core:{rules:["block","inline","references","replacements","smartquotes","references","abbr2","footnote_tail"]},block:{rules:["blockquote","code","fences","footnote","heading","hr","htmlblock","lheading","list","paragraph","table"]},inline:{rules:["autolink","backticks","del","emphasis","entity","escape","footnote_ref","htmltag","links","newline","text"]}}},full:{options:{html:!1,xhtmlOut:!1,breaks:!1,langPrefix:"language-",linkTarget:"",typographer:!1,quotes:"“”‘’",highlight:null,maxNesting:20},components:{core:{},block:{},inline:{}}},commonmark:{options:{html:!0,xhtmlOut:!0,breaks:!1,langPrefix:"language-",linkTarget:"",typographer:!1,quotes:"“”‘’",highlight:null,maxNesting:20},components:{core:{rules:["block","inline","references","abbr2"]},block:{rules:["blockquote","code","fences","heading","hr","htmlblock","lheading","list","paragraph"]},inline:{rules:["autolink","backticks","emphasis","entity","escape","htmltag","links","newline","text"]}}}};function StateCore(s,o,i){this.src=o,this.env=i,this.options=s.options,this.tokens=[],this.inlineMode=!1,this.inline=s.inline,this.block=s.block,this.renderer=s.renderer,this.typographer=s.typographer}function Remarkable(s,o){"string"!=typeof s&&(o=s,s="default"),o&&null!=o.linkify&&console.warn("linkify option is removed. Use linkify plugin instead:\n\nimport Remarkable from 'remarkable';\nimport linkify from 'remarkable/linkify';\nnew Remarkable().use(linkify)\n"),this.inline=new ParserInline,this.block=new ParserBlock,this.core=new Core,this.renderer=new Renderer,this.ruler=new Ruler,this.options={},this.configure(qC[s]),this.set(o||{})}Remarkable.prototype.set=function(s){index_browser_assign(this.options,s)},Remarkable.prototype.configure=function(s){var o=this;if(!s)throw new Error("Wrong `remarkable` preset, check name/content");s.options&&o.set(s.options),s.components&&Object.keys(s.components).forEach((function(i){s.components[i].rules&&o[i].ruler.enable(s.components[i].rules,!0)}))},Remarkable.prototype.use=function(s,o){return s(this,o),this},Remarkable.prototype.parse=function(s,o){var i=new StateCore(this,s,o);return this.core.process(i),i.tokens},Remarkable.prototype.render=function(s,o){return o=o||{},this.renderer.render(this.parse(s,o),this.options,o)},Remarkable.prototype.parseInline=function(s,o){var i=new StateCore(this,s,o);return i.inlineMode=!0,this.core.process(i),i.tokens},Remarkable.prototype.renderInline=function(s,o){return o=o||{},this.renderer.render(this.parseInline(s,o),this.options,o)};function indexOf(s,o){if(Array.prototype.indexOf)return s.indexOf(o);for(var i=0,u=s.length;i=0;i--)!0===o(s[i])&&s.splice(i,1)}function throwUnhandledCaseError(s){throw new Error("Unhandled case for value: '".concat(s,"'"))}var $C=function(){function HtmlTag(s){void 0===s&&(s={}),this.tagName="",this.attrs={},this.innerHTML="",this.whitespaceRegex=/\s+/,this.tagName=s.tagName||"",this.attrs=s.attrs||{},this.innerHTML=s.innerHtml||s.innerHTML||""}return HtmlTag.prototype.setTagName=function(s){return this.tagName=s,this},HtmlTag.prototype.getTagName=function(){return this.tagName||""},HtmlTag.prototype.setAttr=function(s,o){return this.getAttrs()[s]=o,this},HtmlTag.prototype.getAttr=function(s){return this.getAttrs()[s]},HtmlTag.prototype.setAttrs=function(s){return Object.assign(this.getAttrs(),s),this},HtmlTag.prototype.getAttrs=function(){return this.attrs||(this.attrs={})},HtmlTag.prototype.setClass=function(s){return this.setAttr("class",s)},HtmlTag.prototype.addClass=function(s){for(var o,i=this.getClass(),u=this.whitespaceRegex,_=i?i.split(u):[],w=s.split(u);o=w.shift();)-1===indexOf(_,o)&&_.push(o);return this.getAttrs().class=_.join(" "),this},HtmlTag.prototype.removeClass=function(s){for(var o,i=this.getClass(),u=this.whitespaceRegex,_=i?i.split(u):[],w=s.split(u);_.length&&(o=w.shift());){var x=indexOf(_,o);-1!==x&&_.splice(x,1)}return this.getAttrs().class=_.join(" "),this},HtmlTag.prototype.getClass=function(){return this.getAttrs().class||""},HtmlTag.prototype.hasClass=function(s){return-1!==(" "+this.getClass()+" ").indexOf(" "+s+" ")},HtmlTag.prototype.setInnerHTML=function(s){return this.innerHTML=s,this},HtmlTag.prototype.setInnerHtml=function(s){return this.setInnerHTML(s)},HtmlTag.prototype.getInnerHTML=function(){return this.innerHTML||""},HtmlTag.prototype.getInnerHtml=function(){return this.getInnerHTML()},HtmlTag.prototype.toAnchorString=function(){var s=this.getTagName(),o=this.buildAttrsStr();return["<",s,o=o?" "+o:"",">",this.getInnerHtml(),""].join("")},HtmlTag.prototype.buildAttrsStr=function(){if(!this.attrs)return"";var s=this.getAttrs(),o=[];for(var i in s)s.hasOwnProperty(i)&&o.push(i+'="'+s[i]+'"');return o.join(" ")},HtmlTag}();var VC=function(){function AnchorTagBuilder(s){void 0===s&&(s={}),this.newWindow=!1,this.truncate={},this.className="",this.newWindow=s.newWindow||!1,this.truncate=s.truncate||{},this.className=s.className||""}return AnchorTagBuilder.prototype.build=function(s){return new $C({tagName:"a",attrs:this.createAttrs(s),innerHtml:this.processAnchorText(s.getAnchorText())})},AnchorTagBuilder.prototype.createAttrs=function(s){var o={href:s.getAnchorHref()},i=this.createCssClass(s);return i&&(o.class=i),this.newWindow&&(o.target="_blank",o.rel="noopener noreferrer"),this.truncate&&this.truncate.length&&this.truncate.length=w)return x.host.length==o?(x.host.substr(0,o-_)+i).substr(0,w+u):buildSegment(j,w).substr(0,w+u);var L="";if(x.path&&(L+="/"+x.path),x.query&&(L+="?"+x.query),L){if((j+L).length>=w)return(j+L).length==o?(j+L).substr(0,o):(j+buildSegment(L,w-j.length)).substr(0,w+u);j+=L}if(x.fragment){var B="#"+x.fragment;if((j+B).length>=w)return(j+B).length==o?(j+B).substr(0,o):(j+buildSegment(B,w-j.length)).substr(0,w+u);j+=B}if(x.scheme&&x.host){var $=x.scheme+"://";if((j+$).length0&&(V=j.substr(-1*Math.floor(w/2))),(j.substr(0,Math.ceil(w/2))+i+V).substr(0,w+u)}(s,i):"middle"===u?function truncateMiddle(s,o,i){if(s.length<=o)return s;var u,_;null==i?(i="…",u=8,_=3):(u=i.length,_=i.length);var w=o-_,x="";return w>0&&(x=s.substr(-1*Math.floor(w/2))),(s.substr(0,Math.ceil(w/2))+i+x).substr(0,w+u)}(s,i):function truncateEnd(s,o,i){return function ellipsis(s,o,i){var u;return s.length>o&&(null==i?(i="…",u=3):u=i.length,s=s.substring(0,o-u)+i),s}(s,o,i)}(s,i)},AnchorTagBuilder}(),UC=function(){function Match(s){this.__jsduckDummyDocProp=null,this.matchedText="",this.offset=0,this.tagBuilder=s.tagBuilder,this.matchedText=s.matchedText,this.offset=s.offset}return Match.prototype.getMatchedText=function(){return this.matchedText},Match.prototype.setOffset=function(s){this.offset=s},Match.prototype.getOffset=function(){return this.offset},Match.prototype.getCssClassSuffixes=function(){return[this.getType()]},Match.prototype.buildTag=function(){return this.tagBuilder.build(this)},Match}(),extendStatics=function(s,o){return extendStatics=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(s,o){s.__proto__=o}||function(s,o){for(var i in o)Object.prototype.hasOwnProperty.call(o,i)&&(s[i]=o[i])},extendStatics(s,o)};function tslib_es6_extends(s,o){if("function"!=typeof o&&null!==o)throw new TypeError("Class extends value "+String(o)+" is not a constructor or null");function __(){this.constructor=s}extendStatics(s,o),s.prototype=null===o?Object.create(o):(__.prototype=o.prototype,new __)}var __assign=function(){return __assign=Object.assign||function __assign(s){for(var o,i=1,u=arguments.length;i-1},UrlMatchValidator.isValidUriScheme=function(s){var o=s.match(this.uriSchemeRegex),i=o&&o[0].toLowerCase();return"javascript:"!==i&&"vbscript:"!==i},UrlMatchValidator.urlMatchDoesNotHaveProtocolOrDot=function(s,o){return!(!s||o&&this.hasFullProtocolRegex.test(o)||-1!==s.indexOf("."))},UrlMatchValidator.urlMatchDoesNotHaveAtLeastOneWordChar=function(s,o){return!(!s||!o)&&(!this.hasFullProtocolRegex.test(o)&&!this.hasWordCharAfterProtocolRegex.test(s))},UrlMatchValidator.hasFullProtocolRegex=/^[A-Za-z][-.+A-Za-z0-9]*:\/\//,UrlMatchValidator.uriSchemeRegex=/^[A-Za-z][-.+A-Za-z0-9]*:/,UrlMatchValidator.hasWordCharAfterProtocolRegex=new RegExp(":[^\\s]*?["+nO+"]"),UrlMatchValidator.ipRegex=/[0-9][0-9]?[0-9]?\.[0-9][0-9]?[0-9]?\.[0-9][0-9]?[0-9]?\.[0-9][0-9]?[0-9]?(:[0-9]*)?\/?$/,UrlMatchValidator}(),vO=(zC=new RegExp("[/?#](?:["+aO+"\\-+&@#/%=~_()|'$*\\[\\]{}?!:,.;^✓]*["+aO+"\\-+&@#/%=~_()|'$*\\[\\]{}✓])?"),new RegExp(["(?:","(",/(?:[A-Za-z][-.+A-Za-z0-9]{0,63}:(?![A-Za-z][-.+A-Za-z0-9]{0,63}:\/\/)(?!\d+\/?)(?:\/\/)?)/.source,getDomainNameStr(2),")","|","(","(//)?",/(?:www\.)/.source,getDomainNameStr(6),")","|","(","(//)?",getDomainNameStr(10)+"\\.",hO.source,"(?![-"+iO+"])",")",")","(?::[0-9]+)?","(?:"+zC.source+")?"].join(""),"gi")),bO=new RegExp("["+aO+"]"),_O=function(s){function UrlMatcher(o){var i=s.call(this,o)||this;return i.stripPrefix={scheme:!0,www:!0},i.stripTrailingSlash=!0,i.decodePercentEncoding=!0,i.matcherRegex=vO,i.wordCharRegExp=bO,i.stripPrefix=o.stripPrefix,i.stripTrailingSlash=o.stripTrailingSlash,i.decodePercentEncoding=o.decodePercentEncoding,i}return tslib_es6_extends(UrlMatcher,s),UrlMatcher.prototype.parseMatches=function(s){for(var o,i=this.matcherRegex,u=this.stripPrefix,_=this.stripTrailingSlash,w=this.decodePercentEncoding,x=this.tagBuilder,C=[],_loop_1=function(){var i=o[0],L=o[1],B=o[4],$=o[5],V=o[9],U=o.index,z=$||V,Y=s.charAt(U-1);if(!yO.isValid(i,L))return"continue";if(U>0&&"@"===Y)return"continue";if(U>0&&z&&j.wordCharRegExp.test(Y))return"continue";if(/\?$/.test(i)&&(i=i.substr(0,i.length-1)),j.matchHasUnbalancedClosingParen(i))i=i.substr(0,i.length-1);else{var Z=j.matchHasInvalidCharAfterTld(i,L);Z>-1&&(i=i.substr(0,Z))}var ee=["http://","https://"].find((function(s){return!!L&&-1!==L.indexOf(s)}));if(ee){var ie=i.indexOf(ee);i=i.substr(ie),L=L.substr(ie),U+=ie}var ae=L?"scheme":B?"www":"tld",le=!!L;C.push(new GC({tagBuilder:x,matchedText:i,offset:U,urlMatchType:ae,url:i,protocolUrlMatch:le,protocolRelativeMatch:!!z,stripPrefix:u,stripTrailingSlash:_,decodePercentEncoding:w}))},j=this;null!==(o=i.exec(s));)_loop_1();return C},UrlMatcher.prototype.matchHasUnbalancedClosingParen=function(s){var o,i=s.charAt(s.length-1);if(")"===i)o="(";else if("]"===i)o="[";else{if("}"!==i)return!1;o="{"}for(var u=0,_=0,w=s.length-1;_-1&&w-x<=140){var _=s.slice(x,w),C=new KC({tagBuilder:o,matchedText:_,offset:x,serviceName:i,hashtag:_.slice(1)});u.push(C)}}},HashtagMatcher}(YC),SO=["twitter","facebook","instagram","tiktok"],xO=new RegExp("".concat(/(?:(?:(?:(\+)?\d{1,3}[-\040.]?)?\(?\d{3}\)?[-\040.]?\d{3}[-\040.]?\d{4})|(?:(\+)(?:9[976]\d|8[987530]\d|6[987]\d|5[90]\d|42\d|3[875]\d|2[98654321]\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)[-\040.]?(?:\d[-\040.]?){6,12}\d+))([,;]+[0-9]+#?)*/.source,"|").concat(/(0([1-9]{1}-?[1-9]\d{3}|[1-9]{2}-?\d{3}|[1-9]{2}\d{1}-?\d{2}|[1-9]{2}\d{2}-?\d{1})-?\d{4}|0[789]0-?\d{4}-?\d{4}|050-?\d{4}-?\d{4})/.source),"g"),kO=function(s){function PhoneMatcher(){var o=null!==s&&s.apply(this,arguments)||this;return o.matcherRegex=xO,o}return tslib_es6_extends(PhoneMatcher,s),PhoneMatcher.prototype.parseMatches=function(s){for(var o,i=this.matcherRegex,u=this.tagBuilder,_=[];null!==(o=i.exec(s));){var w=o[0],x=w.replace(/[^0-9,;#]/g,""),C=!(!o[1]&&!o[2]),j=0==o.index?"":s.substr(o.index-1,1),L=s.substr(o.index+w.length,1),B=!j.match(/\d/)&&!L.match(/\d/);this.testMatch(o[3])&&this.testMatch(w)&&B&&_.push(new JC({tagBuilder:u,matchedText:w,offset:o.index,number:x,plusSign:C}))}return _},PhoneMatcher.prototype.testMatch=function(s){return QC.test(s)},PhoneMatcher}(YC),CO=new RegExp("@[_".concat(aO,"]{1,50}(?![_").concat(aO,"])"),"g"),OO=new RegExp("@[_.".concat(aO,"]{1,30}(?![_").concat(aO,"])"),"g"),AO=new RegExp("@[-_.".concat(aO,"]{1,50}(?![-_").concat(aO,"])"),"g"),jO=new RegExp("@[_.".concat(aO,"]{1,23}[_").concat(aO,"](?![_").concat(aO,"])"),"g"),IO=new RegExp("[^"+aO+"]"),PO=function(s){function MentionMatcher(o){var i=s.call(this,o)||this;return i.serviceName="twitter",i.matcherRegexes={twitter:CO,instagram:OO,soundcloud:AO,tiktok:jO},i.nonWordCharRegex=IO,i.serviceName=o.serviceName,i}return tslib_es6_extends(MentionMatcher,s),MentionMatcher.prototype.parseMatches=function(s){var o,i=this.serviceName,u=this.matcherRegexes[this.serviceName],_=this.nonWordCharRegex,w=this.tagBuilder,x=[];if(!u)return x;for(;null!==(o=u.exec(s));){var C=o.index,j=s.charAt(C-1);if(0===C||_.test(j)){var L=o[0].replace(/\.+$/g,""),B=L.slice(1);x.push(new HC({tagBuilder:w,matchedText:L,offset:C,serviceName:i,mention:B}))}}return x},MentionMatcher}(YC);function parseHtml(s,o){for(var i=o.onOpenTag,u=o.onCloseTag,_=o.onText,w=o.onComment,x=o.onDoctype,C=new MO,j=0,L=s.length,B=0,$=0,V=C;j"===s?(V=new MO(__assign(__assign({},V),{name:captureTagName()})),emitTagAndPreviousTextNode()):XC.test(s)||ZC.test(s)||":"===s||resetToDataState()}function stateEndTagOpen(s){">"===s?resetToDataState():XC.test(s)?B=3:resetToDataState()}function stateBeforeAttributeName(s){eO.test(s)||("/"===s?B=12:">"===s?emitTagAndPreviousTextNode():"<"===s?startNewTag():"="===s||tO.test(s)||rO.test(s)?resetToDataState():B=5)}function stateAttributeName(s){eO.test(s)?B=6:"/"===s?B=12:"="===s?B=7:">"===s?emitTagAndPreviousTextNode():"<"===s?startNewTag():tO.test(s)&&resetToDataState()}function stateAfterAttributeName(s){eO.test(s)||("/"===s?B=12:"="===s?B=7:">"===s?emitTagAndPreviousTextNode():"<"===s?startNewTag():tO.test(s)?resetToDataState():B=5)}function stateBeforeAttributeValue(s){eO.test(s)||('"'===s?B=8:"'"===s?B=9:/[>=`]/.test(s)?resetToDataState():"<"===s?startNewTag():B=10)}function stateAttributeValueDoubleQuoted(s){'"'===s&&(B=11)}function stateAttributeValueSingleQuoted(s){"'"===s&&(B=11)}function stateAttributeValueUnquoted(s){eO.test(s)?B=4:">"===s?emitTagAndPreviousTextNode():"<"===s&&startNewTag()}function stateAfterAttributeValueQuoted(s){eO.test(s)?B=4:"/"===s?B=12:">"===s?emitTagAndPreviousTextNode():"<"===s?startNewTag():(B=4,function reconsumeCurrentCharacter(){j--}())}function stateSelfClosingStartTag(s){">"===s?(V=new MO(__assign(__assign({},V),{isClosing:!0})),emitTagAndPreviousTextNode()):B=4}function stateMarkupDeclarationOpen(o){"--"===s.substr(j,2)?(j+=2,V=new MO(__assign(__assign({},V),{type:"comment"})),B=14):"DOCTYPE"===s.substr(j,7).toUpperCase()?(j+=7,V=new MO(__assign(__assign({},V),{type:"doctype"})),B=20):resetToDataState()}function stateCommentStart(s){"-"===s?B=15:">"===s?resetToDataState():B=16}function stateCommentStartDash(s){"-"===s?B=18:">"===s?resetToDataState():B=16}function stateComment(s){"-"===s&&(B=17)}function stateCommentEndDash(s){B="-"===s?18:16}function stateCommentEnd(s){">"===s?emitTagAndPreviousTextNode():"!"===s?B=19:"-"===s||(B=16)}function stateCommentEndBang(s){"-"===s?B=17:">"===s?emitTagAndPreviousTextNode():B=16}function stateDoctype(s){">"===s?emitTagAndPreviousTextNode():"<"===s&&startNewTag()}function resetToDataState(){B=0,V=C}function startNewTag(){B=1,V=new MO({idx:j})}function emitTagAndPreviousTextNode(){var o=s.slice($,V.idx);o&&_(o,$),"comment"===V.type?w(V.idx):"doctype"===V.type?x(V.idx):(V.isOpening&&i(V.name,V.idx),V.isClosing&&u(V.name,V.idx)),resetToDataState(),$=j+1}function captureTagName(){var o=V.idx+(V.isClosing?2:1);return s.slice(o,j).toLowerCase()}$=0&&u++},onText:function(s,i){if(0===u){var w=function splitAndCapture(s,o){if(!o.global)throw new Error("`splitRegex` must have the 'g' flag set");for(var i,u=[],_=0;i=o.exec(s);)u.push(s.substring(_,i.index)),u.push(i[0]),_=i.index+i[0].length;return u.push(s.substring(_)),u}(s,/( | |<|<|>|>|"|"|')/gi),x=i;w.forEach((function(s,i){if(i%2==0){var u=o.parseText(s,x);_.push.apply(_,u)}x+=s.length}))}},onCloseTag:function(s){i.indexOf(s)>=0&&(u=Math.max(u-1,0))},onComment:function(s){},onDoctype:function(s){}}),_=this.compactMatches(_),_=this.removeUnwantedMatches(_)},Autolinker.prototype.compactMatches=function(s){s.sort((function(s,o){return s.getOffset()-o.getOffset()}));for(var o=0;o_?o:o+1;s.splice(x,1);continue}if(s[o+1].getOffset()/g,">"));for(var o=this.parse(s),i=[],u=0,_=0,w=o.length;_\s]/i.test(s)}function isLinkClose(s){return/^<\/a\s*>/i.test(s)}function createLinkifier(){var s=[],o=new NO({stripPrefix:!1,url:!0,email:!0,replaceFn:function(o){switch(o.getType()){case"url":s.push({text:o.matchedText,url:o.getUrl()});break;case"email":s.push({text:o.matchedText,url:"mailto:"+o.getEmail().replace(/^mailto:/i,"")})}return!1}});return{links:s,autolinker:o}}function parseTokens(s){var o,i,u,_,w,x,C,j,L,B,$,V,U,z=s.tokens,Y=null;for(i=0,u=z.length;i=0;o--)if("link_close"!==(w=_[o]).type){if("htmltag"===w.type&&(isLinkOpen(w.content)&&$>0&&$--,isLinkClose(w.content)&&$++),!($>0)&&"text"===w.type&&RO.test(w.content)){if(Y||(V=(Y=createLinkifier()).links,U=Y.autolinker),x=w.content,V.length=0,U.link(x),!V.length)continue;for(C=[],B=w.level,j=0;j({useUnsafeMarkdown:!1})}){if("string"!=typeof s)return null;const u=new Remarkable({html:!0,typographer:!0,breaks:!0,linkTarget:"_blank"}).use(linkify);u.core.ruler.disable(["replacements","smartquotes"]);const{useUnsafeMarkdown:_}=i(),w=u.render(s),x=sanitizer(w,{useUnsafeMarkdown:_});return s&&w&&x?Pe.createElement("div",{className:Hn()(o,"markdown"),dangerouslySetInnerHTML:{__html:x}}):null};function sanitizer(s,{useUnsafeMarkdown:o=!1}={}){const i=o,u=o?[]:["style","class"];return o&&!sanitizer.hasWarnedAboutDeprecation&&(console.warn("useUnsafeMarkdown display configuration parameter is deprecated since >3.26.0 and will be removed in v4.0.0."),sanitizer.hasWarnedAboutDeprecation=!0),LO().sanitize(s,{ADD_ATTR:["target"],FORBID_TAGS:["style","form"],ALLOW_DATA_ATTR:i,FORBID_ATTR:u})}sanitizer.hasWarnedAboutDeprecation=!1;class BaseLayout extends Pe.Component{render(){const{errSelectors:s,specSelectors:o,getComponent:i}=this.props,u=i("SvgAssets"),_=i("InfoContainer",!0),w=i("VersionPragmaFilter"),x=i("operations",!0),C=i("Models",!0),j=i("Webhooks",!0),L=i("Row"),B=i("Col"),$=i("errors",!0),V=i("ServersContainer",!0),U=i("SchemesContainer",!0),z=i("AuthorizeBtnContainer",!0),Y=i("FilterContainer",!0),Z=o.isSwagger2(),ee=o.isOAS3(),ie=o.isOAS31(),ae=!o.specStr(),le=o.loadingStatus();let ce=null;if("loading"===le&&(ce=Pe.createElement("div",{className:"info"},Pe.createElement("div",{className:"loading-container"},Pe.createElement("div",{className:"loading"})))),"failed"===le&&(ce=Pe.createElement("div",{className:"info"},Pe.createElement("div",{className:"loading-container"},Pe.createElement("h4",{className:"title"},"Failed to load API definition."),Pe.createElement($,null)))),"failedConfig"===le){const o=s.lastError(),i=o?o.get("message"):"";ce=Pe.createElement("div",{className:"info failed-config"},Pe.createElement("div",{className:"loading-container"},Pe.createElement("h4",{className:"title"},"Failed to load remote configuration."),Pe.createElement("p",null,i)))}if(!ce&&ae&&(ce=Pe.createElement("h4",null,"No API definition provided.")),ce)return Pe.createElement("div",{className:"swagger-ui"},Pe.createElement("div",{className:"loading-container"},ce));const pe=o.servers(),de=o.schemes(),fe=pe&&pe.size,ye=de&&de.size,be=!!o.securityDefinitions();return Pe.createElement("div",{className:"swagger-ui"},Pe.createElement(u,null),Pe.createElement(w,{isSwagger2:Z,isOAS3:ee,alsoShow:Pe.createElement($,null)},Pe.createElement($,null),Pe.createElement(L,{className:"information-container"},Pe.createElement(B,{mobile:12},Pe.createElement(_,null))),fe||ye||be?Pe.createElement("div",{className:"scheme-container"},Pe.createElement(B,{className:"schemes wrapper",mobile:12},fe||ye?Pe.createElement("div",{className:"schemes-server-container"},fe?Pe.createElement(V,null):null,ye?Pe.createElement(U,null):null):null,be?Pe.createElement(z,null):null)):null,Pe.createElement(Y,null),Pe.createElement(L,null,Pe.createElement(B,{mobile:12,desktop:12},Pe.createElement(x,null))),ie&&Pe.createElement(L,{className:"webhooks-container"},Pe.createElement(B,{mobile:12,desktop:12},Pe.createElement(j,null))),Pe.createElement(L,null,Pe.createElement(B,{mobile:12,desktop:12},Pe.createElement(C,null)))))}}const core_components=()=>({components:{App:fk,authorizationPopup:AuthorizationPopup,authorizeBtn:AuthorizeBtn,AuthorizeBtnContainer,authorizeOperationBtn:AuthorizeOperationBtn,auths:Auths,AuthItem:auth_item_Auths,authError:AuthError,oauth2:Oauth2,apiKeyAuth:ApiKeyAuth,basicAuth:BasicAuth,clear:Clear,liveResponse:LiveResponse,InitializedInput,info:qk,InfoContainer,InfoUrl,InfoBasePath,Contact:Vk,License:zk,JumpToPath,CopyToClipboardBtn,onlineValidatorBadge:OnlineValidatorBadge,operations:Operations,operation:operation_Operation,OperationSummary,OperationSummaryMethod,OperationSummaryPath,responses:responses_Responses,response:response_Response,ResponseExtension:response_extension,responseBody:ResponseBody,parameters:Parameters,parameterRow:ParameterRow,execute:Execute,headers:headers_Headers,errors:Errors,contentType:ContentType,overview:Overview,footer:Footer,FilterContainer,ParamBody,curl:Curl,Property:property,TryItOutButton,Markdown:BO,BaseLayout,VersionPragmaFilter,VersionStamp:version_stamp,OperationExt:operation_extensions,OperationExtRow:operation_extension_row,ParameterExt:parameter_extension,ParameterIncludeEmpty,OperationTag,OperationContainer,OpenAPIVersion:openapi_version,DeepLink:deep_link,SvgAssets:svg_assets,Example:example_Example,ExamplesSelect,ExamplesSelectValueRetainer}}),form_components=()=>({components:{...ye}}),base=()=>[configsPlugin,util,logs,view,view_legacy,plugins_spec,err,icons,plugins_layout,json_schema_5,json_schema_5_samples,core_components,form_components,swagger_client,auth,downloadUrlPlugin,deep_linking,filter,on_complete,plugins_request_snippets,syntax_highlighting,versions,safe_render()],FO=(0,qe.Map)();function onlyOAS3(s){return(o,i)=>(...u)=>{if(i.getSystem().specSelectors.isOAS3()){const o=s(...u);return"function"==typeof o?o(i):o}return o(...u)}}const qO=onlyOAS3(Ss()(null)),$O=onlyOAS3(((s,o)=>s=>s.getSystem().specSelectors.findSchema(o))),VO=onlyOAS3((()=>s=>{const o=s.getSystem().specSelectors.specJson().getIn(["components","schemas"]);return qe.Map.isMap(o)?o:FO})),UO=onlyOAS3((()=>s=>s.getSystem().specSelectors.specJson().hasIn(["servers",0]))),zO=onlyOAS3(Ut(Ms,(s=>s.getIn(["components","securitySchemes"])||null))),wrap_selectors_validOperationMethods=(s,o)=>(i,...u)=>o.specSelectors.isOAS3()?o.oas3Selectors.validOperationMethods():s(...u),WO=qO,KO=qO,HO=qO,JO=qO,GO=qO;const YO=function wrap_selectors_onlyOAS3(s){return(o,i)=>(...u)=>{if(i.getSystem().specSelectors.isOAS3()){let o=i.getState().getIn(["spec","resolvedSubtrees","components","securitySchemes"]);return s(i,o,...u)}return o(...u)}}(Ut((s=>s),(({specSelectors:s})=>s.securityDefinitions()),((s,o)=>{let i=(0,qe.List)();return o?(o.entrySeq().forEach((([s,o])=>{const u=o.get("type");if("oauth2"===u&&o.get("flows").entrySeq().forEach((([u,_])=>{let w=(0,qe.fromJS)({flow:u,authorizationUrl:_.get("authorizationUrl"),tokenUrl:_.get("tokenUrl"),scopes:_.get("scopes"),type:o.get("type"),description:o.get("description")});i=i.push(new qe.Map({[s]:w.filter((s=>void 0!==s))}))})),"http"!==u&&"apiKey"!==u||(i=i.push(new qe.Map({[s]:o}))),"openIdConnect"===u&&o.get("openIdConnectData")){let u=o.get("openIdConnectData");(u.get("grant_types_supported")||["authorization_code","implicit"]).forEach((_=>{let w=u.get("scopes_supported")&&u.get("scopes_supported").reduce(((s,o)=>s.set(o,"")),new qe.Map),x=(0,qe.fromJS)({flow:_,authorizationUrl:u.get("authorization_endpoint"),tokenUrl:u.get("token_endpoint"),scopes:w,type:"oauth2",openIdConnectUrl:o.get("openIdConnectUrl")});i=i.push(new qe.Map({[s]:x.filter((s=>void 0!==s))}))}))}})),i):i})));function OAS3ComponentWrapFactory(s){return(o,i)=>u=>"function"==typeof i.specSelectors?.isOAS3?i.specSelectors.isOAS3()?Pe.createElement(s,Rn()({},u,i,{Ori:o})):Pe.createElement(o,u):(console.warn("OAS3 wrapper: couldn't get spec"),null)}const XO=(0,qe.Map)(),selectors_isSwagger2=()=>s=>function isSwagger2(s){const o=s.get("swagger");return"string"==typeof o&&"2.0"===o}(s.getSystem().specSelectors.specJson()),selectors_isOAS30=()=>s=>function isOAS30(s){const o=s.get("openapi");return"string"==typeof o&&/^3\.0\.([0123])(?:-rc[012])?$/.test(o)}(s.getSystem().specSelectors.specJson()),selectors_isOAS3=()=>s=>s.getSystem().specSelectors.isOAS30();function selectors_onlyOAS3(s){return(o,...i)=>u=>{if(u.specSelectors.isOAS3()){const _=s(o,...i);return"function"==typeof _?_(u):_}return null}}const ZO=selectors_onlyOAS3((()=>s=>s.specSelectors.specJson().get("servers",XO))),findSchema=(s,o)=>{const i=s.getIn(["resolvedSubtrees","components","schemas",o],null),u=s.getIn(["json","components","schemas",o],null);return i||u||null},QO=selectors_onlyOAS3(((s,{callbacks:o,specPath:i})=>s=>{const u=s.specSelectors.validOperationMethods();return qe.Map.isMap(o)?o.reduce(((s,o,_)=>{if(!qe.Map.isMap(o))return s;const w=o.reduce(((s,o,w)=>{if(!qe.Map.isMap(o))return s;const x=o.entrySeq().filter((([s])=>u.includes(s))).map((([s,o])=>({operation:(0,qe.Map)({operation:o}),method:s,path:w,callbackName:_,specPath:i.concat([_,w,s])})));return s.concat(x)}),(0,qe.List)());return s.concat(w)}),(0,qe.List)()).groupBy((s=>s.callbackName)).map((s=>s.toArray())).toObject():{}})),callbacks=({callbacks:s,specPath:o,specSelectors:i,getComponent:u})=>{const _=i.callbacksOperations({callbacks:s,specPath:o}),w=Object.keys(_),x=u("OperationContainer",!0);return 0===w.length?Pe.createElement("span",null,"No callbacks"):Pe.createElement("div",null,w.map((s=>Pe.createElement("div",{key:`${s}`},Pe.createElement("h2",null,s),_[s].map((o=>Pe.createElement(x,{key:`${s}-${o.path}-${o.method}`,op:o.operation,tag:"callbacks",method:o.method,path:o.path,specPath:o.specPath,allowTryItOut:!1})))))))},getDefaultRequestBodyValue=(s,o,i,u)=>{const _=s.getIn(["content",o])??(0,qe.OrderedMap)(),w=_.get("schema",(0,qe.OrderedMap)()).toJS(),x=void 0!==_.get("examples"),C=_.get("example"),j=x?_.getIn(["examples",i,"value"]):C;return stringify(u.getSampleSchema(w,o,{includeWriteOnly:!0},j))},components_request_body=({userHasEditedBody:s,requestBody:o,requestBodyValue:i,requestBodyInclusionSetting:u,requestBodyErrors:_,getComponent:w,getConfigs:x,specSelectors:C,fn:j,contentType:L,isExecute:B,specPath:$,onChange:V,onChangeIncludeEmpty:U,activeExamplesKey:z,updateActiveExamplesKey:Y,setRetainRequestBodyValueFlag:Z})=>{const handleFile=s=>{V(s.target.files[0])},setIsIncludedOptions=s=>{let o={key:s,shouldDispatchInit:!1,defaultValue:!0};return"no value"===u.get(s,"no value")&&(o.shouldDispatchInit=!0),o},ee=w("Markdown",!0),ie=w("modelExample"),ae=w("RequestBodyEditor"),le=w("HighlightCode",!0),ce=w("ExamplesSelectValueRetainer"),pe=w("Example"),de=w("ParameterIncludeEmpty"),{showCommonExtensions:fe}=x(),ye=o?.get("description")??null,be=o?.get("content")??new qe.OrderedMap;L=L||be.keySeq().first()||"";const _e=be.get(L)??(0,qe.OrderedMap)(),we=_e.get("schema",(0,qe.OrderedMap)()),Se=_e.get("examples",null),xe=Se?.map(((s,i)=>{const u=s?.get("value",null);return u&&(s=s.set("value",getDefaultRequestBodyValue(o,L,i,j),u)),s}));if(_=qe.List.isList(_)?_:(0,qe.List)(),!_e.size)return null;const Te="object"===_e.getIn(["schema","type"]),Re="binary"===_e.getIn(["schema","format"]),$e="base64"===_e.getIn(["schema","format"]);if("application/octet-stream"===L||0===L.indexOf("image/")||0===L.indexOf("audio/")||0===L.indexOf("video/")||Re||$e){const s=w("Input");return B?Pe.createElement(s,{type:"file",onChange:handleFile}):Pe.createElement("i",null,"Example values are not available for ",Pe.createElement("code",null,L)," media types.")}if(Te&&("application/x-www-form-urlencoded"===L||0===L.indexOf("multipart/"))&&we.get("properties",(0,qe.OrderedMap)()).size>0){const s=w("JsonSchemaForm"),o=w("ParameterExt"),x=we.get("properties",(0,qe.OrderedMap)());return i=qe.Map.isMap(i)?i:(0,qe.OrderedMap)(),Pe.createElement("div",{className:"table-container"},ye&&Pe.createElement(ee,{source:ye}),Pe.createElement("table",null,Pe.createElement("tbody",null,qe.Map.isMap(x)&&x.entrySeq().map((([x,C])=>{if(C.get("readOnly"))return;const L=C.get("oneOf")?.get(0)?.toJS(),$=C.get("anyOf")?.get(0)?.toJS();C=(0,qe.fromJS)(j.mergeJsonSchema(C.toJS(),L??$??{}));let z=fe?getCommonExtensions(C):null;const Y=we.get("required",(0,qe.List)()).includes(x),Z=C.get("type"),ie=C.get("format"),ae=C.get("description"),le=i.getIn([x,"value"]),ce=i.getIn([x,"errors"])||_,pe=u.get(x)||!1;let ye=j.getSampleSchema(C,!1,{includeWriteOnly:!0});!1===ye&&(ye="false"),0===ye&&(ye="0"),"string"!=typeof ye&&"object"===Z&&(ye=stringify(ye)),"string"==typeof ye&&"array"===Z&&(ye=JSON.parse(ye));const be="string"===Z&&("binary"===ie||"base64"===ie);return Pe.createElement("tr",{key:x,className:"parameters","data-property-name":x},Pe.createElement("td",{className:"parameters-col_name"},Pe.createElement("div",{className:Y?"parameter__name required":"parameter__name"},x,Y?Pe.createElement("span",null," *"):null),Pe.createElement("div",{className:"parameter__type"},Z,ie&&Pe.createElement("span",{className:"prop-format"},"($",ie,")"),fe&&z.size?z.entrySeq().map((([s,i])=>Pe.createElement(o,{key:`${s}-${i}`,xKey:s,xVal:i}))):null),Pe.createElement("div",{className:"parameter__deprecated"},C.get("deprecated")?"deprecated":null)),Pe.createElement("td",{className:"parameters-col_description"},Pe.createElement(ee,{source:ae}),B?Pe.createElement("div",null,Pe.createElement(s,{fn:j,dispatchInitialValue:!be,schema:C,description:x,getComponent:w,value:void 0===le?ye:le,required:Y,errors:ce,onChange:s=>{V(s,[x])}}),Y?null:Pe.createElement(de,{onChange:s=>U(x,s),isIncluded:pe,isIncludedOptions:setIsIncludedOptions(x),isDisabled:Array.isArray(le)?0!==le.length:!isEmptyValue(le)})):null))})))))}const ze=getDefaultRequestBodyValue(o,L,z,j);let We=null;return getKnownSyntaxHighlighterLanguage(ze)&&(We="json"),Pe.createElement("div",null,ye&&Pe.createElement(ee,{source:ye}),xe?Pe.createElement(ce,{userHasEditedBody:s,examples:xe,currentKey:z,currentUserInputValue:i,onSelect:s=>{Y(s)},updateValue:V,defaultToFirstExample:!0,getComponent:w,setRetainRequestBodyValueFlag:Z}):null,B?Pe.createElement("div",null,Pe.createElement(ae,{value:i,errors:_,defaultValue:ze,onChange:V,getComponent:w})):Pe.createElement(ie,{getComponent:w,getConfigs:x,specSelectors:C,expandDepth:1,isExecute:B,schema:_e.get("schema"),specPath:$.push("content",L),example:Pe.createElement(le,{className:"body-param__example",language:We},stringify(i)||ze),includeWriteOnly:!0}),xe?Pe.createElement(pe,{example:xe.get(z),getComponent:w,getConfigs:x}):null)};class operation_link_OperationLink extends Pe.Component{render(){const{link:s,name:o,getComponent:i}=this.props,u=i("Markdown",!0);let _=s.get("operationId")||s.get("operationRef"),w=s.get("parameters")&&s.get("parameters").toJS(),x=s.get("description");return Pe.createElement("div",{className:"operation-link"},Pe.createElement("div",{className:"description"},Pe.createElement("b",null,Pe.createElement("code",null,o)),x?Pe.createElement(u,{source:x}):null),Pe.createElement("pre",null,"Operation `",_,"`",Pe.createElement("br",null),Pe.createElement("br",null),"Parameters ",function padString(s,o){if("string"!=typeof o)return"";return o.split("\n").map(((o,i)=>i>0?Array(s+1).join(" ")+o:o)).join("\n")}(0,JSON.stringify(w,null,2))||"{}",Pe.createElement("br",null)))}}const eA=operation_link_OperationLink,components_servers=({servers:s,currentServer:o,setSelectedServer:i,setServerVariableValue:u,getServerVariable:_,getEffectiveServerValue:w})=>{const x=(s.find((s=>s.get("url")===o))||(0,qe.OrderedMap)()).get("variables")||(0,qe.OrderedMap)(),C=0!==x.size;(0,Pe.useEffect)((()=>{o||i(s.first()?.get("url"))}),[]),(0,Pe.useEffect)((()=>{const _=s.find((s=>s.get("url")===o));if(!_)return void i(s.first().get("url"));(_.get("variables")||(0,qe.OrderedMap)()).map(((s,i)=>{u({server:o,key:i,val:s.get("default")||""})}))}),[o,s]);const j=(0,Pe.useCallback)((s=>{i(s.target.value)}),[i]),L=(0,Pe.useCallback)((s=>{const i=s.target.getAttribute("data-variable"),_=s.target.value;u({server:o,key:i,val:_})}),[u,o]);return Pe.createElement("div",{className:"servers"},Pe.createElement("label",{htmlFor:"servers"},Pe.createElement("select",{onChange:j,value:o,id:"servers"},s.valueSeq().map((s=>Pe.createElement("option",{value:s.get("url"),key:s.get("url")},s.get("url"),s.get("description")&&` - ${s.get("description")}`))).toArray())),C&&Pe.createElement("div",null,Pe.createElement("div",{className:"computed-url"},"Computed URL:",Pe.createElement("code",null,w(o))),Pe.createElement("h4",null,"Server variables"),Pe.createElement("table",null,Pe.createElement("tbody",null,x.entrySeq().map((([s,i])=>Pe.createElement("tr",{key:s},Pe.createElement("td",null,s),Pe.createElement("td",null,i.get("enum")?Pe.createElement("select",{"data-variable":s,onChange:L},i.get("enum").map((i=>Pe.createElement("option",{selected:i===_(o,s),key:i,value:i},i)))):Pe.createElement("input",{type:"text",value:_(o,s)||"",onChange:L,"data-variable":s})))))))))};class ServersContainer extends Pe.Component{render(){const{specSelectors:s,oas3Selectors:o,oas3Actions:i,getComponent:u}=this.props,_=s.servers(),w=u("Servers");return _&&_.size?Pe.createElement("div",null,Pe.createElement("span",{className:"servers-title"},"Servers"),Pe.createElement(w,{servers:_,currentServer:o.selectedServer(),setSelectedServer:i.setSelectedServer,setServerVariableValue:i.setServerVariableValue,getServerVariable:o.serverVariableValue,getEffectiveServerValue:o.serverEffectiveValue})):null}}const tA=Function.prototype;class RequestBodyEditor extends Pe.PureComponent{static defaultProps={onChange:tA,userHasEditedBody:!1};constructor(s,o){super(s,o),this.state={value:stringify(s.value)||s.defaultValue},s.onChange(s.value)}applyDefaultValue=s=>{const{onChange:o,defaultValue:i}=s||this.props;return this.setState({value:i}),o(i)};onChange=s=>{this.props.onChange(stringify(s))};onDomChange=s=>{const o=s.target.value;this.setState({value:o},(()=>this.onChange(o)))};UNSAFE_componentWillReceiveProps(s){this.props.value!==s.value&&s.value!==this.state.value&&this.setState({value:stringify(s.value)}),!s.value&&s.defaultValue&&this.state.value&&this.applyDefaultValue(s)}render(){let{getComponent:s,errors:o}=this.props,{value:i}=this.state,u=o.size>0;const _=s("TextArea");return Pe.createElement("div",{className:"body-param"},Pe.createElement(_,{className:Hn()("body-param__text",{invalid:u}),title:o.size?o.join(", "):"",value:i,onChange:this.onDomChange}))}}class HttpAuth extends Pe.Component{constructor(s,o){super(s,o);let{name:i,schema:u}=this.props,_=this.getValue();this.state={name:i,schema:u,value:_}}getValue(){let{name:s,authorized:o}=this.props;return o&&o.getIn([s,"value"])}onChange=s=>{let{onChange:o}=this.props,{value:i,name:u}=s.target,_=Object.assign({},this.state.value);u?_[u]=i:_=i,this.setState({value:_},(()=>o(this.state)))};render(){let{schema:s,getComponent:o,errSelectors:i,name:u}=this.props;const _=o("Input"),w=o("Row"),x=o("Col"),C=o("authError"),j=o("Markdown",!0),L=o("JumpToPath",!0),B=(s.get("scheme")||"").toLowerCase();let $=this.getValue(),V=i.allErrors().filter((s=>s.get("authId")===u));if("basic"===B){let o=$?$.get("username"):null;return Pe.createElement("div",null,Pe.createElement("h4",null,Pe.createElement("code",null,u||s.get("name")),"  (http, Basic)",Pe.createElement(L,{path:["securityDefinitions",u]})),o&&Pe.createElement("h6",null,"Authorized"),Pe.createElement(w,null,Pe.createElement(j,{source:s.get("description")})),Pe.createElement(w,null,Pe.createElement("label",{htmlFor:"auth-basic-username"},"Username:"),o?Pe.createElement("code",null," ",o," "):Pe.createElement(x,null,Pe.createElement(_,{id:"auth-basic-username",type:"text",required:"required",name:"username","aria-label":"auth-basic-username",onChange:this.onChange,autoFocus:!0}))),Pe.createElement(w,null,Pe.createElement("label",{htmlFor:"auth-basic-password"},"Password:"),o?Pe.createElement("code",null," ****** "):Pe.createElement(x,null,Pe.createElement(_,{id:"auth-basic-password",autoComplete:"new-password",name:"password",type:"password","aria-label":"auth-basic-password",onChange:this.onChange}))),V.valueSeq().map(((s,o)=>Pe.createElement(C,{error:s,key:o}))))}return"bearer"===B?Pe.createElement("div",null,Pe.createElement("h4",null,Pe.createElement("code",null,u||s.get("name")),"  (http, Bearer)",Pe.createElement(L,{path:["securityDefinitions",u]})),$&&Pe.createElement("h6",null,"Authorized"),Pe.createElement(w,null,Pe.createElement(j,{source:s.get("description")})),Pe.createElement(w,null,Pe.createElement("label",{htmlFor:"auth-bearer-value"},"Value:"),$?Pe.createElement("code",null," ****** "):Pe.createElement(x,null,Pe.createElement(_,{id:"auth-bearer-value",type:"text","aria-label":"auth-bearer-value",onChange:this.onChange,autoFocus:!0}))),V.valueSeq().map(((s,o)=>Pe.createElement(C,{error:s,key:o})))):Pe.createElement("div",null,Pe.createElement("em",null,Pe.createElement("b",null,u)," HTTP authentication: unsupported scheme ",`'${B}'`))}}class operation_servers_OperationServers extends Pe.Component{setSelectedServer=s=>{const{path:o,method:i}=this.props;return this.forceUpdate(),this.props.setSelectedServer(s,`${o}:${i}`)};setServerVariableValue=s=>{const{path:o,method:i}=this.props;return this.forceUpdate(),this.props.setServerVariableValue({...s,namespace:`${o}:${i}`})};getSelectedServer=()=>{const{path:s,method:o}=this.props;return this.props.getSelectedServer(`${s}:${o}`)};getServerVariable=(s,o)=>{const{path:i,method:u}=this.props;return this.props.getServerVariable({namespace:`${i}:${u}`,server:s},o)};getEffectiveServerValue=s=>{const{path:o,method:i}=this.props;return this.props.getEffectiveServerValue({server:s,namespace:`${o}:${i}`})};render(){const{operationServers:s,pathServers:o,getComponent:i}=this.props;if(!s&&!o)return null;const u=i("Servers"),_=s||o,w=s?"operation":"path";return Pe.createElement("div",{className:"opblock-section operation-servers"},Pe.createElement("div",{className:"opblock-section-header"},Pe.createElement("div",{className:"tab-header"},Pe.createElement("h4",{className:"opblock-title"},"Servers"))),Pe.createElement("div",{className:"opblock-description-wrapper"},Pe.createElement("h4",{className:"message"},"These ",w,"-level options override the global server options."),Pe.createElement(u,{servers:_,currentServer:this.getSelectedServer(),setSelectedServer:this.setSelectedServer,setServerVariableValue:this.setServerVariableValue,getServerVariable:this.getServerVariable,getEffectiveServerValue:this.getEffectiveServerValue})))}}const rA={Callbacks:callbacks,HttpAuth,RequestBody:components_request_body,Servers:components_servers,ServersContainer,RequestBodyEditor,OperationServers:operation_servers_OperationServers,operationLink:eA},nA=new Remarkable("commonmark");nA.block.ruler.enable(["table"]),nA.set({linkTarget:"_blank"});const sA=OAS3ComponentWrapFactory((({source:s,className:o="",getConfigs:i=()=>({useUnsafeMarkdown:!1})})=>{if("string"!=typeof s)return null;if(s){const{useUnsafeMarkdown:u}=i(),_=sanitizer(nA.render(s),{useUnsafeMarkdown:u});let w;return"string"==typeof _&&(w=_.trim()),Pe.createElement("div",{dangerouslySetInnerHTML:{__html:w},className:Hn()(o,"renderedMarkdown")})}return null})),oA=OAS3ComponentWrapFactory((({Ori:s,...o})=>{const{schema:i,getComponent:u,errSelectors:_,authorized:w,onAuthChange:x,name:C}=o,j=u("HttpAuth");return"http"===i.get("type")?Pe.createElement(j,{key:C,schema:i,name:C,errSelectors:_,authorized:w,getComponent:u,onChange:x}):Pe.createElement(s,o)})),iA=OAS3ComponentWrapFactory(OnlineValidatorBadge);class ModelComponent extends Pe.Component{render(){let{getConfigs:s,schema:o,Ori:i}=this.props,u=["model-box"],_=null;return!0===o.get("deprecated")&&(u.push("deprecated"),_=Pe.createElement("span",{className:"model-deprecated-warning"},"Deprecated:")),Pe.createElement("div",{className:u.join(" ")},_,Pe.createElement(i,Rn()({},this.props,{getConfigs:s,depth:1,expandDepth:this.props.expandDepth||0})))}}const aA=OAS3ComponentWrapFactory(ModelComponent),lA=OAS3ComponentWrapFactory((({Ori:s,...o})=>{const{schema:i,getComponent:u,errors:_,onChange:w}=o,x=i&&i.get?i.get("format"):null,C=i&&i.get?i.get("type"):null,j=u("Input");return C&&"string"===C&&x&&("binary"===x||"base64"===x)?Pe.createElement(j,{type:"file",className:_.length?"invalid":"",title:_.length?_:"",onChange:s=>{w(s.target.files[0])},disabled:s.isDisabled}):Pe.createElement(s,o)})),cA={Markdown:sA,AuthItem:oA,OpenAPIVersion:function OAS30ComponentWrapFactory(s){return(o,i)=>u=>"function"==typeof i.specSelectors?.isOAS30?i.specSelectors.isOAS30()?Pe.createElement(s,Rn()({},u,i,{Ori:o})):Pe.createElement(o,u):(console.warn("OAS30 wrapper: couldn't get spec"),null)}((s=>{const{Ori:o}=s;return Pe.createElement(o,{oasVersion:"3.0"})})),JsonSchema_string:lA,model:aA,onlineValidatorBadge:iA},uA="oas3_set_servers",pA="oas3_set_request_body_value",hA="oas3_set_request_body_retain_flag",dA="oas3_set_request_body_inclusion",fA="oas3_set_active_examples_member",mA="oas3_set_request_content_type",gA="oas3_set_response_content_type",yA="oas3_set_server_variable_value",vA="oas3_set_request_body_validate_error",bA="oas3_clear_request_body_validate_error",_A="oas3_clear_request_body_value";function setSelectedServer(s,o){return{type:uA,payload:{selectedServerUrl:s,namespace:o}}}function setRequestBodyValue({value:s,pathMethod:o}){return{type:pA,payload:{value:s,pathMethod:o}}}const setRetainRequestBodyValueFlag=({value:s,pathMethod:o})=>({type:hA,payload:{value:s,pathMethod:o}});function setRequestBodyInclusion({value:s,pathMethod:o,name:i}){return{type:dA,payload:{value:s,pathMethod:o,name:i}}}function setActiveExamplesMember({name:s,pathMethod:o,contextType:i,contextName:u}){return{type:fA,payload:{name:s,pathMethod:o,contextType:i,contextName:u}}}function setRequestContentType({value:s,pathMethod:o}){return{type:mA,payload:{value:s,pathMethod:o}}}function setResponseContentType({value:s,path:o,method:i}){return{type:gA,payload:{value:s,path:o,method:i}}}function setServerVariableValue({server:s,namespace:o,key:i,val:u}){return{type:yA,payload:{server:s,namespace:o,key:i,val:u}}}const setRequestBodyValidateError=({path:s,method:o,validationErrors:i})=>({type:vA,payload:{path:s,method:o,validationErrors:i}}),clearRequestBodyValidateError=({path:s,method:o})=>({type:bA,payload:{path:s,method:o}}),initRequestBodyValidateError=({pathMethod:s})=>({type:bA,payload:{path:s[0],method:s[1]}}),clearRequestBodyValue=({pathMethod:s})=>({type:_A,payload:{pathMethod:s}});var EA=__webpack_require__(60680),wA=__webpack_require__.n(EA);const oas3_selectors_onlyOAS3=s=>(o,...i)=>u=>{if(u.getSystem().specSelectors.isOAS3()){const _=s(o,...i);return"function"==typeof _?_(u):_}return null};const SA=oas3_selectors_onlyOAS3(((s,o)=>{const i=o?[o,"selectedServer"]:["selectedServer"];return s.getIn(i)||""})),xA=oas3_selectors_onlyOAS3(((s,o,i)=>s.getIn(["requestData",o,i,"bodyValue"])||null)),kA=oas3_selectors_onlyOAS3(((s,o,i)=>s.getIn(["requestData",o,i,"retainBodyValue"])||!1)),selectDefaultRequestBodyValue=(s,o,i)=>s=>{const{oas3Selectors:u,specSelectors:_,fn:w}=s.getSystem();if(_.isOAS3()){const s=u.requestContentType(o,i);if(s)return getDefaultRequestBodyValue(_.specResolvedSubtree(["paths",o,i,"requestBody"]),s,u.activeExamplesMember(o,i,"requestBody","requestBody"),w)}return null},CA=oas3_selectors_onlyOAS3(((s,o,i)=>s=>{const{oas3Selectors:u,specSelectors:_,fn:w}=s;let x=!1;const C=u.requestContentType(o,i);let j=u.requestBodyValue(o,i);const L=_.specResolvedSubtree(["paths",o,i,"requestBody"]);if(!L)return!1;if(qe.Map.isMap(j)&&(j=stringify(j.mapEntries((s=>qe.Map.isMap(s[1])?[s[0],s[1].get("value")]:s)).toJS())),qe.List.isList(j)&&(j=stringify(j)),C){const s=getDefaultRequestBodyValue(L,C,u.activeExamplesMember(o,i,"requestBody","requestBody"),w);x=!!j&&j!==s}return x})),OA=oas3_selectors_onlyOAS3(((s,o,i)=>s.getIn(["requestData",o,i,"bodyInclusion"])||(0,qe.Map)())),AA=oas3_selectors_onlyOAS3(((s,o,i)=>s.getIn(["requestData",o,i,"errors"])||null)),jA=oas3_selectors_onlyOAS3(((s,o,i,u,_)=>s.getIn(["examples",o,i,u,_,"activeExample"])||null)),IA=oas3_selectors_onlyOAS3(((s,o,i)=>s.getIn(["requestData",o,i,"requestContentType"])||null)),PA=oas3_selectors_onlyOAS3(((s,o,i)=>s.getIn(["requestData",o,i,"responseContentType"])||null)),MA=oas3_selectors_onlyOAS3(((s,o,i)=>{let u;if("string"!=typeof o){const{server:s,namespace:_}=o;u=_?[_,"serverVariableValues",s,i]:["serverVariableValues",s,i]}else{u=["serverVariableValues",o,i]}return s.getIn(u)||null})),TA=oas3_selectors_onlyOAS3(((s,o)=>{let i;if("string"!=typeof o){const{server:s,namespace:u}=o;i=u?[u,"serverVariableValues",s]:["serverVariableValues",s]}else{i=["serverVariableValues",o]}return s.getIn(i)||(0,qe.OrderedMap)()})),NA=oas3_selectors_onlyOAS3(((s,o)=>{var i,u;if("string"!=typeof o){const{server:_,namespace:w}=o;u=_,i=w?s.getIn([w,"serverVariableValues",u]):s.getIn(["serverVariableValues",u])}else u=o,i=s.getIn(["serverVariableValues",u]);i=i||(0,qe.OrderedMap)();let _=u;return i.map(((s,o)=>{_=_.replace(new RegExp(`{${wA()(o)}}`,"g"),s)})),_})),RA=function validateRequestBodyIsRequired(s){return(...o)=>i=>{const u=i.getSystem().specSelectors.specJson();let _=[...o][1]||[];return!u.getIn(["paths",..._,"requestBody","required"])||s(...o)}}(((s,o)=>((s,o)=>(o=o||[],!!s.getIn(["requestData",...o,"bodyValue"])))(s,o))),validateShallowRequired=(s,{oas3RequiredRequestBodyContentType:o,oas3RequestContentType:i,oas3RequestBodyValue:u})=>{let _=[];if(!qe.Map.isMap(u))return _;let w=[];return Object.keys(o.requestContentType).forEach((s=>{if(s===i){o.requestContentType[s].forEach((s=>{w.indexOf(s)<0&&w.push(s)}))}})),w.forEach((s=>{u.getIn([s,"value"])||_.push(s)})),_},DA=Ss()(["get","put","post","delete","options","head","patch","trace"]),LA={[uA]:(s,{payload:{selectedServerUrl:o,namespace:i}})=>{const u=i?[i,"selectedServer"]:["selectedServer"];return s.setIn(u,o)},[pA]:(s,{payload:{value:o,pathMethod:i}})=>{let[u,_]=i;if(!qe.Map.isMap(o))return s.setIn(["requestData",u,_,"bodyValue"],o);let w,x=s.getIn(["requestData",u,_,"bodyValue"])||(0,qe.Map)();qe.Map.isMap(x)||(x=(0,qe.Map)());const[...C]=o.keys();return C.forEach((s=>{let i=o.getIn([s]);x.has(s)&&qe.Map.isMap(i)||(w=x.setIn([s,"value"],i))})),s.setIn(["requestData",u,_,"bodyValue"],w)},[hA]:(s,{payload:{value:o,pathMethod:i}})=>{let[u,_]=i;return s.setIn(["requestData",u,_,"retainBodyValue"],o)},[dA]:(s,{payload:{value:o,pathMethod:i,name:u}})=>{let[_,w]=i;return s.setIn(["requestData",_,w,"bodyInclusion",u],o)},[fA]:(s,{payload:{name:o,pathMethod:i,contextType:u,contextName:_}})=>{let[w,x]=i;return s.setIn(["examples",w,x,u,_,"activeExample"],o)},[mA]:(s,{payload:{value:o,pathMethod:i}})=>{let[u,_]=i;return s.setIn(["requestData",u,_,"requestContentType"],o)},[gA]:(s,{payload:{value:o,path:i,method:u}})=>s.setIn(["requestData",i,u,"responseContentType"],o),[yA]:(s,{payload:{server:o,namespace:i,key:u,val:_}})=>{const w=i?[i,"serverVariableValues",o,u]:["serverVariableValues",o,u];return s.setIn(w,_)},[vA]:(s,{payload:{path:o,method:i,validationErrors:u}})=>{let _=[];if(_.push("Required field is not provided"),u.missingBodyValue)return s.setIn(["requestData",o,i,"errors"],(0,qe.fromJS)(_));if(u.missingRequiredKeys&&u.missingRequiredKeys.length>0){const{missingRequiredKeys:w}=u;return s.updateIn(["requestData",o,i,"bodyValue"],(0,qe.fromJS)({}),(s=>w.reduce(((s,o)=>s.setIn([o,"errors"],(0,qe.fromJS)(_))),s)))}return console.warn("unexpected result: SET_REQUEST_BODY_VALIDATE_ERROR"),s},[bA]:(s,{payload:{path:o,method:i}})=>{const u=s.getIn(["requestData",o,i,"bodyValue"]);if(!qe.Map.isMap(u))return s.setIn(["requestData",o,i,"errors"],(0,qe.fromJS)([]));const[..._]=u.keys();return _?s.updateIn(["requestData",o,i,"bodyValue"],(0,qe.fromJS)({}),(s=>_.reduce(((s,o)=>s.setIn([o,"errors"],(0,qe.fromJS)([]))),s))):s},[_A]:(s,{payload:{pathMethod:o}})=>{let[i,u]=o;const _=s.getIn(["requestData",i,u,"bodyValue"]);return _?qe.Map.isMap(_)?s.setIn(["requestData",i,u,"bodyValue"],(0,qe.Map)()):s.setIn(["requestData",i,u,"bodyValue"],""):s}};function oas3(){return{components:rA,wrapComponents:cA,statePlugins:{spec:{wrapSelectors:be,selectors:we},auth:{wrapSelectors:_e},oas3:{actions:{...Se},reducers:LA,selectors:{...xe}}}}}const webhooks=({specSelectors:s,getComponent:o})=>{const i=s.selectWebhooksOperations(),u=Object.keys(i),_=o("OperationContainer",!0);return 0===u.length?null:Pe.createElement("div",{className:"webhooks"},Pe.createElement("h2",null,"Webhooks"),u.map((s=>Pe.createElement("div",{key:`${s}-webhook`},i[s].map((o=>Pe.createElement(_,{key:`${s}-${o.method}-webhook`,op:o.operation,tag:"webhooks",method:o.method,path:s,specPath:(0,qe.List)(o.specPath),allowTryItOut:!1})))))))},oas31_components_license=({getComponent:s,specSelectors:o})=>{const i=o.selectLicenseNameField(),u=o.selectLicenseUrl(),_=s("Link");return Pe.createElement("div",{className:"info__license"},u?Pe.createElement("div",{className:"info__license__url"},Pe.createElement(_,{target:"_blank",href:sanitizeUrl(u)},i)):Pe.createElement("span",null,i))},oas31_components_contact=({getComponent:s,specSelectors:o})=>{const i=o.selectContactNameField(),u=o.selectContactUrl(),_=o.selectContactEmailField(),w=s("Link");return Pe.createElement("div",{className:"info__contact"},u&&Pe.createElement("div",null,Pe.createElement(w,{href:sanitizeUrl(u),target:"_blank"},i," - Website")),_&&Pe.createElement(w,{href:sanitizeUrl(`mailto:${_}`)},u?`Send email to ${i}`:`Contact ${i}`))},oas31_components_info=({getComponent:s,specSelectors:o})=>{const i=o.version(),u=o.url(),_=o.basePath(),w=o.host(),x=o.selectInfoSummaryField(),C=o.selectInfoDescriptionField(),j=o.selectInfoTitleField(),L=o.selectInfoTermsOfServiceUrl(),B=o.selectExternalDocsUrl(),$=o.selectExternalDocsDescriptionField(),V=o.contact(),U=o.license(),z=s("Markdown",!0),Y=s("Link"),Z=s("VersionStamp"),ee=s("OpenAPIVersion"),ie=s("InfoUrl"),ae=s("InfoBasePath"),le=s("License",!0),ce=s("Contact",!0),pe=s("JsonSchemaDialect",!0);return Pe.createElement("div",{className:"info"},Pe.createElement("hgroup",{className:"main"},Pe.createElement("h2",{className:"title"},j,Pe.createElement("span",null,i&&Pe.createElement(Z,{version:i}),Pe.createElement(ee,{oasVersion:"3.1"}))),(w||_)&&Pe.createElement(ae,{host:w,basePath:_}),u&&Pe.createElement(ie,{getComponent:s,url:u})),x&&Pe.createElement("p",{className:"info__summary"},x),Pe.createElement("div",{className:"info__description description"},Pe.createElement(z,{source:C})),L&&Pe.createElement("div",{className:"info__tos"},Pe.createElement(Y,{target:"_blank",href:sanitizeUrl(L)},"Terms of service")),V.size>0&&Pe.createElement(ce,null),U.size>0&&Pe.createElement(le,null),B&&Pe.createElement(Y,{className:"info__extdocs",target:"_blank",href:sanitizeUrl(B)},$||B),Pe.createElement(pe,null))},json_schema_dialect=({getComponent:s,specSelectors:o})=>{const i=o.selectJsonSchemaDialectField(),u=o.selectJsonSchemaDialectDefault(),_=s("Link");return Pe.createElement(Pe.Fragment,null,i&&i===u&&Pe.createElement("p",{className:"info__jsonschemadialect"},"JSON Schema dialect:"," ",Pe.createElement(_,{target:"_blank",href:sanitizeUrl(i)},i)),i&&i!==u&&Pe.createElement("div",{className:"error-wrapper"},Pe.createElement("div",{className:"no-margin"},Pe.createElement("div",{className:"errors"},Pe.createElement("div",{className:"errors-wrapper"},Pe.createElement("h4",{className:"center"},"Warning"),Pe.createElement("p",{className:"message"},Pe.createElement("strong",null,"OpenAPI.jsonSchemaDialect")," field contains a value different from the default value of"," ",Pe.createElement(_,{target:"_blank",href:u},u),". Values different from the default one are currently not supported. Please either omit the field or provide it with the default value."))))))},version_pragma_filter=({bypass:s,isSwagger2:o,isOAS3:i,isOAS31:u,alsoShow:_,children:w})=>s?Pe.createElement("div",null,w):o&&(i||u)?Pe.createElement("div",{className:"version-pragma"},_,Pe.createElement("div",{className:"version-pragma__message version-pragma__message--ambiguous"},Pe.createElement("div",null,Pe.createElement("h3",null,"Unable to render this definition"),Pe.createElement("p",null,Pe.createElement("code",null,"swagger")," and ",Pe.createElement("code",null,"openapi")," fields cannot be present in the same Swagger or OpenAPI definition. Please remove one of the fields."),Pe.createElement("p",null,"Supported version fields are ",Pe.createElement("code",null,'swagger: "2.0"')," and those that match ",Pe.createElement("code",null,"openapi: 3.x.y")," (for example,"," ",Pe.createElement("code",null,"openapi: 3.1.0"),").")))):o||i||u?Pe.createElement("div",null,w):Pe.createElement("div",{className:"version-pragma"},_,Pe.createElement("div",{className:"version-pragma__message version-pragma__message--missing"},Pe.createElement("div",null,Pe.createElement("h3",null,"Unable to render this definition"),Pe.createElement("p",null,"The provided definition does not specify a valid version field."),Pe.createElement("p",null,"Please indicate a valid Swagger or OpenAPI version field. Supported version fields are ",Pe.createElement("code",null,'swagger: "2.0"')," and those that match ",Pe.createElement("code",null,"openapi: 3.x.y")," (for example,"," ",Pe.createElement("code",null,"openapi: 3.1.0"),").")))),getModelName=s=>"string"==typeof s&&s.includes("#/components/schemas/")?(s=>{const o=s.replace(/~1/g,"/").replace(/~0/g,"~");try{return decodeURIComponent(o)}catch{return o}})(s.replace(/^.*#\/components\/schemas\//,"")):null,BA=(0,Pe.forwardRef)((({schema:s,getComponent:o,onToggle:i=()=>{}},u)=>{const _=o("JSONSchema202012"),w=getModelName(s.get("$$ref")),x=(0,Pe.useCallback)(((s,o)=>{i(w,o)}),[w,i]);return Pe.createElement(_,{name:w,schema:s.toJS(),ref:u,onExpand:x})})),FA=BA,models=({specActions:s,specSelectors:o,layoutSelectors:i,layoutActions:u,getComponent:_,getConfigs:w,fn:x})=>{const C=o.selectSchemas(),j=Object.keys(C).length>0,L=["components","schemas"],{docExpansion:B,defaultModelsExpandDepth:$}=w(),V=$>0&&"none"!==B,U=i.isShown(L,V),z=_("Collapse"),Y=_("JSONSchema202012"),Z=_("ArrowUpIcon"),ee=_("ArrowDownIcon"),{getTitle:ie}=x.jsonSchema202012.useFn();(0,Pe.useEffect)((()=>{const i=U&&$>1,u=null!=o.specResolvedSubtree(L);i&&!u&&s.requestResolvedSubtree(L)}),[U,$]);const ae=(0,Pe.useCallback)((()=>{u.show(L,!U)}),[U]),le=(0,Pe.useCallback)((s=>{null!==s&&u.readyToScroll(L,s)}),[]),handleJSONSchema202012Ref=s=>o=>{null!==o&&u.readyToScroll([...L,s],o)},handleJSONSchema202012Expand=i=>(u,_)=>{if(_){const u=[...L,i];null!=o.specResolvedSubtree(u)||s.requestResolvedSubtree([...L,i])}};return!j||$<0?null:Pe.createElement("section",{className:Hn()("models",{"is-open":U}),ref:le},Pe.createElement("h4",null,Pe.createElement("button",{"aria-expanded":U,className:"models-control",onClick:ae},Pe.createElement("span",null,"Schemas"),U?Pe.createElement(Z,null):Pe.createElement(ee,null))),Pe.createElement(z,{isOpened:U},Object.entries(C).map((([s,o])=>{const i=ie(o,{lookup:"basic"})||s;return Pe.createElement(Y,{key:s,ref:handleJSONSchema202012Ref(s),schema:o,name:i,onExpand:handleJSONSchema202012Expand(s)})}))))},mutual_tls_auth=({schema:s,getComponent:o})=>{const i=o("JumpToPath",!0);return Pe.createElement("div",null,Pe.createElement("h4",null,s.get("name")," (mutualTLS)"," ",Pe.createElement(i,{path:["securityDefinitions",s.get("name")]})),Pe.createElement("p",null,"Mutual TLS is required by this API/Operation. Certificates are managed via your Operating System and/or your browser."),Pe.createElement("p",null,s.get("description")))};class auths_Auths extends Pe.Component{constructor(s,o){super(s,o),this.state={}}onAuthChange=s=>{let{name:o}=s;this.setState({[o]:s})};submitAuth=s=>{s.preventDefault();let{authActions:o}=this.props;o.authorizeWithPersistOption(this.state)};logoutClick=s=>{s.preventDefault();let{authActions:o,definitions:i}=this.props,u=i.map(((s,o)=>o)).toArray();this.setState(u.reduce(((s,o)=>(s[o]="",s)),{})),o.logoutWithPersistOption(u)};close=s=>{s.preventDefault();let{authActions:o}=this.props;o.showDefinitions(!1)};render(){let{definitions:s,getComponent:o,authSelectors:i,errSelectors:u}=this.props;const _=o("AuthItem"),w=o("oauth2",!0),x=o("Button"),C=i.authorized(),j=s.filter(((s,o)=>!!C.get(o))),L=s.filter((s=>"oauth2"!==s.get("type")&&"mutualTLS"!==s.get("type"))),B=s.filter((s=>"oauth2"===s.get("type"))),$=s.filter((s=>"mutualTLS"===s.get("type")));return Pe.createElement("div",{className:"auth-container"},L.size>0&&Pe.createElement("form",{onSubmit:this.submitAuth},L.map(((s,i)=>Pe.createElement(_,{key:i,schema:s,name:i,getComponent:o,onAuthChange:this.onAuthChange,authorized:C,errSelectors:u}))).toArray(),Pe.createElement("div",{className:"auth-btn-wrapper"},L.size===j.size?Pe.createElement(x,{className:"btn modal-btn auth",onClick:this.logoutClick,"aria-label":"Remove authorization"},"Logout"):Pe.createElement(x,{type:"submit",className:"btn modal-btn auth authorize","aria-label":"Apply credentials"},"Authorize"),Pe.createElement(x,{className:"btn modal-btn auth btn-done",onClick:this.close},"Close"))),B.size>0?Pe.createElement("div",null,Pe.createElement("div",{className:"scope-def"},Pe.createElement("p",null,"Scopes are used to grant an application different levels of access to data on behalf of the end user. Each API may declare one or more scopes."),Pe.createElement("p",null,"API requires the following scopes. Select which ones you want to grant to Swagger UI.")),s.filter((s=>"oauth2"===s.get("type"))).map(((s,o)=>Pe.createElement("div",{key:o},Pe.createElement(w,{authorized:C,schema:s,name:o})))).toArray()):null,$.size>0&&Pe.createElement("div",null,$.map(((s,i)=>Pe.createElement(_,{key:i,schema:s,name:i,getComponent:o,onAuthChange:this.onAuthChange,authorized:C,errSelectors:u}))).toArray()))}}const qA=auths_Auths,isOAS31=s=>{const o=s.get("openapi");return"string"==typeof o&&/^3\.1\.(?:[1-9]\d*|0)$/.test(o)},fn_createOnlyOAS31Selector=s=>(o,...i)=>u=>{if(u.getSystem().specSelectors.isOAS31()){const _=s(o,...i);return"function"==typeof _?_(u):_}return null},createOnlyOAS31SelectorWrapper=s=>(o,i)=>(u,..._)=>{if(i.getSystem().specSelectors.isOAS31()){const w=s(u,..._);return"function"==typeof w?w(o,i):w}return o(..._)},fn_createSystemSelector=s=>(o,...i)=>u=>{const _=s(o,u,...i);return"function"==typeof _?_(u):_},createOnlyOAS31ComponentWrapper=s=>(o,i)=>u=>i.specSelectors.isOAS31()?Pe.createElement(s,Rn()({},u,{originalComponent:o,getSystem:i.getSystem})):Pe.createElement(o,u),$A=createOnlyOAS31ComponentWrapper((({getSystem:s})=>{const o=s().getComponent("OAS31License",!0);return Pe.createElement(o,null)})),VA=createOnlyOAS31ComponentWrapper((({getSystem:s})=>{const o=s().getComponent("OAS31Contact",!0);return Pe.createElement(o,null)})),UA=createOnlyOAS31ComponentWrapper((({getSystem:s})=>{const o=s().getComponent("OAS31Info",!0);return Pe.createElement(o,null)})),zA=createOnlyOAS31ComponentWrapper((({getSystem:s,...o})=>{const i=s(),{getComponent:u,fn:_,getConfigs:w}=i,x=w(),C=u("OAS31Model"),j=u("JSONSchema202012"),L=u("JSONSchema202012Keyword$schema"),B=u("JSONSchema202012Keyword$vocabulary"),$=u("JSONSchema202012Keyword$id"),V=u("JSONSchema202012Keyword$anchor"),U=u("JSONSchema202012Keyword$dynamicAnchor"),z=u("JSONSchema202012Keyword$ref"),Y=u("JSONSchema202012Keyword$dynamicRef"),Z=u("JSONSchema202012Keyword$defs"),ee=u("JSONSchema202012Keyword$comment"),ie=u("JSONSchema202012KeywordAllOf"),ae=u("JSONSchema202012KeywordAnyOf"),le=u("JSONSchema202012KeywordOneOf"),ce=u("JSONSchema202012KeywordNot"),pe=u("JSONSchema202012KeywordIf"),de=u("JSONSchema202012KeywordThen"),fe=u("JSONSchema202012KeywordElse"),ye=u("JSONSchema202012KeywordDependentSchemas"),be=u("JSONSchema202012KeywordPrefixItems"),_e=u("JSONSchema202012KeywordItems"),we=u("JSONSchema202012KeywordContains"),Se=u("JSONSchema202012KeywordProperties"),xe=u("JSONSchema202012KeywordPatternProperties"),Te=u("JSONSchema202012KeywordAdditionalProperties"),Re=u("JSONSchema202012KeywordPropertyNames"),qe=u("JSONSchema202012KeywordUnevaluatedItems"),$e=u("JSONSchema202012KeywordUnevaluatedProperties"),ze=u("JSONSchema202012KeywordType"),We=u("JSONSchema202012KeywordEnum"),He=u("JSONSchema202012KeywordConst"),Ye=u("JSONSchema202012KeywordConstraint"),Xe=u("JSONSchema202012KeywordDependentRequired"),Qe=u("JSONSchema202012KeywordContentSchema"),et=u("JSONSchema202012KeywordTitle"),tt=u("JSONSchema202012KeywordDescription"),rt=u("JSONSchema202012KeywordDefault"),nt=u("JSONSchema202012KeywordDeprecated"),st=u("JSONSchema202012KeywordReadOnly"),ot=u("JSONSchema202012KeywordWriteOnly"),it=u("JSONSchema202012Accordion"),at=u("JSONSchema202012ExpandDeepButton"),lt=u("JSONSchema202012ChevronRightIcon"),ct=u("withJSONSchema202012Context")(C,{config:{default$schema:"https://spec.openapis.org/oas/3.1/dialect/base",defaultExpandedLevels:x.defaultModelExpandDepth,includeReadOnly:Boolean(o.includeReadOnly),includeWriteOnly:Boolean(o.includeWriteOnly)},components:{JSONSchema:j,Keyword$schema:L,Keyword$vocabulary:B,Keyword$id:$,Keyword$anchor:V,Keyword$dynamicAnchor:U,Keyword$ref:z,Keyword$dynamicRef:Y,Keyword$defs:Z,Keyword$comment:ee,KeywordAllOf:ie,KeywordAnyOf:ae,KeywordOneOf:le,KeywordNot:ce,KeywordIf:pe,KeywordThen:de,KeywordElse:fe,KeywordDependentSchemas:ye,KeywordPrefixItems:be,KeywordItems:_e,KeywordContains:we,KeywordProperties:Se,KeywordPatternProperties:xe,KeywordAdditionalProperties:Te,KeywordPropertyNames:Re,KeywordUnevaluatedItems:qe,KeywordUnevaluatedProperties:$e,KeywordType:ze,KeywordEnum:We,KeywordConst:He,KeywordConstraint:Ye,KeywordDependentRequired:Xe,KeywordContentSchema:Qe,KeywordTitle:et,KeywordDescription:tt,KeywordDefault:rt,KeywordDeprecated:nt,KeywordReadOnly:st,KeywordWriteOnly:ot,Accordion:it,ExpandDeepButton:at,ChevronRightIcon:lt},fn:{upperFirst:_.upperFirst,isExpandable:_.jsonSchema202012.isExpandable,getProperties:_.jsonSchema202012.getProperties}});return Pe.createElement(ct,o)})),WA=zA,KA=createOnlyOAS31ComponentWrapper((({getSystem:s})=>{const{getComponent:o,fn:i,getConfigs:u}=s(),_=u();if(KA.ModelsWithJSONSchemaContext)return Pe.createElement(KA.ModelsWithJSONSchemaContext,null);const w=o("OAS31Models",!0),x=o("JSONSchema202012"),C=o("JSONSchema202012Keyword$schema"),j=o("JSONSchema202012Keyword$vocabulary"),L=o("JSONSchema202012Keyword$id"),B=o("JSONSchema202012Keyword$anchor"),$=o("JSONSchema202012Keyword$dynamicAnchor"),V=o("JSONSchema202012Keyword$ref"),U=o("JSONSchema202012Keyword$dynamicRef"),z=o("JSONSchema202012Keyword$defs"),Y=o("JSONSchema202012Keyword$comment"),Z=o("JSONSchema202012KeywordAllOf"),ee=o("JSONSchema202012KeywordAnyOf"),ie=o("JSONSchema202012KeywordOneOf"),ae=o("JSONSchema202012KeywordNot"),le=o("JSONSchema202012KeywordIf"),ce=o("JSONSchema202012KeywordThen"),pe=o("JSONSchema202012KeywordElse"),de=o("JSONSchema202012KeywordDependentSchemas"),fe=o("JSONSchema202012KeywordPrefixItems"),ye=o("JSONSchema202012KeywordItems"),be=o("JSONSchema202012KeywordContains"),_e=o("JSONSchema202012KeywordProperties"),we=o("JSONSchema202012KeywordPatternProperties"),Se=o("JSONSchema202012KeywordAdditionalProperties"),xe=o("JSONSchema202012KeywordPropertyNames"),Te=o("JSONSchema202012KeywordUnevaluatedItems"),Re=o("JSONSchema202012KeywordUnevaluatedProperties"),qe=o("JSONSchema202012KeywordType"),$e=o("JSONSchema202012KeywordEnum"),ze=o("JSONSchema202012KeywordConst"),We=o("JSONSchema202012KeywordConstraint"),He=o("JSONSchema202012KeywordDependentRequired"),Ye=o("JSONSchema202012KeywordContentSchema"),Xe=o("JSONSchema202012KeywordTitle"),Qe=o("JSONSchema202012KeywordDescription"),et=o("JSONSchema202012KeywordDefault"),tt=o("JSONSchema202012KeywordDeprecated"),rt=o("JSONSchema202012KeywordReadOnly"),nt=o("JSONSchema202012KeywordWriteOnly"),st=o("JSONSchema202012Accordion"),ot=o("JSONSchema202012ExpandDeepButton"),it=o("JSONSchema202012ChevronRightIcon"),at=o("withJSONSchema202012Context");return KA.ModelsWithJSONSchemaContext=at(w,{config:{default$schema:"https://spec.openapis.org/oas/3.1/dialect/base",defaultExpandedLevels:_.defaultModelsExpandDepth-1,includeReadOnly:!0,includeWriteOnly:!0},components:{JSONSchema:x,Keyword$schema:C,Keyword$vocabulary:j,Keyword$id:L,Keyword$anchor:B,Keyword$dynamicAnchor:$,Keyword$ref:V,Keyword$dynamicRef:U,Keyword$defs:z,Keyword$comment:Y,KeywordAllOf:Z,KeywordAnyOf:ee,KeywordOneOf:ie,KeywordNot:ae,KeywordIf:le,KeywordThen:ce,KeywordElse:pe,KeywordDependentSchemas:de,KeywordPrefixItems:fe,KeywordItems:ye,KeywordContains:be,KeywordProperties:_e,KeywordPatternProperties:we,KeywordAdditionalProperties:Se,KeywordPropertyNames:xe,KeywordUnevaluatedItems:Te,KeywordUnevaluatedProperties:Re,KeywordType:qe,KeywordEnum:$e,KeywordConst:ze,KeywordConstraint:We,KeywordDependentRequired:He,KeywordContentSchema:Ye,KeywordTitle:Xe,KeywordDescription:Qe,KeywordDefault:et,KeywordDeprecated:tt,KeywordReadOnly:rt,KeywordWriteOnly:nt,Accordion:st,ExpandDeepButton:ot,ChevronRightIcon:it},fn:{upperFirst:i.upperFirst,isExpandable:i.jsonSchema202012.isExpandable,getProperties:i.jsonSchema202012.getProperties}}),Pe.createElement(KA.ModelsWithJSONSchemaContext,null)}));KA.ModelsWithJSONSchemaContext=null;const HA=KA,wrap_components_version_pragma_filter=(s,o)=>s=>{const i=o.specSelectors.isOAS31(),u=o.getComponent("OAS31VersionPragmaFilter");return Pe.createElement(u,Rn()({isOAS31:i},s))},JA=createOnlyOAS31ComponentWrapper((({originalComponent:s,...o})=>{const{getComponent:i,schema:u}=o,_=i("MutualTLSAuth",!0);return"mutualTLS"===u.get("type")?Pe.createElement(_,{schema:u}):Pe.createElement(s,o)})),GA=JA,YA=createOnlyOAS31ComponentWrapper((({getSystem:s,...o})=>{const i=s().getComponent("OAS31Auths",!0);return Pe.createElement(i,o)})),XA=(0,qe.Map)(),ZA=Ut(((s,o)=>o.specSelectors.specJson()),isOAS31),selectors_webhooks=()=>s=>{const o=s.specSelectors.specJson().get("webhooks");return qe.Map.isMap(o)?o:XA},QA=Ut([(s,o)=>o.specSelectors.webhooks(),(s,o)=>o.specSelectors.validOperationMethods(),(s,o)=>o.specSelectors.specResolvedSubtree(["webhooks"])],((s,o)=>s.reduce(((s,i,u)=>{if(!qe.Map.isMap(i))return s;const _=i.entrySeq().filter((([s])=>o.includes(s))).map((([s,o])=>({operation:(0,qe.Map)({operation:o}),method:s,path:u,specPath:["webhooks",u,s]})));return s.concat(_)}),(0,qe.List)()).groupBy((s=>s.path)).map((s=>s.toArray())).toObject())),selectors_license=()=>s=>{const o=s.specSelectors.info().get("license");return qe.Map.isMap(o)?o:XA},selectLicenseNameField=()=>s=>s.specSelectors.license().get("name","License"),selectLicenseUrlField=()=>s=>s.specSelectors.license().get("url"),ej=Ut([(s,o)=>o.specSelectors.url(),(s,o)=>o.oas3Selectors.selectedServer(),(s,o)=>o.specSelectors.selectLicenseUrlField()],((s,o,i)=>{if(i)return safeBuildUrl(i,s,{selectedServer:o})})),selectLicenseIdentifierField=()=>s=>s.specSelectors.license().get("identifier"),selectors_contact=()=>s=>{const o=s.specSelectors.info().get("contact");return qe.Map.isMap(o)?o:XA},selectContactNameField=()=>s=>s.specSelectors.contact().get("name","the developer"),selectContactEmailField=()=>s=>s.specSelectors.contact().get("email"),selectContactUrlField=()=>s=>s.specSelectors.contact().get("url"),fj=Ut([(s,o)=>o.specSelectors.url(),(s,o)=>o.oas3Selectors.selectedServer(),(s,o)=>o.specSelectors.selectContactUrlField()],((s,o,i)=>{if(i)return safeBuildUrl(i,s,{selectedServer:o})})),selectInfoTitleField=()=>s=>s.specSelectors.info().get("title"),selectInfoSummaryField=()=>s=>s.specSelectors.info().get("summary"),selectInfoDescriptionField=()=>s=>s.specSelectors.info().get("description"),selectInfoTermsOfServiceField=()=>s=>s.specSelectors.info().get("termsOfService"),mj=Ut([(s,o)=>o.specSelectors.url(),(s,o)=>o.oas3Selectors.selectedServer(),(s,o)=>o.specSelectors.selectInfoTermsOfServiceField()],((s,o,i)=>{if(i)return safeBuildUrl(i,s,{selectedServer:o})})),selectExternalDocsDescriptionField=()=>s=>s.specSelectors.externalDocs().get("description"),selectExternalDocsUrlField=()=>s=>s.specSelectors.externalDocs().get("url"),_j=Ut([(s,o)=>o.specSelectors.url(),(s,o)=>o.oas3Selectors.selectedServer(),(s,o)=>o.specSelectors.selectExternalDocsUrlField()],((s,o,i)=>{if(i)return safeBuildUrl(i,s,{selectedServer:o})})),selectJsonSchemaDialectField=()=>s=>s.specSelectors.specJson().get("jsonSchemaDialect"),selectJsonSchemaDialectDefault=()=>"https://spec.openapis.org/oas/3.1/dialect/base",Cj=Ut(((s,o)=>o.specSelectors.definitions()),((s,o)=>o.specSelectors.specResolvedSubtree(["components","schemas"])),((s,o)=>qe.Map.isMap(s)?qe.Map.isMap(o)?Object.entries(s.toJS()).reduce(((s,[i,u])=>{const _=o.get(i);return s[i]=_?.toJS()||u,s}),{}):s.toJS():{})),wrap_selectors_isOAS3=(s,o)=>(i,...u)=>o.specSelectors.isOAS31()||s(...u),Aj=createOnlyOAS31SelectorWrapper((()=>(s,o)=>o.oas31Selectors.selectLicenseUrl())),Nj=createOnlyOAS31SelectorWrapper((()=>(s,o)=>{const i=o.specSelectors.securityDefinitions();let u=s();return i?(i.entrySeq().forEach((([s,o])=>{"mutualTLS"===o.get("type")&&(u=u.push(new qe.Map({[s]:o})))})),u):u})),Bj=Ut([(s,o)=>o.specSelectors.url(),(s,o)=>o.oas3Selectors.selectedServer(),(s,o)=>o.specSelectors.selectLicenseUrlField(),(s,o)=>o.specSelectors.selectLicenseIdentifierField()],((s,o,i,u)=>i?safeBuildUrl(i,s,{selectedServer:o}):u?`https://spdx.org/licenses/${u}.html`:void 0)),keywords_Example=({schema:s,getSystem:o})=>{const{fn:i}=o(),{hasKeyword:u,stringify:_}=i.jsonSchema202012.useFn();return u(s,"example")?Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--example"},Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"Example"),Pe.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--const"},_(s.example))):null},keywords_Xml=({schema:s,getSystem:o})=>{const i=s?.xml||{},{fn:u,getComponent:_}=o(),{useIsExpandedDeeply:w,useComponent:x}=u.jsonSchema202012,C=w(),j=!!(i.name||i.namespace||i.prefix),[L,B]=(0,Pe.useState)(C),[$,V]=(0,Pe.useState)(!1),U=x("Accordion"),z=x("ExpandDeepButton"),Y=_("JSONSchema202012DeepExpansionContext")(),Z=(0,Pe.useCallback)((()=>{B((s=>!s))}),[]),ee=(0,Pe.useCallback)(((s,o)=>{B(o),V(o)}),[]);return 0===Object.keys(i).length?null:Pe.createElement(Y.Provider,{value:$},Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--xml"},j?Pe.createElement(Pe.Fragment,null,Pe.createElement(U,{expanded:L,onChange:Z},Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"XML")),Pe.createElement(z,{expanded:L,onClick:ee})):Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"XML"),!0===i.attribute&&Pe.createElement("span",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--muted"},"attribute"),!0===i.wrapped&&Pe.createElement("span",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--muted"},"wrapped"),Pe.createElement("strong",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--primary"},"object"),Pe.createElement("ul",{className:Hn()("json-schema-2020-12-keyword__children",{"json-schema-2020-12-keyword__children--collapsed":!L})},L&&Pe.createElement(Pe.Fragment,null,i.name&&Pe.createElement("li",{className:"json-schema-2020-12-property"},Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword"},Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"name"),Pe.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},i.name))),i.namespace&&Pe.createElement("li",{className:"json-schema-2020-12-property"},Pe.createElement("div",{className:"json-schema-2020-12-keyword"},Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"namespace"),Pe.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},i.namespace))),i.prefix&&Pe.createElement("li",{className:"json-schema-2020-12-property"},Pe.createElement("div",{className:"json-schema-2020-12-keyword"},Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"prefix"),Pe.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},i.prefix)))))))},Discriminator_DiscriminatorMapping=({discriminator:s})=>{const o=s?.mapping||{};return 0===Object.keys(o).length?null:Object.entries(o).map((([s,o])=>Pe.createElement("div",{key:`${s}-${o}`,className:"json-schema-2020-12-keyword"},Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},s),Pe.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},o))))},keywords_Discriminator_Discriminator=({schema:s,getSystem:o})=>{const i=s?.discriminator||{},{fn:u,getComponent:_}=o(),{useIsExpandedDeeply:w,useComponent:x}=u.jsonSchema202012,C=w(),j=!!i.mapping,[L,B]=(0,Pe.useState)(C),[$,V]=(0,Pe.useState)(!1),U=x("Accordion"),z=x("ExpandDeepButton"),Y=_("JSONSchema202012DeepExpansionContext")(),Z=(0,Pe.useCallback)((()=>{B((s=>!s))}),[]),ee=(0,Pe.useCallback)(((s,o)=>{B(o),V(o)}),[]);return 0===Object.keys(i).length?null:Pe.createElement(Y.Provider,{value:$},Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--discriminator"},j?Pe.createElement(Pe.Fragment,null,Pe.createElement(U,{expanded:L,onChange:Z},Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"Discriminator")),Pe.createElement(z,{expanded:L,onClick:ee})):Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"Discriminator"),i.propertyName&&Pe.createElement("span",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--muted"},i.propertyName),Pe.createElement("strong",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--primary"},"object"),Pe.createElement("ul",{className:Hn()("json-schema-2020-12-keyword__children",{"json-schema-2020-12-keyword__children--collapsed":!L})},L&&Pe.createElement("li",{className:"json-schema-2020-12-property"},Pe.createElement(Discriminator_DiscriminatorMapping,{discriminator:i})))))},keywords_ExternalDocs=({schema:s,getSystem:o})=>{const i=s?.externalDocs||{},{fn:u,getComponent:_}=o(),{useIsExpandedDeeply:w,useComponent:x}=u.jsonSchema202012,C=w(),j=!(!i.description&&!i.url),[L,B]=(0,Pe.useState)(C),[$,V]=(0,Pe.useState)(!1),U=x("Accordion"),z=x("ExpandDeepButton"),Y=_("JSONSchema202012KeywordDescription"),Z=_("Link"),ee=_("JSONSchema202012DeepExpansionContext")(),ie=(0,Pe.useCallback)((()=>{B((s=>!s))}),[]),ae=(0,Pe.useCallback)(((s,o)=>{B(o),V(o)}),[]);return 0===Object.keys(i).length?null:Pe.createElement(ee.Provider,{value:$},Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--externalDocs"},j?Pe.createElement(Pe.Fragment,null,Pe.createElement(U,{expanded:L,onChange:ie},Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"External documentation")),Pe.createElement(z,{expanded:L,onClick:ae})):Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"External documentation"),Pe.createElement("strong",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--primary"},"object"),Pe.createElement("ul",{className:Hn()("json-schema-2020-12-keyword__children",{"json-schema-2020-12-keyword__children--collapsed":!L})},L&&Pe.createElement(Pe.Fragment,null,i.description&&Pe.createElement("li",{className:"json-schema-2020-12-property"},Pe.createElement(Y,{schema:i,getSystem:o})),i.url&&Pe.createElement("li",{className:"json-schema-2020-12-property"},Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword"},Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"url"),Pe.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},Pe.createElement(Z,{target:"_blank",href:sanitizeUrl(i.url)},i.url))))))))},keywords_Description=({schema:s,getSystem:o})=>{if(!s?.description)return null;const{getComponent:i}=o(),u=i("Markdown");return Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--description"},Pe.createElement("div",{className:"json-schema-2020-12-core-keyword__value json-schema-2020-12-core-keyword__value--secondary"},Pe.createElement(u,{source:s.description})))},$j=createOnlyOAS31ComponentWrapper(keywords_Description),zj=createOnlyOAS31ComponentWrapper((({schema:s,getSystem:o,originalComponent:i})=>{const{getComponent:u}=o(),_=u("JSONSchema202012KeywordDiscriminator"),w=u("JSONSchema202012KeywordXml"),x=u("JSONSchema202012KeywordExample"),C=u("JSONSchema202012KeywordExternalDocs");return Pe.createElement(Pe.Fragment,null,Pe.createElement(i,{schema:s}),Pe.createElement(_,{schema:s,getSystem:o}),Pe.createElement(w,{schema:s,getSystem:o}),Pe.createElement(C,{schema:s,getSystem:o}),Pe.createElement(x,{schema:s,getSystem:o}))})),Kj=zj,keywords_Properties=({schema:s,getSystem:o})=>{const{fn:i}=o(),{useComponent:u}=i.jsonSchema202012,{getDependentRequired:_,getProperties:w}=i.jsonSchema202012.useFn(),x=i.jsonSchema202012.useConfig(),C=Array.isArray(s?.required)?s.required:[],j=u("JSONSchema"),L=w(s,x);return 0===Object.keys(L).length?null:Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--properties"},Pe.createElement("ul",null,Object.entries(L).map((([o,i])=>{const u=C.includes(o),w=_(o,s);return Pe.createElement("li",{key:o,className:Hn()("json-schema-2020-12-property",{"json-schema-2020-12-property--required":u})},Pe.createElement(j,{name:o,schema:i,dependentRequired:w}))}))))},Jj=createOnlyOAS31ComponentWrapper(keywords_Properties),getProperties=(s,{includeReadOnly:o,includeWriteOnly:i})=>{if(!s?.properties)return{};const u=Object.entries(s.properties).filter((([,s])=>(!(!0===s?.readOnly)||o)&&(!(!0===s?.writeOnly)||i)));return Object.fromEntries(u)};const Gj=function oas31_after_load_afterLoad({fn:s,getSystem:o}){if(s.jsonSchema202012){const i=((s,o)=>{const{fn:i}=o();if("function"!=typeof s)return null;const{hasKeyword:u}=i.jsonSchema202012;return o=>s(o)||u(o,"example")||o?.xml||o?.discriminator||o?.externalDocs})(s.jsonSchema202012.isExpandable,o);Object.assign(this.fn.jsonSchema202012,{isExpandable:i,getProperties})}if("function"==typeof s.sampleFromSchema&&s.jsonSchema202012){const i=((s,o)=>{const{fn:i,specSelectors:u}=o;return Object.fromEntries(Object.entries(s).map((([s,o])=>{const _=i[s];return[s,(...s)=>u.isOAS31()?o(...s):"function"==typeof _?_(...s):void 0]})))})({sampleFromSchema:s.jsonSchema202012.sampleFromSchema,sampleFromSchemaGeneric:s.jsonSchema202012.sampleFromSchemaGeneric,createXMLExample:s.jsonSchema202012.createXMLExample,memoizedSampleFromSchema:s.jsonSchema202012.memoizedSampleFromSchema,memoizedCreateXMLExample:s.jsonSchema202012.memoizedCreateXMLExample,getJsonSampleSchema:s.jsonSchema202012.getJsonSampleSchema,getYamlSampleSchema:s.jsonSchema202012.getYamlSampleSchema,getXmlSampleSchema:s.jsonSchema202012.getXmlSampleSchema,getSampleSchema:s.jsonSchema202012.getSampleSchema,mergeJsonSchema:s.jsonSchema202012.mergeJsonSchema},o());Object.assign(this.fn,i)}},oas31=({fn:s})=>{const o=s.createSystemSelector||fn_createSystemSelector,i=s.createOnlyOAS31Selector||fn_createOnlyOAS31Selector;return{afterLoad:Gj,fn:{isOAS31,createSystemSelector:fn_createSystemSelector,createOnlyOAS31Selector:fn_createOnlyOAS31Selector},components:{Webhooks:webhooks,JsonSchemaDialect:json_schema_dialect,MutualTLSAuth:mutual_tls_auth,OAS31Info:oas31_components_info,OAS31License:oas31_components_license,OAS31Contact:oas31_components_contact,OAS31VersionPragmaFilter:version_pragma_filter,OAS31Model:FA,OAS31Models:models,OAS31Auths:qA,JSONSchema202012KeywordExample:keywords_Example,JSONSchema202012KeywordXml:keywords_Xml,JSONSchema202012KeywordDiscriminator:keywords_Discriminator_Discriminator,JSONSchema202012KeywordExternalDocs:keywords_ExternalDocs},wrapComponents:{InfoContainer:UA,License:$A,Contact:VA,VersionPragmaFilter:wrap_components_version_pragma_filter,Model:WA,Models:HA,AuthItem:GA,auths:YA,JSONSchema202012KeywordDescription:$j,JSONSchema202012KeywordDefault:Kj,JSONSchema202012KeywordProperties:Jj},statePlugins:{auth:{wrapSelectors:{definitionsToAuthorize:Nj}},spec:{selectors:{isOAS31:o(ZA),license:selectors_license,selectLicenseNameField,selectLicenseUrlField,selectLicenseIdentifierField:i(selectLicenseIdentifierField),selectLicenseUrl:o(ej),contact:selectors_contact,selectContactNameField,selectContactEmailField,selectContactUrlField,selectContactUrl:o(fj),selectInfoTitleField,selectInfoSummaryField:i(selectInfoSummaryField),selectInfoDescriptionField,selectInfoTermsOfServiceField,selectInfoTermsOfServiceUrl:o(mj),selectExternalDocsDescriptionField,selectExternalDocsUrlField,selectExternalDocsUrl:o(_j),webhooks:i(selectors_webhooks),selectWebhooksOperations:i(o(QA)),selectJsonSchemaDialectField,selectJsonSchemaDialectDefault,selectSchemas:o(Cj)},wrapSelectors:{isOAS3:wrap_selectors_isOAS3,selectLicenseUrl:Aj}},oas31:{selectors:{selectLicenseUrl:i(o(Bj))}}}}},Xj=ts().object,eI=ts().bool,tI=(ts().oneOfType([Xj,eI]),(0,Pe.createContext)(null));tI.displayName="JSONSchemaContext";const rI=(0,Pe.createContext)(0);rI.displayName="JSONSchemaLevelContext";const nI=(0,Pe.createContext)(!1);nI.displayName="JSONSchemaDeepExpansionContext";const sI=(0,Pe.createContext)(new Set),useConfig=()=>{const{config:s}=(0,Pe.useContext)(tI);return s},useComponent=s=>{const{components:o}=(0,Pe.useContext)(tI);return o[s]||null},useFn=(s=void 0)=>{const{fn:o}=(0,Pe.useContext)(tI);return void 0!==s?o[s]:o},useLevel=()=>{const s=(0,Pe.useContext)(rI);return[s,s+1]},useIsExpanded=()=>{const[s]=useLevel(),{defaultExpandedLevels:o}=useConfig();return o-s>0},useIsExpandedDeeply=()=>(0,Pe.useContext)(nI),useRenderedSchemas=(s=void 0)=>{if(void 0===s)return(0,Pe.useContext)(sI);const o=(0,Pe.useContext)(sI);return new Set([...o,s])},oI=(0,Pe.forwardRef)((({schema:s,name:o="",dependentRequired:i=[],onExpand:u=()=>{}},_)=>{const w=useFn(),x=useIsExpanded(),C=useIsExpandedDeeply(),[j,L]=(0,Pe.useState)(x||C),[B,$]=(0,Pe.useState)(C),[V,U]=useLevel(),z=(()=>{const[s]=useLevel();return s>0})(),Y=w.isExpandable(s)||i.length>0,Z=(s=>useRenderedSchemas().has(s))(s),ee=useRenderedSchemas(s),ie=w.stringifyConstraints(s),ae=useComponent("Accordion"),le=useComponent("Keyword$schema"),ce=useComponent("Keyword$vocabulary"),pe=useComponent("Keyword$id"),de=useComponent("Keyword$anchor"),fe=useComponent("Keyword$dynamicAnchor"),ye=useComponent("Keyword$ref"),be=useComponent("Keyword$dynamicRef"),_e=useComponent("Keyword$defs"),we=useComponent("Keyword$comment"),Se=useComponent("KeywordAllOf"),xe=useComponent("KeywordAnyOf"),Te=useComponent("KeywordOneOf"),Re=useComponent("KeywordNot"),qe=useComponent("KeywordIf"),$e=useComponent("KeywordThen"),ze=useComponent("KeywordElse"),We=useComponent("KeywordDependentSchemas"),He=useComponent("KeywordPrefixItems"),Ye=useComponent("KeywordItems"),Xe=useComponent("KeywordContains"),Qe=useComponent("KeywordProperties"),et=useComponent("KeywordPatternProperties"),tt=useComponent("KeywordAdditionalProperties"),rt=useComponent("KeywordPropertyNames"),nt=useComponent("KeywordUnevaluatedItems"),st=useComponent("KeywordUnevaluatedProperties"),ot=useComponent("KeywordType"),it=useComponent("KeywordEnum"),at=useComponent("KeywordConst"),lt=useComponent("KeywordConstraint"),ct=useComponent("KeywordDependentRequired"),ut=useComponent("KeywordContentSchema"),pt=useComponent("KeywordTitle"),ht=useComponent("KeywordDescription"),dt=useComponent("KeywordDefault"),mt=useComponent("KeywordDeprecated"),gt=useComponent("KeywordReadOnly"),yt=useComponent("KeywordWriteOnly"),vt=useComponent("ExpandDeepButton");(0,Pe.useEffect)((()=>{$(C)}),[C]),(0,Pe.useEffect)((()=>{$(B)}),[B]);const bt=(0,Pe.useCallback)(((s,o)=>{L(o),!o&&$(!1),u(s,o,!1)}),[u]),_t=(0,Pe.useCallback)(((s,o)=>{L(o),$(o),u(s,o,!0)}),[u]);return Pe.createElement(rI.Provider,{value:U},Pe.createElement(nI.Provider,{value:B},Pe.createElement(sI.Provider,{value:ee},Pe.createElement("article",{ref:_,"data-json-schema-level":V,className:Hn()("json-schema-2020-12",{"json-schema-2020-12--embedded":z,"json-schema-2020-12--circular":Z})},Pe.createElement("div",{className:"json-schema-2020-12-head"},Y&&!Z?Pe.createElement(Pe.Fragment,null,Pe.createElement(ae,{expanded:j,onChange:bt},Pe.createElement(pt,{title:o,schema:s})),Pe.createElement(vt,{expanded:j,onClick:_t})):Pe.createElement(pt,{title:o,schema:s}),Pe.createElement(mt,{schema:s}),Pe.createElement(gt,{schema:s}),Pe.createElement(yt,{schema:s}),Pe.createElement(ot,{schema:s,isCircular:Z}),ie.length>0&&ie.map((s=>Pe.createElement(lt,{key:`${s.scope}-${s.value}`,constraint:s})))),Pe.createElement("div",{className:Hn()("json-schema-2020-12-body",{"json-schema-2020-12-body--collapsed":!j})},j&&Pe.createElement(Pe.Fragment,null,Pe.createElement(ht,{schema:s}),!Z&&Y&&Pe.createElement(Pe.Fragment,null,Pe.createElement(Qe,{schema:s}),Pe.createElement(et,{schema:s}),Pe.createElement(tt,{schema:s}),Pe.createElement(st,{schema:s}),Pe.createElement(rt,{schema:s}),Pe.createElement(Se,{schema:s}),Pe.createElement(xe,{schema:s}),Pe.createElement(Te,{schema:s}),Pe.createElement(Re,{schema:s}),Pe.createElement(qe,{schema:s}),Pe.createElement($e,{schema:s}),Pe.createElement(ze,{schema:s}),Pe.createElement(We,{schema:s}),Pe.createElement(He,{schema:s}),Pe.createElement(Ye,{schema:s}),Pe.createElement(nt,{schema:s}),Pe.createElement(Xe,{schema:s}),Pe.createElement(ut,{schema:s})),Pe.createElement(it,{schema:s}),Pe.createElement(at,{schema:s}),Pe.createElement(ct,{schema:s,dependentRequired:i}),Pe.createElement(dt,{schema:s}),Pe.createElement(le,{schema:s}),Pe.createElement(ce,{schema:s}),Pe.createElement(pe,{schema:s}),Pe.createElement(de,{schema:s}),Pe.createElement(fe,{schema:s}),Pe.createElement(ye,{schema:s}),!Z&&Y&&Pe.createElement(_e,{schema:s}),Pe.createElement(be,{schema:s}),Pe.createElement(we,{schema:s})))))))})),iI=oI,keywords_$schema=({schema:s})=>s?.$schema?Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--$schema"},Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"$schema"),Pe.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},s.$schema)):null,$vocabulary_$vocabulary=({schema:s})=>{const o=useIsExpanded(),i=useIsExpandedDeeply(),[u,_]=(0,Pe.useState)(o||i),w=useComponent("Accordion"),x=(0,Pe.useCallback)((()=>{_((s=>!s))}),[]);return s?.$vocabulary?"object"!=typeof s.$vocabulary?null:Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--$vocabulary"},Pe.createElement(w,{expanded:u,onChange:x},Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"$vocabulary")),Pe.createElement("strong",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--primary"},"object"),Pe.createElement("ul",null,u&&Object.entries(s.$vocabulary).map((([s,o])=>Pe.createElement("li",{key:s,className:Hn()("json-schema-2020-12-$vocabulary-uri",{"json-schema-2020-12-$vocabulary-uri--disabled":!o})},Pe.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},s)))))):null},keywords_$id=({schema:s})=>s?.$id?Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--$id"},Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"$id"),Pe.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},s.$id)):null,keywords_$anchor=({schema:s})=>s?.$anchor?Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--$anchor"},Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"$anchor"),Pe.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},s.$anchor)):null,keywords_$dynamicAnchor=({schema:s})=>s?.$dynamicAnchor?Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--$dynamicAnchor"},Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"$dynamicAnchor"),Pe.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},s.$dynamicAnchor)):null,keywords_$ref=({schema:s})=>s?.$ref?Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--$ref"},Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"$ref"),Pe.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},s.$ref)):null,keywords_$dynamicRef=({schema:s})=>s?.$dynamicRef?Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--$dynamicRef"},Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"$dynamicRef"),Pe.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},s.$dynamicRef)):null,keywords_$defs=({schema:s})=>{const o=s?.$defs||{},i=useIsExpanded(),u=useIsExpandedDeeply(),[_,w]=(0,Pe.useState)(i||u),[x,C]=(0,Pe.useState)(!1),j=useComponent("Accordion"),L=useComponent("ExpandDeepButton"),B=useComponent("JSONSchema"),$=(0,Pe.useCallback)((()=>{w((s=>!s))}),[]),V=(0,Pe.useCallback)(((s,o)=>{w(o),C(o)}),[]);return 0===Object.keys(o).length?null:Pe.createElement(nI.Provider,{value:x},Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--$defs"},Pe.createElement(j,{expanded:_,onChange:$},Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"$defs")),Pe.createElement(L,{expanded:_,onClick:V}),Pe.createElement("strong",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--primary"},"object"),Pe.createElement("ul",{className:Hn()("json-schema-2020-12-keyword__children",{"json-schema-2020-12-keyword__children--collapsed":!_})},_&&Pe.createElement(Pe.Fragment,null,Object.entries(o).map((([s,o])=>Pe.createElement("li",{key:s,className:"json-schema-2020-12-property"},Pe.createElement(B,{name:s,schema:o}))))))))},keywords_$comment=({schema:s})=>s?.$comment?Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--$comment"},Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"$comment"),Pe.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},s.$comment)):null,keywords_AllOf=({schema:s})=>{const o=s?.allOf||[],i=useFn(),u=useIsExpanded(),_=useIsExpandedDeeply(),[w,x]=(0,Pe.useState)(u||_),[C,j]=(0,Pe.useState)(!1),L=useComponent("Accordion"),B=useComponent("ExpandDeepButton"),$=useComponent("JSONSchema"),V=useComponent("KeywordType"),U=(0,Pe.useCallback)((()=>{x((s=>!s))}),[]),z=(0,Pe.useCallback)(((s,o)=>{x(o),j(o)}),[]);return Array.isArray(o)&&0!==o.length?Pe.createElement(nI.Provider,{value:C},Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--allOf"},Pe.createElement(L,{expanded:w,onChange:U},Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"All of")),Pe.createElement(B,{expanded:w,onClick:z}),Pe.createElement(V,{schema:{allOf:o}}),Pe.createElement("ul",{className:Hn()("json-schema-2020-12-keyword__children",{"json-schema-2020-12-keyword__children--collapsed":!w})},w&&Pe.createElement(Pe.Fragment,null,o.map(((s,o)=>Pe.createElement("li",{key:`#${o}`,className:"json-schema-2020-12-property"},Pe.createElement($,{name:`#${o} ${i.getTitle(s)}`,schema:s})))))))):null},keywords_AnyOf=({schema:s})=>{const o=s?.anyOf||[],i=useFn(),u=useIsExpanded(),_=useIsExpandedDeeply(),[w,x]=(0,Pe.useState)(u||_),[C,j]=(0,Pe.useState)(!1),L=useComponent("Accordion"),B=useComponent("ExpandDeepButton"),$=useComponent("JSONSchema"),V=useComponent("KeywordType"),U=(0,Pe.useCallback)((()=>{x((s=>!s))}),[]),z=(0,Pe.useCallback)(((s,o)=>{x(o),j(o)}),[]);return Array.isArray(o)&&0!==o.length?Pe.createElement(nI.Provider,{value:C},Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--anyOf"},Pe.createElement(L,{expanded:w,onChange:U},Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Any of")),Pe.createElement(B,{expanded:w,onClick:z}),Pe.createElement(V,{schema:{anyOf:o}}),Pe.createElement("ul",{className:Hn()("json-schema-2020-12-keyword__children",{"json-schema-2020-12-keyword__children--collapsed":!w})},w&&Pe.createElement(Pe.Fragment,null,o.map(((s,o)=>Pe.createElement("li",{key:`#${o}`,className:"json-schema-2020-12-property"},Pe.createElement($,{name:`#${o} ${i.getTitle(s)}`,schema:s})))))))):null},keywords_OneOf=({schema:s})=>{const o=s?.oneOf||[],i=useFn(),u=useIsExpanded(),_=useIsExpandedDeeply(),[w,x]=(0,Pe.useState)(u||_),[C,j]=(0,Pe.useState)(!1),L=useComponent("Accordion"),B=useComponent("ExpandDeepButton"),$=useComponent("JSONSchema"),V=useComponent("KeywordType"),U=(0,Pe.useCallback)((()=>{x((s=>!s))}),[]),z=(0,Pe.useCallback)(((s,o)=>{x(o),j(o)}),[]);return Array.isArray(o)&&0!==o.length?Pe.createElement(nI.Provider,{value:C},Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--oneOf"},Pe.createElement(L,{expanded:w,onChange:U},Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"One of")),Pe.createElement(B,{expanded:w,onClick:z}),Pe.createElement(V,{schema:{oneOf:o}}),Pe.createElement("ul",{className:Hn()("json-schema-2020-12-keyword__children",{"json-schema-2020-12-keyword__children--collapsed":!w})},w&&Pe.createElement(Pe.Fragment,null,o.map(((s,o)=>Pe.createElement("li",{key:`#${o}`,className:"json-schema-2020-12-property"},Pe.createElement($,{name:`#${o} ${i.getTitle(s)}`,schema:s})))))))):null},keywords_Not=({schema:s})=>{const o=useFn(),i=useComponent("JSONSchema");if(!o.hasKeyword(s,"not"))return null;const u=Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Not");return Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--not"},Pe.createElement(i,{name:u,schema:s.not}))},keywords_If=({schema:s})=>{const o=useFn(),i=useComponent("JSONSchema");if(!o.hasKeyword(s,"if"))return null;const u=Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"If");return Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--if"},Pe.createElement(i,{name:u,schema:s.if}))},keywords_Then=({schema:s})=>{const o=useFn(),i=useComponent("JSONSchema");if(!o.hasKeyword(s,"then"))return null;const u=Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Then");return Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--then"},Pe.createElement(i,{name:u,schema:s.then}))},keywords_Else=({schema:s})=>{const o=useFn(),i=useComponent("JSONSchema");if(!o.hasKeyword(s,"else"))return null;const u=Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Else");return Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--if"},Pe.createElement(i,{name:u,schema:s.else}))},keywords_DependentSchemas=({schema:s})=>{const o=s?.dependentSchemas||[],i=useIsExpanded(),u=useIsExpandedDeeply(),[_,w]=(0,Pe.useState)(i||u),[x,C]=(0,Pe.useState)(!1),j=useComponent("Accordion"),L=useComponent("ExpandDeepButton"),B=useComponent("JSONSchema"),$=(0,Pe.useCallback)((()=>{w((s=>!s))}),[]),V=(0,Pe.useCallback)(((s,o)=>{w(o),C(o)}),[]);return"object"!=typeof o||0===Object.keys(o).length?null:Pe.createElement(nI.Provider,{value:x},Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--dependentSchemas"},Pe.createElement(j,{expanded:_,onChange:$},Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Dependent schemas")),Pe.createElement(L,{expanded:_,onClick:V}),Pe.createElement("strong",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--primary"},"object"),Pe.createElement("ul",{className:Hn()("json-schema-2020-12-keyword__children",{"json-schema-2020-12-keyword__children--collapsed":!_})},_&&Pe.createElement(Pe.Fragment,null,Object.entries(o).map((([s,o])=>Pe.createElement("li",{key:s,className:"json-schema-2020-12-property"},Pe.createElement(B,{name:s,schema:o}))))))))},keywords_PrefixItems=({schema:s})=>{const o=s?.prefixItems||[],i=useFn(),u=useIsExpanded(),_=useIsExpandedDeeply(),[w,x]=(0,Pe.useState)(u||_),[C,j]=(0,Pe.useState)(!1),L=useComponent("Accordion"),B=useComponent("ExpandDeepButton"),$=useComponent("JSONSchema"),V=useComponent("KeywordType"),U=(0,Pe.useCallback)((()=>{x((s=>!s))}),[]),z=(0,Pe.useCallback)(((s,o)=>{x(o),j(o)}),[]);return Array.isArray(o)&&0!==o.length?Pe.createElement(nI.Provider,{value:C},Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--prefixItems"},Pe.createElement(L,{expanded:w,onChange:U},Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Prefix items")),Pe.createElement(B,{expanded:w,onClick:z}),Pe.createElement(V,{schema:{prefixItems:o}}),Pe.createElement("ul",{className:Hn()("json-schema-2020-12-keyword__children",{"json-schema-2020-12-keyword__children--collapsed":!w})},w&&Pe.createElement(Pe.Fragment,null,o.map(((s,o)=>Pe.createElement("li",{key:`#${o}`,className:"json-schema-2020-12-property"},Pe.createElement($,{name:`#${o} ${i.getTitle(s)}`,schema:s})))))))):null},keywords_Items=({schema:s})=>{const o=useFn(),i=useComponent("JSONSchema");if(!o.hasKeyword(s,"items"))return null;const u=Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Items");return Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--items"},Pe.createElement(i,{name:u,schema:s.items}))},keywords_Contains=({schema:s})=>{const o=useFn(),i=useComponent("JSONSchema");if(!o.hasKeyword(s,"contains"))return null;const u=Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Contains");return Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--contains"},Pe.createElement(i,{name:u,schema:s.contains}))},keywords_Properties_Properties=({schema:s})=>{const o=useFn(),i=s?.properties||{},u=Array.isArray(s?.required)?s.required:[],_=useComponent("JSONSchema");return 0===Object.keys(i).length?null:Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--properties"},Pe.createElement("ul",null,Object.entries(i).map((([i,w])=>{const x=u.includes(i),C=o.getDependentRequired(i,s);return Pe.createElement("li",{key:i,className:Hn()("json-schema-2020-12-property",{"json-schema-2020-12-property--required":x})},Pe.createElement(_,{name:i,schema:w,dependentRequired:C}))}))))},PatternProperties_PatternProperties=({schema:s})=>{const o=s?.patternProperties||{},i=useComponent("JSONSchema");return 0===Object.keys(o).length?null:Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--patternProperties"},Pe.createElement("ul",null,Object.entries(o).map((([s,o])=>Pe.createElement("li",{key:s,className:"json-schema-2020-12-property"},Pe.createElement(i,{name:s,schema:o}))))))},keywords_AdditionalProperties=({schema:s})=>{const o=useFn(),{additionalProperties:i}=s,u=useComponent("JSONSchema");if(!o.hasKeyword(s,"additionalProperties"))return null;const _=Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Additional properties");return Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--additionalProperties"},!0===i?Pe.createElement(Pe.Fragment,null,_,Pe.createElement("span",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--primary"},"allowed")):!1===i?Pe.createElement(Pe.Fragment,null,_,Pe.createElement("span",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--primary"},"forbidden")):Pe.createElement(u,{name:_,schema:i}))},keywords_PropertyNames=({schema:s})=>{const o=useFn(),{propertyNames:i}=s,u=useComponent("JSONSchema"),_=Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Property names");return o.hasKeyword(s,"propertyNames")?Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--propertyNames"},Pe.createElement(u,{name:_,schema:i})):null},keywords_UnevaluatedItems=({schema:s})=>{const o=useFn(),{unevaluatedItems:i}=s,u=useComponent("JSONSchema");if(!o.hasKeyword(s,"unevaluatedItems"))return null;const _=Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Unevaluated items");return Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--unevaluatedItems"},Pe.createElement(u,{name:_,schema:i}))},keywords_UnevaluatedProperties=({schema:s})=>{const o=useFn(),{unevaluatedProperties:i}=s,u=useComponent("JSONSchema");if(!o.hasKeyword(s,"unevaluatedProperties"))return null;const _=Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Unevaluated properties");return Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--unevaluatedProperties"},Pe.createElement(u,{name:_,schema:i}))},keywords_Type=({schema:s,isCircular:o=!1})=>{const i=useFn().getType(s),u=o?" [circular]":"";return Pe.createElement("strong",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--primary"},`${i}${u}`)},Enum_Enum=({schema:s})=>{const o=useFn();return Array.isArray(s?.enum)?Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--enum"},Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Allowed values"),Pe.createElement("ul",null,s.enum.map((s=>{const i=o.stringify(s);return Pe.createElement("li",{key:i},Pe.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--const"},i))})))):null},keywords_Const=({schema:s})=>{const o=useFn();return o.hasKeyword(s,"const")?Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--const"},Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Const"),Pe.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--const"},o.stringify(s.const))):null},Constraint=({constraint:s})=>Pe.createElement("span",{className:`json-schema-2020-12__constraint json-schema-2020-12__constraint--${s.scope}`},s.value),aI=Pe.memo(Constraint),DependentRequired_DependentRequired=({dependentRequired:s})=>0===s.length?null:Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--dependentRequired"},Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Required when defined"),Pe.createElement("ul",null,s.map((s=>Pe.createElement("li",{key:s},Pe.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--warning"},s)))))),keywords_ContentSchema=({schema:s})=>{const o=useFn(),i=useComponent("JSONSchema");if(!o.hasKeyword(s,"contentSchema"))return null;const u=Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Content schema");return Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--contentSchema"},Pe.createElement(i,{name:u,schema:s.contentSchema}))},Title_Title=({title:s="",schema:o})=>{const i=useFn(),u=s||i.getTitle(o);return u?Pe.createElement("div",{className:"json-schema-2020-12__title"},u):null},keywords_Description_Description=({schema:s})=>s?.description?Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--description"},Pe.createElement("div",{className:"json-schema-2020-12-core-keyword__value json-schema-2020-12-core-keyword__value--secondary"},s.description)):null,keywords_Default=({schema:s})=>{const o=useFn();return o.hasKeyword(s,"default")?Pe.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--default"},Pe.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Default"),Pe.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--const"},o.stringify(s.default))):null},keywords_Deprecated=({schema:s})=>!0!==s?.deprecated?null:Pe.createElement("span",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--warning"},"deprecated"),keywords_ReadOnly=({schema:s})=>!0!==s?.readOnly?null:Pe.createElement("span",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--muted"},"read-only"),keywords_WriteOnly=({schema:s})=>!0!==s?.writeOnly?null:Pe.createElement("span",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--muted"},"write-only"),Accordion_Accordion=({expanded:s=!1,children:o,onChange:i})=>{const u=useComponent("ChevronRightIcon"),_=(0,Pe.useCallback)((o=>{i(o,!s)}),[s,i]);return Pe.createElement("button",{type:"button",className:"json-schema-2020-12-accordion",onClick:_},Pe.createElement("div",{className:"json-schema-2020-12-accordion__children"},o),Pe.createElement("span",{className:Hn()("json-schema-2020-12-accordion__icon",{"json-schema-2020-12-accordion__icon--expanded":s,"json-schema-2020-12-accordion__icon--collapsed":!s})},Pe.createElement(u,null)))},ExpandDeepButton_ExpandDeepButton=({expanded:s,onClick:o})=>{const i=(0,Pe.useCallback)((i=>{o(i,!s)}),[s,o]);return Pe.createElement("button",{type:"button",className:"json-schema-2020-12-expand-deep-button",onClick:i},s?"Collapse all":"Expand all")},icons_ChevronRight=()=>Pe.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",width:"24",height:"24",viewBox:"0 0 24 24"},Pe.createElement("path",{d:"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"})),fn_upperFirst=s=>"string"==typeof s?`${s.charAt(0).toUpperCase()}${s.slice(1)}`:s,getTitle=(s,{lookup:o="extended"}={})=>{const i=useFn();if(null!=s?.title)return i.upperFirst(String(s.title));if("extended"===o){if(null!=s?.$anchor)return i.upperFirst(String(s.$anchor));if(null!=s?.$id)return String(s.$id)}return""},getType=(s,o=new WeakSet)=>{const i=useFn();if(null==s)return"any";if(i.isBooleanJSONSchema(s))return s?"any":"never";if("object"!=typeof s)return"any";if(o.has(s))return"any";o.add(s);const{type:u,prefixItems:_,items:w}=s,getArrayType=()=>{if(Array.isArray(_)){const s=_.map((s=>getType(s,o))),i=w?getType(w,o):"any";return`array<[${s.join(", ")}], ${i}>`}if(w){return`array<${getType(w,o)}>`}return"array"};if(s.not&&"any"===getType(s.not))return"never";const handleCombiningKeywords=(i,u)=>{if(Array.isArray(s[i])){return`(${s[i].map((s=>getType(s,o))).join(u)})`}return null},x=[Array.isArray(u)?u.map((s=>"array"===s?getArrayType():s)).join(" | "):"array"===u?getArrayType():["null","boolean","object","array","number","integer","string"].includes(u)?u:(()=>{if(Object.hasOwn(s,"prefixItems")||Object.hasOwn(s,"items")||Object.hasOwn(s,"contains"))return getArrayType();if(Object.hasOwn(s,"properties")||Object.hasOwn(s,"additionalProperties")||Object.hasOwn(s,"patternProperties"))return"object";if(["int32","int64"].includes(s.format))return"integer";if(["float","double"].includes(s.format))return"number";if(Object.hasOwn(s,"minimum")||Object.hasOwn(s,"maximum")||Object.hasOwn(s,"exclusiveMinimum")||Object.hasOwn(s,"exclusiveMaximum")||Object.hasOwn(s,"multipleOf"))return"number | integer";if(Object.hasOwn(s,"pattern")||Object.hasOwn(s,"format")||Object.hasOwn(s,"minLength")||Object.hasOwn(s,"maxLength"))return"string";if(void 0!==s.const){if(null===s.const)return"null";if("boolean"==typeof s.const)return"boolean";if("number"==typeof s.const)return Number.isInteger(s.const)?"integer":"number";if("string"==typeof s.const)return"string";if(Array.isArray(s.const))return"array";if("object"==typeof s.const)return"object"}return null})(),handleCombiningKeywords("oneOf"," | "),handleCombiningKeywords("anyOf"," | "),handleCombiningKeywords("allOf"," & ")].filter(Boolean).join(" | ");return o.delete(s),x||"any"},isBooleanJSONSchema=s=>"boolean"==typeof s,hasKeyword=(s,o)=>null!==s&&"object"==typeof s&&Object.hasOwn(s,o),isExpandable=s=>{const o=useFn();return s?.$schema||s?.$vocabulary||s?.$id||s?.$anchor||s?.$dynamicAnchor||s?.$ref||s?.$dynamicRef||s?.$defs||s?.$comment||s?.allOf||s?.anyOf||s?.oneOf||o.hasKeyword(s,"not")||o.hasKeyword(s,"if")||o.hasKeyword(s,"then")||o.hasKeyword(s,"else")||s?.dependentSchemas||s?.prefixItems||o.hasKeyword(s,"items")||o.hasKeyword(s,"contains")||s?.properties||s?.patternProperties||o.hasKeyword(s,"additionalProperties")||o.hasKeyword(s,"propertyNames")||o.hasKeyword(s,"unevaluatedItems")||o.hasKeyword(s,"unevaluatedProperties")||s?.description||s?.enum||o.hasKeyword(s,"const")||o.hasKeyword(s,"contentSchema")||o.hasKeyword(s,"default")},fn_stringify=s=>null===s||["number","bigint","boolean"].includes(typeof s)?String(s):Array.isArray(s)?`[${s.map(fn_stringify).join(", ")}]`:JSON.stringify(s),stringifyConstraintRange=(s,o,i)=>{const u="number"==typeof o,_="number"==typeof i;return u&&_?o===i?`${o} ${s}`:`[${o}, ${i}] ${s}`:u?`>= ${o} ${s}`:_?`<= ${i} ${s}`:null},stringifyConstraints=s=>{const o=[],i=(s=>{if("number"!=typeof s?.multipleOf)return null;if(s.multipleOf<=0)return null;if(1===s.multipleOf)return null;const{multipleOf:o}=s;if(Number.isInteger(o))return`multiple of ${o}`;const i=10**o.toString().split(".")[1].length;return`multiple of ${o*i}/${i}`})(s);null!==i&&o.push({scope:"number",value:i});const u=(s=>{const o=s?.minimum,i=s?.maximum,u=s?.exclusiveMinimum,_=s?.exclusiveMaximum,w="number"==typeof o,x="number"==typeof i,C="number"==typeof u,j="number"==typeof _,L=C&&(!w||o_);if((w||C)&&(x||j))return`${L?"(":"["}${L?u:o}, ${B?_:i}${B?")":"]"}`;if(w||C)return`${L?">":"≥"} ${L?u:o}`;if(x||j)return`${B?"<":"≤"} ${B?_:i}`;return null})(s);null!==u&&o.push({scope:"number",value:u}),s?.format&&o.push({scope:"string",value:s.format});const _=stringifyConstraintRange("characters",s?.minLength,s?.maxLength);null!==_&&o.push({scope:"string",value:_}),s?.pattern&&o.push({scope:"string",value:`matches ${s?.pattern}`}),s?.contentMediaType&&o.push({scope:"string",value:`media type: ${s.contentMediaType}`}),s?.contentEncoding&&o.push({scope:"string",value:`encoding: ${s.contentEncoding}`});const w=stringifyConstraintRange(s?.hasUniqueItems?"unique items":"items",s?.minItems,s?.maxItems);null!==w&&o.push({scope:"array",value:w});const x=stringifyConstraintRange("contained items",s?.minContains,s?.maxContains);null!==x&&o.push({scope:"array",value:x});const C=stringifyConstraintRange("properties",s?.minProperties,s?.maxProperties);return null!==C&&o.push({scope:"object",value:C}),o},getDependentRequired=(s,o)=>o?.dependentRequired?Array.from(Object.entries(o.dependentRequired).reduce(((o,[i,u])=>Array.isArray(u)&&u.includes(s)?(o.add(i),o):o),new Set)):[],withJSONSchemaContext=(s,o={})=>{const i={components:{JSONSchema:iI,Keyword$schema:keywords_$schema,Keyword$vocabulary:$vocabulary_$vocabulary,Keyword$id:keywords_$id,Keyword$anchor:keywords_$anchor,Keyword$dynamicAnchor:keywords_$dynamicAnchor,Keyword$ref:keywords_$ref,Keyword$dynamicRef:keywords_$dynamicRef,Keyword$defs:keywords_$defs,Keyword$comment:keywords_$comment,KeywordAllOf:keywords_AllOf,KeywordAnyOf:keywords_AnyOf,KeywordOneOf:keywords_OneOf,KeywordNot:keywords_Not,KeywordIf:keywords_If,KeywordThen:keywords_Then,KeywordElse:keywords_Else,KeywordDependentSchemas:keywords_DependentSchemas,KeywordPrefixItems:keywords_PrefixItems,KeywordItems:keywords_Items,KeywordContains:keywords_Contains,KeywordProperties:keywords_Properties_Properties,KeywordPatternProperties:PatternProperties_PatternProperties,KeywordAdditionalProperties:keywords_AdditionalProperties,KeywordPropertyNames:keywords_PropertyNames,KeywordUnevaluatedItems:keywords_UnevaluatedItems,KeywordUnevaluatedProperties:keywords_UnevaluatedProperties,KeywordType:keywords_Type,KeywordEnum:Enum_Enum,KeywordConst:keywords_Const,KeywordConstraint:aI,KeywordDependentRequired:DependentRequired_DependentRequired,KeywordContentSchema:keywords_ContentSchema,KeywordTitle:Title_Title,KeywordDescription:keywords_Description_Description,KeywordDefault:keywords_Default,KeywordDeprecated:keywords_Deprecated,KeywordReadOnly:keywords_ReadOnly,KeywordWriteOnly:keywords_WriteOnly,Accordion:Accordion_Accordion,ExpandDeepButton:ExpandDeepButton_ExpandDeepButton,ChevronRightIcon:icons_ChevronRight,...o.components},config:{default$schema:"https://json-schema.org/draft/2020-12/schema",defaultExpandedLevels:0,...o.config},fn:{upperFirst:fn_upperFirst,getTitle,getType,isBooleanJSONSchema,hasKeyword,isExpandable,stringify:fn_stringify,stringifyConstraints,getDependentRequired,...o.fn}},HOC=o=>Pe.createElement(tI.Provider,{value:i},Pe.createElement(s,o));return HOC.contexts={JSONSchemaContext:tI},HOC.displayName=s.displayName,HOC},json_schema_2020_12=()=>({components:{JSONSchema202012:iI,JSONSchema202012Keyword$schema:keywords_$schema,JSONSchema202012Keyword$vocabulary:$vocabulary_$vocabulary,JSONSchema202012Keyword$id:keywords_$id,JSONSchema202012Keyword$anchor:keywords_$anchor,JSONSchema202012Keyword$dynamicAnchor:keywords_$dynamicAnchor,JSONSchema202012Keyword$ref:keywords_$ref,JSONSchema202012Keyword$dynamicRef:keywords_$dynamicRef,JSONSchema202012Keyword$defs:keywords_$defs,JSONSchema202012Keyword$comment:keywords_$comment,JSONSchema202012KeywordAllOf:keywords_AllOf,JSONSchema202012KeywordAnyOf:keywords_AnyOf,JSONSchema202012KeywordOneOf:keywords_OneOf,JSONSchema202012KeywordNot:keywords_Not,JSONSchema202012KeywordIf:keywords_If,JSONSchema202012KeywordThen:keywords_Then,JSONSchema202012KeywordElse:keywords_Else,JSONSchema202012KeywordDependentSchemas:keywords_DependentSchemas,JSONSchema202012KeywordPrefixItems:keywords_PrefixItems,JSONSchema202012KeywordItems:keywords_Items,JSONSchema202012KeywordContains:keywords_Contains,JSONSchema202012KeywordProperties:keywords_Properties_Properties,JSONSchema202012KeywordPatternProperties:PatternProperties_PatternProperties,JSONSchema202012KeywordAdditionalProperties:keywords_AdditionalProperties,JSONSchema202012KeywordPropertyNames:keywords_PropertyNames,JSONSchema202012KeywordUnevaluatedItems:keywords_UnevaluatedItems,JSONSchema202012KeywordUnevaluatedProperties:keywords_UnevaluatedProperties,JSONSchema202012KeywordType:keywords_Type,JSONSchema202012KeywordEnum:Enum_Enum,JSONSchema202012KeywordConst:keywords_Const,JSONSchema202012KeywordConstraint:aI,JSONSchema202012KeywordDependentRequired:DependentRequired_DependentRequired,JSONSchema202012KeywordContentSchema:keywords_ContentSchema,JSONSchema202012KeywordTitle:Title_Title,JSONSchema202012KeywordDescription:keywords_Description_Description,JSONSchema202012KeywordDefault:keywords_Default,JSONSchema202012KeywordDeprecated:keywords_Deprecated,JSONSchema202012KeywordReadOnly:keywords_ReadOnly,JSONSchema202012KeywordWriteOnly:keywords_WriteOnly,JSONSchema202012Accordion:Accordion_Accordion,JSONSchema202012ExpandDeepButton:ExpandDeepButton_ExpandDeepButton,JSONSchema202012ChevronRightIcon:icons_ChevronRight,withJSONSchema202012Context:withJSONSchemaContext,JSONSchema202012DeepExpansionContext:()=>nI},fn:{upperFirst:fn_upperFirst,jsonSchema202012:{isExpandable,hasKeyword,useFn,useConfig,useComponent,useIsExpandedDeeply}}});var lI=__webpack_require__(11331),cI=__webpack_require__.n(lI);const array=(s,{sample:o})=>((s,o={})=>{const{minItems:i,maxItems:u,uniqueItems:_}=o,{contains:w,minContains:x,maxContains:C}=o;let j=[...s];if(null!=w&&"object"==typeof w){if(Number.isInteger(x)&&x>1){const s=j.at(0);for(let o=1;o0&&(j=s.slice(0,u)),Number.isInteger(i)&&i>0)for(let s=0;j.length{throw new Error("Not implemented")},bytes=s=>St()(s),random_pick=s=>s.at(0),predicates_isBooleanJSONSchema=s=>"boolean"==typeof s,isJSONSchemaObject=s=>cI()(s),isJSONSchema=s=>predicates_isBooleanJSONSchema(s)||isJSONSchemaObject(s);const uI=class Registry{data={};register(s,o){this.data[s]=o}unregister(s){void 0===s?this.data={}:delete this.data[s]}get(s){return this.data[s]}},int32=()=>2**30>>>0,int64=()=>2**53-1,generators_float=()=>.1,generators_double=()=>.1,email=()=>"user@example.com",idn_email=()=>"실례@example.com",hostname=()=>"example.com",idn_hostname=()=>"실례.com",ipv4=()=>"198.51.100.42",ipv6=()=>"2001:0db8:5b96:0000:0000:426f:8e17:642a",uri=()=>"https://example.com/",uri_reference=()=>"path/index.html",iri=()=>"https://실례.com/",iri_reference=()=>"path/실례.html",uuid=()=>"3fa85f64-5717-4562-b3fc-2c963f66afa6",uri_template=()=>"https://example.com/dictionary/{term:1}/{term}",json_pointer=()=>"/a/b/c",relative_json_pointer=()=>"1/0",date_time=()=>(new Date).toISOString(),date=()=>(new Date).toISOString().substring(0,10),time=()=>(new Date).toISOString().substring(11),duration=()=>"P3D",generators_password=()=>"********",regex=()=>"^[a-z]+$";const pI=new class FormatRegistry extends uI{#t={int32,int64,float:generators_float,double:generators_double,email,"idn-email":idn_email,hostname,"idn-hostname":idn_hostname,ipv4,ipv6,uri,"uri-reference":uri_reference,iri,"iri-reference":iri_reference,uuid,"uri-template":uri_template,"json-pointer":json_pointer,"relative-json-pointer":relative_json_pointer,"date-time":date_time,date,time,duration,password:generators_password,regex};data={...this.#t};get defaults(){return{...this.#t}}},formatAPI=(s,o)=>"function"==typeof o?pI.register(s,o):null===o?pI.unregister(s):pI.get(s);formatAPI.getDefaults=()=>pI.defaults;const hI=formatAPI;var dI=__webpack_require__(48287).Buffer;const _7bit=s=>dI.from(s).toString("ascii");var fI=__webpack_require__(48287).Buffer;const _8bit=s=>fI.from(s).toString("utf8");var mI=__webpack_require__(48287).Buffer;const encoders_binary=s=>mI.from(s).toString("binary"),quoted_printable=s=>{let o="";for(let i=0;i=33&&u<=60||u>=62&&u<=126||9===u||32===u)o+=s.charAt(i);else if(13===u||10===u)o+="\r\n";else if(u>126){const u=unescape(encodeURIComponent(s.charAt(i)));for(let s=0;sgI.from(s).toString("hex");var yI=__webpack_require__(48287).Buffer;const base32=s=>{const o=yI.from(s).toString("utf8"),i="ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";let u=0,_="",w=0,x=0;for(let s=0;s=5;)_+=i.charAt(w>>>x-5&31),x-=5;x>0&&(_+=i.charAt(w<<5-x&31),u=(8-8*o.length%5)%5);for(let s=0;svI.from(s).toString("base64");var bI=__webpack_require__(48287).Buffer;const base64url=s=>bI.from(s).toString("base64url");const _I=new class EncoderRegistry extends uI{#t={"7bit":_7bit,"8bit":_8bit,binary:encoders_binary,"quoted-printable":quoted_printable,base16,base32,base64,base64url};data={...this.#t};get defaults(){return{...this.#t}}},encoderAPI=(s,o)=>"function"==typeof o?_I.register(s,o):null===o?_I.unregister(s):_I.get(s);encoderAPI.getDefaults=()=>_I.defaults;const EI=encoderAPI,wI={"text/plain":()=>"string","text/css":()=>".selector { border: 1px solid red }","text/csv":()=>"value1,value2,value3","text/html":()=>"

    content

    ","text/calendar":()=>"BEGIN:VCALENDAR","text/javascript":()=>"console.dir('Hello world!');","text/xml":()=>'John Doe',"text/*":()=>"string"},SI={"image/*":()=>bytes(25).toString("binary")},xI={"audio/*":()=>bytes(25).toString("binary")},kI={"video/*":()=>bytes(25).toString("binary")},CI={"application/json":()=>'{"key":"value"}',"application/ld+json":()=>'{"name": "John Doe"}',"application/x-httpd-php":()=>"Hello World!

    '; ?>","application/rtf":()=>String.raw`{\rtf1\adeflang1025\ansi\ansicpg1252\uc1`,"application/x-sh":()=>'echo "Hello World!"',"application/xhtml+xml":()=>"

    content

    ","application/*":()=>bytes(25).toString("binary")};const OI=new class MediaTypeRegistry extends uI{#t={...wI,...SI,...xI,...kI,...CI};data={...this.#t};get defaults(){return{...this.#t}}},mediaTypeAPI=(s,o)=>{if("function"==typeof o)return OI.register(s,o);if(null===o)return OI.unregister(s);const i=s.split(";").at(0),u=`${i.split("/").at(0)}/*`;return OI.get(s)||OI.get(i)||OI.get(u)};mediaTypeAPI.getDefaults=()=>OI.defaults;const AI=mediaTypeAPI,applyStringConstraints=(s,o={})=>{const{maxLength:i,minLength:u}=o;let _=s;if(Number.isInteger(i)&&i>0&&(_=_.slice(0,i)),Number.isInteger(u)&&u>0){let s=0;for(;_.length{const{contentEncoding:i,contentMediaType:u,contentSchema:_}=s,{pattern:w,format:x}=s,C=EI(i)||Mx();let j;return j="string"==typeof w?applyStringConstraints((s=>{try{return new(us())(s).gen()}catch{return"string"}})(w),s):"string"==typeof x?(s=>{const{format:o}=s,i=hI(o);return"function"==typeof i?i(s):"string"})(s):isJSONSchema(_)&&"string"==typeof u&&void 0!==o?Array.isArray(o)||"object"==typeof o?JSON.stringify(o):applyStringConstraints(String(o),s):"string"==typeof u?(s=>{const{contentMediaType:o}=s,i=AI(o);return"function"==typeof i?i(s):"string"})(s):applyStringConstraints("string",s),C(j)},applyNumberConstraints=(s,o={})=>{const{minimum:i,maximum:u,exclusiveMinimum:_,exclusiveMaximum:w}=o,{multipleOf:x}=o,C=Number.isInteger(s)?1:Number.EPSILON;let j="number"==typeof i?i:null,L="number"==typeof u?u:null,B=s;if("number"==typeof _&&(j=null!==j?Math.max(j,_+C):_+C),"number"==typeof w&&(L=null!==L?Math.min(L,w-C):w-C),B=j>L&&s||j||L||B,"number"==typeof x&&x>0){const s=B%x;B=0===s?B:B+x-s}return B},types_number=s=>{const{format:o}=s;let i;return i="string"==typeof o?(s=>{const{format:o}=s,i=hI(o);return"function"==typeof i?i(s):0})(s):0,applyNumberConstraints(i,s)},types_integer=s=>{const{format:o}=s;let i;return i="string"==typeof o?(s=>{const{format:o}=s,i=hI(o);if("function"==typeof i)return i(s);switch(o){case"int32":return int32();case"int64":return int64()}return 0})(s):0,applyNumberConstraints(i,s)},types_boolean=s=>"boolean"!=typeof s.default||s.default,jI=new Proxy({array,object,string:types_string,number:types_number,integer:types_integer,boolean:types_boolean,null:()=>null},{get:(s,o)=>"string"==typeof o&&Object.hasOwn(s,o)?s[o]:()=>`Unknown Type: ${o}`}),II=["array","object","number","integer","string","boolean","null"],hasExample=s=>{if(!isJSONSchemaObject(s))return!1;const{examples:o,example:i,default:u}=s;return!!(Array.isArray(o)&&o.length>=1)||(void 0!==u||void 0!==i)},extractExample=s=>{if(!isJSONSchemaObject(s))return null;const{examples:o,example:i,default:u}=s;return Array.isArray(o)&&o.length>=1?o.at(0):void 0!==u?u:void 0!==i?i:void 0},PI={array:["items","prefixItems","contains","maxContains","minContains","maxItems","minItems","uniqueItems","unevaluatedItems"],object:["properties","additionalProperties","patternProperties","propertyNames","minProperties","maxProperties","required","dependentSchemas","dependentRequired","unevaluatedProperties"],string:["pattern","format","minLength","maxLength","contentEncoding","contentMediaType","contentSchema"],integer:["minimum","maximum","exclusiveMinimum","exclusiveMaximum","multipleOf"]};PI.number=PI.integer;const MI="string",inferTypeFromValue=s=>void 0===s?null:null===s?"null":Array.isArray(s)?"array":Number.isInteger(s)?"integer":typeof s,foldType=s=>{if(Array.isArray(s)&&s.length>=1){if(s.includes("array"))return"array";if(s.includes("object"))return"object";{const o=random_pick(s);if(II.includes(o))return o}}return II.includes(s)?s:null},inferType=(s,o=new WeakSet)=>{if(!isJSONSchemaObject(s))return MI;if(o.has(s))return MI;o.add(s);let{type:i,const:u}=s;if(i=foldType(i),"string"!=typeof i){const o=Object.keys(PI);e:for(let u=0;u{if(Array.isArray(s[i])){const u=s[i].map((s=>inferType(s,o)));return foldType(u)}return null},u=combineTypes("allOf"),_=combineTypes("anyOf"),w=combineTypes("oneOf"),x=s.not?inferType(s.not,o):null;(u||_||w||x)&&(i=foldType([u,_,w,x].filter(Boolean)))}if("string"!=typeof i&&hasExample(s)){const o=extractExample(s),u=inferTypeFromValue(o);i="string"==typeof u?u:i}return o.delete(s),i||MI},type_getType=s=>inferType(s),typeCast=s=>predicates_isBooleanJSONSchema(s)?(s=>!1===s?{not:{}}:{})(s):isJSONSchemaObject(s)?s:{},merge_merge=(s,o,i={})=>{if(predicates_isBooleanJSONSchema(s)&&!0===s)return!0;if(predicates_isBooleanJSONSchema(s)&&!1===s)return!1;if(predicates_isBooleanJSONSchema(o)&&!0===o)return!0;if(predicates_isBooleanJSONSchema(o)&&!1===o)return!1;if(!isJSONSchema(s))return o;if(!isJSONSchema(o))return s;const u={...o,...s};if(o.type&&s.type&&Array.isArray(o.type)&&"string"==typeof o.type){const i=normalizeArray(o.type).concat(s.type);u.type=Array.from(new Set(i))}if(Array.isArray(o.required)&&Array.isArray(s.required)&&(u.required=[...new Set([...s.required,...o.required])]),o.properties&&s.properties){const _=new Set([...Object.keys(o.properties),...Object.keys(s.properties)]);u.properties={};for(const w of _){const _=o.properties[w]||{},x=s.properties[w]||{};_.readOnly&&!i.includeReadOnly||_.writeOnly&&!i.includeWriteOnly?u.required=(u.required||[]).filter((s=>s!==w)):u.properties[w]=merge_merge(x,_,i)}}return isJSONSchema(o.items)&&isJSONSchema(s.items)&&(u.items=merge_merge(s.items,o.items,i)),isJSONSchema(o.contains)&&isJSONSchema(s.contains)&&(u.contains=merge_merge(s.contains,o.contains,i)),isJSONSchema(o.contentSchema)&&isJSONSchema(s.contentSchema)&&(u.contentSchema=merge_merge(s.contentSchema,o.contentSchema,i)),u},TI=merge_merge,main_sampleFromSchemaGeneric=(s,o={},i=void 0,u=!1)=>{if(null==s&&void 0===i)return;"function"==typeof s?.toJS&&(s=s.toJS()),s=typeCast(s);let _=void 0!==i||hasExample(s);const w=!_&&Array.isArray(s.oneOf)&&s.oneOf.length>0,x=!_&&Array.isArray(s.anyOf)&&s.anyOf.length>0;if(!_&&(w||x)){const i=typeCast(random_pick(w?s.oneOf:s.anyOf));!(s=TI(s,i,o)).xml&&i.xml&&(s.xml=i.xml),hasExample(s)&&hasExample(i)&&(_=!0)}const C={};let{xml:j,properties:L,additionalProperties:B,items:$,contains:V}=s||{},U=type_getType(s),{includeReadOnly:z,includeWriteOnly:Y}=o;j=j||{};let Z,{name:ee,prefix:ie,namespace:ae}=j,le={};if(Object.hasOwn(s,"type")||(s.type=U),u&&(ee=ee||"notagname",Z=(ie?`${ie}:`:"")+ee,ae)){C[ie?`xmlns:${ie}`:"xmlns"]=ae}u&&(le[Z]=[]);const ce=objectify(L);let pe,de=0;const hasExceededMaxProperties=()=>Number.isInteger(s.maxProperties)&&s.maxProperties>0&&de>=s.maxProperties,canAddProperty=o=>!(Number.isInteger(s.maxProperties)&&s.maxProperties>0)||!hasExceededMaxProperties()&&(!(o=>!Array.isArray(s.required)||0===s.required.length||!s.required.includes(o))(o)||s.maxProperties-de-(()=>{if(!Array.isArray(s.required)||0===s.required.length)return 0;let o=0;return u?s.required.forEach((s=>o+=void 0===le[s]?0:1)):s.required.forEach((s=>{o+=void 0===le[Z]?.find((o=>void 0!==o[s]))?0:1})),s.required.length-o})()>0);if(pe=u?(i,_=void 0)=>{if(s&&ce[i]){if(ce[i].xml=ce[i].xml||{},ce[i].xml.attribute){const s=Array.isArray(ce[i].enum)?random_pick(ce[i].enum):void 0;if(hasExample(ce[i]))C[ce[i].xml.name||i]=extractExample(ce[i]);else if(void 0!==s)C[ce[i].xml.name||i]=s;else{const s=typeCast(ce[i]),o=type_getType(s),u=ce[i].xml.name||i;C[u]=jI[o](s)}return}ce[i].xml.name=ce[i].xml.name||i}else ce[i]||!1===B||(ce[i]={xml:{name:i}});let w=main_sampleFromSchemaGeneric(ce[i],o,_,u);canAddProperty(i)&&(de++,Array.isArray(w)?le[Z]=le[Z].concat(w):le[Z].push(w))}:(i,_)=>{if(canAddProperty(i)){if(cI()(s.discriminator?.mapping)&&s.discriminator.propertyName===i&&"string"==typeof s.$$ref){for(const o in s.discriminator.mapping)if(-1!==s.$$ref.search(s.discriminator.mapping[o])){le[i]=o;break}}else le[i]=main_sampleFromSchemaGeneric(ce[i],o,_,u);de++}},_){let _;if(_=void 0!==i?i:extractExample(s),!u){if("number"==typeof _&&"string"===U)return`${_}`;if("string"!=typeof _||"string"===U)return _;try{return JSON.parse(_)}catch{return _}}if("array"===U){if(!Array.isArray(_)){if("string"==typeof _)return _;_=[_]}let i=[];return isJSONSchemaObject($)&&($.xml=$.xml||j||{},$.xml.name=$.xml.name||j.name,i=_.map((s=>main_sampleFromSchemaGeneric($,o,s,u)))),isJSONSchemaObject(V)&&(V.xml=V.xml||j||{},V.xml.name=V.xml.name||j.name,i=[main_sampleFromSchemaGeneric(V,o,void 0,u),...i]),i=jI.array(s,{sample:i}),j.wrapped?(le[Z]=i,hs()(C)||le[Z].push({_attr:C})):le=i,le}if("object"===U){if("string"==typeof _)return _;for(const s in _)Object.hasOwn(_,s)&&(ce[s]?.readOnly&&!z||ce[s]?.writeOnly&&!Y||(ce[s]?.xml?.attribute?C[ce[s].xml.name||s]=_[s]:pe(s,_[s])));return hs()(C)||le[Z].push({_attr:C}),le}return le[Z]=hs()(C)?_:[{_attr:C},_],le}if("array"===U){let i=[];if(isJSONSchemaObject(V))if(u&&(V.xml=V.xml||s.xml||{},V.xml.name=V.xml.name||j.name),Array.isArray(V.anyOf)){const{anyOf:s,..._}=$;i.push(...V.anyOf.map((s=>main_sampleFromSchemaGeneric(TI(s,_,o),o,void 0,u))))}else if(Array.isArray(V.oneOf)){const{oneOf:s,..._}=$;i.push(...V.oneOf.map((s=>main_sampleFromSchemaGeneric(TI(s,_,o),o,void 0,u))))}else{if(!(!u||u&&j.wrapped))return main_sampleFromSchemaGeneric(V,o,void 0,u);i.push(main_sampleFromSchemaGeneric(V,o,void 0,u))}if(isJSONSchemaObject($))if(u&&($.xml=$.xml||s.xml||{},$.xml.name=$.xml.name||j.name),Array.isArray($.anyOf)){const{anyOf:s,..._}=$;i.push(...$.anyOf.map((s=>main_sampleFromSchemaGeneric(TI(s,_,o),o,void 0,u))))}else if(Array.isArray($.oneOf)){const{oneOf:s,..._}=$;i.push(...$.oneOf.map((s=>main_sampleFromSchemaGeneric(TI(s,_,o),o,void 0,u))))}else{if(!(!u||u&&j.wrapped))return main_sampleFromSchemaGeneric($,o,void 0,u);i.push(main_sampleFromSchemaGeneric($,o,void 0,u))}return i=jI.array(s,{sample:i}),u&&j.wrapped?(le[Z]=i,hs()(C)||le[Z].push({_attr:C}),le):i}if("object"===U){for(let s in ce)Object.hasOwn(ce,s)&&(ce[s]?.deprecated||ce[s]?.readOnly&&!z||ce[s]?.writeOnly&&!Y||pe(s));if(u&&C&&le[Z].push({_attr:C}),hasExceededMaxProperties())return le;if(predicates_isBooleanJSONSchema(B)&&B)u?le[Z].push({additionalProp:"Anything can be here"}):le.additionalProp1={},de++;else if(isJSONSchemaObject(B)){const i=B,_=main_sampleFromSchemaGeneric(i,o,void 0,u);if(u&&"string"==typeof i?.xml?.name&&"notagname"!==i?.xml?.name)le[Z].push(_);else{const o=Number.isInteger(s.minProperties)&&s.minProperties>0&&de{const u=main_sampleFromSchemaGeneric(s,o,i,!0);if(u)return"string"==typeof u?u:ls()(u,{declaration:!0,indent:"\t"})},main_sampleFromSchema=(s,o,i)=>main_sampleFromSchemaGeneric(s,o,i,!1),main_resolver=(s,o,i)=>[s,JSON.stringify(o),JSON.stringify(i)],NI=utils_memoizeN(main_createXMLExample,main_resolver),RI=utils_memoizeN(main_sampleFromSchema,main_resolver);const DI=new class OptionRegistry extends uI{#t={};data={...this.#t};get defaults(){return{...this.#t}}},api_optionAPI=(s,o)=>(void 0!==o&&DI.register(s,o),DI.get(s)),LI=[{when:/json/,shouldStringifyTypes:["string"]}],BI=["object"],fn_get_json_sample_schema=s=>(o,i,u,_)=>{const{fn:w}=s(),x=w.jsonSchema202012.memoizedSampleFromSchema(o,i,_),C=typeof x,j=LI.reduce(((s,o)=>o.when.test(u)?[...s,...o.shouldStringifyTypes]:s),BI);return mt()(j,(s=>s===C))?JSON.stringify(x,null,2):x},fn_get_yaml_sample_schema=s=>(o,i,u,_)=>{const{fn:w}=s(),x=w.jsonSchema202012.getJsonSampleSchema(o,i,u,_);let C;try{C=mn.dump(mn.load(x),{lineWidth:-1},{schema:nn}),"\n"===C[C.length-1]&&(C=C.slice(0,C.length-1))}catch(s){return console.error(s),"error: could not generate yaml example"}return C.replace(/\t/g," ")},fn_get_xml_sample_schema=s=>(o,i,u)=>{const{fn:_}=s();if(o&&!o.xml&&(o.xml={}),o&&!o.xml.name){if(!o.$$ref&&(o.type||o.items||o.properties||o.additionalProperties))return'\n\x3c!-- XML example cannot be generated; root element name is undefined --\x3e';if(o.$$ref){let s=o.$$ref.match(/\S*\/(\S+)$/);o.xml.name=s[1]}}return _.jsonSchema202012.memoizedCreateXMLExample(o,i,u)},fn_get_sample_schema=s=>(o,i="",u={},_=void 0)=>{const{fn:w}=s();return"function"==typeof o?.toJS&&(o=o.toJS()),"function"==typeof _?.toJS&&(_=_.toJS()),/xml/.test(i)?w.jsonSchema202012.getXmlSampleSchema(o,u,_):/(yaml|yml)/.test(i)?w.jsonSchema202012.getYamlSampleSchema(o,u,i,_):w.jsonSchema202012.getJsonSampleSchema(o,u,i,_)},json_schema_2020_12_samples=({getSystem:s})=>{const o=fn_get_json_sample_schema(s),i=fn_get_yaml_sample_schema(s),u=fn_get_xml_sample_schema(s),_=fn_get_sample_schema(s);return{fn:{jsonSchema202012:{sampleFromSchema:main_sampleFromSchema,sampleFromSchemaGeneric:main_sampleFromSchemaGeneric,sampleOptionAPI:api_optionAPI,sampleEncoderAPI:EI,sampleFormatAPI:hI,sampleMediaTypeAPI:AI,createXMLExample:main_createXMLExample,memoizedSampleFromSchema:RI,memoizedCreateXMLExample:NI,getJsonSampleSchema:o,getYamlSampleSchema:i,getXmlSampleSchema:u,getSampleSchema:_,mergeJsonSchema:TI}}}};function PresetApis(){return[base,oas3,json_schema_2020_12,json_schema_2020_12_samples,oas31]}const inline_plugin=s=>()=>({fn:s.fn,components:s.components}),factorization_system=s=>{const o=We()({layout:{layout:s.layout,filter:s.filter},spec:{spec:"",url:s.url},requestSnippets:s.requestSnippets},s.initialState);if(s.initialState)for(const[i,u]of Object.entries(s.initialState))void 0===u&&delete o[i];return{system:{configs:s.configs},plugins:s.presets,state:o}},sources_query=()=>s=>{const o=s.queryConfigEnabled?(()=>{const s=new URLSearchParams(at.location.search);return Object.fromEntries(s)})():{};return Object.entries(o).reduce(((s,[o,i])=>("config"===o?s.configUrl=i:"urls.primaryName"===o?s[o]=i:s=ao()(s,o,i),s)),{})},sources_url=({url:s,system:o})=>async i=>{if(!s)return{};if("function"!=typeof o.configsActions?.getConfigByUrl)return{};const u=(()=>{const s={};return s.promise=new Promise(((o,i)=>{s.resolve=o,s.reject=i})),s})();return o.configsActions.getConfigByUrl({url:s,loadRemoteConfig:!0,requestInterceptor:i.requestInterceptor,responseInterceptor:i.responseInterceptor},(s=>{u.resolve(s)})),u.promise},runtime=()=>()=>{const s={};return globalThis.location&&(s.oauth2RedirectUrl=`${globalThis.location.protocol}//${globalThis.location.host}${globalThis.location.pathname.substring(0,globalThis.location.pathname.lastIndexOf("/"))}/oauth2-redirect.html`),s},FI=Object.freeze({dom_id:null,domNode:null,spec:{},url:"",urls:null,configUrl:null,layout:"BaseLayout",docExpansion:"list",maxDisplayedTags:-1,filter:!1,validatorUrl:"https://validator.swagger.io/validator",oauth2RedirectUrl:void 0,persistAuthorization:!1,configs:{},displayOperationId:!1,displayRequestDuration:!1,deepLinking:!1,tryItOutEnabled:!1,requestInterceptor:s=>(s.curlOptions=[],s),responseInterceptor:s=>s,showMutatedRequest:!0,defaultModelRendering:"example",defaultModelExpandDepth:1,defaultModelsExpandDepth:1,showExtensions:!1,showCommonExtensions:!1,withCredentials:!1,requestSnippetsEnabled:!1,requestSnippets:{generators:{curl_bash:{title:"cURL (bash)",syntax:"bash"},curl_powershell:{title:"cURL (PowerShell)",syntax:"powershell"},curl_cmd:{title:"cURL (CMD)",syntax:"bash"}},defaultExpanded:!0,languages:null},supportedSubmitMethods:["get","put","post","delete","options","head","patch","trace"],queryConfigEnabled:!1,presets:[PresetApis],plugins:[],initialState:{},fn:{},components:{},syntaxHighlight:{activated:!0,theme:"agate"},operationsSorter:null,tagsSorter:null,onComplete:null,modelPropertyMacro:null,parameterMacro:null});var qI=__webpack_require__(61448),$I=__webpack_require__.n(qI),VI=__webpack_require__(77731),UI=__webpack_require__.n(VI);const type_casters_array=(s,o=[])=>Array.isArray(s)?s:o,type_casters_boolean=(s,o=!1)=>!0===s||"true"===s||1===s||"1"===s||!1!==s&&"false"!==s&&0!==s&&"0"!==s&&o,dom_node=s=>null===s||"null"===s?null:s,type_casters_filter=s=>{const o=String(s);return type_casters_boolean(s,o)},type_casters_function=(s,o)=>"function"==typeof s?s:o,nullable_array=s=>Array.isArray(s)?s:null,nullable_function=s=>"function"==typeof s?s:null,nullable_string=s=>null===s||"null"===s?null:String(s),type_casters_number=(s,o=-1)=>{const i=parseInt(s,10);return Number.isNaN(i)?o:i},type_casters_object=(s,o={})=>cI()(s)?s:o,sorter=s=>"function"==typeof s||"string"==typeof s?s:null,type_casters_string=s=>String(s),syntax_highlight=(s,o)=>cI()(s)?s:!1===s||"false"===s||0===s||"0"===s?{activated:!1}:o,undefined_string=s=>void 0===s||"undefined"===s?void 0:String(s),zI={components:{typeCaster:type_casters_object},configs:{typeCaster:type_casters_object},configUrl:{typeCaster:nullable_string},deepLinking:{typeCaster:type_casters_boolean,defaultValue:FI.deepLinking},defaultModelExpandDepth:{typeCaster:type_casters_number,defaultValue:FI.defaultModelExpandDepth},defaultModelRendering:{typeCaster:type_casters_string},defaultModelsExpandDepth:{typeCaster:type_casters_number,defaultValue:FI.defaultModelsExpandDepth},displayOperationId:{typeCaster:type_casters_boolean,defaultValue:FI.displayOperationId},displayRequestDuration:{typeCaster:type_casters_boolean,defaultValue:FI.displayRequestDuration},docExpansion:{typeCaster:type_casters_string},dom_id:{typeCaster:nullable_string},domNode:{typeCaster:dom_node},filter:{typeCaster:type_casters_filter},fn:{typeCaster:type_casters_object},initialState:{typeCaster:type_casters_object},layout:{typeCaster:type_casters_string},maxDisplayedTags:{typeCaster:type_casters_number,defaultValue:FI.maxDisplayedTags},modelPropertyMacro:{typeCaster:nullable_function},oauth2RedirectUrl:{typeCaster:undefined_string},onComplete:{typeCaster:nullable_function},operationsSorter:{typeCaster:sorter},paramaterMacro:{typeCaster:nullable_function},persistAuthorization:{typeCaster:type_casters_boolean,defaultValue:FI.persistAuthorization},plugins:{typeCaster:type_casters_array,defaultValue:FI.plugins},presets:{typeCaster:type_casters_array,defaultValue:FI.presets},requestInterceptor:{typeCaster:type_casters_function,defaultValue:FI.requestInterceptor},requestSnippets:{typeCaster:type_casters_object,defaultValue:FI.requestSnippets},requestSnippetsEnabled:{typeCaster:type_casters_boolean,defaultValue:FI.requestSnippetsEnabled},responseInterceptor:{typeCaster:type_casters_function,defaultValue:FI.responseInterceptor},showCommonExtensions:{typeCaster:type_casters_boolean,defaultValue:FI.showCommonExtensions},showExtensions:{typeCaster:type_casters_boolean,defaultValue:FI.showExtensions},showMutatedRequest:{typeCaster:type_casters_boolean,defaultValue:FI.showMutatedRequest},spec:{typeCaster:type_casters_object,defaultValue:FI.spec},supportedSubmitMethods:{typeCaster:type_casters_array,defaultValue:FI.supportedSubmitMethods},syntaxHighlight:{typeCaster:syntax_highlight,defaultValue:FI.syntaxHighlight},"syntaxHighlight.activated":{typeCaster:type_casters_boolean,defaultValue:FI.syntaxHighlight.activated},"syntaxHighlight.theme":{typeCaster:type_casters_string},tagsSorter:{typeCaster:sorter},tryItOutEnabled:{typeCaster:type_casters_boolean,defaultValue:FI.tryItOutEnabled},url:{typeCaster:type_casters_string},urls:{typeCaster:nullable_array},"urls.primaryName":{typeCaster:type_casters_string},validatorUrl:{typeCaster:nullable_string},withCredentials:{typeCaster:type_casters_boolean,defaultValue:FI.withCredentials}},type_cast=s=>Object.entries(zI).reduce(((s,[o,{typeCaster:i,defaultValue:u}])=>{if($I()(s,o)){const _=i(jn()(s,o),u);s=UI()(o,_,s)}return s}),{...s}),config_merge=(s,...o)=>{let i=Symbol.for("domNode"),u=Symbol.for("primaryName");const _=[];for(const s of o){const o={...s};Object.hasOwn(o,"domNode")&&(i=o.domNode,delete o.domNode),Object.hasOwn(o,"urls.primaryName")?(u=o["urls.primaryName"],delete o["urls.primaryName"]):Array.isArray(o.urls)&&Object.hasOwn(o.urls,"primaryName")&&(u=o.urls.primaryName,delete o.urls.primaryName),_.push(o)}const w=We()(s,..._);return i!==Symbol.for("domNode")&&(w.domNode=i),u!==Symbol.for("primaryName")&&Array.isArray(w.urls)&&(w.urls.primaryName=u),type_cast(w)};function SwaggerUI(s){const o=sources_query()(s),i=runtime()(),u=SwaggerUI.config.merge({},SwaggerUI.config.defaults,i,s,o),_=factorization_system(u),w=inline_plugin(u),x=new Store(_);x.register([u.plugins,w]);const C=x.getSystem(),persistConfigs=s=>{x.setConfigs(s),C.configsActions.loaded()},updateSpec=s=>{!o.url&&"object"==typeof s.spec&&Object.keys(s.spec).length>0?(C.specActions.updateUrl(""),C.specActions.updateLoadingStatus("success"),C.specActions.updateSpec(JSON.stringify(s.spec))):"function"==typeof C.specActions.download&&s.url&&!s.urls&&(C.specActions.updateUrl(s.url),C.specActions.download(s.url))},render=s=>{if(s.domNode)C.render(s.domNode,"App");else if(s.dom_id){const o=document.querySelector(s.dom_id);C.render(o,"App")}else null===s.dom_id||null===s.domNode||console.error("Skipped rendering: no `dom_id` or `domNode` was specified")};return u.configUrl?((async()=>{const{configUrl:s}=u,i=await sources_url({url:s,system:C})(u),_=SwaggerUI.config.merge({},u,i,o);persistConfigs(_),null!==i&&updateSpec(_),render(_)})(),C):(persistConfigs(u),updateSpec(u),render(u),C)}SwaggerUI.System=Store,SwaggerUI.config={defaults:FI,merge:config_merge,typeCast:type_cast,typeCastMappings:zI},SwaggerUI.presets={base,apis:PresetApis},SwaggerUI.plugins={Auth:auth,Configs:configsPlugin,DeepLining:deep_linking,Err:err,Filter:filter,Icons:icons,JSONSchema5:json_schema_5,JSONSchema5Samples:json_schema_5_samples,JSONSchema202012:json_schema_2020_12,JSONSchema202012Samples:json_schema_2020_12_samples,Layout:plugins_layout,Logs:logs,OpenAPI30:oas3,OpenAPI31:oas3,OnComplete:on_complete,RequestSnippets:plugins_request_snippets,Spec:plugins_spec,SwaggerClient:swagger_client,Util:util,View:view,ViewLegacy:view_legacy,DownloadUrl:downloadUrlPlugin,SyntaxHighlighting:syntax_highlighting,Versions:versions,SafeRender:safe_render};const WI=SwaggerUI})(),_=_.default})())); \ No newline at end of file diff --git a/server/internal/httpapi/docs/swagger-ui/swagger-ui-standalone-preset.js b/server/internal/httpapi/docs/swagger-ui/swagger-ui-standalone-preset.js new file mode 100644 index 0000000..6ea56ce --- /dev/null +++ b/server/internal/httpapi/docs/swagger-ui/swagger-ui-standalone-preset.js @@ -0,0 +1,2 @@ +/*! For license information please see swagger-ui-standalone-preset.js.LICENSE.txt */ +!function webpackUniversalModuleDefinition(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.SwaggerUIStandalonePreset=t():e.SwaggerUIStandalonePreset=t()}(this,(()=>(()=>{var e={9119:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.BLANK_URL=t.relativeFirstCharacters=t.whitespaceEscapeCharsRegex=t.urlSchemeRegex=t.ctrlCharactersRegex=t.htmlCtrlEntityRegex=t.htmlEntitiesRegex=t.invalidProtocolRegex=void 0,t.invalidProtocolRegex=/^([^\w]*)(javascript|data|vbscript)/im,t.htmlEntitiesRegex=/&#(\w+)(^\w|;)?/g,t.htmlCtrlEntityRegex=/&(newline|tab);/gi,t.ctrlCharactersRegex=/[\u0000-\u001F\u007F-\u009F\u2000-\u200D\uFEFF]/gim,t.urlSchemeRegex=/^.+(:|:)/gim,t.whitespaceEscapeCharsRegex=/(\\|%5[cC])((%(6[eE]|72|74))|[nrt])/g,t.relativeFirstCharacters=[".","/"],t.BLANK_URL="about:blank"},6750:(e,t,r)=>{"use strict";var n=r(9119);function decodeURI(e){try{return decodeURIComponent(e)}catch(t){return e}}},7526:(e,t)=>{"use strict";t.byteLength=function byteLength(e){var t=getLens(e),r=t[0],n=t[1];return 3*(r+n)/4-n},t.toByteArray=function toByteArray(e){var t,r,o=getLens(e),a=o[0],s=o[1],u=new i(function _byteLength(e,t,r){return 3*(t+r)/4-r}(0,a,s)),c=0,f=s>0?a-4:a;for(r=0;r>16&255,u[c++]=t>>8&255,u[c++]=255&t;2===s&&(t=n[e.charCodeAt(r)]<<2|n[e.charCodeAt(r+1)]>>4,u[c++]=255&t);1===s&&(t=n[e.charCodeAt(r)]<<10|n[e.charCodeAt(r+1)]<<4|n[e.charCodeAt(r+2)]>>2,u[c++]=t>>8&255,u[c++]=255&t);return u},t.fromByteArray=function fromByteArray(e){for(var t,n=e.length,i=n%3,o=[],a=16383,s=0,u=n-i;su?u:s+a));1===i?(t=e[n-1],o.push(r[t>>2]+r[t<<4&63]+"==")):2===i&&(t=(e[n-2]<<8)+e[n-1],o.push(r[t>>10]+r[t>>4&63]+r[t<<2&63]+"="));return o.join("")};for(var r=[],n=[],i="undefined"!=typeof Uint8Array?Uint8Array:Array,o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",a=0;a<64;++a)r[a]=o[a],n[o.charCodeAt(a)]=a;function getLens(e){var t=e.length;if(t%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var r=e.indexOf("=");return-1===r&&(r=t),[r,r===t?0:4-r%4]}function encodeChunk(e,t,n){for(var i,o,a=[],s=t;s>18&63]+r[o>>12&63]+r[o>>6&63]+r[63&o]);return a.join("")}n["-".charCodeAt(0)]=62,n["_".charCodeAt(0)]=63},8287:(e,t,r)=>{"use strict";const n=r(7526),i=r(251),o="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):null;t.Buffer=Buffer,t.SlowBuffer=function SlowBuffer(e){+e!=e&&(e=0);return Buffer.alloc(+e)},t.INSPECT_MAX_BYTES=50;const a=2147483647;function createBuffer(e){if(e>a)throw new RangeError('The value "'+e+'" is invalid for option "size"');const t=new Uint8Array(e);return Object.setPrototypeOf(t,Buffer.prototype),t}function Buffer(e,t,r){if("number"==typeof e){if("string"==typeof t)throw new TypeError('The "string" argument must be of type string. Received type number');return allocUnsafe(e)}return from(e,t,r)}function from(e,t,r){if("string"==typeof e)return function fromString(e,t){"string"==typeof t&&""!==t||(t="utf8");if(!Buffer.isEncoding(t))throw new TypeError("Unknown encoding: "+t);const r=0|byteLength(e,t);let n=createBuffer(r);const i=n.write(e,t);i!==r&&(n=n.slice(0,i));return n}(e,t);if(ArrayBuffer.isView(e))return function fromArrayView(e){if(isInstance(e,Uint8Array)){const t=new Uint8Array(e);return fromArrayBuffer(t.buffer,t.byteOffset,t.byteLength)}return fromArrayLike(e)}(e);if(null==e)throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof e);if(isInstance(e,ArrayBuffer)||e&&isInstance(e.buffer,ArrayBuffer))return fromArrayBuffer(e,t,r);if("undefined"!=typeof SharedArrayBuffer&&(isInstance(e,SharedArrayBuffer)||e&&isInstance(e.buffer,SharedArrayBuffer)))return fromArrayBuffer(e,t,r);if("number"==typeof e)throw new TypeError('The "value" argument must not be of type number. Received type number');const n=e.valueOf&&e.valueOf();if(null!=n&&n!==e)return Buffer.from(n,t,r);const i=function fromObject(e){if(Buffer.isBuffer(e)){const t=0|checked(e.length),r=createBuffer(t);return 0===r.length||e.copy(r,0,0,t),r}if(void 0!==e.length)return"number"!=typeof e.length||numberIsNaN(e.length)?createBuffer(0):fromArrayLike(e);if("Buffer"===e.type&&Array.isArray(e.data))return fromArrayLike(e.data)}(e);if(i)return i;if("undefined"!=typeof Symbol&&null!=Symbol.toPrimitive&&"function"==typeof e[Symbol.toPrimitive])return Buffer.from(e[Symbol.toPrimitive]("string"),t,r);throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof e)}function assertSize(e){if("number"!=typeof e)throw new TypeError('"size" argument must be of type number');if(e<0)throw new RangeError('The value "'+e+'" is invalid for option "size"')}function allocUnsafe(e){return assertSize(e),createBuffer(e<0?0:0|checked(e))}function fromArrayLike(e){const t=e.length<0?0:0|checked(e.length),r=createBuffer(t);for(let n=0;n=a)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+a.toString(16)+" bytes");return 0|e}function byteLength(e,t){if(Buffer.isBuffer(e))return e.length;if(ArrayBuffer.isView(e)||isInstance(e,ArrayBuffer))return e.byteLength;if("string"!=typeof e)throw new TypeError('The "string" argument must be one of type string, Buffer, or ArrayBuffer. Received type '+typeof e);const r=e.length,n=arguments.length>2&&!0===arguments[2];if(!n&&0===r)return 0;let i=!1;for(;;)switch(t){case"ascii":case"latin1":case"binary":return r;case"utf8":case"utf-8":return utf8ToBytes(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*r;case"hex":return r>>>1;case"base64":return base64ToBytes(e).length;default:if(i)return n?-1:utf8ToBytes(e).length;t=(""+t).toLowerCase(),i=!0}}function slowToString(e,t,r){let n=!1;if((void 0===t||t<0)&&(t=0),t>this.length)return"";if((void 0===r||r>this.length)&&(r=this.length),r<=0)return"";if((r>>>=0)<=(t>>>=0))return"";for(e||(e="utf8");;)switch(e){case"hex":return hexSlice(this,t,r);case"utf8":case"utf-8":return utf8Slice(this,t,r);case"ascii":return asciiSlice(this,t,r);case"latin1":case"binary":return latin1Slice(this,t,r);case"base64":return base64Slice(this,t,r);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return utf16leSlice(this,t,r);default:if(n)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),n=!0}}function swap(e,t,r){const n=e[t];e[t]=e[r],e[r]=n}function bidirectionalIndexOf(e,t,r,n,i){if(0===e.length)return-1;if("string"==typeof r?(n=r,r=0):r>2147483647?r=2147483647:r<-2147483648&&(r=-2147483648),numberIsNaN(r=+r)&&(r=i?0:e.length-1),r<0&&(r=e.length+r),r>=e.length){if(i)return-1;r=e.length-1}else if(r<0){if(!i)return-1;r=0}if("string"==typeof t&&(t=Buffer.from(t,n)),Buffer.isBuffer(t))return 0===t.length?-1:arrayIndexOf(e,t,r,n,i);if("number"==typeof t)return t&=255,"function"==typeof Uint8Array.prototype.indexOf?i?Uint8Array.prototype.indexOf.call(e,t,r):Uint8Array.prototype.lastIndexOf.call(e,t,r):arrayIndexOf(e,[t],r,n,i);throw new TypeError("val must be string, number or Buffer")}function arrayIndexOf(e,t,r,n,i){let o,a=1,s=e.length,u=t.length;if(void 0!==n&&("ucs2"===(n=String(n).toLowerCase())||"ucs-2"===n||"utf16le"===n||"utf-16le"===n)){if(e.length<2||t.length<2)return-1;a=2,s/=2,u/=2,r/=2}function read(e,t){return 1===a?e[t]:e.readUInt16BE(t*a)}if(i){let n=-1;for(o=r;os&&(r=s-u),o=r;o>=0;o--){let r=!0;for(let n=0;ni&&(n=i):n=i;const o=t.length;let a;for(n>o/2&&(n=o/2),a=0;a>8,i=r%256,o.push(i),o.push(n);return o}(t,e.length-r),e,r,n)}function base64Slice(e,t,r){return 0===t&&r===e.length?n.fromByteArray(e):n.fromByteArray(e.slice(t,r))}function utf8Slice(e,t,r){r=Math.min(e.length,r);const n=[];let i=t;for(;i239?4:t>223?3:t>191?2:1;if(i+a<=r){let r,n,s,u;switch(a){case 1:t<128&&(o=t);break;case 2:r=e[i+1],128==(192&r)&&(u=(31&t)<<6|63&r,u>127&&(o=u));break;case 3:r=e[i+1],n=e[i+2],128==(192&r)&&128==(192&n)&&(u=(15&t)<<12|(63&r)<<6|63&n,u>2047&&(u<55296||u>57343)&&(o=u));break;case 4:r=e[i+1],n=e[i+2],s=e[i+3],128==(192&r)&&128==(192&n)&&128==(192&s)&&(u=(15&t)<<18|(63&r)<<12|(63&n)<<6|63&s,u>65535&&u<1114112&&(o=u))}}null===o?(o=65533,a=1):o>65535&&(o-=65536,n.push(o>>>10&1023|55296),o=56320|1023&o),n.push(o),i+=a}return function decodeCodePointsArray(e){const t=e.length;if(t<=s)return String.fromCharCode.apply(String,e);let r="",n=0;for(;nn.length?(Buffer.isBuffer(t)||(t=Buffer.from(t)),t.copy(n,i)):Uint8Array.prototype.set.call(n,t,i);else{if(!Buffer.isBuffer(t))throw new TypeError('"list" argument must be an Array of Buffers');t.copy(n,i)}i+=t.length}return n},Buffer.byteLength=byteLength,Buffer.prototype._isBuffer=!0,Buffer.prototype.swap16=function swap16(){const e=this.length;if(e%2!=0)throw new RangeError("Buffer size must be a multiple of 16-bits");for(let t=0;tr&&(e+=" ... "),""},o&&(Buffer.prototype[o]=Buffer.prototype.inspect),Buffer.prototype.compare=function compare(e,t,r,n,i){if(isInstance(e,Uint8Array)&&(e=Buffer.from(e,e.offset,e.byteLength)),!Buffer.isBuffer(e))throw new TypeError('The "target" argument must be one of type Buffer or Uint8Array. Received type '+typeof e);if(void 0===t&&(t=0),void 0===r&&(r=e?e.length:0),void 0===n&&(n=0),void 0===i&&(i=this.length),t<0||r>e.length||n<0||i>this.length)throw new RangeError("out of range index");if(n>=i&&t>=r)return 0;if(n>=i)return-1;if(t>=r)return 1;if(this===e)return 0;let o=(i>>>=0)-(n>>>=0),a=(r>>>=0)-(t>>>=0);const s=Math.min(o,a),u=this.slice(n,i),c=e.slice(t,r);for(let e=0;e>>=0,isFinite(r)?(r>>>=0,void 0===n&&(n="utf8")):(n=r,r=void 0)}const i=this.length-t;if((void 0===r||r>i)&&(r=i),e.length>0&&(r<0||t<0)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");n||(n="utf8");let o=!1;for(;;)switch(n){case"hex":return hexWrite(this,e,t,r);case"utf8":case"utf-8":return utf8Write(this,e,t,r);case"ascii":case"latin1":case"binary":return asciiWrite(this,e,t,r);case"base64":return base64Write(this,e,t,r);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return ucs2Write(this,e,t,r);default:if(o)throw new TypeError("Unknown encoding: "+n);n=(""+n).toLowerCase(),o=!0}},Buffer.prototype.toJSON=function toJSON(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};const s=4096;function asciiSlice(e,t,r){let n="";r=Math.min(e.length,r);for(let i=t;in)&&(r=n);let i="";for(let n=t;nr)throw new RangeError("Trying to access beyond buffer length")}function checkInt(e,t,r,n,i,o){if(!Buffer.isBuffer(e))throw new TypeError('"buffer" argument must be a Buffer instance');if(t>i||te.length)throw new RangeError("Index out of range")}function wrtBigUInt64LE(e,t,r,n,i){checkIntBI(t,n,i,e,r,7);let o=Number(t&BigInt(4294967295));e[r++]=o,o>>=8,e[r++]=o,o>>=8,e[r++]=o,o>>=8,e[r++]=o;let a=Number(t>>BigInt(32)&BigInt(4294967295));return e[r++]=a,a>>=8,e[r++]=a,a>>=8,e[r++]=a,a>>=8,e[r++]=a,r}function wrtBigUInt64BE(e,t,r,n,i){checkIntBI(t,n,i,e,r,7);let o=Number(t&BigInt(4294967295));e[r+7]=o,o>>=8,e[r+6]=o,o>>=8,e[r+5]=o,o>>=8,e[r+4]=o;let a=Number(t>>BigInt(32)&BigInt(4294967295));return e[r+3]=a,a>>=8,e[r+2]=a,a>>=8,e[r+1]=a,a>>=8,e[r]=a,r+8}function checkIEEE754(e,t,r,n,i,o){if(r+n>e.length)throw new RangeError("Index out of range");if(r<0)throw new RangeError("Index out of range")}function writeFloat(e,t,r,n,o){return t=+t,r>>>=0,o||checkIEEE754(e,0,r,4),i.write(e,t,r,n,23,4),r+4}function writeDouble(e,t,r,n,o){return t=+t,r>>>=0,o||checkIEEE754(e,0,r,8),i.write(e,t,r,n,52,8),r+8}Buffer.prototype.slice=function slice(e,t){const r=this.length;(e=~~e)<0?(e+=r)<0&&(e=0):e>r&&(e=r),(t=void 0===t?r:~~t)<0?(t+=r)<0&&(t=0):t>r&&(t=r),t>>=0,t>>>=0,r||checkOffset(e,t,this.length);let n=this[e],i=1,o=0;for(;++o>>=0,t>>>=0,r||checkOffset(e,t,this.length);let n=this[e+--t],i=1;for(;t>0&&(i*=256);)n+=this[e+--t]*i;return n},Buffer.prototype.readUint8=Buffer.prototype.readUInt8=function readUInt8(e,t){return e>>>=0,t||checkOffset(e,1,this.length),this[e]},Buffer.prototype.readUint16LE=Buffer.prototype.readUInt16LE=function readUInt16LE(e,t){return e>>>=0,t||checkOffset(e,2,this.length),this[e]|this[e+1]<<8},Buffer.prototype.readUint16BE=Buffer.prototype.readUInt16BE=function readUInt16BE(e,t){return e>>>=0,t||checkOffset(e,2,this.length),this[e]<<8|this[e+1]},Buffer.prototype.readUint32LE=Buffer.prototype.readUInt32LE=function readUInt32LE(e,t){return e>>>=0,t||checkOffset(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},Buffer.prototype.readUint32BE=Buffer.prototype.readUInt32BE=function readUInt32BE(e,t){return e>>>=0,t||checkOffset(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},Buffer.prototype.readBigUInt64LE=defineBigIntMethod((function readBigUInt64LE(e){validateNumber(e>>>=0,"offset");const t=this[e],r=this[e+7];void 0!==t&&void 0!==r||boundsError(e,this.length-8);const n=t+256*this[++e]+65536*this[++e]+this[++e]*2**24,i=this[++e]+256*this[++e]+65536*this[++e]+r*2**24;return BigInt(n)+(BigInt(i)<>>=0,"offset");const t=this[e],r=this[e+7];void 0!==t&&void 0!==r||boundsError(e,this.length-8);const n=t*2**24+65536*this[++e]+256*this[++e]+this[++e],i=this[++e]*2**24+65536*this[++e]+256*this[++e]+r;return(BigInt(n)<>>=0,t>>>=0,r||checkOffset(e,t,this.length);let n=this[e],i=1,o=0;for(;++o=i&&(n-=Math.pow(2,8*t)),n},Buffer.prototype.readIntBE=function readIntBE(e,t,r){e>>>=0,t>>>=0,r||checkOffset(e,t,this.length);let n=t,i=1,o=this[e+--n];for(;n>0&&(i*=256);)o+=this[e+--n]*i;return i*=128,o>=i&&(o-=Math.pow(2,8*t)),o},Buffer.prototype.readInt8=function readInt8(e,t){return e>>>=0,t||checkOffset(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},Buffer.prototype.readInt16LE=function readInt16LE(e,t){e>>>=0,t||checkOffset(e,2,this.length);const r=this[e]|this[e+1]<<8;return 32768&r?4294901760|r:r},Buffer.prototype.readInt16BE=function readInt16BE(e,t){e>>>=0,t||checkOffset(e,2,this.length);const r=this[e+1]|this[e]<<8;return 32768&r?4294901760|r:r},Buffer.prototype.readInt32LE=function readInt32LE(e,t){return e>>>=0,t||checkOffset(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},Buffer.prototype.readInt32BE=function readInt32BE(e,t){return e>>>=0,t||checkOffset(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},Buffer.prototype.readBigInt64LE=defineBigIntMethod((function readBigInt64LE(e){validateNumber(e>>>=0,"offset");const t=this[e],r=this[e+7];void 0!==t&&void 0!==r||boundsError(e,this.length-8);const n=this[e+4]+256*this[e+5]+65536*this[e+6]+(r<<24);return(BigInt(n)<>>=0,"offset");const t=this[e],r=this[e+7];void 0!==t&&void 0!==r||boundsError(e,this.length-8);const n=(t<<24)+65536*this[++e]+256*this[++e]+this[++e];return(BigInt(n)<>>=0,t||checkOffset(e,4,this.length),i.read(this,e,!0,23,4)},Buffer.prototype.readFloatBE=function readFloatBE(e,t){return e>>>=0,t||checkOffset(e,4,this.length),i.read(this,e,!1,23,4)},Buffer.prototype.readDoubleLE=function readDoubleLE(e,t){return e>>>=0,t||checkOffset(e,8,this.length),i.read(this,e,!0,52,8)},Buffer.prototype.readDoubleBE=function readDoubleBE(e,t){return e>>>=0,t||checkOffset(e,8,this.length),i.read(this,e,!1,52,8)},Buffer.prototype.writeUintLE=Buffer.prototype.writeUIntLE=function writeUIntLE(e,t,r,n){if(e=+e,t>>>=0,r>>>=0,!n){checkInt(this,e,t,r,Math.pow(2,8*r)-1,0)}let i=1,o=0;for(this[t]=255&e;++o>>=0,r>>>=0,!n){checkInt(this,e,t,r,Math.pow(2,8*r)-1,0)}let i=r-1,o=1;for(this[t+i]=255&e;--i>=0&&(o*=256);)this[t+i]=e/o&255;return t+r},Buffer.prototype.writeUint8=Buffer.prototype.writeUInt8=function writeUInt8(e,t,r){return e=+e,t>>>=0,r||checkInt(this,e,t,1,255,0),this[t]=255&e,t+1},Buffer.prototype.writeUint16LE=Buffer.prototype.writeUInt16LE=function writeUInt16LE(e,t,r){return e=+e,t>>>=0,r||checkInt(this,e,t,2,65535,0),this[t]=255&e,this[t+1]=e>>>8,t+2},Buffer.prototype.writeUint16BE=Buffer.prototype.writeUInt16BE=function writeUInt16BE(e,t,r){return e=+e,t>>>=0,r||checkInt(this,e,t,2,65535,0),this[t]=e>>>8,this[t+1]=255&e,t+2},Buffer.prototype.writeUint32LE=Buffer.prototype.writeUInt32LE=function writeUInt32LE(e,t,r){return e=+e,t>>>=0,r||checkInt(this,e,t,4,4294967295,0),this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e,t+4},Buffer.prototype.writeUint32BE=Buffer.prototype.writeUInt32BE=function writeUInt32BE(e,t,r){return e=+e,t>>>=0,r||checkInt(this,e,t,4,4294967295,0),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},Buffer.prototype.writeBigUInt64LE=defineBigIntMethod((function writeBigUInt64LE(e,t=0){return wrtBigUInt64LE(this,e,t,BigInt(0),BigInt("0xffffffffffffffff"))})),Buffer.prototype.writeBigUInt64BE=defineBigIntMethod((function writeBigUInt64BE(e,t=0){return wrtBigUInt64BE(this,e,t,BigInt(0),BigInt("0xffffffffffffffff"))})),Buffer.prototype.writeIntLE=function writeIntLE(e,t,r,n){if(e=+e,t>>>=0,!n){const n=Math.pow(2,8*r-1);checkInt(this,e,t,r,n-1,-n)}let i=0,o=1,a=0;for(this[t]=255&e;++i>>=0,!n){const n=Math.pow(2,8*r-1);checkInt(this,e,t,r,n-1,-n)}let i=r-1,o=1,a=0;for(this[t+i]=255&e;--i>=0&&(o*=256);)e<0&&0===a&&0!==this[t+i+1]&&(a=1),this[t+i]=(e/o|0)-a&255;return t+r},Buffer.prototype.writeInt8=function writeInt8(e,t,r){return e=+e,t>>>=0,r||checkInt(this,e,t,1,127,-128),e<0&&(e=255+e+1),this[t]=255&e,t+1},Buffer.prototype.writeInt16LE=function writeInt16LE(e,t,r){return e=+e,t>>>=0,r||checkInt(this,e,t,2,32767,-32768),this[t]=255&e,this[t+1]=e>>>8,t+2},Buffer.prototype.writeInt16BE=function writeInt16BE(e,t,r){return e=+e,t>>>=0,r||checkInt(this,e,t,2,32767,-32768),this[t]=e>>>8,this[t+1]=255&e,t+2},Buffer.prototype.writeInt32LE=function writeInt32LE(e,t,r){return e=+e,t>>>=0,r||checkInt(this,e,t,4,2147483647,-2147483648),this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24,t+4},Buffer.prototype.writeInt32BE=function writeInt32BE(e,t,r){return e=+e,t>>>=0,r||checkInt(this,e,t,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},Buffer.prototype.writeBigInt64LE=defineBigIntMethod((function writeBigInt64LE(e,t=0){return wrtBigUInt64LE(this,e,t,-BigInt("0x8000000000000000"),BigInt("0x7fffffffffffffff"))})),Buffer.prototype.writeBigInt64BE=defineBigIntMethod((function writeBigInt64BE(e,t=0){return wrtBigUInt64BE(this,e,t,-BigInt("0x8000000000000000"),BigInt("0x7fffffffffffffff"))})),Buffer.prototype.writeFloatLE=function writeFloatLE(e,t,r){return writeFloat(this,e,t,!0,r)},Buffer.prototype.writeFloatBE=function writeFloatBE(e,t,r){return writeFloat(this,e,t,!1,r)},Buffer.prototype.writeDoubleLE=function writeDoubleLE(e,t,r){return writeDouble(this,e,t,!0,r)},Buffer.prototype.writeDoubleBE=function writeDoubleBE(e,t,r){return writeDouble(this,e,t,!1,r)},Buffer.prototype.copy=function copy(e,t,r,n){if(!Buffer.isBuffer(e))throw new TypeError("argument should be a Buffer");if(r||(r=0),n||0===n||(n=this.length),t>=e.length&&(t=e.length),t||(t=0),n>0&&n=this.length)throw new RangeError("Index out of range");if(n<0)throw new RangeError("sourceEnd out of bounds");n>this.length&&(n=this.length),e.length-t>>=0,r=void 0===r?this.length:r>>>0,e||(e=0),"number"==typeof e)for(i=t;i=n+4;r-=3)t=`_${e.slice(r-3,r)}${t}`;return`${e.slice(0,r)}${t}`}function checkIntBI(e,t,r,n,i,o){if(e>r||e3?0===t||t===BigInt(0)?`>= 0${n} and < 2${n} ** ${8*(o+1)}${n}`:`>= -(2${n} ** ${8*(o+1)-1}${n}) and < 2 ** ${8*(o+1)-1}${n}`:`>= ${t}${n} and <= ${r}${n}`,new u.ERR_OUT_OF_RANGE("value",i,e)}!function checkBounds(e,t,r){validateNumber(t,"offset"),void 0!==e[t]&&void 0!==e[t+r]||boundsError(t,e.length-(r+1))}(n,i,o)}function validateNumber(e,t){if("number"!=typeof e)throw new u.ERR_INVALID_ARG_TYPE(t,"number",e)}function boundsError(e,t,r){if(Math.floor(e)!==e)throw validateNumber(e,r),new u.ERR_OUT_OF_RANGE(r||"offset","an integer",e);if(t<0)throw new u.ERR_BUFFER_OUT_OF_BOUNDS;throw new u.ERR_OUT_OF_RANGE(r||"offset",`>= ${r?1:0} and <= ${t}`,e)}E("ERR_BUFFER_OUT_OF_BOUNDS",(function(e){return e?`${e} is outside of buffer bounds`:"Attempt to access memory outside buffer bounds"}),RangeError),E("ERR_INVALID_ARG_TYPE",(function(e,t){return`The "${e}" argument must be of type number. Received type ${typeof t}`}),TypeError),E("ERR_OUT_OF_RANGE",(function(e,t,r){let n=`The value of "${e}" is out of range.`,i=r;return Number.isInteger(r)&&Math.abs(r)>2**32?i=addNumericalSeparator(String(r)):"bigint"==typeof r&&(i=String(r),(r>BigInt(2)**BigInt(32)||r<-(BigInt(2)**BigInt(32)))&&(i=addNumericalSeparator(i)),i+="n"),n+=` It must be ${t}. Received ${i}`,n}),RangeError);const c=/[^+/0-9A-Za-z-_]/g;function utf8ToBytes(e,t){let r;t=t||1/0;const n=e.length;let i=null;const o=[];for(let a=0;a55295&&r<57344){if(!i){if(r>56319){(t-=3)>-1&&o.push(239,191,189);continue}if(a+1===n){(t-=3)>-1&&o.push(239,191,189);continue}i=r;continue}if(r<56320){(t-=3)>-1&&o.push(239,191,189),i=r;continue}r=65536+(i-55296<<10|r-56320)}else i&&(t-=3)>-1&&o.push(239,191,189);if(i=null,r<128){if((t-=1)<0)break;o.push(r)}else if(r<2048){if((t-=2)<0)break;o.push(r>>6|192,63&r|128)}else if(r<65536){if((t-=3)<0)break;o.push(r>>12|224,r>>6&63|128,63&r|128)}else{if(!(r<1114112))throw new Error("Invalid code point");if((t-=4)<0)break;o.push(r>>18|240,r>>12&63|128,r>>6&63|128,63&r|128)}}return o}function base64ToBytes(e){return n.toByteArray(function base64clean(e){if((e=(e=e.split("=")[0]).trim().replace(c,"")).length<2)return"";for(;e.length%4!=0;)e+="=";return e}(e))}function blitBuffer(e,t,r,n){let i;for(i=0;i=t.length||i>=e.length);++i)t[i+r]=e[i];return i}function isInstance(e,t){return e instanceof t||null!=e&&null!=e.constructor&&null!=e.constructor.name&&e.constructor.name===t.name}function numberIsNaN(e){return e!=e}const f=function(){const e="0123456789abcdef",t=new Array(256);for(let r=0;r<16;++r){const n=16*r;for(let i=0;i<16;++i)t[n+i]=e[r]+e[i]}return t}();function defineBigIntMethod(e){return"undefined"==typeof BigInt?BufferBigIntNotDefined:e}function BufferBigIntNotDefined(){throw new Error("BigInt not supported")}},2205:function(e,t,r){var n;n=void 0!==r.g?r.g:this,e.exports=function(e){if(e.CSS&&e.CSS.escape)return e.CSS.escape;var cssEscape=function(e){if(0==arguments.length)throw new TypeError("`CSS.escape` requires an argument.");for(var t,r=String(e),n=r.length,i=-1,o="",a=r.charCodeAt(0);++i=1&&t<=31||127==t||0==i&&t>=48&&t<=57||1==i&&t>=48&&t<=57&&45==a?"\\"+t.toString(16)+" ":0==i&&1==n&&45==t||!(t>=128||45==t||95==t||t>=48&&t<=57||t>=65&&t<=90||t>=97&&t<=122)?"\\"+r.charAt(i):r.charAt(i):o+="�";return o};return e.CSS||(e.CSS={}),e.CSS.escape=cssEscape,cssEscape}(n)},251:(e,t)=>{t.read=function(e,t,r,n,i){var o,a,s=8*i-n-1,u=(1<>1,f=-7,l=r?i-1:0,h=r?-1:1,p=e[t+l];for(l+=h,o=p&(1<<-f)-1,p>>=-f,f+=s;f>0;o=256*o+e[t+l],l+=h,f-=8);for(a=o&(1<<-f)-1,o>>=-f,f+=n;f>0;a=256*a+e[t+l],l+=h,f-=8);if(0===o)o=1-c;else{if(o===u)return a?NaN:1/0*(p?-1:1);a+=Math.pow(2,n),o-=c}return(p?-1:1)*a*Math.pow(2,o-n)},t.write=function(e,t,r,n,i,o){var a,s,u,c=8*o-i-1,f=(1<>1,h=23===i?Math.pow(2,-24)-Math.pow(2,-77):0,p=n?0:o-1,d=n?1:-1,_=t<0||0===t&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(s=isNaN(t)?1:0,a=f):(a=Math.floor(Math.log(t)/Math.LN2),t*(u=Math.pow(2,-a))<1&&(a--,u*=2),(t+=a+l>=1?h/u:h*Math.pow(2,1-l))*u>=2&&(a++,u/=2),a+l>=f?(s=0,a=f):a+l>=1?(s=(t*u-1)*Math.pow(2,i),a+=l):(s=t*Math.pow(2,l-1)*Math.pow(2,i),a=0));i>=8;e[r+p]=255&s,p+=d,s/=256,i-=8);for(a=a<0;e[r+p]=255&a,p+=d,a/=256,c-=8);e[r+p-d]|=128*_}},9404:function(e){e.exports=function(){"use strict";var e=Array.prototype.slice;function createClass(e,t){t&&(e.prototype=Object.create(t.prototype)),e.prototype.constructor=e}function Iterable(e){return isIterable(e)?e:Seq(e)}function KeyedIterable(e){return isKeyed(e)?e:KeyedSeq(e)}function IndexedIterable(e){return isIndexed(e)?e:IndexedSeq(e)}function SetIterable(e){return isIterable(e)&&!isAssociative(e)?e:SetSeq(e)}function isIterable(e){return!(!e||!e[t])}function isKeyed(e){return!(!e||!e[r])}function isIndexed(e){return!(!e||!e[n])}function isAssociative(e){return isKeyed(e)||isIndexed(e)}function isOrdered(e){return!(!e||!e[i])}createClass(KeyedIterable,Iterable),createClass(IndexedIterable,Iterable),createClass(SetIterable,Iterable),Iterable.isIterable=isIterable,Iterable.isKeyed=isKeyed,Iterable.isIndexed=isIndexed,Iterable.isAssociative=isAssociative,Iterable.isOrdered=isOrdered,Iterable.Keyed=KeyedIterable,Iterable.Indexed=IndexedIterable,Iterable.Set=SetIterable;var t="@@__IMMUTABLE_ITERABLE__@@",r="@@__IMMUTABLE_KEYED__@@",n="@@__IMMUTABLE_INDEXED__@@",i="@@__IMMUTABLE_ORDERED__@@",o="delete",a=5,s=1<>>0;if(""+r!==t||4294967295===r)return NaN;t=r}return t<0?ensureSize(e)+t:t}function returnTrue(){return!0}function wholeSlice(e,t,r){return(0===e||void 0!==r&&e<=-r)&&(void 0===t||void 0!==r&&t>=r)}function resolveBegin(e,t){return resolveIndex(e,t,0)}function resolveEnd(e,t){return resolveIndex(e,t,t)}function resolveIndex(e,t,r){return void 0===e?r:e<0?Math.max(0,t+e):void 0===t?e:Math.min(t,e)}var h=0,p=1,d=2,_="function"==typeof Symbol&&Symbol.iterator,y="@@iterator",m=_||y;function Iterator(e){this.next=e}function iteratorValue(e,t,r,n){var i=0===e?t:1===e?r:[t,r];return n?n.value=i:n={value:i,done:!1},n}function iteratorDone(){return{value:void 0,done:!0}}function hasIterator(e){return!!getIteratorFn(e)}function isIterator(e){return e&&"function"==typeof e.next}function getIterator(e){var t=getIteratorFn(e);return t&&t.call(e)}function getIteratorFn(e){var t=e&&(_&&e[_]||e[y]);if("function"==typeof t)return t}function isArrayLike(e){return e&&"number"==typeof e.length}function Seq(e){return null==e?emptySequence():isIterable(e)?e.toSeq():seqFromValue(e)}function KeyedSeq(e){return null==e?emptySequence().toKeyedSeq():isIterable(e)?isKeyed(e)?e.toSeq():e.fromEntrySeq():keyedSeqFromValue(e)}function IndexedSeq(e){return null==e?emptySequence():isIterable(e)?isKeyed(e)?e.entrySeq():e.toIndexedSeq():indexedSeqFromValue(e)}function SetSeq(e){return(null==e?emptySequence():isIterable(e)?isKeyed(e)?e.entrySeq():e:indexedSeqFromValue(e)).toSetSeq()}Iterator.prototype.toString=function(){return"[Iterator]"},Iterator.KEYS=h,Iterator.VALUES=p,Iterator.ENTRIES=d,Iterator.prototype.inspect=Iterator.prototype.toSource=function(){return this.toString()},Iterator.prototype[m]=function(){return this},createClass(Seq,Iterable),Seq.of=function(){return Seq(arguments)},Seq.prototype.toSeq=function(){return this},Seq.prototype.toString=function(){return this.__toString("Seq {","}")},Seq.prototype.cacheResult=function(){return!this._cache&&this.__iterateUncached&&(this._cache=this.entrySeq().toArray(),this.size=this._cache.length),this},Seq.prototype.__iterate=function(e,t){return seqIterate(this,e,t,!0)},Seq.prototype.__iterator=function(e,t){return seqIterator(this,e,t,!0)},createClass(KeyedSeq,Seq),KeyedSeq.prototype.toKeyedSeq=function(){return this},createClass(IndexedSeq,Seq),IndexedSeq.of=function(){return IndexedSeq(arguments)},IndexedSeq.prototype.toIndexedSeq=function(){return this},IndexedSeq.prototype.toString=function(){return this.__toString("Seq [","]")},IndexedSeq.prototype.__iterate=function(e,t){return seqIterate(this,e,t,!1)},IndexedSeq.prototype.__iterator=function(e,t){return seqIterator(this,e,t,!1)},createClass(SetSeq,Seq),SetSeq.of=function(){return SetSeq(arguments)},SetSeq.prototype.toSetSeq=function(){return this},Seq.isSeq=isSeq,Seq.Keyed=KeyedSeq,Seq.Set=SetSeq,Seq.Indexed=IndexedSeq;var g,v,b,w="@@__IMMUTABLE_SEQ__@@";function ArraySeq(e){this._array=e,this.size=e.length}function ObjectSeq(e){var t=Object.keys(e);this._object=e,this._keys=t,this.size=t.length}function IterableSeq(e){this._iterable=e,this.size=e.length||e.size}function IteratorSeq(e){this._iterator=e,this._iteratorCache=[]}function isSeq(e){return!(!e||!e[w])}function emptySequence(){return g||(g=new ArraySeq([]))}function keyedSeqFromValue(e){var t=Array.isArray(e)?new ArraySeq(e).fromEntrySeq():isIterator(e)?new IteratorSeq(e).fromEntrySeq():hasIterator(e)?new IterableSeq(e).fromEntrySeq():"object"==typeof e?new ObjectSeq(e):void 0;if(!t)throw new TypeError("Expected Array or iterable object of [k, v] entries, or keyed object: "+e);return t}function indexedSeqFromValue(e){var t=maybeIndexedSeqFromValue(e);if(!t)throw new TypeError("Expected Array or iterable object of values: "+e);return t}function seqFromValue(e){var t=maybeIndexedSeqFromValue(e)||"object"==typeof e&&new ObjectSeq(e);if(!t)throw new TypeError("Expected Array or iterable object of values, or keyed object: "+e);return t}function maybeIndexedSeqFromValue(e){return isArrayLike(e)?new ArraySeq(e):isIterator(e)?new IteratorSeq(e):hasIterator(e)?new IterableSeq(e):void 0}function seqIterate(e,t,r,n){var i=e._cache;if(i){for(var o=i.length-1,a=0;a<=o;a++){var s=i[r?o-a:a];if(!1===t(s[1],n?s[0]:a,e))return a+1}return a}return e.__iterateUncached(t,r)}function seqIterator(e,t,r,n){var i=e._cache;if(i){var o=i.length-1,a=0;return new Iterator((function(){var e=i[r?o-a:a];return a++>o?iteratorDone():iteratorValue(t,n?e[0]:a-1,e[1])}))}return e.__iteratorUncached(t,r)}function fromJS(e,t){return t?fromJSWith(t,e,"",{"":e}):fromJSDefault(e)}function fromJSWith(e,t,r,n){return Array.isArray(t)?e.call(n,r,IndexedSeq(t).map((function(r,n){return fromJSWith(e,r,n,t)}))):isPlainObj(t)?e.call(n,r,KeyedSeq(t).map((function(r,n){return fromJSWith(e,r,n,t)}))):t}function fromJSDefault(e){return Array.isArray(e)?IndexedSeq(e).map(fromJSDefault).toList():isPlainObj(e)?KeyedSeq(e).map(fromJSDefault).toMap():e}function isPlainObj(e){return e&&(e.constructor===Object||void 0===e.constructor)}function is(e,t){if(e===t||e!=e&&t!=t)return!0;if(!e||!t)return!1;if("function"==typeof e.valueOf&&"function"==typeof t.valueOf){if((e=e.valueOf())===(t=t.valueOf())||e!=e&&t!=t)return!0;if(!e||!t)return!1}return!("function"!=typeof e.equals||"function"!=typeof t.equals||!e.equals(t))}function deepEqual(e,t){if(e===t)return!0;if(!isIterable(t)||void 0!==e.size&&void 0!==t.size&&e.size!==t.size||void 0!==e.__hash&&void 0!==t.__hash&&e.__hash!==t.__hash||isKeyed(e)!==isKeyed(t)||isIndexed(e)!==isIndexed(t)||isOrdered(e)!==isOrdered(t))return!1;if(0===e.size&&0===t.size)return!0;var r=!isAssociative(e);if(isOrdered(e)){var n=e.entries();return t.every((function(e,t){var i=n.next().value;return i&&is(i[1],e)&&(r||is(i[0],t))}))&&n.next().done}var i=!1;if(void 0===e.size)if(void 0===t.size)"function"==typeof e.cacheResult&&e.cacheResult();else{i=!0;var o=e;e=t,t=o}var a=!0,s=t.__iterate((function(t,n){if(r?!e.has(t):i?!is(t,e.get(n,c)):!is(e.get(n,c),t))return a=!1,!1}));return a&&e.size===s}function Repeat(e,t){if(!(this instanceof Repeat))return new Repeat(e,t);if(this._value=e,this.size=void 0===t?1/0:Math.max(0,t),0===this.size){if(v)return v;v=this}}function invariant(e,t){if(!e)throw new Error(t)}function Range(e,t,r){if(!(this instanceof Range))return new Range(e,t,r);if(invariant(0!==r,"Cannot step a Range by 0"),e=e||0,void 0===t&&(t=1/0),r=void 0===r?1:Math.abs(r),tn?iteratorDone():iteratorValue(e,i,r[t?n-i++:i++])}))},createClass(ObjectSeq,KeyedSeq),ObjectSeq.prototype.get=function(e,t){return void 0===t||this.has(e)?this._object[e]:t},ObjectSeq.prototype.has=function(e){return this._object.hasOwnProperty(e)},ObjectSeq.prototype.__iterate=function(e,t){for(var r=this._object,n=this._keys,i=n.length-1,o=0;o<=i;o++){var a=n[t?i-o:o];if(!1===e(r[a],a,this))return o+1}return o},ObjectSeq.prototype.__iterator=function(e,t){var r=this._object,n=this._keys,i=n.length-1,o=0;return new Iterator((function(){var a=n[t?i-o:o];return o++>i?iteratorDone():iteratorValue(e,a,r[a])}))},ObjectSeq.prototype[i]=!0,createClass(IterableSeq,IndexedSeq),IterableSeq.prototype.__iterateUncached=function(e,t){if(t)return this.cacheResult().__iterate(e,t);var r=getIterator(this._iterable),n=0;if(isIterator(r))for(var i;!(i=r.next()).done&&!1!==e(i.value,n++,this););return n},IterableSeq.prototype.__iteratorUncached=function(e,t){if(t)return this.cacheResult().__iterator(e,t);var r=getIterator(this._iterable);if(!isIterator(r))return new Iterator(iteratorDone);var n=0;return new Iterator((function(){var t=r.next();return t.done?t:iteratorValue(e,n++,t.value)}))},createClass(IteratorSeq,IndexedSeq),IteratorSeq.prototype.__iterateUncached=function(e,t){if(t)return this.cacheResult().__iterate(e,t);for(var r,n=this._iterator,i=this._iteratorCache,o=0;o=n.length){var t=r.next();if(t.done)return t;n[i]=t.value}return iteratorValue(e,i,n[i++])}))},createClass(Repeat,IndexedSeq),Repeat.prototype.toString=function(){return 0===this.size?"Repeat []":"Repeat [ "+this._value+" "+this.size+" times ]"},Repeat.prototype.get=function(e,t){return this.has(e)?this._value:t},Repeat.prototype.includes=function(e){return is(this._value,e)},Repeat.prototype.slice=function(e,t){var r=this.size;return wholeSlice(e,t,r)?this:new Repeat(this._value,resolveEnd(t,r)-resolveBegin(e,r))},Repeat.prototype.reverse=function(){return this},Repeat.prototype.indexOf=function(e){return is(this._value,e)?0:-1},Repeat.prototype.lastIndexOf=function(e){return is(this._value,e)?this.size:-1},Repeat.prototype.__iterate=function(e,t){for(var r=0;r=0&&t=0&&rr?iteratorDone():iteratorValue(e,o++,a)}))},Range.prototype.equals=function(e){return e instanceof Range?this._start===e._start&&this._end===e._end&&this._step===e._step:deepEqual(this,e)},createClass(Collection,Iterable),createClass(KeyedCollection,Collection),createClass(IndexedCollection,Collection),createClass(SetCollection,Collection),Collection.Keyed=KeyedCollection,Collection.Indexed=IndexedCollection,Collection.Set=SetCollection;var I="function"==typeof Math.imul&&-2===Math.imul(4294967295,2)?Math.imul:function imul(e,t){var r=65535&(e|=0),n=65535&(t|=0);return r*n+((e>>>16)*n+r*(t>>>16)<<16>>>0)|0};function smi(e){return e>>>1&1073741824|3221225471&e}function hash(e){if(!1===e||null==e)return 0;if("function"==typeof e.valueOf&&(!1===(e=e.valueOf())||null==e))return 0;if(!0===e)return 1;var t=typeof e;if("number"===t){if(e!=e||e===1/0)return 0;var r=0|e;for(r!==e&&(r^=4294967295*e);e>4294967295;)r^=e/=4294967295;return smi(r)}if("string"===t)return e.length>j?cachedHashString(e):hashString(e);if("function"==typeof e.hashCode)return e.hashCode();if("object"===t)return hashJSObj(e);if("function"==typeof e.toString)return hashString(e.toString());throw new Error("Value type "+t+" cannot be hashed.")}function cachedHashString(e){var t=D[e];return void 0===t&&(t=hashString(e),P===z&&(P=0,D={}),P++,D[e]=t),t}function hashString(e){for(var t=0,r=0;r0)switch(e.nodeType){case 1:return e.uniqueID;case 9:return e.documentElement&&e.documentElement.uniqueID}}var k,C="function"==typeof WeakMap;C&&(k=new WeakMap);var q=0,L="__immutablehash__";"function"==typeof Symbol&&(L=Symbol(L));var j=16,z=255,P=0,D={};function assertNotInfinite(e){invariant(e!==1/0,"Cannot perform this action with an infinite size.")}function Map(e){return null==e?emptyMap():isMap(e)&&!isOrdered(e)?e:emptyMap().withMutations((function(t){var r=KeyedIterable(e);assertNotInfinite(r.size),r.forEach((function(e,r){return t.set(r,e)}))}))}function isMap(e){return!(!e||!e[W])}createClass(Map,KeyedCollection),Map.of=function(){var t=e.call(arguments,0);return emptyMap().withMutations((function(e){for(var r=0;r=t.length)throw new Error("Missing value for key: "+t[r]);e.set(t[r],t[r+1])}}))},Map.prototype.toString=function(){return this.__toString("Map {","}")},Map.prototype.get=function(e,t){return this._root?this._root.get(0,void 0,e,t):t},Map.prototype.set=function(e,t){return updateMap(this,e,t)},Map.prototype.setIn=function(e,t){return this.updateIn(e,c,(function(){return t}))},Map.prototype.remove=function(e){return updateMap(this,e,c)},Map.prototype.deleteIn=function(e){return this.updateIn(e,(function(){return c}))},Map.prototype.update=function(e,t,r){return 1===arguments.length?e(this):this.updateIn([e],t,r)},Map.prototype.updateIn=function(e,t,r){r||(r=t,t=void 0);var n=updateInDeepMap(this,forceIterator(e),t,r);return n===c?void 0:n},Map.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._root=null,this.__hash=void 0,this.__altered=!0,this):emptyMap()},Map.prototype.merge=function(){return mergeIntoMapWith(this,void 0,arguments)},Map.prototype.mergeWith=function(t){return mergeIntoMapWith(this,t,e.call(arguments,1))},Map.prototype.mergeIn=function(t){var r=e.call(arguments,1);return this.updateIn(t,emptyMap(),(function(e){return"function"==typeof e.merge?e.merge.apply(e,r):r[r.length-1]}))},Map.prototype.mergeDeep=function(){return mergeIntoMapWith(this,deepMerger,arguments)},Map.prototype.mergeDeepWith=function(t){var r=e.call(arguments,1);return mergeIntoMapWith(this,deepMergerWith(t),r)},Map.prototype.mergeDeepIn=function(t){var r=e.call(arguments,1);return this.updateIn(t,emptyMap(),(function(e){return"function"==typeof e.mergeDeep?e.mergeDeep.apply(e,r):r[r.length-1]}))},Map.prototype.sort=function(e){return OrderedMap(sortFactory(this,e))},Map.prototype.sortBy=function(e,t){return OrderedMap(sortFactory(this,t,e))},Map.prototype.withMutations=function(e){var t=this.asMutable();return e(t),t.wasAltered()?t.__ensureOwner(this.__ownerID):this},Map.prototype.asMutable=function(){return this.__ownerID?this:this.__ensureOwner(new OwnerID)},Map.prototype.asImmutable=function(){return this.__ensureOwner()},Map.prototype.wasAltered=function(){return this.__altered},Map.prototype.__iterator=function(e,t){return new MapIterator(this,e,t)},Map.prototype.__iterate=function(e,t){var r=this,n=0;return this._root&&this._root.iterate((function(t){return n++,e(t[1],t[0],r)}),t),n},Map.prototype.__ensureOwner=function(e){return e===this.__ownerID?this:e?makeMap(this.size,this._root,e,this.__hash):(this.__ownerID=e,this.__altered=!1,this)},Map.isMap=isMap;var U,W="@@__IMMUTABLE_MAP__@@",K=Map.prototype;function ArrayMapNode(e,t){this.ownerID=e,this.entries=t}function BitmapIndexedNode(e,t,r){this.ownerID=e,this.bitmap=t,this.nodes=r}function HashArrayMapNode(e,t,r){this.ownerID=e,this.count=t,this.nodes=r}function HashCollisionNode(e,t,r){this.ownerID=e,this.keyHash=t,this.entries=r}function ValueNode(e,t,r){this.ownerID=e,this.keyHash=t,this.entry=r}function MapIterator(e,t,r){this._type=t,this._reverse=r,this._stack=e._root&&mapIteratorFrame(e._root)}function mapIteratorValue(e,t){return iteratorValue(e,t[0],t[1])}function mapIteratorFrame(e,t){return{node:e,index:0,__prev:t}}function makeMap(e,t,r,n){var i=Object.create(K);return i.size=e,i._root=t,i.__ownerID=r,i.__hash=n,i.__altered=!1,i}function emptyMap(){return U||(U=makeMap(0))}function updateMap(e,t,r){var n,i;if(e._root){var o=MakeRef(f),a=MakeRef(l);if(n=updateNode(e._root,e.__ownerID,0,void 0,t,r,o,a),!a.value)return e;i=e.size+(o.value?r===c?-1:1:0)}else{if(r===c)return e;i=1,n=new ArrayMapNode(e.__ownerID,[[t,r]])}return e.__ownerID?(e.size=i,e._root=n,e.__hash=void 0,e.__altered=!0,e):n?makeMap(i,n):emptyMap()}function updateNode(e,t,r,n,i,o,a,s){return e?e.update(t,r,n,i,o,a,s):o===c?e:(SetRef(s),SetRef(a),new ValueNode(t,n,[i,o]))}function isLeafNode(e){return e.constructor===ValueNode||e.constructor===HashCollisionNode}function mergeIntoNode(e,t,r,n,i){if(e.keyHash===n)return new HashCollisionNode(t,n,[e.entry,i]);var o,s=(0===r?e.keyHash:e.keyHash>>>r)&u,c=(0===r?n:n>>>r)&u;return new BitmapIndexedNode(t,1<>>=1)a[u]=1&r?t[o++]:void 0;return a[n]=i,new HashArrayMapNode(e,o+1,a)}function mergeIntoMapWith(e,t,r){for(var n=[],i=0;i>1&1431655765))+(e>>2&858993459))+(e>>4)&252645135,e+=e>>8,127&(e+=e>>16)}function setIn(e,t,r,n){var i=n?e:arrCopy(e);return i[t]=r,i}function spliceIn(e,t,r,n){var i=e.length+1;if(n&&t+1===i)return e[t]=r,e;for(var o=new Array(i),a=0,s=0;s=V)return createNodes(e,u,n,i);var p=e&&e===this.ownerID,d=p?u:arrCopy(u);return h?s?f===l-1?d.pop():d[f]=d.pop():d[f]=[n,i]:d.push([n,i]),p?(this.entries=d,this):new ArrayMapNode(e,d)}},BitmapIndexedNode.prototype.get=function(e,t,r,n){void 0===t&&(t=hash(r));var i=1<<((0===e?t:t>>>e)&u),o=this.bitmap;return o&i?this.nodes[popCount(o&i-1)].get(e+a,t,r,n):n},BitmapIndexedNode.prototype.update=function(e,t,r,n,i,o,s){void 0===r&&(r=hash(n));var f=(0===t?r:r>>>t)&u,l=1<=$)return expandNodes(e,_,h,f,m);if(p&&!m&&2===_.length&&isLeafNode(_[1^d]))return _[1^d];if(p&&m&&1===_.length&&isLeafNode(m))return m;var g=e&&e===this.ownerID,v=p?m?h:h^l:h|l,b=p?m?setIn(_,d,m,g):spliceOut(_,d,g):spliceIn(_,d,m,g);return g?(this.bitmap=v,this.nodes=b,this):new BitmapIndexedNode(e,v,b)},HashArrayMapNode.prototype.get=function(e,t,r,n){void 0===t&&(t=hash(r));var i=(0===e?t:t>>>e)&u,o=this.nodes[i];return o?o.get(e+a,t,r,n):n},HashArrayMapNode.prototype.update=function(e,t,r,n,i,o,s){void 0===r&&(r=hash(n));var f=(0===t?r:r>>>t)&u,l=i===c,h=this.nodes,p=h[f];if(l&&!p)return this;var d=updateNode(p,e,t+a,r,n,i,o,s);if(d===p)return this;var _=this.count;if(p){if(!d&&--_0&&n=0&&e>>t&u;if(n>=this.array.length)return new VNode([],e);var i,o=0===n;if(t>0){var s=this.array[n];if((i=s&&s.removeBefore(e,t-a,r))===s&&o)return this}if(o&&!i)return this;var c=editableVNode(this,e);if(!o)for(var f=0;f>>t&u;if(i>=this.array.length)return this;if(t>0){var o=this.array[i];if((n=o&&o.removeAfter(e,t-a,r))===o&&i===this.array.length-1)return this}var s=editableVNode(this,e);return s.array.splice(i+1),n&&(s.array[i]=n),s};var J,ee,te={};function iterateList(e,t){var r=e._origin,n=e._capacity,i=getTailOffset(n),o=e._tail;return iterateNodeOrLeaf(e._root,e._level,0);function iterateNodeOrLeaf(e,t,r){return 0===t?iterateLeaf(e,r):iterateNode(e,t,r)}function iterateLeaf(e,a){var u=a===i?o&&o.array:e&&e.array,c=a>r?0:r-a,f=n-a;return f>s&&(f=s),function(){if(c===f)return te;var e=t?--f:c++;return u&&u[e]}}function iterateNode(e,i,o){var u,c=e&&e.array,f=o>r?0:r-o>>i,l=1+(n-o>>i);return l>s&&(l=s),function(){for(;;){if(u){var e=u();if(e!==te)return e;u=null}if(f===l)return te;var r=t?--l:f++;u=iterateNodeOrLeaf(c&&c[r],i-a,o+(r<=e.size||t<0)return e.withMutations((function(e){t<0?setListBounds(e,t).set(0,r):setListBounds(e,0,t+1).set(t,r)}));t+=e._origin;var n=e._tail,i=e._root,o=MakeRef(l);return t>=getTailOffset(e._capacity)?n=updateVNode(n,e.__ownerID,0,t,r,o):i=updateVNode(i,e.__ownerID,e._level,t,r,o),o.value?e.__ownerID?(e._root=i,e._tail=n,e.__hash=void 0,e.__altered=!0,e):makeList(e._origin,e._capacity,e._level,i,n):e}function updateVNode(e,t,r,n,i,o){var s,c=n>>>r&u,f=e&&c0){var l=e&&e.array[c],h=updateVNode(l,t,r-a,n,i,o);return h===l?e:((s=editableVNode(e,t)).array[c]=h,s)}return f&&e.array[c]===i?e:(SetRef(o),s=editableVNode(e,t),void 0===i&&c===s.array.length-1?s.array.pop():s.array[c]=i,s)}function editableVNode(e,t){return t&&e&&t===e.ownerID?e:new VNode(e?e.array.slice():[],t)}function listNodeFor(e,t){if(t>=getTailOffset(e._capacity))return e._tail;if(t<1<0;)r=r.array[t>>>n&u],n-=a;return r}}function setListBounds(e,t,r){void 0!==t&&(t|=0),void 0!==r&&(r|=0);var n=e.__ownerID||new OwnerID,i=e._origin,o=e._capacity,s=i+t,c=void 0===r?o:r<0?o+r:i+r;if(s===i&&c===o)return e;if(s>=c)return e.clear();for(var f=e._level,l=e._root,h=0;s+h<0;)l=new VNode(l&&l.array.length?[void 0,l]:[],n),h+=1<<(f+=a);h&&(s+=h,i+=h,c+=h,o+=h);for(var p=getTailOffset(o),d=getTailOffset(c);d>=1<p?new VNode([],n):_;if(_&&d>p&&sa;g-=a){var v=p>>>g&u;m=m.array[v]=editableVNode(m.array[v],n)}m.array[p>>>a&u]=_}if(c=d)s-=d,c-=d,f=a,l=null,y=y&&y.removeBefore(n,0,s);else if(s>i||d>>f&u;if(b!==d>>>f&u)break;b&&(h+=(1<i&&(l=l.removeBefore(n,f,s-h)),l&&di&&(i=s.size),isIterable(a)||(s=s.map((function(e){return fromJS(e)}))),n.push(s)}return i>e.size&&(e=e.setSize(i)),mergeIntoCollectionWith(e,t,n)}function getTailOffset(e){return e>>a<=s&&a.size>=2*o.size?(n=(i=a.filter((function(e,t){return void 0!==e&&u!==t}))).toKeyedSeq().map((function(e){return e[0]})).flip().toMap(),e.__ownerID&&(n.__ownerID=i.__ownerID=e.__ownerID)):(n=o.remove(t),i=u===a.size-1?a.pop():a.set(u,void 0))}else if(f){if(r===a.get(u)[1])return e;n=o,i=a.set(u,[t,r])}else n=o.set(t,a.size),i=a.set(a.size,[t,r]);return e.__ownerID?(e.size=n.size,e._map=n,e._list=i,e.__hash=void 0,e):makeOrderedMap(n,i)}function ToKeyedSequence(e,t){this._iter=e,this._useKeys=t,this.size=e.size}function ToIndexedSequence(e){this._iter=e,this.size=e.size}function ToSetSequence(e){this._iter=e,this.size=e.size}function FromEntriesSequence(e){this._iter=e,this.size=e.size}function flipFactory(e){var t=makeSequence(e);return t._iter=e,t.size=e.size,t.flip=function(){return e},t.reverse=function(){var t=e.reverse.apply(this);return t.flip=function(){return e.reverse()},t},t.has=function(t){return e.includes(t)},t.includes=function(t){return e.has(t)},t.cacheResult=cacheResultThrough,t.__iterateUncached=function(t,r){var n=this;return e.__iterate((function(e,r){return!1!==t(r,e,n)}),r)},t.__iteratorUncached=function(t,r){if(t===d){var n=e.__iterator(t,r);return new Iterator((function(){var e=n.next();if(!e.done){var t=e.value[0];e.value[0]=e.value[1],e.value[1]=t}return e}))}return e.__iterator(t===p?h:p,r)},t}function mapFactory(e,t,r){var n=makeSequence(e);return n.size=e.size,n.has=function(t){return e.has(t)},n.get=function(n,i){var o=e.get(n,c);return o===c?i:t.call(r,o,n,e)},n.__iterateUncached=function(n,i){var o=this;return e.__iterate((function(e,i,a){return!1!==n(t.call(r,e,i,a),i,o)}),i)},n.__iteratorUncached=function(n,i){var o=e.__iterator(d,i);return new Iterator((function(){var i=o.next();if(i.done)return i;var a=i.value,s=a[0];return iteratorValue(n,s,t.call(r,a[1],s,e),i)}))},n}function reverseFactory(e,t){var r=makeSequence(e);return r._iter=e,r.size=e.size,r.reverse=function(){return e},e.flip&&(r.flip=function(){var t=flipFactory(e);return t.reverse=function(){return e.flip()},t}),r.get=function(r,n){return e.get(t?r:-1-r,n)},r.has=function(r){return e.has(t?r:-1-r)},r.includes=function(t){return e.includes(t)},r.cacheResult=cacheResultThrough,r.__iterate=function(t,r){var n=this;return e.__iterate((function(e,r){return t(e,r,n)}),!r)},r.__iterator=function(t,r){return e.__iterator(t,!r)},r}function filterFactory(e,t,r,n){var i=makeSequence(e);return n&&(i.has=function(n){var i=e.get(n,c);return i!==c&&!!t.call(r,i,n,e)},i.get=function(n,i){var o=e.get(n,c);return o!==c&&t.call(r,o,n,e)?o:i}),i.__iterateUncached=function(i,o){var a=this,s=0;return e.__iterate((function(e,o,u){if(t.call(r,e,o,u))return s++,i(e,n?o:s-1,a)}),o),s},i.__iteratorUncached=function(i,o){var a=e.__iterator(d,o),s=0;return new Iterator((function(){for(;;){var o=a.next();if(o.done)return o;var u=o.value,c=u[0],f=u[1];if(t.call(r,f,c,e))return iteratorValue(i,n?c:s++,f,o)}}))},i}function countByFactory(e,t,r){var n=Map().asMutable();return e.__iterate((function(i,o){n.update(t.call(r,i,o,e),0,(function(e){return e+1}))})),n.asImmutable()}function groupByFactory(e,t,r){var n=isKeyed(e),i=(isOrdered(e)?OrderedMap():Map()).asMutable();e.__iterate((function(o,a){i.update(t.call(r,o,a,e),(function(e){return(e=e||[]).push(n?[a,o]:o),e}))}));var o=iterableClass(e);return i.map((function(t){return reify(e,o(t))}))}function sliceFactory(e,t,r,n){var i=e.size;if(void 0!==t&&(t|=0),void 0!==r&&(r===1/0?r=i:r|=0),wholeSlice(t,r,i))return e;var o=resolveBegin(t,i),a=resolveEnd(r,i);if(o!=o||a!=a)return sliceFactory(e.toSeq().cacheResult(),t,r,n);var s,u=a-o;u==u&&(s=u<0?0:u);var c=makeSequence(e);return c.size=0===s?s:e.size&&s||void 0,!n&&isSeq(e)&&s>=0&&(c.get=function(t,r){return(t=wrapIndex(this,t))>=0&&ts)return iteratorDone();var e=i.next();return n||t===p?e:iteratorValue(t,u-1,t===h?void 0:e.value[1],e)}))},c}function takeWhileFactory(e,t,r){var n=makeSequence(e);return n.__iterateUncached=function(n,i){var o=this;if(i)return this.cacheResult().__iterate(n,i);var a=0;return e.__iterate((function(e,i,s){return t.call(r,e,i,s)&&++a&&n(e,i,o)})),a},n.__iteratorUncached=function(n,i){var o=this;if(i)return this.cacheResult().__iterator(n,i);var a=e.__iterator(d,i),s=!0;return new Iterator((function(){if(!s)return iteratorDone();var e=a.next();if(e.done)return e;var i=e.value,u=i[0],c=i[1];return t.call(r,c,u,o)?n===d?e:iteratorValue(n,u,c,e):(s=!1,iteratorDone())}))},n}function skipWhileFactory(e,t,r,n){var i=makeSequence(e);return i.__iterateUncached=function(i,o){var a=this;if(o)return this.cacheResult().__iterate(i,o);var s=!0,u=0;return e.__iterate((function(e,o,c){if(!s||!(s=t.call(r,e,o,c)))return u++,i(e,n?o:u-1,a)})),u},i.__iteratorUncached=function(i,o){var a=this;if(o)return this.cacheResult().__iterator(i,o);var s=e.__iterator(d,o),u=!0,c=0;return new Iterator((function(){var e,o,f;do{if((e=s.next()).done)return n||i===p?e:iteratorValue(i,c++,i===h?void 0:e.value[1],e);var l=e.value;o=l[0],f=l[1],u&&(u=t.call(r,f,o,a))}while(u);return i===d?e:iteratorValue(i,o,f,e)}))},i}function concatFactory(e,t){var r=isKeyed(e),n=[e].concat(t).map((function(e){return isIterable(e)?r&&(e=KeyedIterable(e)):e=r?keyedSeqFromValue(e):indexedSeqFromValue(Array.isArray(e)?e:[e]),e})).filter((function(e){return 0!==e.size}));if(0===n.length)return e;if(1===n.length){var i=n[0];if(i===e||r&&isKeyed(i)||isIndexed(e)&&isIndexed(i))return i}var o=new ArraySeq(n);return r?o=o.toKeyedSeq():isIndexed(e)||(o=o.toSetSeq()),(o=o.flatten(!0)).size=n.reduce((function(e,t){if(void 0!==e){var r=t.size;if(void 0!==r)return e+r}}),0),o}function flattenFactory(e,t,r){var n=makeSequence(e);return n.__iterateUncached=function(n,i){var o=0,a=!1;function flatDeep(e,s){var u=this;e.__iterate((function(e,i){return(!t||s0}function zipWithFactory(e,t,r){var n=makeSequence(e);return n.size=new ArraySeq(r).map((function(e){return e.size})).min(),n.__iterate=function(e,t){for(var r,n=this.__iterator(p,t),i=0;!(r=n.next()).done&&!1!==e(r.value,i++,this););return i},n.__iteratorUncached=function(e,n){var i=r.map((function(e){return e=Iterable(e),getIterator(n?e.reverse():e)})),o=0,a=!1;return new Iterator((function(){var r;return a||(r=i.map((function(e){return e.next()})),a=r.some((function(e){return e.done}))),a?iteratorDone():iteratorValue(e,o++,t.apply(null,r.map((function(e){return e.value}))))}))},n}function reify(e,t){return isSeq(e)?t:e.constructor(t)}function validateEntry(e){if(e!==Object(e))throw new TypeError("Expected [K, V] tuple: "+e)}function resolveSize(e){return assertNotInfinite(e.size),ensureSize(e)}function iterableClass(e){return isKeyed(e)?KeyedIterable:isIndexed(e)?IndexedIterable:SetIterable}function makeSequence(e){return Object.create((isKeyed(e)?KeyedSeq:isIndexed(e)?IndexedSeq:SetSeq).prototype)}function cacheResultThrough(){return this._iter.cacheResult?(this._iter.cacheResult(),this.size=this._iter.size,this):Seq.prototype.cacheResult.call(this)}function defaultComparator(e,t){return e>t?1:e=0;r--)t={value:arguments[r],next:t};return this.__ownerID?(this.size=e,this._head=t,this.__hash=void 0,this.__altered=!0,this):makeStack(e,t)},Stack.prototype.pushAll=function(e){if(0===(e=IndexedIterable(e)).size)return this;assertNotInfinite(e.size);var t=this.size,r=this._head;return e.reverse().forEach((function(e){t++,r={value:e,next:r}})),this.__ownerID?(this.size=t,this._head=r,this.__hash=void 0,this.__altered=!0,this):makeStack(t,r)},Stack.prototype.pop=function(){return this.slice(1)},Stack.prototype.unshift=function(){return this.push.apply(this,arguments)},Stack.prototype.unshiftAll=function(e){return this.pushAll(e)},Stack.prototype.shift=function(){return this.pop.apply(this,arguments)},Stack.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._head=void 0,this.__hash=void 0,this.__altered=!0,this):emptyStack()},Stack.prototype.slice=function(e,t){if(wholeSlice(e,t,this.size))return this;var r=resolveBegin(e,this.size);if(resolveEnd(t,this.size)!==this.size)return IndexedCollection.prototype.slice.call(this,e,t);for(var n=this.size-r,i=this._head;r--;)i=i.next;return this.__ownerID?(this.size=n,this._head=i,this.__hash=void 0,this.__altered=!0,this):makeStack(n,i)},Stack.prototype.__ensureOwner=function(e){return e===this.__ownerID?this:e?makeStack(this.size,this._head,e,this.__hash):(this.__ownerID=e,this.__altered=!1,this)},Stack.prototype.__iterate=function(e,t){if(t)return this.reverse().__iterate(e);for(var r=0,n=this._head;n&&!1!==e(n.value,r++,this);)n=n.next;return r},Stack.prototype.__iterator=function(e,t){if(t)return this.reverse().__iterator(e);var r=0,n=this._head;return new Iterator((function(){if(n){var t=n.value;return n=n.next,iteratorValue(e,r++,t)}return iteratorDone()}))},Stack.isStack=isStack;var ue,ce="@@__IMMUTABLE_STACK__@@",fe=Stack.prototype;function makeStack(e,t,r,n){var i=Object.create(fe);return i.size=e,i._head=t,i.__ownerID=r,i.__hash=n,i.__altered=!1,i}function emptyStack(){return ue||(ue=makeStack(0))}function mixin(e,t){var keyCopier=function(r){e.prototype[r]=t[r]};return Object.keys(t).forEach(keyCopier),Object.getOwnPropertySymbols&&Object.getOwnPropertySymbols(t).forEach(keyCopier),e}fe[ce]=!0,fe.withMutations=K.withMutations,fe.asMutable=K.asMutable,fe.asImmutable=K.asImmutable,fe.wasAltered=K.wasAltered,Iterable.Iterator=Iterator,mixin(Iterable,{toArray:function(){assertNotInfinite(this.size);var e=new Array(this.size||0);return this.valueSeq().__iterate((function(t,r){e[r]=t})),e},toIndexedSeq:function(){return new ToIndexedSequence(this)},toJS:function(){return this.toSeq().map((function(e){return e&&"function"==typeof e.toJS?e.toJS():e})).__toJS()},toJSON:function(){return this.toSeq().map((function(e){return e&&"function"==typeof e.toJSON?e.toJSON():e})).__toJS()},toKeyedSeq:function(){return new ToKeyedSequence(this,!0)},toMap:function(){return Map(this.toKeyedSeq())},toObject:function(){assertNotInfinite(this.size);var e={};return this.__iterate((function(t,r){e[r]=t})),e},toOrderedMap:function(){return OrderedMap(this.toKeyedSeq())},toOrderedSet:function(){return OrderedSet(isKeyed(this)?this.valueSeq():this)},toSet:function(){return Set(isKeyed(this)?this.valueSeq():this)},toSetSeq:function(){return new ToSetSequence(this)},toSeq:function(){return isIndexed(this)?this.toIndexedSeq():isKeyed(this)?this.toKeyedSeq():this.toSetSeq()},toStack:function(){return Stack(isKeyed(this)?this.valueSeq():this)},toList:function(){return List(isKeyed(this)?this.valueSeq():this)},toString:function(){return"[Iterable]"},__toString:function(e,t){return 0===this.size?e+t:e+" "+this.toSeq().map(this.__toStringMapper).join(", ")+" "+t},concat:function(){return reify(this,concatFactory(this,e.call(arguments,0)))},includes:function(e){return this.some((function(t){return is(t,e)}))},entries:function(){return this.__iterator(d)},every:function(e,t){assertNotInfinite(this.size);var r=!0;return this.__iterate((function(n,i,o){if(!e.call(t,n,i,o))return r=!1,!1})),r},filter:function(e,t){return reify(this,filterFactory(this,e,t,!0))},find:function(e,t,r){var n=this.findEntry(e,t);return n?n[1]:r},forEach:function(e,t){return assertNotInfinite(this.size),this.__iterate(t?e.bind(t):e)},join:function(e){assertNotInfinite(this.size),e=void 0!==e?""+e:",";var t="",r=!0;return this.__iterate((function(n){r?r=!1:t+=e,t+=null!=n?n.toString():""})),t},keys:function(){return this.__iterator(h)},map:function(e,t){return reify(this,mapFactory(this,e,t))},reduce:function(e,t,r){var n,i;return assertNotInfinite(this.size),arguments.length<2?i=!0:n=t,this.__iterate((function(t,o,a){i?(i=!1,n=t):n=e.call(r,n,t,o,a)})),n},reduceRight:function(e,t,r){var n=this.toKeyedSeq().reverse();return n.reduce.apply(n,arguments)},reverse:function(){return reify(this,reverseFactory(this,!0))},slice:function(e,t){return reify(this,sliceFactory(this,e,t,!0))},some:function(e,t){return!this.every(not(e),t)},sort:function(e){return reify(this,sortFactory(this,e))},values:function(){return this.__iterator(p)},butLast:function(){return this.slice(0,-1)},isEmpty:function(){return void 0!==this.size?0===this.size:!this.some((function(){return!0}))},count:function(e,t){return ensureSize(e?this.toSeq().filter(e,t):this)},countBy:function(e,t){return countByFactory(this,e,t)},equals:function(e){return deepEqual(this,e)},entrySeq:function(){var e=this;if(e._cache)return new ArraySeq(e._cache);var t=e.toSeq().map(entryMapper).toIndexedSeq();return t.fromEntrySeq=function(){return e.toSeq()},t},filterNot:function(e,t){return this.filter(not(e),t)},findEntry:function(e,t,r){var n=r;return this.__iterate((function(r,i,o){if(e.call(t,r,i,o))return n=[i,r],!1})),n},findKey:function(e,t){var r=this.findEntry(e,t);return r&&r[0]},findLast:function(e,t,r){return this.toKeyedSeq().reverse().find(e,t,r)},findLastEntry:function(e,t,r){return this.toKeyedSeq().reverse().findEntry(e,t,r)},findLastKey:function(e,t){return this.toKeyedSeq().reverse().findKey(e,t)},first:function(){return this.find(returnTrue)},flatMap:function(e,t){return reify(this,flatMapFactory(this,e,t))},flatten:function(e){return reify(this,flattenFactory(this,e,!0))},fromEntrySeq:function(){return new FromEntriesSequence(this)},get:function(e,t){return this.find((function(t,r){return is(r,e)}),void 0,t)},getIn:function(e,t){for(var r,n=this,i=forceIterator(e);!(r=i.next()).done;){var o=r.value;if((n=n&&n.get?n.get(o,c):c)===c)return t}return n},groupBy:function(e,t){return groupByFactory(this,e,t)},has:function(e){return this.get(e,c)!==c},hasIn:function(e){return this.getIn(e,c)!==c},isSubset:function(e){return e="function"==typeof e.includes?e:Iterable(e),this.every((function(t){return e.includes(t)}))},isSuperset:function(e){return(e="function"==typeof e.isSubset?e:Iterable(e)).isSubset(this)},keyOf:function(e){return this.findKey((function(t){return is(t,e)}))},keySeq:function(){return this.toSeq().map(keyMapper).toIndexedSeq()},last:function(){return this.toSeq().reverse().first()},lastKeyOf:function(e){return this.toKeyedSeq().reverse().keyOf(e)},max:function(e){return maxFactory(this,e)},maxBy:function(e,t){return maxFactory(this,t,e)},min:function(e){return maxFactory(this,e?neg(e):defaultNegComparator)},minBy:function(e,t){return maxFactory(this,t?neg(t):defaultNegComparator,e)},rest:function(){return this.slice(1)},skip:function(e){return this.slice(Math.max(0,e))},skipLast:function(e){return reify(this,this.toSeq().reverse().skip(e).reverse())},skipWhile:function(e,t){return reify(this,skipWhileFactory(this,e,t,!0))},skipUntil:function(e,t){return this.skipWhile(not(e),t)},sortBy:function(e,t){return reify(this,sortFactory(this,t,e))},take:function(e){return this.slice(0,Math.max(0,e))},takeLast:function(e){return reify(this,this.toSeq().reverse().take(e).reverse())},takeWhile:function(e,t){return reify(this,takeWhileFactory(this,e,t))},takeUntil:function(e,t){return this.takeWhile(not(e),t)},valueSeq:function(){return this.toIndexedSeq()},hashCode:function(){return this.__hash||(this.__hash=hashIterable(this))}});var le=Iterable.prototype;le[t]=!0,le[m]=le.values,le.__toJS=le.toArray,le.__toStringMapper=quoteString,le.inspect=le.toSource=function(){return this.toString()},le.chain=le.flatMap,le.contains=le.includes,mixin(KeyedIterable,{flip:function(){return reify(this,flipFactory(this))},mapEntries:function(e,t){var r=this,n=0;return reify(this,this.toSeq().map((function(i,o){return e.call(t,[o,i],n++,r)})).fromEntrySeq())},mapKeys:function(e,t){var r=this;return reify(this,this.toSeq().flip().map((function(n,i){return e.call(t,n,i,r)})).flip())}});var he=KeyedIterable.prototype;function keyMapper(e,t){return t}function entryMapper(e,t){return[t,e]}function not(e){return function(){return!e.apply(this,arguments)}}function neg(e){return function(){return-e.apply(this,arguments)}}function quoteString(e){return"string"==typeof e?JSON.stringify(e):String(e)}function defaultZipper(){return arrCopy(arguments)}function defaultNegComparator(e,t){return et?-1:0}function hashIterable(e){if(e.size===1/0)return 0;var t=isOrdered(e),r=isKeyed(e),n=t?1:0;return murmurHashOfSize(e.__iterate(r?t?function(e,t){n=31*n+hashMerge(hash(e),hash(t))|0}:function(e,t){n=n+hashMerge(hash(e),hash(t))|0}:t?function(e){n=31*n+hash(e)|0}:function(e){n=n+hash(e)|0}),n)}function murmurHashOfSize(e,t){return t=I(t,3432918353),t=I(t<<15|t>>>-15,461845907),t=I(t<<13|t>>>-13,5),t=I((t=t+3864292196^e)^t>>>16,2246822507),t=smi((t=I(t^t>>>13,3266489909))^t>>>16)}function hashMerge(e,t){return e^t+2654435769+(e<<6)+(e>>2)}return he[r]=!0,he[m]=le.entries,he.__toJS=le.toObject,he.__toStringMapper=function(e,t){return JSON.stringify(t)+": "+quoteString(e)},mixin(IndexedIterable,{toKeyedSeq:function(){return new ToKeyedSequence(this,!1)},filter:function(e,t){return reify(this,filterFactory(this,e,t,!1))},findIndex:function(e,t){var r=this.findEntry(e,t);return r?r[0]:-1},indexOf:function(e){var t=this.keyOf(e);return void 0===t?-1:t},lastIndexOf:function(e){var t=this.lastKeyOf(e);return void 0===t?-1:t},reverse:function(){return reify(this,reverseFactory(this,!1))},slice:function(e,t){return reify(this,sliceFactory(this,e,t,!1))},splice:function(e,t){var r=arguments.length;if(t=Math.max(0|t,0),0===r||2===r&&!t)return this;e=resolveBegin(e,e<0?this.count():this.size);var n=this.slice(0,e);return reify(this,1===r?n:n.concat(arrCopy(arguments,2),this.slice(e+t)))},findLastIndex:function(e,t){var r=this.findLastEntry(e,t);return r?r[0]:-1},first:function(){return this.get(0)},flatten:function(e){return reify(this,flattenFactory(this,e,!1))},get:function(e,t){return(e=wrapIndex(this,e))<0||this.size===1/0||void 0!==this.size&&e>this.size?t:this.find((function(t,r){return r===e}),void 0,t)},has:function(e){return(e=wrapIndex(this,e))>=0&&(void 0!==this.size?this.size===1/0||e{"function"==typeof Object.create?e.exports=function inherits(e,t){t&&(e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}))}:e.exports=function inherits(e,t){if(t){e.super_=t;var TempCtor=function(){};TempCtor.prototype=t.prototype,e.prototype=new TempCtor,e.prototype.constructor=e}}},5580:(e,t,r)=>{var n=r(6110)(r(9325),"DataView");e.exports=n},1549:(e,t,r)=>{var n=r(2032),i=r(3862),o=r(6721),a=r(2749),s=r(5749);function Hash(e){var t=-1,r=null==e?0:e.length;for(this.clear();++t{var n=r(3702),i=r(80),o=r(4739),a=r(8655),s=r(1175);function ListCache(e){var t=-1,r=null==e?0:e.length;for(this.clear();++t{var n=r(6110)(r(9325),"Map");e.exports=n},3661:(e,t,r)=>{var n=r(3040),i=r(7670),o=r(289),a=r(4509),s=r(2949);function MapCache(e){var t=-1,r=null==e?0:e.length;for(this.clear();++t{var n=r(6110)(r(9325),"Promise");e.exports=n},6545:(e,t,r)=>{var n=r(6110)(r(9325),"Set");e.exports=n},8859:(e,t,r)=>{var n=r(3661),i=r(1380),o=r(1459);function SetCache(e){var t=-1,r=null==e?0:e.length;for(this.__data__=new n;++t{var n=r(79),i=r(1420),o=r(938),a=r(3605),s=r(9817),u=r(945);function Stack(e){var t=this.__data__=new n(e);this.size=t.size}Stack.prototype.clear=i,Stack.prototype.delete=o,Stack.prototype.get=a,Stack.prototype.has=s,Stack.prototype.set=u,e.exports=Stack},1873:(e,t,r)=>{var n=r(9325).Symbol;e.exports=n},7828:(e,t,r)=>{var n=r(9325).Uint8Array;e.exports=n},8303:(e,t,r)=>{var n=r(6110)(r(9325),"WeakMap");e.exports=n},9770:e=>{e.exports=function arrayFilter(e,t){for(var r=-1,n=null==e?0:e.length,i=0,o=[];++r{var n=r(8096),i=r(2428),o=r(6449),a=r(3656),s=r(361),u=r(7167),c=Object.prototype.hasOwnProperty;e.exports=function arrayLikeKeys(e,t){var r=o(e),f=!r&&i(e),l=!r&&!f&&a(e),h=!r&&!f&&!l&&u(e),p=r||f||l||h,d=p?n(e.length,String):[],_=d.length;for(var y in e)!t&&!c.call(e,y)||p&&("length"==y||l&&("offset"==y||"parent"==y)||h&&("buffer"==y||"byteLength"==y||"byteOffset"==y)||s(y,_))||d.push(y);return d}},4932:e=>{e.exports=function arrayMap(e,t){for(var r=-1,n=null==e?0:e.length,i=Array(n);++r{e.exports=function arrayPush(e,t){for(var r=-1,n=t.length,i=e.length;++r{e.exports=function arrayReduce(e,t,r,n){var i=-1,o=null==e?0:e.length;for(n&&o&&(r=e[++i]);++i{e.exports=function arraySome(e,t){for(var r=-1,n=null==e?0:e.length;++r{e.exports=function asciiToArray(e){return e.split("")}},1733:e=>{var t=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g;e.exports=function asciiWords(e){return e.match(t)||[]}},6547:(e,t,r)=>{var n=r(3360),i=r(5288),o=Object.prototype.hasOwnProperty;e.exports=function assignValue(e,t,r){var a=e[t];o.call(e,t)&&i(a,r)&&(void 0!==r||t in e)||n(e,t,r)}},6025:(e,t,r)=>{var n=r(5288);e.exports=function assocIndexOf(e,t){for(var r=e.length;r--;)if(n(e[r][0],t))return r;return-1}},3360:(e,t,r)=>{var n=r(3243);e.exports=function baseAssignValue(e,t,r){"__proto__"==t&&n?n(e,t,{configurable:!0,enumerable:!0,value:r,writable:!0}):e[t]=r}},909:(e,t,r)=>{var n=r(641),i=r(8329)(n);e.exports=i},2523:e=>{e.exports=function baseFindIndex(e,t,r,n){for(var i=e.length,o=r+(n?1:-1);n?o--:++o{var n=r(3221)();e.exports=n},641:(e,t,r)=>{var n=r(6649),i=r(5950);e.exports=function baseForOwn(e,t){return e&&n(e,t,i)}},7422:(e,t,r)=>{var n=r(1769),i=r(7797);e.exports=function baseGet(e,t){for(var r=0,o=(t=n(t,e)).length;null!=e&&r{var n=r(4528),i=r(6449);e.exports=function baseGetAllKeys(e,t,r){var o=t(e);return i(e)?o:n(o,r(e))}},2552:(e,t,r)=>{var n=r(1873),i=r(659),o=r(9350),a=n?n.toStringTag:void 0;e.exports=function baseGetTag(e){return null==e?void 0===e?"[object Undefined]":"[object Null]":a&&a in Object(e)?i(e):o(e)}},8077:e=>{e.exports=function baseHasIn(e,t){return null!=e&&t in Object(e)}},7534:(e,t,r)=>{var n=r(2552),i=r(346);e.exports=function baseIsArguments(e){return i(e)&&"[object Arguments]"==n(e)}},270:(e,t,r)=>{var n=r(7068),i=r(346);e.exports=function baseIsEqual(e,t,r,o,a){return e===t||(null==e||null==t||!i(e)&&!i(t)?e!=e&&t!=t:n(e,t,r,o,baseIsEqual,a))}},7068:(e,t,r)=>{var n=r(7217),i=r(5911),o=r(1986),a=r(689),s=r(5861),u=r(6449),c=r(3656),f=r(7167),l="[object Arguments]",h="[object Array]",p="[object Object]",d=Object.prototype.hasOwnProperty;e.exports=function baseIsEqualDeep(e,t,r,_,y,m){var g=u(e),v=u(t),b=g?h:s(e),w=v?h:s(t),I=(b=b==l?p:b)==p,x=(w=w==l?p:w)==p,B=b==w;if(B&&c(e)){if(!c(t))return!1;g=!0,I=!1}if(B&&!I)return m||(m=new n),g||f(e)?i(e,t,r,_,y,m):o(e,t,b,r,_,y,m);if(!(1&r)){var k=I&&d.call(e,"__wrapped__"),C=x&&d.call(t,"__wrapped__");if(k||C){var q=k?e.value():e,L=C?t.value():t;return m||(m=new n),y(q,L,r,_,m)}}return!!B&&(m||(m=new n),a(e,t,r,_,y,m))}},1799:(e,t,r)=>{var n=r(7217),i=r(270);e.exports=function baseIsMatch(e,t,r,o){var a=r.length,s=a,u=!o;if(null==e)return!s;for(e=Object(e);a--;){var c=r[a];if(u&&c[2]?c[1]!==e[c[0]]:!(c[0]in e))return!1}for(;++a{var n=r(1882),i=r(7296),o=r(3805),a=r(7473),s=/^\[object .+?Constructor\]$/,u=Function.prototype,c=Object.prototype,f=u.toString,l=c.hasOwnProperty,h=RegExp("^"+f.call(l).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");e.exports=function baseIsNative(e){return!(!o(e)||i(e))&&(n(e)?h:s).test(a(e))}},4901:(e,t,r)=>{var n=r(2552),i=r(294),o=r(346),a={};a["[object Float32Array]"]=a["[object Float64Array]"]=a["[object Int8Array]"]=a["[object Int16Array]"]=a["[object Int32Array]"]=a["[object Uint8Array]"]=a["[object Uint8ClampedArray]"]=a["[object Uint16Array]"]=a["[object Uint32Array]"]=!0,a["[object Arguments]"]=a["[object Array]"]=a["[object ArrayBuffer]"]=a["[object Boolean]"]=a["[object DataView]"]=a["[object Date]"]=a["[object Error]"]=a["[object Function]"]=a["[object Map]"]=a["[object Number]"]=a["[object Object]"]=a["[object RegExp]"]=a["[object Set]"]=a["[object String]"]=a["[object WeakMap]"]=!1,e.exports=function baseIsTypedArray(e){return o(e)&&i(e.length)&&!!a[n(e)]}},5389:(e,t,r)=>{var n=r(3663),i=r(7978),o=r(3488),a=r(6449),s=r(583);e.exports=function baseIteratee(e){return"function"==typeof e?e:null==e?o:"object"==typeof e?a(e)?i(e[0],e[1]):n(e):s(e)}},8984:(e,t,r)=>{var n=r(5527),i=r(3650),o=Object.prototype.hasOwnProperty;e.exports=function baseKeys(e){if(!n(e))return i(e);var t=[];for(var r in Object(e))o.call(e,r)&&"constructor"!=r&&t.push(r);return t}},3663:(e,t,r)=>{var n=r(1799),i=r(776),o=r(7197);e.exports=function baseMatches(e){var t=i(e);return 1==t.length&&t[0][2]?o(t[0][0],t[0][1]):function(r){return r===e||n(r,e,t)}}},7978:(e,t,r)=>{var n=r(270),i=r(8156),o=r(631),a=r(8586),s=r(756),u=r(7197),c=r(7797);e.exports=function baseMatchesProperty(e,t){return a(e)&&s(t)?u(c(e),t):function(r){var a=i(r,e);return void 0===a&&a===t?o(r,e):n(t,a,3)}}},7237:e=>{e.exports=function baseProperty(e){return function(t){return null==t?void 0:t[e]}}},7255:(e,t,r)=>{var n=r(7422);e.exports=function basePropertyDeep(e){return function(t){return n(t,e)}}},4552:e=>{e.exports=function basePropertyOf(e){return function(t){return null==e?void 0:e[t]}}},5160:e=>{e.exports=function baseSlice(e,t,r){var n=-1,i=e.length;t<0&&(t=-t>i?0:i+t),(r=r>i?i:r)<0&&(r+=i),i=t>r?0:r-t>>>0,t>>>=0;for(var o=Array(i);++n{var n=r(909);e.exports=function baseSome(e,t){var r;return n(e,(function(e,n,i){return!(r=t(e,n,i))})),!!r}},8096:e=>{e.exports=function baseTimes(e,t){for(var r=-1,n=Array(e);++r{var n=r(1873),i=r(4932),o=r(6449),a=r(4394),s=n?n.prototype:void 0,u=s?s.toString:void 0;e.exports=function baseToString(e){if("string"==typeof e)return e;if(o(e))return i(e,baseToString)+"";if(a(e))return u?u.call(e):"";var t=e+"";return"0"==t&&1/e==-1/0?"-0":t}},4128:(e,t,r)=>{var n=r(1800),i=/^\s+/;e.exports=function baseTrim(e){return e?e.slice(0,n(e)+1).replace(i,""):e}},7301:e=>{e.exports=function baseUnary(e){return function(t){return e(t)}}},1234:e=>{e.exports=function baseZipObject(e,t,r){for(var n=-1,i=e.length,o=t.length,a={};++n{e.exports=function cacheHas(e,t){return e.has(t)}},1769:(e,t,r)=>{var n=r(6449),i=r(8586),o=r(1802),a=r(3222);e.exports=function castPath(e,t){return n(e)?e:i(e,t)?[e]:o(a(e))}},8754:(e,t,r)=>{var n=r(5160);e.exports=function castSlice(e,t,r){var i=e.length;return r=void 0===r?i:r,!t&&r>=i?e:n(e,t,r)}},5481:(e,t,r)=>{var n=r(9325)["__core-js_shared__"];e.exports=n},8329:(e,t,r)=>{var n=r(4894);e.exports=function createBaseEach(e,t){return function(r,i){if(null==r)return r;if(!n(r))return e(r,i);for(var o=r.length,a=t?o:-1,s=Object(r);(t?a--:++a{e.exports=function createBaseFor(e){return function(t,r,n){for(var i=-1,o=Object(t),a=n(t),s=a.length;s--;){var u=a[e?s:++i];if(!1===r(o[u],u,o))break}return t}}},2507:(e,t,r)=>{var n=r(8754),i=r(9698),o=r(3912),a=r(3222);e.exports=function createCaseFirst(e){return function(t){t=a(t);var r=i(t)?o(t):void 0,s=r?r[0]:t.charAt(0),u=r?n(r,1).join(""):t.slice(1);return s[e]()+u}}},5539:(e,t,r)=>{var n=r(882),i=r(828),o=r(6645),a=RegExp("['’]","g");e.exports=function createCompounder(e){return function(t){return n(o(i(t).replace(a,"")),e,"")}}},2006:(e,t,r)=>{var n=r(5389),i=r(4894),o=r(5950);e.exports=function createFind(e){return function(t,r,a){var s=Object(t);if(!i(t)){var u=n(r,3);t=o(t),r=function(e){return u(s[e],e,s)}}var c=e(t,r,a);return c>-1?s[u?t[c]:c]:void 0}}},4647:(e,t,r)=>{var n=r(4552)({À:"A",Á:"A",Â:"A",Ã:"A",Ä:"A",Å:"A",à:"a",á:"a",â:"a",ã:"a",ä:"a",å:"a",Ç:"C",ç:"c",Ð:"D",ð:"d",È:"E",É:"E",Ê:"E",Ë:"E",è:"e",é:"e",ê:"e",ë:"e",Ì:"I",Í:"I",Î:"I",Ï:"I",ì:"i",í:"i",î:"i",ï:"i",Ñ:"N",ñ:"n",Ò:"O",Ó:"O",Ô:"O",Õ:"O",Ö:"O",Ø:"O",ò:"o",ó:"o",ô:"o",õ:"o",ö:"o",ø:"o",Ù:"U",Ú:"U",Û:"U",Ü:"U",ù:"u",ú:"u",û:"u",ü:"u",Ý:"Y",ý:"y",ÿ:"y",Æ:"Ae",æ:"ae",Þ:"Th",þ:"th",ß:"ss",Ā:"A",Ă:"A",Ą:"A",ā:"a",ă:"a",ą:"a",Ć:"C",Ĉ:"C",Ċ:"C",Č:"C",ć:"c",ĉ:"c",ċ:"c",č:"c",Ď:"D",Đ:"D",ď:"d",đ:"d",Ē:"E",Ĕ:"E",Ė:"E",Ę:"E",Ě:"E",ē:"e",ĕ:"e",ė:"e",ę:"e",ě:"e",Ĝ:"G",Ğ:"G",Ġ:"G",Ģ:"G",ĝ:"g",ğ:"g",ġ:"g",ģ:"g",Ĥ:"H",Ħ:"H",ĥ:"h",ħ:"h",Ĩ:"I",Ī:"I",Ĭ:"I",Į:"I",İ:"I",ĩ:"i",ī:"i",ĭ:"i",į:"i",ı:"i",Ĵ:"J",ĵ:"j",Ķ:"K",ķ:"k",ĸ:"k",Ĺ:"L",Ļ:"L",Ľ:"L",Ŀ:"L",Ł:"L",ĺ:"l",ļ:"l",ľ:"l",ŀ:"l",ł:"l",Ń:"N",Ņ:"N",Ň:"N",Ŋ:"N",ń:"n",ņ:"n",ň:"n",ŋ:"n",Ō:"O",Ŏ:"O",Ő:"O",ō:"o",ŏ:"o",ő:"o",Ŕ:"R",Ŗ:"R",Ř:"R",ŕ:"r",ŗ:"r",ř:"r",Ś:"S",Ŝ:"S",Ş:"S",Š:"S",ś:"s",ŝ:"s",ş:"s",š:"s",Ţ:"T",Ť:"T",Ŧ:"T",ţ:"t",ť:"t",ŧ:"t",Ũ:"U",Ū:"U",Ŭ:"U",Ů:"U",Ű:"U",Ų:"U",ũ:"u",ū:"u",ŭ:"u",ů:"u",ű:"u",ų:"u",Ŵ:"W",ŵ:"w",Ŷ:"Y",ŷ:"y",Ÿ:"Y",Ź:"Z",Ż:"Z",Ž:"Z",ź:"z",ż:"z",ž:"z",IJ:"IJ",ij:"ij",Œ:"Oe",œ:"oe",ʼn:"'n",ſ:"s"});e.exports=n},3243:(e,t,r)=>{var n=r(6110),i=function(){try{var e=n(Object,"defineProperty");return e({},"",{}),e}catch(e){}}();e.exports=i},5911:(e,t,r)=>{var n=r(8859),i=r(4248),o=r(9219);e.exports=function equalArrays(e,t,r,a,s,u){var c=1&r,f=e.length,l=t.length;if(f!=l&&!(c&&l>f))return!1;var h=u.get(e),p=u.get(t);if(h&&p)return h==t&&p==e;var d=-1,_=!0,y=2&r?new n:void 0;for(u.set(e,t),u.set(t,e);++d{var n=r(1873),i=r(7828),o=r(5288),a=r(5911),s=r(317),u=r(4247),c=n?n.prototype:void 0,f=c?c.valueOf:void 0;e.exports=function equalByTag(e,t,r,n,c,l,h){switch(r){case"[object DataView]":if(e.byteLength!=t.byteLength||e.byteOffset!=t.byteOffset)return!1;e=e.buffer,t=t.buffer;case"[object ArrayBuffer]":return!(e.byteLength!=t.byteLength||!l(new i(e),new i(t)));case"[object Boolean]":case"[object Date]":case"[object Number]":return o(+e,+t);case"[object Error]":return e.name==t.name&&e.message==t.message;case"[object RegExp]":case"[object String]":return e==t+"";case"[object Map]":var p=s;case"[object Set]":var d=1&n;if(p||(p=u),e.size!=t.size&&!d)return!1;var _=h.get(e);if(_)return _==t;n|=2,h.set(e,t);var y=a(p(e),p(t),n,c,l,h);return h.delete(e),y;case"[object Symbol]":if(f)return f.call(e)==f.call(t)}return!1}},689:(e,t,r)=>{var n=r(2),i=Object.prototype.hasOwnProperty;e.exports=function equalObjects(e,t,r,o,a,s){var u=1&r,c=n(e),f=c.length;if(f!=n(t).length&&!u)return!1;for(var l=f;l--;){var h=c[l];if(!(u?h in t:i.call(t,h)))return!1}var p=s.get(e),d=s.get(t);if(p&&d)return p==t&&d==e;var _=!0;s.set(e,t),s.set(t,e);for(var y=u;++l{var n="object"==typeof r.g&&r.g&&r.g.Object===Object&&r.g;e.exports=n},2:(e,t,r)=>{var n=r(2199),i=r(4664),o=r(5950);e.exports=function getAllKeys(e){return n(e,o,i)}},2651:(e,t,r)=>{var n=r(4218);e.exports=function getMapData(e,t){var r=e.__data__;return n(t)?r["string"==typeof t?"string":"hash"]:r.map}},776:(e,t,r)=>{var n=r(756),i=r(5950);e.exports=function getMatchData(e){for(var t=i(e),r=t.length;r--;){var o=t[r],a=e[o];t[r]=[o,a,n(a)]}return t}},6110:(e,t,r)=>{var n=r(5083),i=r(392);e.exports=function getNative(e,t){var r=i(e,t);return n(r)?r:void 0}},659:(e,t,r)=>{var n=r(1873),i=Object.prototype,o=i.hasOwnProperty,a=i.toString,s=n?n.toStringTag:void 0;e.exports=function getRawTag(e){var t=o.call(e,s),r=e[s];try{e[s]=void 0;var n=!0}catch(e){}var i=a.call(e);return n&&(t?e[s]=r:delete e[s]),i}},4664:(e,t,r)=>{var n=r(9770),i=r(3345),o=Object.prototype.propertyIsEnumerable,a=Object.getOwnPropertySymbols,s=a?function(e){return null==e?[]:(e=Object(e),n(a(e),(function(t){return o.call(e,t)})))}:i;e.exports=s},5861:(e,t,r)=>{var n=r(5580),i=r(8223),o=r(2804),a=r(6545),s=r(8303),u=r(2552),c=r(7473),f="[object Map]",l="[object Promise]",h="[object Set]",p="[object WeakMap]",d="[object DataView]",_=c(n),y=c(i),m=c(o),g=c(a),v=c(s),b=u;(n&&b(new n(new ArrayBuffer(1)))!=d||i&&b(new i)!=f||o&&b(o.resolve())!=l||a&&b(new a)!=h||s&&b(new s)!=p)&&(b=function(e){var t=u(e),r="[object Object]"==t?e.constructor:void 0,n=r?c(r):"";if(n)switch(n){case _:return d;case y:return f;case m:return l;case g:return h;case v:return p}return t}),e.exports=b},392:e=>{e.exports=function getValue(e,t){return null==e?void 0:e[t]}},9326:(e,t,r)=>{var n=r(1769),i=r(2428),o=r(6449),a=r(361),s=r(294),u=r(7797);e.exports=function hasPath(e,t,r){for(var c=-1,f=(t=n(t,e)).length,l=!1;++c{var t=RegExp("[\\u200d\\ud800-\\udfff\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff\\ufe0e\\ufe0f]");e.exports=function hasUnicode(e){return t.test(e)}},5434:e=>{var t=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/;e.exports=function hasUnicodeWord(e){return t.test(e)}},2032:(e,t,r)=>{var n=r(1042);e.exports=function hashClear(){this.__data__=n?n(null):{},this.size=0}},3862:e=>{e.exports=function hashDelete(e){var t=this.has(e)&&delete this.__data__[e];return this.size-=t?1:0,t}},6721:(e,t,r)=>{var n=r(1042),i=Object.prototype.hasOwnProperty;e.exports=function hashGet(e){var t=this.__data__;if(n){var r=t[e];return"__lodash_hash_undefined__"===r?void 0:r}return i.call(t,e)?t[e]:void 0}},2749:(e,t,r)=>{var n=r(1042),i=Object.prototype.hasOwnProperty;e.exports=function hashHas(e){var t=this.__data__;return n?void 0!==t[e]:i.call(t,e)}},5749:(e,t,r)=>{var n=r(1042);e.exports=function hashSet(e,t){var r=this.__data__;return this.size+=this.has(e)?0:1,r[e]=n&&void 0===t?"__lodash_hash_undefined__":t,this}},361:e=>{var t=/^(?:0|[1-9]\d*)$/;e.exports=function isIndex(e,r){var n=typeof e;return!!(r=null==r?9007199254740991:r)&&("number"==n||"symbol"!=n&&t.test(e))&&e>-1&&e%1==0&&e{var n=r(5288),i=r(4894),o=r(361),a=r(3805);e.exports=function isIterateeCall(e,t,r){if(!a(r))return!1;var s=typeof t;return!!("number"==s?i(r)&&o(t,r.length):"string"==s&&t in r)&&n(r[t],e)}},8586:(e,t,r)=>{var n=r(6449),i=r(4394),o=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,a=/^\w*$/;e.exports=function isKey(e,t){if(n(e))return!1;var r=typeof e;return!("number"!=r&&"symbol"!=r&&"boolean"!=r&&null!=e&&!i(e))||(a.test(e)||!o.test(e)||null!=t&&e in Object(t))}},4218:e=>{e.exports=function isKeyable(e){var t=typeof e;return"string"==t||"number"==t||"symbol"==t||"boolean"==t?"__proto__"!==e:null===e}},7296:(e,t,r)=>{var n,i=r(5481),o=(n=/[^.]+$/.exec(i&&i.keys&&i.keys.IE_PROTO||""))?"Symbol(src)_1."+n:"";e.exports=function isMasked(e){return!!o&&o in e}},5527:e=>{var t=Object.prototype;e.exports=function isPrototype(e){var r=e&&e.constructor;return e===("function"==typeof r&&r.prototype||t)}},756:(e,t,r)=>{var n=r(3805);e.exports=function isStrictComparable(e){return e==e&&!n(e)}},3702:e=>{e.exports=function listCacheClear(){this.__data__=[],this.size=0}},80:(e,t,r)=>{var n=r(6025),i=Array.prototype.splice;e.exports=function listCacheDelete(e){var t=this.__data__,r=n(t,e);return!(r<0)&&(r==t.length-1?t.pop():i.call(t,r,1),--this.size,!0)}},4739:(e,t,r)=>{var n=r(6025);e.exports=function listCacheGet(e){var t=this.__data__,r=n(t,e);return r<0?void 0:t[r][1]}},8655:(e,t,r)=>{var n=r(6025);e.exports=function listCacheHas(e){return n(this.__data__,e)>-1}},1175:(e,t,r)=>{var n=r(6025);e.exports=function listCacheSet(e,t){var r=this.__data__,i=n(r,e);return i<0?(++this.size,r.push([e,t])):r[i][1]=t,this}},3040:(e,t,r)=>{var n=r(1549),i=r(79),o=r(8223);e.exports=function mapCacheClear(){this.size=0,this.__data__={hash:new n,map:new(o||i),string:new n}}},7670:(e,t,r)=>{var n=r(2651);e.exports=function mapCacheDelete(e){var t=n(this,e).delete(e);return this.size-=t?1:0,t}},289:(e,t,r)=>{var n=r(2651);e.exports=function mapCacheGet(e){return n(this,e).get(e)}},4509:(e,t,r)=>{var n=r(2651);e.exports=function mapCacheHas(e){return n(this,e).has(e)}},2949:(e,t,r)=>{var n=r(2651);e.exports=function mapCacheSet(e,t){var r=n(this,e),i=r.size;return r.set(e,t),this.size+=r.size==i?0:1,this}},317:e=>{e.exports=function mapToArray(e){var t=-1,r=Array(e.size);return e.forEach((function(e,n){r[++t]=[n,e]})),r}},7197:e=>{e.exports=function matchesStrictComparable(e,t){return function(r){return null!=r&&(r[e]===t&&(void 0!==t||e in Object(r)))}}},2224:(e,t,r)=>{var n=r(104);e.exports=function memoizeCapped(e){var t=n(e,(function(e){return 500===r.size&&r.clear(),e})),r=t.cache;return t}},1042:(e,t,r)=>{var n=r(6110)(Object,"create");e.exports=n},3650:(e,t,r)=>{var n=r(4335)(Object.keys,Object);e.exports=n},6009:(e,t,r)=>{e=r.nmd(e);var n=r(4840),i=t&&!t.nodeType&&t,o=i&&e&&!e.nodeType&&e,a=o&&o.exports===i&&n.process,s=function(){try{var e=o&&o.require&&o.require("util").types;return e||a&&a.binding&&a.binding("util")}catch(e){}}();e.exports=s},9350:e=>{var t=Object.prototype.toString;e.exports=function objectToString(e){return t.call(e)}},4335:e=>{e.exports=function overArg(e,t){return function(r){return e(t(r))}}},9325:(e,t,r)=>{var n=r(4840),i="object"==typeof self&&self&&self.Object===Object&&self,o=n||i||Function("return this")();e.exports=o},1380:e=>{e.exports=function setCacheAdd(e){return this.__data__.set(e,"__lodash_hash_undefined__"),this}},1459:e=>{e.exports=function setCacheHas(e){return this.__data__.has(e)}},4247:e=>{e.exports=function setToArray(e){var t=-1,r=Array(e.size);return e.forEach((function(e){r[++t]=e})),r}},1420:(e,t,r)=>{var n=r(79);e.exports=function stackClear(){this.__data__=new n,this.size=0}},938:e=>{e.exports=function stackDelete(e){var t=this.__data__,r=t.delete(e);return this.size=t.size,r}},3605:e=>{e.exports=function stackGet(e){return this.__data__.get(e)}},9817:e=>{e.exports=function stackHas(e){return this.__data__.has(e)}},945:(e,t,r)=>{var n=r(79),i=r(8223),o=r(3661);e.exports=function stackSet(e,t){var r=this.__data__;if(r instanceof n){var a=r.__data__;if(!i||a.length<199)return a.push([e,t]),this.size=++r.size,this;r=this.__data__=new o(a)}return r.set(e,t),this.size=r.size,this}},3912:(e,t,r)=>{var n=r(1074),i=r(9698),o=r(2054);e.exports=function stringToArray(e){return i(e)?o(e):n(e)}},1802:(e,t,r)=>{var n=r(2224),i=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,o=/\\(\\)?/g,a=n((function(e){var t=[];return 46===e.charCodeAt(0)&&t.push(""),e.replace(i,(function(e,r,n,i){t.push(n?i.replace(o,"$1"):r||e)})),t}));e.exports=a},7797:(e,t,r)=>{var n=r(4394);e.exports=function toKey(e){if("string"==typeof e||n(e))return e;var t=e+"";return"0"==t&&1/e==-1/0?"-0":t}},7473:e=>{var t=Function.prototype.toString;e.exports=function toSource(e){if(null!=e){try{return t.call(e)}catch(e){}try{return e+""}catch(e){}}return""}},1800:e=>{var t=/\s/;e.exports=function trimmedEndIndex(e){for(var r=e.length;r--&&t.test(e.charAt(r)););return r}},2054:e=>{var t="\\ud800-\\udfff",r="["+t+"]",n="[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]",i="\\ud83c[\\udffb-\\udfff]",o="[^"+t+"]",a="(?:\\ud83c[\\udde6-\\uddff]){2}",s="[\\ud800-\\udbff][\\udc00-\\udfff]",u="(?:"+n+"|"+i+")"+"?",c="[\\ufe0e\\ufe0f]?",f=c+u+("(?:\\u200d(?:"+[o,a,s].join("|")+")"+c+u+")*"),l="(?:"+[o+n+"?",n,a,s,r].join("|")+")",h=RegExp(i+"(?="+i+")|"+l+f,"g");e.exports=function unicodeToArray(e){return e.match(h)||[]}},2225:e=>{var t="\\ud800-\\udfff",r="\\u2700-\\u27bf",n="a-z\\xdf-\\xf6\\xf8-\\xff",i="A-Z\\xc0-\\xd6\\xd8-\\xde",o="\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",a="["+o+"]",s="\\d+",u="["+r+"]",c="["+n+"]",f="[^"+t+o+s+r+n+i+"]",l="(?:\\ud83c[\\udde6-\\uddff]){2}",h="[\\ud800-\\udbff][\\udc00-\\udfff]",p="["+i+"]",d="(?:"+c+"|"+f+")",_="(?:"+p+"|"+f+")",y="(?:['’](?:d|ll|m|re|s|t|ve))?",m="(?:['’](?:D|LL|M|RE|S|T|VE))?",g="(?:[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]|\\ud83c[\\udffb-\\udfff])?",v="[\\ufe0e\\ufe0f]?",b=v+g+("(?:\\u200d(?:"+["[^"+t+"]",l,h].join("|")+")"+v+g+")*"),w="(?:"+[u,l,h].join("|")+")"+b,I=RegExp([p+"?"+c+"+"+y+"(?="+[a,p,"$"].join("|")+")",_+"+"+m+"(?="+[a,p+d,"$"].join("|")+")",p+"?"+d+"+"+y,p+"+"+m,"\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])","\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])",s,w].join("|"),"g");e.exports=function unicodeWords(e){return e.match(I)||[]}},4058:(e,t,r)=>{var n=r(4792),i=r(5539)((function(e,t,r){return t=t.toLowerCase(),e+(r?n(t):t)}));e.exports=i},4792:(e,t,r)=>{var n=r(3222),i=r(5808);e.exports=function capitalize(e){return i(n(e).toLowerCase())}},828:(e,t,r)=>{var n=r(4647),i=r(3222),o=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,a=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]","g");e.exports=function deburr(e){return(e=i(e))&&e.replace(o,n).replace(a,"")}},5288:e=>{e.exports=function eq(e,t){return e===t||e!=e&&t!=t}},7309:(e,t,r)=>{var n=r(2006)(r(4713));e.exports=n},4713:(e,t,r)=>{var n=r(2523),i=r(5389),o=r(1489),a=Math.max;e.exports=function findIndex(e,t,r){var s=null==e?0:e.length;if(!s)return-1;var u=null==r?0:o(r);return u<0&&(u=a(s+u,0)),n(e,i(t,3),u)}},8156:(e,t,r)=>{var n=r(7422);e.exports=function get(e,t,r){var i=null==e?void 0:n(e,t);return void 0===i?r:i}},631:(e,t,r)=>{var n=r(8077),i=r(9326);e.exports=function hasIn(e,t){return null!=e&&i(e,t,n)}},3488:e=>{e.exports=function identity(e){return e}},2428:(e,t,r)=>{var n=r(7534),i=r(346),o=Object.prototype,a=o.hasOwnProperty,s=o.propertyIsEnumerable,u=n(function(){return arguments}())?n:function(e){return i(e)&&a.call(e,"callee")&&!s.call(e,"callee")};e.exports=u},6449:e=>{var t=Array.isArray;e.exports=t},4894:(e,t,r)=>{var n=r(1882),i=r(294);e.exports=function isArrayLike(e){return null!=e&&i(e.length)&&!n(e)}},3656:(e,t,r)=>{e=r.nmd(e);var n=r(9325),i=r(9935),o=t&&!t.nodeType&&t,a=o&&e&&!e.nodeType&&e,s=a&&a.exports===o?n.Buffer:void 0,u=(s?s.isBuffer:void 0)||i;e.exports=u},1882:(e,t,r)=>{var n=r(2552),i=r(3805);e.exports=function isFunction(e){if(!i(e))return!1;var t=n(e);return"[object Function]"==t||"[object GeneratorFunction]"==t||"[object AsyncFunction]"==t||"[object Proxy]"==t}},294:e=>{e.exports=function isLength(e){return"number"==typeof e&&e>-1&&e%1==0&&e<=9007199254740991}},3805:e=>{e.exports=function isObject(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}},346:e=>{e.exports=function isObjectLike(e){return null!=e&&"object"==typeof e}},4394:(e,t,r)=>{var n=r(2552),i=r(346);e.exports=function isSymbol(e){return"symbol"==typeof e||i(e)&&"[object Symbol]"==n(e)}},7167:(e,t,r)=>{var n=r(4901),i=r(7301),o=r(6009),a=o&&o.isTypedArray,s=a?i(a):n;e.exports=s},5950:(e,t,r)=>{var n=r(695),i=r(8984),o=r(4894);e.exports=function keys(e){return o(e)?n(e):i(e)}},104:(e,t,r)=>{var n=r(3661);function memoize(e,t){if("function"!=typeof e||null!=t&&"function"!=typeof t)throw new TypeError("Expected a function");var memoized=function(){var r=arguments,n=t?t.apply(this,r):r[0],i=memoized.cache;if(i.has(n))return i.get(n);var o=e.apply(this,r);return memoized.cache=i.set(n,o)||i,o};return memoized.cache=new(memoize.Cache||n),memoized}memoize.Cache=n,e.exports=memoize},583:(e,t,r)=>{var n=r(7237),i=r(7255),o=r(8586),a=r(7797);e.exports=function property(e){return o(e)?n(a(e)):i(e)}},2426:(e,t,r)=>{var n=r(4248),i=r(5389),o=r(916),a=r(6449),s=r(6800);e.exports=function some(e,t,r){var u=a(e)?n:o;return r&&s(e,t,r)&&(t=void 0),u(e,i(t,3))}},3345:e=>{e.exports=function stubArray(){return[]}},9935:e=>{e.exports=function stubFalse(){return!1}},7400:(e,t,r)=>{var n=r(9374),i=1/0;e.exports=function toFinite(e){return e?(e=n(e))===i||e===-1/0?17976931348623157e292*(e<0?-1:1):e==e?e:0:0===e?e:0}},1489:(e,t,r)=>{var n=r(7400);e.exports=function toInteger(e){var t=n(e),r=t%1;return t==t?r?t-r:t:0}},9374:(e,t,r)=>{var n=r(4128),i=r(3805),o=r(4394),a=/^[-+]0x[0-9a-f]+$/i,s=/^0b[01]+$/i,u=/^0o[0-7]+$/i,c=parseInt;e.exports=function toNumber(e){if("number"==typeof e)return e;if(o(e))return NaN;if(i(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=i(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=n(e);var r=s.test(e);return r||u.test(e)?c(e.slice(2),r?2:8):a.test(e)?NaN:+e}},3222:(e,t,r)=>{var n=r(7556);e.exports=function toString(e){return null==e?"":n(e)}},5808:(e,t,r)=>{var n=r(2507)("toUpperCase");e.exports=n},6645:(e,t,r)=>{var n=r(1733),i=r(5434),o=r(3222),a=r(2225);e.exports=function words(e,t,r){return e=o(e),void 0===(t=r?void 0:t)?i(e)?a(e):n(e):e.match(t)||[]}},7248:(e,t,r)=>{var n=r(6547),i=r(1234);e.exports=function zipObject(e,t){return i(e||[],t||[],n)}},5606:e=>{var t,r,n=e.exports={};function defaultSetTimout(){throw new Error("setTimeout has not been defined")}function defaultClearTimeout(){throw new Error("clearTimeout has not been defined")}function runTimeout(e){if(t===setTimeout)return setTimeout(e,0);if((t===defaultSetTimout||!t)&&setTimeout)return t=setTimeout,setTimeout(e,0);try{return t(e,0)}catch(r){try{return t.call(null,e,0)}catch(r){return t.call(this,e,0)}}}!function(){try{t="function"==typeof setTimeout?setTimeout:defaultSetTimout}catch(e){t=defaultSetTimout}try{r="function"==typeof clearTimeout?clearTimeout:defaultClearTimeout}catch(e){r=defaultClearTimeout}}();var i,o=[],a=!1,s=-1;function cleanUpNextTick(){a&&i&&(a=!1,i.length?o=i.concat(o):s=-1,o.length&&drainQueue())}function drainQueue(){if(!a){var e=runTimeout(cleanUpNextTick);a=!0;for(var t=o.length;t;){for(i=o,o=[];++s1)for(var r=1;r{"use strict";var n=r(5606),i=65536,o=4294967295;var a=r(2861).Buffer,s=r.g.crypto||r.g.msCrypto;s&&s.getRandomValues?e.exports=function randomBytes(e,t){if(e>o)throw new RangeError("requested too many random bytes");var r=a.allocUnsafe(e);if(e>0)if(e>i)for(var u=0;u{"use strict";var r=Symbol.for("react.element"),n=Symbol.for("react.portal"),i=Symbol.for("react.fragment"),o=Symbol.for("react.strict_mode"),a=Symbol.for("react.profiler"),s=Symbol.for("react.provider"),u=Symbol.for("react.context"),c=Symbol.for("react.forward_ref"),f=Symbol.for("react.suspense"),l=Symbol.for("react.memo"),h=Symbol.for("react.lazy"),p=Symbol.iterator;var d={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},_=Object.assign,y={};function E(e,t,r){this.props=e,this.context=t,this.refs=y,this.updater=r||d}function F(){}function G(e,t,r){this.props=e,this.context=t,this.refs=y,this.updater=r||d}E.prototype.isReactComponent={},E.prototype.setState=function(e,t){if("object"!=typeof e&&"function"!=typeof e&&null!=e)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")},E.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")},F.prototype=E.prototype;var m=G.prototype=new F;m.constructor=G,_(m,E.prototype),m.isPureReactComponent=!0;var g=Array.isArray,v=Object.prototype.hasOwnProperty,b={current:null},w={key:!0,ref:!0,__self:!0,__source:!0};function M(e,t,n){var i,o={},a=null,s=null;if(null!=t)for(i in void 0!==t.ref&&(s=t.ref),void 0!==t.key&&(a=""+t.key),t)v.call(t,i)&&!w.hasOwnProperty(i)&&(o[i]=t[i]);var u=arguments.length-2;if(1===u)o.children=n;else if(1{"use strict";e.exports=r(5287)},2861:(e,t,r)=>{var n=r(8287),i=n.Buffer;function copyProps(e,t){for(var r in e)t[r]=e[r]}function SafeBuffer(e,t,r){return i(e,t,r)}i.from&&i.alloc&&i.allocUnsafe&&i.allocUnsafeSlow?e.exports=n:(copyProps(n,t),t.Buffer=SafeBuffer),SafeBuffer.prototype=Object.create(i.prototype),copyProps(i,SafeBuffer),SafeBuffer.from=function(e,t,r){if("number"==typeof e)throw new TypeError("Argument must not be a number");return i(e,t,r)},SafeBuffer.alloc=function(e,t,r){if("number"!=typeof e)throw new TypeError("Argument must be a number");var n=i(e);return void 0!==t?"string"==typeof r?n.fill(t,r):n.fill(t):n.fill(0),n},SafeBuffer.allocUnsafe=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return i(e)},SafeBuffer.allocUnsafeSlow=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return n.SlowBuffer(e)}},8011:(e,t,r)=>{var n=r(2861).Buffer;function Hash(e,t){this._block=n.alloc(e),this._finalSize=t,this._blockSize=e,this._len=0}Hash.prototype.update=function(e,t){"string"==typeof e&&(t=t||"utf8",e=n.from(e,t));for(var r=this._block,i=this._blockSize,o=e.length,a=this._len,s=0;s=this._finalSize&&(this._update(this._block),this._block.fill(0));var r=8*this._len;if(r<=4294967295)this._block.writeUInt32BE(r,this._blockSize-4);else{var n=(4294967295&r)>>>0,i=(r-n)/4294967296;this._block.writeUInt32BE(i,this._blockSize-8),this._block.writeUInt32BE(n,this._blockSize-4)}this._update(this._block);var o=this._hash();return e?o.toString(e):o},Hash.prototype._update=function(){throw new Error("_update must be implemented by subclass")},e.exports=Hash},2802:(e,t,r)=>{var n=e.exports=function SHA(e){e=e.toLowerCase();var t=n[e];if(!t)throw new Error(e+" is not supported (we accept pull requests)");return new t};n.sha=r(7816),n.sha1=r(3737),n.sha224=r(6710),n.sha256=r(4107),n.sha384=r(2827),n.sha512=r(2890)},7816:(e,t,r)=>{var n=r(6698),i=r(8011),o=r(2861).Buffer,a=[1518500249,1859775393,-1894007588,-899497514],s=new Array(80);function Sha(){this.init(),this._w=s,i.call(this,64,56)}function rotl30(e){return e<<30|e>>>2}function ft(e,t,r,n){return 0===e?t&r|~t&n:2===e?t&r|t&n|r&n:t^r^n}n(Sha,i),Sha.prototype.init=function(){return this._a=1732584193,this._b=4023233417,this._c=2562383102,this._d=271733878,this._e=3285377520,this},Sha.prototype._update=function(e){for(var t,r=this._w,n=0|this._a,i=0|this._b,o=0|this._c,s=0|this._d,u=0|this._e,c=0;c<16;++c)r[c]=e.readInt32BE(4*c);for(;c<80;++c)r[c]=r[c-3]^r[c-8]^r[c-14]^r[c-16];for(var f=0;f<80;++f){var l=~~(f/20),h=0|((t=n)<<5|t>>>27)+ft(l,i,o,s)+u+r[f]+a[l];u=s,s=o,o=rotl30(i),i=n,n=h}this._a=n+this._a|0,this._b=i+this._b|0,this._c=o+this._c|0,this._d=s+this._d|0,this._e=u+this._e|0},Sha.prototype._hash=function(){var e=o.allocUnsafe(20);return e.writeInt32BE(0|this._a,0),e.writeInt32BE(0|this._b,4),e.writeInt32BE(0|this._c,8),e.writeInt32BE(0|this._d,12),e.writeInt32BE(0|this._e,16),e},e.exports=Sha},3737:(e,t,r)=>{var n=r(6698),i=r(8011),o=r(2861).Buffer,a=[1518500249,1859775393,-1894007588,-899497514],s=new Array(80);function Sha1(){this.init(),this._w=s,i.call(this,64,56)}function rotl5(e){return e<<5|e>>>27}function rotl30(e){return e<<30|e>>>2}function ft(e,t,r,n){return 0===e?t&r|~t&n:2===e?t&r|t&n|r&n:t^r^n}n(Sha1,i),Sha1.prototype.init=function(){return this._a=1732584193,this._b=4023233417,this._c=2562383102,this._d=271733878,this._e=3285377520,this},Sha1.prototype._update=function(e){for(var t,r=this._w,n=0|this._a,i=0|this._b,o=0|this._c,s=0|this._d,u=0|this._e,c=0;c<16;++c)r[c]=e.readInt32BE(4*c);for(;c<80;++c)r[c]=(t=r[c-3]^r[c-8]^r[c-14]^r[c-16])<<1|t>>>31;for(var f=0;f<80;++f){var l=~~(f/20),h=rotl5(n)+ft(l,i,o,s)+u+r[f]+a[l]|0;u=s,s=o,o=rotl30(i),i=n,n=h}this._a=n+this._a|0,this._b=i+this._b|0,this._c=o+this._c|0,this._d=s+this._d|0,this._e=u+this._e|0},Sha1.prototype._hash=function(){var e=o.allocUnsafe(20);return e.writeInt32BE(0|this._a,0),e.writeInt32BE(0|this._b,4),e.writeInt32BE(0|this._c,8),e.writeInt32BE(0|this._d,12),e.writeInt32BE(0|this._e,16),e},e.exports=Sha1},6710:(e,t,r)=>{var n=r(6698),i=r(4107),o=r(8011),a=r(2861).Buffer,s=new Array(64);function Sha224(){this.init(),this._w=s,o.call(this,64,56)}n(Sha224,i),Sha224.prototype.init=function(){return this._a=3238371032,this._b=914150663,this._c=812702999,this._d=4144912697,this._e=4290775857,this._f=1750603025,this._g=1694076839,this._h=3204075428,this},Sha224.prototype._hash=function(){var e=a.allocUnsafe(28);return e.writeInt32BE(this._a,0),e.writeInt32BE(this._b,4),e.writeInt32BE(this._c,8),e.writeInt32BE(this._d,12),e.writeInt32BE(this._e,16),e.writeInt32BE(this._f,20),e.writeInt32BE(this._g,24),e},e.exports=Sha224},4107:(e,t,r)=>{var n=r(6698),i=r(8011),o=r(2861).Buffer,a=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298],s=new Array(64);function Sha256(){this.init(),this._w=s,i.call(this,64,56)}function ch(e,t,r){return r^e&(t^r)}function maj(e,t,r){return e&t|r&(e|t)}function sigma0(e){return(e>>>2|e<<30)^(e>>>13|e<<19)^(e>>>22|e<<10)}function sigma1(e){return(e>>>6|e<<26)^(e>>>11|e<<21)^(e>>>25|e<<7)}function gamma0(e){return(e>>>7|e<<25)^(e>>>18|e<<14)^e>>>3}n(Sha256,i),Sha256.prototype.init=function(){return this._a=1779033703,this._b=3144134277,this._c=1013904242,this._d=2773480762,this._e=1359893119,this._f=2600822924,this._g=528734635,this._h=1541459225,this},Sha256.prototype._update=function(e){for(var t,r=this._w,n=0|this._a,i=0|this._b,o=0|this._c,s=0|this._d,u=0|this._e,c=0|this._f,f=0|this._g,l=0|this._h,h=0;h<16;++h)r[h]=e.readInt32BE(4*h);for(;h<64;++h)r[h]=0|(((t=r[h-2])>>>17|t<<15)^(t>>>19|t<<13)^t>>>10)+r[h-7]+gamma0(r[h-15])+r[h-16];for(var p=0;p<64;++p){var d=l+sigma1(u)+ch(u,c,f)+a[p]+r[p]|0,_=sigma0(n)+maj(n,i,o)|0;l=f,f=c,c=u,u=s+d|0,s=o,o=i,i=n,n=d+_|0}this._a=n+this._a|0,this._b=i+this._b|0,this._c=o+this._c|0,this._d=s+this._d|0,this._e=u+this._e|0,this._f=c+this._f|0,this._g=f+this._g|0,this._h=l+this._h|0},Sha256.prototype._hash=function(){var e=o.allocUnsafe(32);return e.writeInt32BE(this._a,0),e.writeInt32BE(this._b,4),e.writeInt32BE(this._c,8),e.writeInt32BE(this._d,12),e.writeInt32BE(this._e,16),e.writeInt32BE(this._f,20),e.writeInt32BE(this._g,24),e.writeInt32BE(this._h,28),e},e.exports=Sha256},2827:(e,t,r)=>{var n=r(6698),i=r(2890),o=r(8011),a=r(2861).Buffer,s=new Array(160);function Sha384(){this.init(),this._w=s,o.call(this,128,112)}n(Sha384,i),Sha384.prototype.init=function(){return this._ah=3418070365,this._bh=1654270250,this._ch=2438529370,this._dh=355462360,this._eh=1731405415,this._fh=2394180231,this._gh=3675008525,this._hh=1203062813,this._al=3238371032,this._bl=914150663,this._cl=812702999,this._dl=4144912697,this._el=4290775857,this._fl=1750603025,this._gl=1694076839,this._hl=3204075428,this},Sha384.prototype._hash=function(){var e=a.allocUnsafe(48);function writeInt64BE(t,r,n){e.writeInt32BE(t,n),e.writeInt32BE(r,n+4)}return writeInt64BE(this._ah,this._al,0),writeInt64BE(this._bh,this._bl,8),writeInt64BE(this._ch,this._cl,16),writeInt64BE(this._dh,this._dl,24),writeInt64BE(this._eh,this._el,32),writeInt64BE(this._fh,this._fl,40),e},e.exports=Sha384},2890:(e,t,r)=>{var n=r(6698),i=r(8011),o=r(2861).Buffer,a=[1116352408,3609767458,1899447441,602891725,3049323471,3964484399,3921009573,2173295548,961987163,4081628472,1508970993,3053834265,2453635748,2937671579,2870763221,3664609560,3624381080,2734883394,310598401,1164996542,607225278,1323610764,1426881987,3590304994,1925078388,4068182383,2162078206,991336113,2614888103,633803317,3248222580,3479774868,3835390401,2666613458,4022224774,944711139,264347078,2341262773,604807628,2007800933,770255983,1495990901,1249150122,1856431235,1555081692,3175218132,1996064986,2198950837,2554220882,3999719339,2821834349,766784016,2952996808,2566594879,3210313671,3203337956,3336571891,1034457026,3584528711,2466948901,113926993,3758326383,338241895,168717936,666307205,1188179964,773529912,1546045734,1294757372,1522805485,1396182291,2643833823,1695183700,2343527390,1986661051,1014477480,2177026350,1206759142,2456956037,344077627,2730485921,1290863460,2820302411,3158454273,3259730800,3505952657,3345764771,106217008,3516065817,3606008344,3600352804,1432725776,4094571909,1467031594,275423344,851169720,430227734,3100823752,506948616,1363258195,659060556,3750685593,883997877,3785050280,958139571,3318307427,1322822218,3812723403,1537002063,2003034995,1747873779,3602036899,1955562222,1575990012,2024104815,1125592928,2227730452,2716904306,2361852424,442776044,2428436474,593698344,2756734187,3733110249,3204031479,2999351573,3329325298,3815920427,3391569614,3928383900,3515267271,566280711,3940187606,3454069534,4118630271,4000239992,116418474,1914138554,174292421,2731055270,289380356,3203993006,460393269,320620315,685471733,587496836,852142971,1086792851,1017036298,365543100,1126000580,2618297676,1288033470,3409855158,1501505948,4234509866,1607167915,987167468,1816402316,1246189591],s=new Array(160);function Sha512(){this.init(),this._w=s,i.call(this,128,112)}function Ch(e,t,r){return r^e&(t^r)}function maj(e,t,r){return e&t|r&(e|t)}function sigma0(e,t){return(e>>>28|t<<4)^(t>>>2|e<<30)^(t>>>7|e<<25)}function sigma1(e,t){return(e>>>14|t<<18)^(e>>>18|t<<14)^(t>>>9|e<<23)}function Gamma0(e,t){return(e>>>1|t<<31)^(e>>>8|t<<24)^e>>>7}function Gamma0l(e,t){return(e>>>1|t<<31)^(e>>>8|t<<24)^(e>>>7|t<<25)}function Gamma1(e,t){return(e>>>19|t<<13)^(t>>>29|e<<3)^e>>>6}function Gamma1l(e,t){return(e>>>19|t<<13)^(t>>>29|e<<3)^(e>>>6|t<<26)}function getCarry(e,t){return e>>>0>>0?1:0}n(Sha512,i),Sha512.prototype.init=function(){return this._ah=1779033703,this._bh=3144134277,this._ch=1013904242,this._dh=2773480762,this._eh=1359893119,this._fh=2600822924,this._gh=528734635,this._hh=1541459225,this._al=4089235720,this._bl=2227873595,this._cl=4271175723,this._dl=1595750129,this._el=2917565137,this._fl=725511199,this._gl=4215389547,this._hl=327033209,this},Sha512.prototype._update=function(e){for(var t=this._w,r=0|this._ah,n=0|this._bh,i=0|this._ch,o=0|this._dh,s=0|this._eh,u=0|this._fh,c=0|this._gh,f=0|this._hh,l=0|this._al,h=0|this._bl,p=0|this._cl,d=0|this._dl,_=0|this._el,y=0|this._fl,m=0|this._gl,g=0|this._hl,v=0;v<32;v+=2)t[v]=e.readInt32BE(4*v),t[v+1]=e.readInt32BE(4*v+4);for(;v<160;v+=2){var b=t[v-30],w=t[v-30+1],I=Gamma0(b,w),x=Gamma0l(w,b),B=Gamma1(b=t[v-4],w=t[v-4+1]),k=Gamma1l(w,b),C=t[v-14],q=t[v-14+1],L=t[v-32],j=t[v-32+1],z=x+q|0,P=I+C+getCarry(z,x)|0;P=(P=P+B+getCarry(z=z+k|0,k)|0)+L+getCarry(z=z+j|0,j)|0,t[v]=P,t[v+1]=z}for(var D=0;D<160;D+=2){P=t[D],z=t[D+1];var U=maj(r,n,i),W=maj(l,h,p),K=sigma0(r,l),V=sigma0(l,r),$=sigma1(s,_),H=sigma1(_,s),Y=a[D],Z=a[D+1],J=Ch(s,u,c),ee=Ch(_,y,m),te=g+H|0,re=f+$+getCarry(te,g)|0;re=(re=(re=re+J+getCarry(te=te+ee|0,ee)|0)+Y+getCarry(te=te+Z|0,Z)|0)+P+getCarry(te=te+z|0,z)|0;var ne=V+W|0,ie=K+U+getCarry(ne,V)|0;f=c,g=m,c=u,m=y,u=s,y=_,s=o+re+getCarry(_=d+te|0,d)|0,o=i,d=p,i=n,p=h,n=r,h=l,r=re+ie+getCarry(l=te+ne|0,te)|0}this._al=this._al+l|0,this._bl=this._bl+h|0,this._cl=this._cl+p|0,this._dl=this._dl+d|0,this._el=this._el+_|0,this._fl=this._fl+y|0,this._gl=this._gl+m|0,this._hl=this._hl+g|0,this._ah=this._ah+r+getCarry(this._al,l)|0,this._bh=this._bh+n+getCarry(this._bl,h)|0,this._ch=this._ch+i+getCarry(this._cl,p)|0,this._dh=this._dh+o+getCarry(this._dl,d)|0,this._eh=this._eh+s+getCarry(this._el,_)|0,this._fh=this._fh+u+getCarry(this._fl,y)|0,this._gh=this._gh+c+getCarry(this._gl,m)|0,this._hh=this._hh+f+getCarry(this._hl,g)|0},Sha512.prototype._hash=function(){var e=o.allocUnsafe(64);function writeInt64BE(t,r,n){e.writeInt32BE(t,n),e.writeInt32BE(r,n+4)}return writeInt64BE(this._ah,this._al,0),writeInt64BE(this._bh,this._bl,8),writeInt64BE(this._ch,this._cl,16),writeInt64BE(this._dh,this._dl,24),writeInt64BE(this._eh,this._el,32),writeInt64BE(this._fh,this._fl,40),writeInt64BE(this._gh,this._gl,48),writeInt64BE(this._hh,this._hl,56),e},e.exports=Sha512},7666:(e,t,r)=>{var n=r(4851),i=r(953);function _extends(){var t;return e.exports=_extends=n?i(t=n).call(t):function(e){for(var t=1;t{"use strict";var n=r(9709);e.exports=n},462:(e,t,r)=>{"use strict";var n=r(975);e.exports=n},2567:(e,t,r)=>{"use strict";r(9307);var n=r(1747);e.exports=n("Function","bind")},3034:(e,t,r)=>{"use strict";var n=r(8280),i=r(2567),o=Function.prototype;e.exports=function(e){var t=e.bind;return e===o||n(o,e)&&t===o.bind?i:t}},9748:(e,t,r)=>{"use strict";r(1340);var n=r(2046);e.exports=n.Object.assign},953:(e,t,r)=>{"use strict";e.exports=r(3375)},4851:(e,t,r)=>{"use strict";e.exports=r(5401)},3375:(e,t,r)=>{"use strict";var n=r(3700);e.exports=n},5401:(e,t,r)=>{"use strict";var n=r(462);e.exports=n},2159:(e,t,r)=>{"use strict";var n=r(2250),i=r(4640),o=TypeError;e.exports=function(e){if(n(e))return e;throw new o(i(e)+" is not a function")}},6624:(e,t,r)=>{"use strict";var n=r(6285),i=String,o=TypeError;e.exports=function(e){if(n(e))return e;throw new o(i(e)+" is not an object")}},4436:(e,t,r)=>{"use strict";var n=r(7374),i=r(4849),o=r(575),createMethod=function(e){return function(t,r,a){var s=n(t),u=o(s);if(0===u)return!e&&-1;var c,f=i(a,u);if(e&&r!=r){for(;u>f;)if((c=s[f++])!=c)return!0}else for(;u>f;f++)if((e||f in s)&&s[f]===r)return e||f||0;return!e&&-1}};e.exports={includes:createMethod(!0),indexOf:createMethod(!1)}},3427:(e,t,r)=>{"use strict";var n=r(1907);e.exports=n([].slice)},5807:(e,t,r)=>{"use strict";var n=r(1907),i=n({}.toString),o=n("".slice);e.exports=function(e){return o(i(e),8,-1)}},1626:(e,t,r)=>{"use strict";var n=r(9447),i=r(4284),o=r(5817);e.exports=n?function(e,t,r){return i.f(e,t,o(1,r))}:function(e,t,r){return e[t]=r,e}},5817:e=>{"use strict";e.exports=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}}},2532:(e,t,r)=>{"use strict";var n=r(5951),i=Object.defineProperty;e.exports=function(e,t){try{i(n,e,{value:t,configurable:!0,writable:!0})}catch(r){n[e]=t}return t}},9447:(e,t,r)=>{"use strict";var n=r(8828);e.exports=!n((function(){return 7!==Object.defineProperty({},1,{get:function(){return 7}})[1]}))},9552:(e,t,r)=>{"use strict";var n=r(5951),i=r(6285),o=n.document,a=i(o)&&i(o.createElement);e.exports=function(e){return a?o.createElement(e):{}}},376:e=>{"use strict";e.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},6794:(e,t,r)=>{"use strict";var n=r(5951).navigator,i=n&&n.userAgent;e.exports=i?String(i):""},798:(e,t,r)=>{"use strict";var n,i,o=r(5951),a=r(6794),s=o.process,u=o.Deno,c=s&&s.versions||u&&u.version,f=c&&c.v8;f&&(i=(n=f.split("."))[0]>0&&n[0]<4?1:+(n[0]+n[1])),!i&&a&&(!(n=a.match(/Edge\/(\d+)/))||n[1]>=74)&&(n=a.match(/Chrome\/(\d+)/))&&(i=+n[1]),e.exports=i},1091:(e,t,r)=>{"use strict";var n=r(5951),i=r(6024),o=r(2361),a=r(2250),s=r(3846).f,u=r(7463),c=r(2046),f=r(8311),l=r(1626),h=r(9724);r(6128);var wrapConstructor=function(e){var Wrapper=function(t,r,n){if(this instanceof Wrapper){switch(arguments.length){case 0:return new e;case 1:return new e(t);case 2:return new e(t,r)}return new e(t,r,n)}return i(e,this,arguments)};return Wrapper.prototype=e.prototype,Wrapper};e.exports=function(e,t){var r,i,p,d,_,y,m,g,v,b=e.target,w=e.global,I=e.stat,x=e.proto,B=w?n:I?n[b]:n[b]&&n[b].prototype,k=w?c:c[b]||l(c,b,{})[b],C=k.prototype;for(d in t)i=!(r=u(w?d:b+(I?".":"#")+d,e.forced))&&B&&h(B,d),y=k[d],i&&(m=e.dontCallGetSet?(v=s(B,d))&&v.value:B[d]),_=i&&m?m:t[d],(r||x||typeof y!=typeof _)&&(g=e.bind&&i?f(_,n):e.wrap&&i?wrapConstructor(_):x&&a(_)?o(_):_,(e.sham||_&&_.sham||y&&y.sham)&&l(g,"sham",!0),l(k,d,g),x&&(h(c,p=b+"Prototype")||l(c,p,{}),l(c[p],d,_),e.real&&C&&(r||!C[d])&&l(C,d,_)))}},8828:e=>{"use strict";e.exports=function(e){try{return!!e()}catch(e){return!0}}},6024:(e,t,r)=>{"use strict";var n=r(1505),i=Function.prototype,o=i.apply,a=i.call;e.exports="object"==typeof Reflect&&Reflect.apply||(n?a.bind(o):function(){return a.apply(o,arguments)})},8311:(e,t,r)=>{"use strict";var n=r(2361),i=r(2159),o=r(1505),a=n(n.bind);e.exports=function(e,t){return i(e),void 0===t?e:o?a(e,t):function(){return e.apply(t,arguments)}}},1505:(e,t,r)=>{"use strict";var n=r(8828);e.exports=!n((function(){var e=function(){}.bind();return"function"!=typeof e||e.hasOwnProperty("prototype")}))},4673:(e,t,r)=>{"use strict";var n=r(1907),i=r(2159),o=r(6285),a=r(9724),s=r(3427),u=r(1505),c=Function,f=n([].concat),l=n([].join),h={};e.exports=u?c.bind:function bind(e){var t=i(this),r=t.prototype,n=s(arguments,1),u=function bound(){var r=f(n,s(arguments));return this instanceof u?function(e,t,r){if(!a(h,t)){for(var n=[],i=0;i{"use strict";var n=r(1505),i=Function.prototype.call;e.exports=n?i.bind(i):function(){return i.apply(i,arguments)}},2361:(e,t,r)=>{"use strict";var n=r(5807),i=r(1907);e.exports=function(e){if("Function"===n(e))return i(e)}},1907:(e,t,r)=>{"use strict";var n=r(1505),i=Function.prototype,o=i.call,a=n&&i.bind.bind(o,o);e.exports=n?a:function(e){return function(){return o.apply(e,arguments)}}},1747:(e,t,r)=>{"use strict";var n=r(5951),i=r(2046);e.exports=function(e,t){var r=i[e+"Prototype"],o=r&&r[t];if(o)return o;var a=n[e],s=a&&a.prototype;return s&&s[t]}},5582:(e,t,r)=>{"use strict";var n=r(2046),i=r(5951),o=r(2250),aFunction=function(e){return o(e)?e:void 0};e.exports=function(e,t){return arguments.length<2?aFunction(n[e])||aFunction(i[e]):n[e]&&n[e][t]||i[e]&&i[e][t]}},9367:(e,t,r)=>{"use strict";var n=r(2159),i=r(7136);e.exports=function(e,t){var r=e[t];return i(r)?void 0:n(r)}},5951:function(e,t,r){"use strict";var check=function(e){return e&&e.Math===Math&&e};e.exports=check("object"==typeof globalThis&&globalThis)||check("object"==typeof window&&window)||check("object"==typeof self&&self)||check("object"==typeof r.g&&r.g)||check("object"==typeof this&&this)||function(){return this}()||Function("return this")()},9724:(e,t,r)=>{"use strict";var n=r(1907),i=r(9298),o=n({}.hasOwnProperty);e.exports=Object.hasOwn||function hasOwn(e,t){return o(i(e),t)}},8530:e=>{"use strict";e.exports={}},3648:(e,t,r)=>{"use strict";var n=r(9447),i=r(8828),o=r(9552);e.exports=!n&&!i((function(){return 7!==Object.defineProperty(o("div"),"a",{get:function(){return 7}}).a}))},6946:(e,t,r)=>{"use strict";var n=r(1907),i=r(8828),o=r(5807),a=Object,s=n("".split);e.exports=i((function(){return!a("z").propertyIsEnumerable(0)}))?function(e){return"String"===o(e)?s(e,""):a(e)}:a},2250:e=>{"use strict";var t="object"==typeof document&&document.all;e.exports=void 0===t&&void 0!==t?function(e){return"function"==typeof e||e===t}:function(e){return"function"==typeof e}},7463:(e,t,r)=>{"use strict";var n=r(8828),i=r(2250),o=/#|\.prototype\./,isForced=function(e,t){var r=s[a(e)];return r===c||r!==u&&(i(t)?n(t):!!t)},a=isForced.normalize=function(e){return String(e).replace(o,".").toLowerCase()},s=isForced.data={},u=isForced.NATIVE="N",c=isForced.POLYFILL="P";e.exports=isForced},7136:e=>{"use strict";e.exports=function(e){return null==e}},6285:(e,t,r)=>{"use strict";var n=r(2250);e.exports=function(e){return"object"==typeof e?null!==e:n(e)}},7376:e=>{"use strict";e.exports=!0},5594:(e,t,r)=>{"use strict";var n=r(5582),i=r(2250),o=r(8280),a=r(3556),s=Object;e.exports=a?function(e){return"symbol"==typeof e}:function(e){var t=n("Symbol");return i(t)&&o(t.prototype,s(e))}},575:(e,t,r)=>{"use strict";var n=r(3121);e.exports=function(e){return n(e.length)}},1176:e=>{"use strict";var t=Math.ceil,r=Math.floor;e.exports=Math.trunc||function trunc(e){var n=+e;return(n>0?r:t)(n)}},9538:(e,t,r)=>{"use strict";var n=r(9447),i=r(1907),o=r(3930),a=r(8828),s=r(2875),u=r(7170),c=r(2574),f=r(9298),l=r(6946),h=Object.assign,p=Object.defineProperty,d=i([].concat);e.exports=!h||a((function(){if(n&&1!==h({b:1},h(p({},"a",{enumerable:!0,get:function(){p(this,"b",{value:3,enumerable:!1})}}),{b:2})).b)return!0;var e={},t={},r=Symbol("assign detection"),i="abcdefghijklmnopqrst";return e[r]=7,i.split("").forEach((function(e){t[e]=e})),7!==h({},e)[r]||s(h({},t)).join("")!==i}))?function assign(e,t){for(var r=f(e),i=arguments.length,a=1,h=u.f,p=c.f;i>a;)for(var _,y=l(arguments[a++]),m=h?d(s(y),h(y)):s(y),g=m.length,v=0;g>v;)_=m[v++],n&&!o(p,y,_)||(r[_]=y[_]);return r}:h},4284:(e,t,r)=>{"use strict";var n=r(9447),i=r(3648),o=r(8661),a=r(6624),s=r(470),u=TypeError,c=Object.defineProperty,f=Object.getOwnPropertyDescriptor,l="enumerable",h="configurable",p="writable";t.f=n?o?function defineProperty(e,t,r){if(a(e),t=s(t),a(r),"function"==typeof e&&"prototype"===t&&"value"in r&&p in r&&!r[p]){var n=f(e,t);n&&n[p]&&(e[t]=r.value,r={configurable:h in r?r[h]:n[h],enumerable:l in r?r[l]:n[l],writable:!1})}return c(e,t,r)}:c:function defineProperty(e,t,r){if(a(e),t=s(t),a(r),i)try{return c(e,t,r)}catch(e){}if("get"in r||"set"in r)throw new u("Accessors not supported");return"value"in r&&(e[t]=r.value),e}},3846:(e,t,r)=>{"use strict";var n=r(9447),i=r(3930),o=r(2574),a=r(5817),s=r(7374),u=r(470),c=r(9724),f=r(3648),l=Object.getOwnPropertyDescriptor;t.f=n?l:function getOwnPropertyDescriptor(e,t){if(e=s(e),t=u(t),f)try{return l(e,t)}catch(e){}if(c(e,t))return a(!i(o.f,e,t),e[t])}},7170:(e,t)=>{"use strict";t.f=Object.getOwnPropertySymbols},8280:(e,t,r)=>{"use strict";var n=r(1907);e.exports=n({}.isPrototypeOf)},3045:(e,t,r)=>{"use strict";var n=r(1907),i=r(9724),o=r(7374),a=r(4436).indexOf,s=r(8530),u=n([].push);e.exports=function(e,t){var r,n=o(e),c=0,f=[];for(r in n)!i(s,r)&&i(n,r)&&u(f,r);for(;t.length>c;)i(n,r=t[c++])&&(~a(f,r)||u(f,r));return f}},2875:(e,t,r)=>{"use strict";var n=r(3045),i=r(376);e.exports=Object.keys||function keys(e){return n(e,i)}},2574:(e,t)=>{"use strict";var r={}.propertyIsEnumerable,n=Object.getOwnPropertyDescriptor,i=n&&!r.call({1:2},1);t.f=i?function propertyIsEnumerable(e){var t=n(this,e);return!!t&&t.enumerable}:r},581:(e,t,r)=>{"use strict";var n=r(3930),i=r(2250),o=r(6285),a=TypeError;e.exports=function(e,t){var r,s;if("string"===t&&i(r=e.toString)&&!o(s=n(r,e)))return s;if(i(r=e.valueOf)&&!o(s=n(r,e)))return s;if("string"!==t&&i(r=e.toString)&&!o(s=n(r,e)))return s;throw new a("Can't convert object to primitive value")}},2046:e=>{"use strict";e.exports={}},4239:(e,t,r)=>{"use strict";var n=r(7136),i=TypeError;e.exports=function(e){if(n(e))throw new i("Can't call method on "+e);return e}},6128:(e,t,r)=>{"use strict";var n=r(7376),i=r(5951),o=r(2532),a="__core-js_shared__",s=e.exports=i[a]||o(a,{});(s.versions||(s.versions=[])).push({version:"3.39.0",mode:n?"pure":"global",copyright:"© 2014-2024 Denis Pushkarev (zloirock.ru)",license:"https://github.com/zloirock/core-js/blob/v3.39.0/LICENSE",source:"https://github.com/zloirock/core-js"})},5816:(e,t,r)=>{"use strict";var n=r(6128);e.exports=function(e,t){return n[e]||(n[e]=t||{})}},9846:(e,t,r)=>{"use strict";var n=r(798),i=r(8828),o=r(5951).String;e.exports=!!Object.getOwnPropertySymbols&&!i((function(){var e=Symbol("symbol detection");return!o(e)||!(Object(e)instanceof Symbol)||!Symbol.sham&&n&&n<41}))},4849:(e,t,r)=>{"use strict";var n=r(5482),i=Math.max,o=Math.min;e.exports=function(e,t){var r=n(e);return r<0?i(r+t,0):o(r,t)}},7374:(e,t,r)=>{"use strict";var n=r(6946),i=r(4239);e.exports=function(e){return n(i(e))}},5482:(e,t,r)=>{"use strict";var n=r(1176);e.exports=function(e){var t=+e;return t!=t||0===t?0:n(t)}},3121:(e,t,r)=>{"use strict";var n=r(5482),i=Math.min;e.exports=function(e){var t=n(e);return t>0?i(t,9007199254740991):0}},9298:(e,t,r)=>{"use strict";var n=r(4239),i=Object;e.exports=function(e){return i(n(e))}},6028:(e,t,r)=>{"use strict";var n=r(3930),i=r(6285),o=r(5594),a=r(9367),s=r(581),u=r(6264),c=TypeError,f=u("toPrimitive");e.exports=function(e,t){if(!i(e)||o(e))return e;var r,u=a(e,f);if(u){if(void 0===t&&(t="default"),r=n(u,e,t),!i(r)||o(r))return r;throw new c("Can't convert object to primitive value")}return void 0===t&&(t="number"),s(e,t)}},470:(e,t,r)=>{"use strict";var n=r(6028),i=r(5594);e.exports=function(e){var t=n(e,"string");return i(t)?t:t+""}},4640:e=>{"use strict";var t=String;e.exports=function(e){try{return t(e)}catch(e){return"Object"}}},6499:(e,t,r)=>{"use strict";var n=r(1907),i=0,o=Math.random(),a=n(1..toString);e.exports=function(e){return"Symbol("+(void 0===e?"":e)+")_"+a(++i+o,36)}},3556:(e,t,r)=>{"use strict";var n=r(9846);e.exports=n&&!Symbol.sham&&"symbol"==typeof Symbol.iterator},8661:(e,t,r)=>{"use strict";var n=r(9447),i=r(8828);e.exports=n&&i((function(){return 42!==Object.defineProperty((function(){}),"prototype",{value:42,writable:!1}).prototype}))},6264:(e,t,r)=>{"use strict";var n=r(5951),i=r(5816),o=r(9724),a=r(6499),s=r(9846),u=r(3556),c=n.Symbol,f=i("wks"),l=u?c.for||c:c&&c.withoutSetter||a;e.exports=function(e){return o(f,e)||(f[e]=s&&o(c,e)?c[e]:l("Symbol."+e)),f[e]}},9307:(e,t,r)=>{"use strict";var n=r(1091),i=r(4673);n({target:"Function",proto:!0,forced:Function.bind!==i},{bind:i})},1340:(e,t,r)=>{"use strict";var n=r(1091),i=r(9538);n({target:"Object",stat:!0,arity:2,forced:Object.assign!==i},{assign:i})},9709:(e,t,r)=>{"use strict";var n=r(3034);e.exports=n},975:(e,t,r)=>{"use strict";var n=r(9748);e.exports=n}},t={};function __webpack_require__(r){var n=t[r];if(void 0!==n)return n.exports;var i=t[r]={id:r,loaded:!1,exports:{}};return e[r].call(i.exports,i,i.exports,__webpack_require__),i.loaded=!0,i.exports}__webpack_require__.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return __webpack_require__.d(t,{a:t}),t},__webpack_require__.d=(e,t)=>{for(var r in t)__webpack_require__.o(t,r)&&!__webpack_require__.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},__webpack_require__.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),__webpack_require__.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),__webpack_require__.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},__webpack_require__.nmd=e=>(e.paths=[],e.children||(e.children=[]),e);var r={};return(()=>{"use strict";__webpack_require__.d(r,{default:()=>ot});var e={};__webpack_require__.r(e),__webpack_require__.d(e,{TOGGLE_CONFIGS:()=>Je,UPDATE_CONFIGS:()=>Ge,downloadConfig:()=>downloadConfig,getConfigByUrl:()=>getConfigByUrl,loaded:()=>loaded,toggle:()=>toggle,update:()=>update});var t={};__webpack_require__.r(t),__webpack_require__.d(t,{get:()=>get});var n=__webpack_require__(6540);class StandaloneLayout extends n.Component{render(){const{getComponent:e}=this.props,t=e("Container"),r=e("Row"),i=e("Col"),o=e("Topbar",!0),a=e("BaseLayout",!0),s=e("onlineValidatorBadge",!0);return n.createElement(t,{className:"swagger-ui"},o?n.createElement(o,null):null,n.createElement(a,null),n.createElement(r,null,n.createElement(i,null,n.createElement(s,null))))}}const i=StandaloneLayout,stadalone_layout=()=>({components:{StandaloneLayout:i}});var o=__webpack_require__(9404),a=__webpack_require__.n(o);__webpack_require__(6750),__webpack_require__(4058),__webpack_require__(5808),__webpack_require__(104),__webpack_require__(7309),__webpack_require__(2426),__webpack_require__(5288),__webpack_require__(1882),__webpack_require__(2205),__webpack_require__(3209),__webpack_require__(2802);const s=function makeWindow(){var e={location:{},history:{},open:()=>{},close:()=>{},File:function(){},FormData:function(){}};if("undefined"==typeof window)return e;try{e=window;for(var t of["File","Blob","FormData"])t in window&&(e[t]=window[t])}catch(e){console.error(e)}return e}();a().Set.of("type","format","items","default","maximum","exclusiveMaximum","minimum","exclusiveMinimum","maxLength","minLength","pattern","maxItems","minItems","uniqueItems","enum","multipleOf");__webpack_require__(8287).Buffer;const parseSearch=()=>{const e=new URLSearchParams(s.location.search);return Object.fromEntries(e)};class TopBar extends n.Component{constructor(e,t){super(e,t),this.state={url:e.specSelectors.url(),selectedIndex:0}}UNSAFE_componentWillReceiveProps(e){this.setState({url:e.specSelectors.url()})}onUrlChange=e=>{let{target:{value:t}}=e;this.setState({url:t})};flushAuthData(){const{persistAuthorization:e}=this.props.getConfigs();e||this.props.authActions.restoreAuthorization({authorized:{}})}loadSpec=e=>{this.flushAuthData(),this.props.specActions.updateUrl(e),this.props.specActions.download(e)};onUrlSelect=e=>{let t=e.target.value||e.target.href;this.loadSpec(t),this.setSelectedUrl(t),e.preventDefault()};downloadUrl=e=>{this.loadSpec(this.state.url),e.preventDefault()};setSearch=e=>{let t=parseSearch();t["urls.primaryName"]=e.name;const r=`${window.location.protocol}//${window.location.host}${window.location.pathname}`;window&&window.history&&window.history.pushState&&window.history.replaceState(null,"",`${r}?${(e=>{const t=new URLSearchParams(Object.entries(e));return String(t)})(t)}`)};setSelectedUrl=e=>{const t=this.props.getConfigs().urls||[];t&&t.length&&e&&t.forEach(((t,r)=>{t.url===e&&(this.setState({selectedIndex:r}),this.setSearch(t))}))};componentDidMount(){const e=this.props.getConfigs(),t=e.urls||[];if(t&&t.length){var r=this.state.selectedIndex;let n=parseSearch()["urls.primaryName"]||e.urls.primaryName;n&&t.forEach(((e,t)=>{e.name===n&&(this.setState({selectedIndex:t}),r=t)})),this.loadSpec(t[r].url)}}onFilterChange=e=>{let{target:{value:t}}=e;this.props.layoutActions.updateFilter(t)};render(){let{getComponent:e,specSelectors:t,getConfigs:r}=this.props;const i=e("Button"),o=e("Link"),a=e("Logo");let s="loading"===t.loadingStatus();const u=["download-url-input"];"failed"===t.loadingStatus()&&u.push("failed"),s&&u.push("loading");const{urls:c}=r();let f=[],l=null;if(c){let e=[];c.forEach(((t,r)=>{e.push(n.createElement("option",{key:r,value:t.url},t.name))})),f.push(n.createElement("label",{className:"select-label",htmlFor:"select"},n.createElement("span",null,"Select a definition"),n.createElement("select",{id:"select",disabled:s,onChange:this.onUrlSelect,value:c[this.state.selectedIndex].url},e)))}else l=this.downloadUrl,f.push(n.createElement("input",{className:u.join(" "),type:"text",onChange:this.onUrlChange,value:this.state.url,disabled:s,id:"download-url-input"})),f.push(n.createElement(i,{className:"download-url-button",onClick:this.downloadUrl},"Explore"));return n.createElement("div",{className:"topbar"},n.createElement("div",{className:"wrapper"},n.createElement("div",{className:"topbar-wrapper"},n.createElement(o,null,n.createElement(a,null)),n.createElement("form",{className:"download-url-wrapper",onSubmit:l},f.map(((e,t)=>(0,n.cloneElement)(e,{key:t})))))))}}const u=TopBar;var c,f,l,h,p,d,_,y,m,g,v,b,w,I,x,B,k,C,q,L,j,z,P,D,U,W,K,V,$,H,Y,Z;function _extends(){return _extends=Object.assign?Object.assign.bind():function(e){for(var t=1;tn.createElement("svg",_extends({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 407 116"},e),c||(c=n.createElement("defs",null,n.createElement("clipPath",{id:"logo_small_svg__clip-SW_TM-logo-on-dark"},n.createElement("path",{d:"M0 0h407v116H0z"})),n.createElement("style",null,".logo_small_svg__cls-2{fill:#fff}.logo_small_svg__cls-3{fill:#85ea2d}"))),n.createElement("g",{id:"logo_small_svg__SW_TM-logo-on-dark",style:{clipPath:"url(#logo_small_svg__clip-SW_TM-logo-on-dark)"}},n.createElement("g",{id:"logo_small_svg__SW_In-Product",transform:"translate(-.301)"},f||(f=n.createElement("path",{id:"logo_small_svg__Path_2936",d:"M359.15 70.674h-.7v-3.682h-1.26v-.6h3.219v.6h-1.259Z",className:"logo_small_svg__cls-2","data-name":"Path 2936"})),l||(l=n.createElement("path",{id:"logo_small_svg__Path_2937",d:"m363.217 70.674-1.242-3.574h-.023q.05.8.05 1.494v2.083h-.636v-4.286h.987l1.19 3.407h.017l1.225-3.407h.99v4.283h-.675v-2.118a30 30 0 0 1 .044-1.453h-.023l-1.286 3.571Z",className:"logo_small_svg__cls-2","data-name":"Path 2937"})),h||(h=n.createElement("path",{id:"logo_small_svg__Path_2938",d:"M50.328 97.669a47.642 47.642 0 1 1 47.643-47.642 47.64 47.64 0 0 1-47.643 47.642",className:"logo_small_svg__cls-3","data-name":"Path 2938"})),p||(p=n.createElement("path",{id:"logo_small_svg__Path_2939",d:"M50.328 4.769A45.258 45.258 0 1 1 5.07 50.027 45.26 45.26 0 0 1 50.328 4.769m0-4.769a50.027 50.027 0 1 0 50.027 50.027A50.027 50.027 0 0 0 50.328 0",className:"logo_small_svg__cls-3","data-name":"Path 2939"})),n.createElement("path",{id:"logo_small_svg__Path_2940",d:"M31.8 33.854c-.154 1.712.058 3.482-.057 5.213a43 43 0 0 1-.693 5.156 9.53 9.53 0 0 1-4.1 5.829c4.079 2.654 4.54 6.771 4.81 10.946.135 2.25.077 4.52.308 6.752.173 1.731.846 2.174 2.636 2.231.73.02 1.48 0 2.327 0v5.349c-5.29.9-9.657-.6-10.734-5.079a31 31 0 0 1-.654-5c-.117-1.789.076-3.578-.058-5.367-.386-4.906-1.02-6.56-5.713-6.791v-6.1a9 9 0 0 1 1.028-.173c2.577-.135 3.674-.924 4.231-3.463a29 29 0 0 0 .481-4.329 82 82 0 0 1 .6-8.406c.673-3.982 3.136-5.906 7.234-6.137 1.154-.057 2.327 0 3.655 0v5.464c-.558.038-1.039.115-1.539.115-3.336-.115-3.51 1.02-3.762 3.79m6.406 12.658h-.077a3.515 3.515 0 1 0-.346 7.021h.231a3.46 3.46 0 0 0 3.655-3.251v-.192a3.523 3.523 0 0 0-3.461-3.578Zm12.062 0a3.373 3.373 0 0 0-3.482 3.251 2 2 0 0 0 .02.327 3.3 3.3 0 0 0 3.578 3.443 3.263 3.263 0 0 0 3.443-3.558 3.308 3.308 0 0 0-3.557-3.463Zm12.351 0a3.59 3.59 0 0 0-3.655 3.482 3.53 3.53 0 0 0 3.536 3.539h.039c1.769.309 3.559-1.4 3.674-3.462a3.57 3.57 0 0 0-3.6-3.559Zm16.948.288c-2.232-.1-3.348-.846-3.9-2.962a21.5 21.5 0 0 1-.635-4.136c-.154-2.578-.135-5.175-.308-7.753-.4-6.117-4.828-8.252-11.254-7.195v5.31c1.019 0 1.808 0 2.6.019 1.366.019 2.4.539 2.539 2.059.135 1.385.135 2.789.27 4.193.269 2.79.422 5.618.9 8.369a8.72 8.72 0 0 0 3.921 5.348c-3.4 2.289-4.406 5.559-4.578 9.234-.1 2.52-.154 5.059-.289 7.6-.115 2.308-.923 3.058-3.251 3.116-.654.019-1.289.077-2.019.115v5.445c1.365 0 2.616.077 3.866 0 3.886-.231 6.233-2.117 7-5.887A49 49 0 0 0 75 63.4c.135-1.923.116-3.866.308-5.771.289-2.982 1.655-4.213 4.636-4.4a4 4 0 0 0 .828-.192v-6.1c-.5-.058-.843-.115-1.208-.135Z","data-name":"Path 2940",style:{fill:"#173647"}}),d||(d=n.createElement("path",{id:"logo_small_svg__Path_2941",d:"M152.273 58.122a11.23 11.23 0 0 1-4.384 9.424q-4.383 3.382-11.9 3.382-8.14 0-12.524-2.1V63.7a33 33 0 0 0 6.137 1.879 32.3 32.3 0 0 0 6.575.689q5.322 0 8.015-2.02a6.63 6.63 0 0 0 2.692-5.62 7.2 7.2 0 0 0-.954-3.9 8.9 8.9 0 0 0-3.194-2.8 44.6 44.6 0 0 0-6.81-2.911q-6.387-2.286-9.126-5.417a11.96 11.96 0 0 1-2.74-8.172A10.16 10.16 0 0 1 128.039 27q3.977-3.131 10.52-3.131a31 31 0 0 1 12.555 2.5L149.455 31a28.4 28.4 0 0 0-11.021-2.38 10.67 10.67 0 0 0-6.606 1.816 5.98 5.98 0 0 0-2.38 5.041 7.7 7.7 0 0 0 .877 3.9 8.24 8.24 0 0 0 2.959 2.786 36.7 36.7 0 0 0 6.371 2.8q7.2 2.566 9.91 5.51a10.84 10.84 0 0 1 2.708 7.649",className:"logo_small_svg__cls-2","data-name":"Path 2941"})),_||(_=n.createElement("path",{id:"logo_small_svg__Path_2942",d:"M185.288 70.3 179 50.17q-.594-1.848-2.222-8.391h-.251q-1.252 5.479-2.192 8.453L167.849 70.3h-6.011l-9.361-34.315h5.447q3.318 12.931 5.057 19.693a80 80 0 0 1 1.988 9.111h.25q.345-1.785 1.112-4.618t1.33-4.493l6.294-19.693h5.635l6.137 19.693a66 66 0 0 1 2.379 9.048h.251a33 33 0 0 1 .673-3.475q.548-2.347 6.528-25.266h5.385L191.456 70.3Z",className:"logo_small_svg__cls-2","data-name":"Path 2942"})),y||(y=n.createElement("path",{id:"logo_small_svg__Path_2943",d:"m225.115 70.3-1.033-4.885h-.25a14.45 14.45 0 0 1-5.119 4.368 15.6 15.6 0 0 1-6.372 1.143q-5.1 0-8-2.63t-2.9-7.483q0-10.4 16.626-10.9l5.823-.188V47.6q0-4.038-1.738-5.964t-5.552-1.923a22.6 22.6 0 0 0-9.706 2.63l-1.6-3.977a24.4 24.4 0 0 1 5.557-2.16 24 24 0 0 1 6.058-.783q6.136 0 9.1 2.724t2.959 8.735V70.3Zm-11.741-3.663a10.55 10.55 0 0 0 7.626-2.66 9.85 9.85 0 0 0 2.771-7.451v-3.1l-5.2.219q-6.2.219-8.939 1.926a5.8 5.8 0 0 0-2.74 5.306 5.35 5.35 0 0 0 1.707 4.29 7.08 7.08 0 0 0 4.775 1.472Z",className:"logo_small_svg__cls-2","data-name":"Path 2943"})),m||(m=n.createElement("path",{id:"logo_small_svg__Path_2944",d:"M264.6 35.987v3.287l-6.356.752a11.16 11.16 0 0 1 2.255 6.856 10.15 10.15 0 0 1-3.444 8.047q-3.444 3-9.456 3a15.7 15.7 0 0 1-2.88-.25Q241.4 59.438 241.4 62.1a2.24 2.24 0 0 0 1.159 2.082 8.46 8.46 0 0 0 3.976.673h6.074q5.573 0 8.563 2.348a8.16 8.16 0 0 1 2.99 6.825 9.74 9.74 0 0 1-4.571 8.688q-4.572 2.989-13.338 2.99-6.732 0-10.379-2.5a8.09 8.09 0 0 1-3.647-7.076 7.95 7.95 0 0 1 2-5.417 10.2 10.2 0 0 1 5.636-3.1 5.43 5.43 0 0 1-2.207-1.847 4.9 4.9 0 0 1-.893-2.912 5.53 5.53 0 0 1 1-3.288 10.5 10.5 0 0 1 3.162-2.723 9.28 9.28 0 0 1-4.336-3.726 10.95 10.95 0 0 1-1.675-6.012q0-5.634 3.382-8.688t9.58-3.052a17.4 17.4 0 0 1 4.853.626Zm-27.367 40.075a4.66 4.66 0 0 0 2.348 4.227 12.97 12.97 0 0 0 6.732 1.44q6.543 0 9.69-1.956a5.99 5.99 0 0 0 3.147-5.307q0-2.787-1.723-3.867t-6.481-1.08h-6.23a8.2 8.2 0 0 0-5.51 1.69 6.04 6.04 0 0 0-1.973 4.853m2.818-29.086a6.98 6.98 0 0 0 2.035 5.448 8.12 8.12 0 0 0 5.667 1.847q7.608 0 7.608-7.389 0-7.733-7.7-7.733a7.63 7.63 0 0 0-5.635 1.972q-1.976 1.973-1.975 5.855",className:"logo_small_svg__cls-2","data-name":"Path 2944"})),g||(g=n.createElement("path",{id:"logo_small_svg__Path_2945",d:"M299.136 35.987v3.287l-6.356.752a11.17 11.17 0 0 1 2.254 6.856 10.15 10.15 0 0 1-3.444 8.047q-3.444 3-9.455 3a15.7 15.7 0 0 1-2.88-.25q-3.32 1.754-3.319 4.415a2.24 2.24 0 0 0 1.158 2.082 8.46 8.46 0 0 0 3.976.673h6.074q5.574 0 8.563 2.348a8.16 8.16 0 0 1 2.99 6.825 9.74 9.74 0 0 1-4.571 8.688q-4.57 2.989-13.337 2.99-6.732 0-10.379-2.5a8.09 8.09 0 0 1-3.648-7.076 7.95 7.95 0 0 1 2-5.417 10.2 10.2 0 0 1 5.636-3.1 5.43 5.43 0 0 1-2.208-1.847 4.9 4.9 0 0 1-.892-2.912 5.53 5.53 0 0 1 1-3.288 10.5 10.5 0 0 1 3.162-2.723 9.27 9.27 0 0 1-4.336-3.726 10.95 10.95 0 0 1-1.675-6.012q0-5.634 3.381-8.688t9.581-3.052a17.4 17.4 0 0 1 4.853.626Zm-27.364 40.075a4.66 4.66 0 0 0 2.348 4.227 12.97 12.97 0 0 0 6.731 1.44q6.544 0 9.691-1.956a5.99 5.99 0 0 0 3.146-5.307q0-2.787-1.722-3.867t-6.481-1.08h-6.23a8.2 8.2 0 0 0-5.511 1.69 6.04 6.04 0 0 0-1.972 4.853m2.818-29.086a6.98 6.98 0 0 0 2.035 5.448 8.12 8.12 0 0 0 5.667 1.847q7.607 0 7.608-7.389 0-7.733-7.7-7.733a7.63 7.63 0 0 0-5.635 1.972q-1.975 1.973-1.975 5.855",className:"logo_small_svg__cls-2","data-name":"Path 2945"})),v||(v=n.createElement("path",{id:"logo_small_svg__Path_2946",d:"M316.778 70.928q-7.608 0-12.007-4.634t-4.4-12.868q0-8.3 4.086-13.181a13.57 13.57 0 0 1 10.974-4.884 12.94 12.94 0 0 1 10.207 4.239q3.762 4.247 3.762 11.2v3.287h-23.643q.156 6.044 3.053 9.174t8.156 3.131a27.6 27.6 0 0 0 10.958-2.317v4.634a27.5 27.5 0 0 1-5.213 1.706 29.3 29.3 0 0 1-5.933.513m-1.409-31.215a8.49 8.49 0 0 0-6.591 2.692 12.4 12.4 0 0 0-2.9 7.452h17.94q0-4.916-2.191-7.53a7.71 7.71 0 0 0-6.258-2.614",className:"logo_small_svg__cls-2","data-name":"Path 2946"})),b||(b=n.createElement("path",{id:"logo_small_svg__Path_2947",d:"M350.9 35.361a20.4 20.4 0 0 1 4.1.375l-.721 4.822a17.7 17.7 0 0 0-3.757-.47 9.14 9.14 0 0 0-7.122 3.382 12.33 12.33 0 0 0-2.959 8.422V70.3h-5.2V35.987h4.29l.6 6.356h.25a15.1 15.1 0 0 1 4.6-5.166 10.36 10.36 0 0 1 5.919-1.816",className:"logo_small_svg__cls-2","data-name":"Path 2947"})),w||(w=n.createElement("path",{id:"logo_small_svg__Path_2948",d:"M255.857 96.638s-3.43-.391-4.85-.391c-2.058 0-3.111.735-3.111 2.18 0 1.568.882 1.935 3.748 2.719 3.527.98 4.8 1.911 4.8 4.777 0 3.675-2.3 5.267-5.61 5.267a36 36 0 0 1-5.487-.662l.27-2.18s3.306.441 5.046.441c2.082 0 3.037-.931 3.037-2.7 0-1.421-.759-1.91-3.331-2.523-3.626-.93-5.193-2.033-5.193-4.948 0-3.381 2.229-4.776 5.585-4.776a37 37 0 0 1 5.315.587Z",className:"logo_small_svg__cls-2","data-name":"Path 2948"})),I||(I=n.createElement("path",{id:"logo_small_svg__Path_2949",d:"M262.967 94.14h4.733l3.748 13.106L275.2 94.14h4.752v16.78H277.2v-14.5h-.145l-4.191 13.816h-2.842l-4.191-13.816h-.145v14.5h-2.719Z",className:"logo_small_svg__cls-2","data-name":"Path 2949"})),x||(x=n.createElement("path",{id:"logo_small_svg__Path_2950",d:"M322.057 94.14H334.3v2.425h-4.728v14.355h-2.743V96.565h-4.777Z",className:"logo_small_svg__cls-2","data-name":"Path 2950"})),B||(B=n.createElement("path",{id:"logo_small_svg__Path_2951",d:"M346.137 94.14c3.332 0 5.12 1.249 5.12 4.361 0 2.033-.637 3.037-1.984 3.772 1.445.563 2.4 1.592 2.4 3.9 0 3.43-2.081 4.752-5.339 4.752h-6.566V94.14Zm-3.65 2.352v4.8h3.6c1.666 0 2.4-.832 2.4-2.474 0-1.617-.833-2.327-2.5-2.327Zm0 7.1v4.973h3.7c1.689 0 2.694-.539 2.694-2.548 0-1.911-1.421-2.425-2.744-2.425Z",className:"logo_small_svg__cls-2","data-name":"Path 2951"})),k||(k=n.createElement("path",{id:"logo_small_svg__Path_2952",d:"M358.414 94.14H369v2.377h-7.864v4.751h6.394v2.332h-6.394v4.924H369v2.4h-10.586Z",className:"logo_small_svg__cls-2","data-name":"Path 2952"})),C||(C=n.createElement("path",{id:"logo_small_svg__Path_2953",d:"M378.747 94.14h5.414l4.164 16.78h-2.744l-1.239-4.92h-5.777l-1.239 4.923h-2.719Zm.361 9.456h4.708l-1.737-7.178h-1.225Z",className:"logo_small_svg__cls-2","data-name":"Path 2953"})),q||(q=n.createElement("path",{id:"logo_small_svg__Path_2954",d:"M397.1 105.947v4.973h-2.719V94.14h6.37c3.7 0 5.683 2.12 5.683 5.843 0 2.376-.956 4.519-2.744 5.352l2.769 5.585h-2.989l-2.426-4.973Zm3.651-9.455H397.1v7.1h3.7c2.057 0 2.841-1.85 2.841-3.589 0-1.9-.934-3.511-2.894-3.511Z",className:"logo_small_svg__cls-2","data-name":"Path 2954"})),L||(L=n.createElement("path",{id:"logo_small_svg__Path_2955",d:"M290.013 94.14h5.413l4.164 16.78h-2.743l-1.239-4.92h-5.777l-1.239 4.923h-2.719Zm.361 9.456h4.707l-1.737-7.178h-1.225Z",className:"logo_small_svg__cls-2","data-name":"Path 2955"})),j||(j=n.createElement("path",{id:"logo_small_svg__Path_2956",d:"M308.362 105.947v4.973h-2.719V94.14h6.369c3.7 0 5.683 2.12 5.683 5.843 0 2.376-.955 4.519-2.743 5.352l2.768 5.585h-2.989l-2.425-4.973Zm3.65-9.455h-3.65v7.1h3.7c2.058 0 2.841-1.85 2.841-3.589-.003-1.903-.931-3.511-2.891-3.511",className:"logo_small_svg__cls-2","data-name":"Path 2956"})),z||(z=n.createElement("path",{id:"logo_small_svg__Path_2957",d:"M130.606 107.643a3.02 3.02 0 0 1-1.18 2.537 5.1 5.1 0 0 1-3.2.91 8 8 0 0 1-3.371-.564v-1.383a9 9 0 0 0 1.652.506 8.7 8.7 0 0 0 1.77.186 3.57 3.57 0 0 0 2.157-.544 1.78 1.78 0 0 0 .725-1.512 1.95 1.95 0 0 0-.257-1.05 2.4 2.4 0 0 0-.86-.754 12 12 0 0 0-1.833-.784 5.84 5.84 0 0 1-2.456-1.458 3.2 3.2 0 0 1-.738-2.2 2.74 2.74 0 0 1 1.071-2.267 4.44 4.44 0 0 1 2.831-.843 8.3 8.3 0 0 1 3.38.675l-.447 1.247a7.6 7.6 0 0 0-2.966-.641 2.88 2.88 0 0 0-1.779.489 1.61 1.61 0 0 0-.64 1.357 2.1 2.1 0 0 0 .236 1.049 2.2 2.2 0 0 0 .8.75 10 10 0 0 0 1.715.754 6.8 6.8 0 0 1 2.667 1.483 2.92 2.92 0 0 1 .723 2.057",className:"logo_small_svg__cls-2","data-name":"Path 2957"})),P||(P=n.createElement("path",{id:"logo_small_svg__Path_2958",d:"M134.447 101.686v5.991a2.4 2.4 0 0 0 .515 1.686 2.1 2.1 0 0 0 1.609.556 2.63 2.63 0 0 0 2.12-.792 4 4 0 0 0 .67-2.587v-4.854h1.4v9.236H139.6l-.2-1.239h-.075a2.8 2.8 0 0 1-1.193 1.045 4 4 0 0 1-1.74.362 3.53 3.53 0 0 1-2.524-.8 3.4 3.4 0 0 1-.839-2.562v-6.042Z",className:"logo_small_svg__cls-2","data-name":"Path 2958"})),D||(D=n.createElement("path",{id:"logo_small_svg__Path_2959",d:"M148.206 111.09a4 4 0 0 1-1.647-.333 3.1 3.1 0 0 1-1.252-1.023h-.1a12 12 0 0 1 .1 1.533v3.8h-1.4v-13.381h1.137l.194 1.264h.067a3.26 3.26 0 0 1 1.256-1.1 3.8 3.8 0 0 1 1.643-.337 3.41 3.41 0 0 1 2.836 1.256 6.68 6.68 0 0 1-.017 7.057 3.42 3.42 0 0 1-2.817 1.264m-.2-8.385a2.48 2.48 0 0 0-2.048.784 4.04 4.04 0 0 0-.649 2.494v.312a4.63 4.63 0 0 0 .649 2.785 2.47 2.47 0 0 0 2.082.839 2.16 2.16 0 0 0 1.875-.969 4.6 4.6 0 0 0 .678-2.671 4.43 4.43 0 0 0-.678-2.651 2.23 2.23 0 0 0-1.915-.923Z",className:"logo_small_svg__cls-2","data-name":"Path 2959"})),U||(U=n.createElement("path",{id:"logo_small_svg__Path_2960",d:"M159.039 111.09a4 4 0 0 1-1.647-.333 3.1 3.1 0 0 1-1.252-1.023h-.1a12 12 0 0 1 .1 1.533v3.8h-1.4v-13.381h1.137l.194 1.264h.067a3.26 3.26 0 0 1 1.256-1.1 3.8 3.8 0 0 1 1.643-.337 3.41 3.41 0 0 1 2.836 1.256 6.68 6.68 0 0 1-.017 7.057 3.42 3.42 0 0 1-2.817 1.264m-.2-8.385a2.48 2.48 0 0 0-2.048.784 4.04 4.04 0 0 0-.649 2.494v.312a4.63 4.63 0 0 0 .649 2.785 2.47 2.47 0 0 0 2.082.839 2.16 2.16 0 0 0 1.875-.969 4.6 4.6 0 0 0 .678-2.671 4.43 4.43 0 0 0-.678-2.651 2.23 2.23 0 0 0-1.911-.923Z",className:"logo_small_svg__cls-2","data-name":"Path 2960"})),W||(W=n.createElement("path",{id:"logo_small_svg__Path_2961",d:"M173.612 106.3a5.1 5.1 0 0 1-1.137 3.527 4 4 0 0 1-3.143 1.268 4.17 4.17 0 0 1-2.2-.581 3.84 3.84 0 0 1-1.483-1.669 5.8 5.8 0 0 1-.522-2.545 5.1 5.1 0 0 1 1.129-3.518 4 4 0 0 1 3.135-1.26 3.9 3.9 0 0 1 3.08 1.29 5.07 5.07 0 0 1 1.141 3.488m-7.036 0a4.4 4.4 0 0 0 .708 2.7 2.81 2.81 0 0 0 4.167 0 4.37 4.37 0 0 0 .712-2.7 4.3 4.3 0 0 0-.712-2.675 2.5 2.5 0 0 0-2.1-.915 2.46 2.46 0 0 0-2.072.9 4.33 4.33 0 0 0-.7 2.69Z",className:"logo_small_svg__cls-2","data-name":"Path 2961"})),K||(K=n.createElement("path",{id:"logo_small_svg__Path_2962",d:"M180.525 101.517a5.5 5.5 0 0 1 1.1.1l-.194 1.3a4.8 4.8 0 0 0-1.011-.127 2.46 2.46 0 0 0-1.917.911 3.32 3.32 0 0 0-.8 2.267v4.955h-1.4v-9.236h1.154l.16 1.71h.068a4.05 4.05 0 0 1 1.238-1.39 2.8 2.8 0 0 1 1.6-.49Z",className:"logo_small_svg__cls-2","data-name":"Path 2962"})),V||(V=n.createElement("path",{id:"logo_small_svg__Path_2963",d:"M187.363 109.936a4.5 4.5 0 0 0 .716-.055 4 4 0 0 0 .548-.114v1.07a2.5 2.5 0 0 1-.67.181 5 5 0 0 1-.8.072q-2.68 0-2.68-2.823v-5.494h-1.323v-.673l1.323-.582.59-1.972h.809v2.141h2.68v1.087h-2.68v5.435a1.87 1.87 0 0 0 .4 1.281 1.38 1.38 0 0 0 1.087.446",className:"logo_small_svg__cls-2","data-name":"Path 2963"})),$||($=n.createElement("path",{id:"logo_small_svg__Path_2964",d:"M194.538 111.09a4.24 4.24 0 0 1-3.231-1.247 4.82 4.82 0 0 1-1.184-3.463 5.36 5.36 0 0 1 1.1-3.548 3.65 3.65 0 0 1 2.954-1.315 3.48 3.48 0 0 1 2.747 1.142 4.38 4.38 0 0 1 1.011 3.013v.885h-6.362a3.66 3.66 0 0 0 .822 2.469 2.84 2.84 0 0 0 2.2.843 7.4 7.4 0 0 0 2.949-.624v1.247a7.4 7.4 0 0 1-1.4.459 8 8 0 0 1-1.6.139Zm-.379-8.4a2.29 2.29 0 0 0-1.774.725 3.34 3.34 0 0 0-.779 2.006h4.828a3.07 3.07 0 0 0-.59-2.027 2.08 2.08 0 0 0-1.685-.706Z",className:"logo_small_svg__cls-2","data-name":"Path 2964"})),H||(H=n.createElement("path",{id:"logo_small_svg__Path_2965",d:"M206.951 109.683h-.076a3.29 3.29 0 0 1-2.9 1.407 3.43 3.43 0 0 1-2.819-1.239 5.45 5.45 0 0 1-1.006-3.522 5.54 5.54 0 0 1 1.011-3.548 3.4 3.4 0 0 1 2.814-1.264 3.36 3.36 0 0 1 2.883 1.365h.109l-.059-.665-.034-.649v-3.759h1.4v13.113h-1.138Zm-2.8.236a2.55 2.55 0 0 0 2.078-.779 3.95 3.95 0 0 0 .644-2.516v-.3a4.64 4.64 0 0 0-.653-2.8 2.48 2.48 0 0 0-2.086-.839 2.14 2.14 0 0 0-1.883.957 4.76 4.76 0 0 0-.653 2.7 4.55 4.55 0 0 0 .649 2.671 2.2 2.2 0 0 0 1.906.906Z",className:"logo_small_svg__cls-2","data-name":"Path 2965"})),Y||(Y=n.createElement("path",{id:"logo_small_svg__Path_2966",d:"M220.712 101.534a3.44 3.44 0 0 1 2.827 1.243 6.65 6.65 0 0 1-.009 7.053 3.42 3.42 0 0 1-2.818 1.26 4 4 0 0 1-1.648-.333 3.1 3.1 0 0 1-1.251-1.023h-.1l-.295 1.188h-1V97.809h1.4V101q0 1.069-.068 1.921h.068a3.32 3.32 0 0 1 2.894-1.387m-.2 1.171a2.44 2.44 0 0 0-2.064.822 6.34 6.34 0 0 0 .017 5.553 2.46 2.46 0 0 0 2.081.839 2.16 2.16 0 0 0 1.922-.94 4.83 4.83 0 0 0 .632-2.7 4.64 4.64 0 0 0-.632-2.689 2.24 2.24 0 0 0-1.959-.885Z",className:"logo_small_svg__cls-2","data-name":"Path 2966"})),Z||(Z=n.createElement("path",{id:"logo_small_svg__Path_2967",d:"M225.758 101.686h1.5l2.023 5.267a20 20 0 0 1 .826 2.6h.067q.109-.431.459-1.471t2.288-6.4h1.5l-3.969 10.518a5.25 5.25 0 0 1-1.378 2.212 2.93 2.93 0 0 1-1.934.653 5.7 5.7 0 0 1-1.264-.143V113.8a5 5 0 0 0 1.037.1 2.136 2.136 0 0 0 2.056-1.618l.514-1.314Z",className:"logo_small_svg__cls-2","data-name":"Path 2967"}))))),components_Logo=()=>n.createElement(logo_small,{height:"40"}),top_bar=()=>({components:{Topbar:u,Logo:components_Logo}});function isNothing(e){return null==e}var J={isNothing,isObject:function js_yaml_isObject(e){return"object"==typeof e&&null!==e},toArray:function toArray(e){return Array.isArray(e)?e:isNothing(e)?[]:[e]},repeat:function repeat(e,t){var r,n="";for(r=0;rs&&(t=n-s+(o=" ... ").length),r-n>s&&(r=n+s-(a=" ...").length),{str:o+e.slice(t,r).replace(/\t/g,"→")+a,pos:n-t+o.length}}function padStart(e,t){return J.repeat(" ",t-e.length)+e}var te=function makeSnippet(e,t){if(t=Object.create(t||null),!e.buffer)return null;t.maxLength||(t.maxLength=79),"number"!=typeof t.indent&&(t.indent=1),"number"!=typeof t.linesBefore&&(t.linesBefore=3),"number"!=typeof t.linesAfter&&(t.linesAfter=2);for(var r,n=/\r?\n|\r|\0/g,i=[0],o=[],a=-1;r=n.exec(e.buffer);)o.push(r.index),i.push(r.index+r[0].length),e.position<=r.index&&a<0&&(a=i.length-2);a<0&&(a=i.length-1);var s,u,c="",f=Math.min(e.line+t.linesAfter,o.length).toString().length,l=t.maxLength-(t.indent+f+3);for(s=1;s<=t.linesBefore&&!(a-s<0);s++)u=getLine(e.buffer,i[a-s],o[a-s],e.position-(i[a]-i[a-s]),l),c=J.repeat(" ",t.indent)+padStart((e.line-s+1).toString(),f)+" | "+u.str+"\n"+c;for(u=getLine(e.buffer,i[a],o[a],e.position,l),c+=J.repeat(" ",t.indent)+padStart((e.line+1).toString(),f)+" | "+u.str+"\n",c+=J.repeat("-",t.indent+f+3+u.pos)+"^\n",s=1;s<=t.linesAfter&&!(a+s>=o.length);s++)u=getLine(e.buffer,i[a+s],o[a+s],e.position-(i[a]-i[a+s]),l),c+=J.repeat(" ",t.indent)+padStart((e.line+s+1).toString(),f)+" | "+u.str+"\n";return c.replace(/\n$/,"")},re=["kind","multi","resolve","construct","instanceOf","predicate","represent","representName","defaultStyle","styleAliases"],ne=["scalar","sequence","mapping"];var ie=function Type$1(e,t){if(t=t||{},Object.keys(t).forEach((function(t){if(-1===re.indexOf(t))throw new ee('Unknown option "'+t+'" is met in definition of "'+e+'" YAML type.')})),this.options=t,this.tag=e,this.kind=t.kind||null,this.resolve=t.resolve||function(){return!0},this.construct=t.construct||function(e){return e},this.instanceOf=t.instanceOf||null,this.predicate=t.predicate||null,this.represent=t.represent||null,this.representName=t.representName||null,this.defaultStyle=t.defaultStyle||null,this.multi=t.multi||!1,this.styleAliases=function compileStyleAliases(e){var t={};return null!==e&&Object.keys(e).forEach((function(r){e[r].forEach((function(e){t[String(e)]=r}))})),t}(t.styleAliases||null),-1===ne.indexOf(this.kind))throw new ee('Unknown kind "'+this.kind+'" is specified for "'+e+'" YAML type.')};function compileList(e,t){var r=[];return e[t].forEach((function(e){var t=r.length;r.forEach((function(r,n){r.tag===e.tag&&r.kind===e.kind&&r.multi===e.multi&&(t=n)})),r[t]=e})),r}function Schema$1(e){return this.extend(e)}Schema$1.prototype.extend=function extend(e){var t=[],r=[];if(e instanceof ie)r.push(e);else if(Array.isArray(e))r=r.concat(e);else{if(!e||!Array.isArray(e.implicit)&&!Array.isArray(e.explicit))throw new ee("Schema.extend argument should be a Type, [ Type ], or a schema definition ({ implicit: [...], explicit: [...] })");e.implicit&&(t=t.concat(e.implicit)),e.explicit&&(r=r.concat(e.explicit))}t.forEach((function(e){if(!(e instanceof ie))throw new ee("Specified list of YAML types (or a single Type object) contains a non-Type object.");if(e.loadKind&&"scalar"!==e.loadKind)throw new ee("There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported.");if(e.multi)throw new ee("There is a multi type in the implicit list of a schema. Multi tags can only be listed as explicit.")})),r.forEach((function(e){if(!(e instanceof ie))throw new ee("Specified list of YAML types (or a single Type object) contains a non-Type object.")}));var n=Object.create(Schema$1.prototype);return n.implicit=(this.implicit||[]).concat(t),n.explicit=(this.explicit||[]).concat(r),n.compiledImplicit=compileList(n,"implicit"),n.compiledExplicit=compileList(n,"explicit"),n.compiledTypeMap=function compileMap(){var e,t,r={scalar:{},sequence:{},mapping:{},fallback:{},multi:{scalar:[],sequence:[],mapping:[],fallback:[]}};function collectType(e){e.multi?(r.multi[e.kind].push(e),r.multi.fallback.push(e)):r[e.kind][e.tag]=r.fallback[e.tag]=e}for(e=0,t=arguments.length;e=0?"0b"+e.toString(2):"-0b"+e.toString(2).slice(1)},octal:function(e){return e>=0?"0o"+e.toString(8):"-0o"+e.toString(8).slice(1)},decimal:function(e){return e.toString(10)},hexadecimal:function(e){return e>=0?"0x"+e.toString(16).toUpperCase():"-0x"+e.toString(16).toUpperCase().slice(1)}},defaultStyle:"decimal",styleAliases:{binary:[2,"bin"],octal:[8,"oct"],decimal:[10,"dec"],hexadecimal:[16,"hex"]}}),pe=new RegExp("^(?:[-+]?(?:[0-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?|[-+]?\\.(?:inf|Inf|INF)|\\.(?:nan|NaN|NAN))$");var de=/^[-+]?[0-9]+e/;var _e=new ie("tag:yaml.org,2002:float",{kind:"scalar",resolve:function resolveYamlFloat(e){return null!==e&&!(!pe.test(e)||"_"===e[e.length-1])},construct:function constructYamlFloat(e){var t,r;return r="-"===(t=e.replace(/_/g,"").toLowerCase())[0]?-1:1,"+-".indexOf(t[0])>=0&&(t=t.slice(1)),".inf"===t?1===r?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY:".nan"===t?NaN:r*parseFloat(t,10)},predicate:function isFloat(e){return"[object Number]"===Object.prototype.toString.call(e)&&(e%1!=0||J.isNegativeZero(e))},represent:function representYamlFloat(e,t){var r;if(isNaN(e))switch(t){case"lowercase":return".nan";case"uppercase":return".NAN";case"camelcase":return".NaN"}else if(Number.POSITIVE_INFINITY===e)switch(t){case"lowercase":return".inf";case"uppercase":return".INF";case"camelcase":return".Inf"}else if(Number.NEGATIVE_INFINITY===e)switch(t){case"lowercase":return"-.inf";case"uppercase":return"-.INF";case"camelcase":return"-.Inf"}else if(J.isNegativeZero(e))return"-0.0";return r=e.toString(10),de.test(r)?r.replace("e",".e"):r},defaultStyle:"lowercase"}),ye=ce.extend({implicit:[fe,le,he,_e]}),me=ye,ge=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$"),ve=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:[Tt]|[ \\t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \\t]*(Z|([-+])([0-9][0-9]?)(?::([0-9][0-9]))?))?$");var be=new ie("tag:yaml.org,2002:timestamp",{kind:"scalar",resolve:function resolveYamlTimestamp(e){return null!==e&&(null!==ge.exec(e)||null!==ve.exec(e))},construct:function constructYamlTimestamp(e){var t,r,n,i,o,a,s,u,c=0,f=null;if(null===(t=ge.exec(e))&&(t=ve.exec(e)),null===t)throw new Error("Date resolve error");if(r=+t[1],n=+t[2]-1,i=+t[3],!t[4])return new Date(Date.UTC(r,n,i));if(o=+t[4],a=+t[5],s=+t[6],t[7]){for(c=t[7].slice(0,3);c.length<3;)c+="0";c=+c}return t[9]&&(f=6e4*(60*+t[10]+ +(t[11]||0)),"-"===t[9]&&(f=-f)),u=new Date(Date.UTC(r,n,i,o,a,s,c)),f&&u.setTime(u.getTime()-f),u},instanceOf:Date,represent:function representYamlTimestamp(e){return e.toISOString()}});var Se=new ie("tag:yaml.org,2002:merge",{kind:"scalar",resolve:function resolveYamlMerge(e){return"<<"===e||null===e}}),we="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n\r";var Ie=new ie("tag:yaml.org,2002:binary",{kind:"scalar",resolve:function resolveYamlBinary(e){if(null===e)return!1;var t,r,n=0,i=e.length,o=we;for(r=0;r64)){if(t<0)return!1;n+=6}return n%8==0},construct:function constructYamlBinary(e){var t,r,n=e.replace(/[\r\n=]/g,""),i=n.length,o=we,a=0,s=[];for(t=0;t>16&255),s.push(a>>8&255),s.push(255&a)),a=a<<6|o.indexOf(n.charAt(t));return 0===(r=i%4*6)?(s.push(a>>16&255),s.push(a>>8&255),s.push(255&a)):18===r?(s.push(a>>10&255),s.push(a>>2&255)):12===r&&s.push(a>>4&255),new Uint8Array(s)},predicate:function isBinary(e){return"[object Uint8Array]"===Object.prototype.toString.call(e)},represent:function representYamlBinary(e){var t,r,n="",i=0,o=e.length,a=we;for(t=0;t>18&63],n+=a[i>>12&63],n+=a[i>>6&63],n+=a[63&i]),i=(i<<8)+e[t];return 0===(r=o%3)?(n+=a[i>>18&63],n+=a[i>>12&63],n+=a[i>>6&63],n+=a[63&i]):2===r?(n+=a[i>>10&63],n+=a[i>>4&63],n+=a[i<<2&63],n+=a[64]):1===r&&(n+=a[i>>2&63],n+=a[i<<4&63],n+=a[64],n+=a[64]),n}}),xe=Object.prototype.hasOwnProperty,Ee=Object.prototype.toString;var Oe=new ie("tag:yaml.org,2002:omap",{kind:"sequence",resolve:function resolveYamlOmap(e){if(null===e)return!0;var t,r,n,i,o,a=[],s=e;for(t=0,r=s.length;t>10),56320+(e-65536&1023))}for(var ze=new Array(256),Pe=new Array(256),Fe=0;Fe<256;Fe++)ze[Fe]=simpleEscapeSequence(Fe)?1:0,Pe[Fe]=simpleEscapeSequence(Fe);function State$1(e,t){this.input=e,this.filename=t.filename||null,this.schema=t.schema||Me,this.onWarning=t.onWarning||null,this.legacy=t.legacy||!1,this.json=t.json||!1,this.listener=t.listener||null,this.implicitTypes=this.schema.compiledImplicit,this.typeMap=this.schema.compiledTypeMap,this.length=e.length,this.position=0,this.line=0,this.lineStart=0,this.lineIndent=0,this.firstTabInLine=-1,this.documents=[]}function generateError(e,t){var r={name:e.filename,buffer:e.input.slice(0,-1),position:e.position,line:e.line,column:e.position-e.lineStart};return r.snippet=te(r),new ee(t,r)}function throwError(e,t){throw generateError(e,t)}function throwWarning(e,t){e.onWarning&&e.onWarning.call(null,generateError(e,t))}var De={YAML:function handleYamlDirective(e,t,r){var n,i,o;null!==e.version&&throwError(e,"duplication of %YAML directive"),1!==r.length&&throwError(e,"YAML directive accepts exactly one argument"),null===(n=/^([0-9]+)\.([0-9]+)$/.exec(r[0]))&&throwError(e,"ill-formed argument of the YAML directive"),i=parseInt(n[1],10),o=parseInt(n[2],10),1!==i&&throwError(e,"unacceptable YAML version of the document"),e.version=r[0],e.checkLineBreaks=o<2,1!==o&&2!==o&&throwWarning(e,"unsupported YAML version of the document")},TAG:function handleTagDirective(e,t,r){var n,i;2!==r.length&&throwError(e,"TAG directive accepts exactly two arguments"),n=r[0],i=r[1],Te.test(n)||throwError(e,"ill-formed tag handle (first argument) of the TAG directive"),qe.call(e.tagMap,n)&&throwError(e,'there is a previously declared suffix for "'+n+'" tag handle'),Re.test(i)||throwError(e,"ill-formed tag prefix (second argument) of the TAG directive");try{i=decodeURIComponent(i)}catch(t){throwError(e,"tag prefix is malformed: "+i)}e.tagMap[n]=i}};function captureSegment(e,t,r,n){var i,o,a,s;if(t1&&(e.result+=J.repeat("\n",t-1))}function readBlockSequence(e,t){var r,n,i=e.tag,o=e.anchor,a=[],s=!1;if(-1!==e.firstTabInLine)return!1;for(null!==e.anchor&&(e.anchorMap[e.anchor]=a),n=e.input.charCodeAt(e.position);0!==n&&(-1!==e.firstTabInLine&&(e.position=e.firstTabInLine,throwError(e,"tab characters must not be used in indentation")),45===n)&&is_WS_OR_EOL(e.input.charCodeAt(e.position+1));)if(s=!0,e.position++,skipSeparationSpace(e,!0,-1)&&e.lineIndent<=t)a.push(null),n=e.input.charCodeAt(e.position);else if(r=e.line,composeNode(e,t,3,!1,!0),a.push(e.result),skipSeparationSpace(e,!0,-1),n=e.input.charCodeAt(e.position),(e.line===r||e.lineIndent>t)&&0!==n)throwError(e,"bad indentation of a sequence entry");else if(e.lineIndentt?d=1:e.lineIndent===t?d=0:e.lineIndentt?d=1:e.lineIndent===t?d=0:e.lineIndentt)&&(m&&(a=e.line,s=e.lineStart,u=e.position),composeNode(e,t,4,!0,i)&&(m?_=e.result:y=e.result),m||(storeMappingPair(e,h,p,d,_,y,a,s,u),d=_=y=null),skipSeparationSpace(e,!0,-1),c=e.input.charCodeAt(e.position)),(e.line===o||e.lineIndent>t)&&0!==c)throwError(e,"bad indentation of a mapping entry");else if(e.lineIndent=0))break;0===i?throwError(e,"bad explicit indentation width of a block scalar; it cannot be less than one"):c?throwError(e,"repeat of an indentation width identifier"):(f=t+i-1,c=!0)}if(is_WHITE_SPACE(o)){do{o=e.input.charCodeAt(++e.position)}while(is_WHITE_SPACE(o));if(35===o)do{o=e.input.charCodeAt(++e.position)}while(!is_EOL(o)&&0!==o)}for(;0!==o;){for(readLineBreak(e),e.lineIndent=0,o=e.input.charCodeAt(e.position);(!c||e.lineIndentf&&(f=e.lineIndent),is_EOL(o))l++;else{if(e.lineIndent0){for(i=a,o=0;i>0;i--)(a=fromHexCode(s=e.input.charCodeAt(++e.position)))>=0?o=(o<<4)+a:throwError(e,"expected hexadecimal character");e.result+=charFromCodepoint(o),e.position++}else throwError(e,"unknown escape sequence");r=n=e.position}else is_EOL(s)?(captureSegment(e,r,n,!0),writeFoldedLines(e,skipSeparationSpace(e,!1,t)),r=n=e.position):e.position===e.lineStart&&testDocumentSeparator(e)?throwError(e,"unexpected end of the document within a double quoted scalar"):(e.position++,n=e.position)}throwError(e,"unexpected end of the stream within a double quoted scalar")}(e,h)?y=!0:!function readAlias(e){var t,r,n;if(42!==(n=e.input.charCodeAt(e.position)))return!1;for(n=e.input.charCodeAt(++e.position),t=e.position;0!==n&&!is_WS_OR_EOL(n)&&!is_FLOW_INDICATOR(n);)n=e.input.charCodeAt(++e.position);return e.position===t&&throwError(e,"name of an alias node must contain at least one character"),r=e.input.slice(t,e.position),qe.call(e.anchorMap,r)||throwError(e,'unidentified alias "'+r+'"'),e.result=e.anchorMap[r],skipSeparationSpace(e,!0,-1),!0}(e)?function readPlainScalar(e,t,r){var n,i,o,a,s,u,c,f,l=e.kind,h=e.result;if(is_WS_OR_EOL(f=e.input.charCodeAt(e.position))||is_FLOW_INDICATOR(f)||35===f||38===f||42===f||33===f||124===f||62===f||39===f||34===f||37===f||64===f||96===f)return!1;if((63===f||45===f)&&(is_WS_OR_EOL(n=e.input.charCodeAt(e.position+1))||r&&is_FLOW_INDICATOR(n)))return!1;for(e.kind="scalar",e.result="",i=o=e.position,a=!1;0!==f;){if(58===f){if(is_WS_OR_EOL(n=e.input.charCodeAt(e.position+1))||r&&is_FLOW_INDICATOR(n))break}else if(35===f){if(is_WS_OR_EOL(e.input.charCodeAt(e.position-1)))break}else{if(e.position===e.lineStart&&testDocumentSeparator(e)||r&&is_FLOW_INDICATOR(f))break;if(is_EOL(f)){if(s=e.line,u=e.lineStart,c=e.lineIndent,skipSeparationSpace(e,!1,-1),e.lineIndent>=t){a=!0,f=e.input.charCodeAt(e.position);continue}e.position=o,e.line=s,e.lineStart=u,e.lineIndent=c;break}}a&&(captureSegment(e,i,o,!1),writeFoldedLines(e,e.line-s),i=o=e.position,a=!1),is_WHITE_SPACE(f)||(o=e.position+1),f=e.input.charCodeAt(++e.position)}return captureSegment(e,i,o,!1),!!e.result||(e.kind=l,e.result=h,!1)}(e,h,1===r)&&(y=!0,null===e.tag&&(e.tag="?")):(y=!0,null===e.tag&&null===e.anchor||throwError(e,"alias node should not have any properties")),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):0===d&&(y=s&&readBlockSequence(e,p))),null===e.tag)null!==e.anchor&&(e.anchorMap[e.anchor]=e.result);else if("?"===e.tag){for(null!==e.result&&"scalar"!==e.kind&&throwError(e,'unacceptable node kind for ! tag; it should be "scalar", not "'+e.kind+'"'),u=0,c=e.implicitTypes.length;u"),null!==e.result&&l.kind!==e.kind&&throwError(e,"unacceptable node kind for !<"+e.tag+'> tag; it should be "'+l.kind+'", not "'+e.kind+'"'),l.resolve(e.result,e.tag)?(e.result=l.construct(e.result,e.tag),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):throwError(e,"cannot resolve a node with !<"+e.tag+"> explicit tag")}return null!==e.listener&&e.listener("close",e),null!==e.tag||null!==e.anchor||y}function readDocument(e){var t,r,n,i,o=e.position,a=!1;for(e.version=null,e.checkLineBreaks=e.legacy,e.tagMap=Object.create(null),e.anchorMap=Object.create(null);0!==(i=e.input.charCodeAt(e.position))&&(skipSeparationSpace(e,!0,-1),i=e.input.charCodeAt(e.position),!(e.lineIndent>0||37!==i));){for(a=!0,i=e.input.charCodeAt(++e.position),t=e.position;0!==i&&!is_WS_OR_EOL(i);)i=e.input.charCodeAt(++e.position);for(n=[],(r=e.input.slice(t,e.position)).length<1&&throwError(e,"directive name must not be less than one character in length");0!==i;){for(;is_WHITE_SPACE(i);)i=e.input.charCodeAt(++e.position);if(35===i){do{i=e.input.charCodeAt(++e.position)}while(0!==i&&!is_EOL(i));break}if(is_EOL(i))break;for(t=e.position;0!==i&&!is_WS_OR_EOL(i);)i=e.input.charCodeAt(++e.position);n.push(e.input.slice(t,e.position))}0!==i&&readLineBreak(e),qe.call(De,r)?De[r](e,r,n):throwWarning(e,'unknown document directive "'+r+'"')}skipSeparationSpace(e,!0,-1),0===e.lineIndent&&45===e.input.charCodeAt(e.position)&&45===e.input.charCodeAt(e.position+1)&&45===e.input.charCodeAt(e.position+2)?(e.position+=3,skipSeparationSpace(e,!0,-1)):a&&throwError(e,"directives end mark is expected"),composeNode(e,e.lineIndent-1,4,!1,!0),skipSeparationSpace(e,!0,-1),e.checkLineBreaks&&Ne.test(e.input.slice(o,e.position))&&throwWarning(e,"non-ASCII line breaks are interpreted as content"),e.documents.push(e.result),e.position===e.lineStart&&testDocumentSeparator(e)?46===e.input.charCodeAt(e.position)&&(e.position+=3,skipSeparationSpace(e,!0,-1)):e.position=55296&&n<=56319&&t+1=56320&&r<=57343?1024*(n-55296)+r-56320+65536:n}function needIndentIndicator(e){return/^\n* /.test(e)}function chooseScalarStyle(e,t,r,n,i,o,a,s){var u,c=0,f=null,l=!1,h=!1,p=-1!==n,d=-1,_=function isPlainSafeFirst(e){return isPrintable(e)&&e!==Ve&&!isWhitespace(e)&&45!==e&&63!==e&&58!==e&&44!==e&&91!==e&&93!==e&&123!==e&&125!==e&&35!==e&&38!==e&&42!==e&&33!==e&&124!==e&&61!==e&&62!==e&&39!==e&&34!==e&&37!==e&&64!==e&&96!==e}(codePointAt(e,0))&&function isPlainSafeLast(e){return!isWhitespace(e)&&58!==e}(codePointAt(e,e.length-1));if(t||a)for(u=0;u=65536?u+=2:u++){if(!isPrintable(c=codePointAt(e,u)))return 5;_=_&&isPlainSafe(c,f,s),f=c}else{for(u=0;u=65536?u+=2:u++){if(10===(c=codePointAt(e,u)))l=!0,p&&(h=h||u-d-1>n&&" "!==e[d+1],d=u);else if(!isPrintable(c))return 5;_=_&&isPlainSafe(c,f,s),f=c}h=h||p&&u-d-1>n&&" "!==e[d+1]}return l||h?r>9&&needIndentIndicator(e)?5:a?2===o?5:2:h?4:3:!_||a||i(e)?2===o?5:2:1}function writeScalar(e,t,r,n,i){e.dump=function(){if(0===t.length)return 2===e.quotingType?'""':"''";if(!e.noCompatMode&&(-1!==He.indexOf(t)||Ye.test(t)))return 2===e.quotingType?'"'+t+'"':"'"+t+"'";var o=e.indent*Math.max(1,r),a=-1===e.lineWidth?-1:Math.max(Math.min(e.lineWidth,40),e.lineWidth-o),s=n||e.flowLevel>-1&&r>=e.flowLevel;switch(chooseScalarStyle(t,s,e.indent,a,(function testAmbiguity(t){return function testImplicitResolving(e,t){var r,n;for(r=0,n=e.implicitTypes.length;r"+blockHeader(t,e.indent)+dropEndingNewline(indentString(function foldString(e,t){var r,n,i=/(\n+)([^\n]*)/g,o=(s=e.indexOf("\n"),s=-1!==s?s:e.length,i.lastIndex=s,foldLine(e.slice(0,s),t)),a="\n"===e[0]||" "===e[0];var s;for(;n=i.exec(e);){var u=n[1],c=n[2];r=" "===c[0],o+=u+(a||r||""===c?"":"\n")+foldLine(c,t),a=r}return o}(t,a),o));case 5:return'"'+function escapeString(e){for(var t,r="",n=0,i=0;i=65536?i+=2:i++)n=codePointAt(e,i),!(t=$e[n])&&isPrintable(n)?(r+=e[i],n>=65536&&(r+=e[i+1])):r+=t||encodeHex(n);return r}(t)+'"';default:throw new ee("impossible error: invalid scalar style")}}()}function blockHeader(e,t){var r=needIndentIndicator(e)?String(t):"",n="\n"===e[e.length-1];return r+(n&&("\n"===e[e.length-2]||"\n"===e)?"+":n?"":"-")+"\n"}function dropEndingNewline(e){return"\n"===e[e.length-1]?e.slice(0,-1):e}function foldLine(e,t){if(""===e||" "===e[0])return e;for(var r,n,i=/ [^ ]/g,o=0,a=0,s=0,u="";r=i.exec(e);)(s=r.index)-o>t&&(n=a>o?a:s,u+="\n"+e.slice(o,n),o=n+1),a=s;return u+="\n",e.length-o>t&&a>o?u+=e.slice(o,a)+"\n"+e.slice(a+1):u+=e.slice(o),u.slice(1)}function writeBlockSequence(e,t,r,n){var i,o,a,s="",u=e.tag;for(i=0,o=r.length;i tag resolver accepts not "'+u+'" style');n=s.represent[u](t,u)}e.dump=n}return!0}return!1}function writeNode(e,t,r,n,i,o,a){e.tag=null,e.dump=r,detectType(e,r,!1)||detectType(e,r,!0);var s,u=We.call(e.dump),c=n;n&&(n=e.flowLevel<0||e.flowLevel>t);var f,l,h="[object Object]"===u||"[object Array]"===u;if(h&&(l=-1!==(f=e.duplicates.indexOf(r))),(null!==e.tag&&"?"!==e.tag||l||2!==e.indent&&t>0)&&(i=!1),l&&e.usedDuplicates[f])e.dump="*ref_"+f;else{if(h&&l&&!e.usedDuplicates[f]&&(e.usedDuplicates[f]=!0),"[object Object]"===u)n&&0!==Object.keys(e.dump).length?(!function writeBlockMapping(e,t,r,n){var i,o,a,s,u,c,f="",l=e.tag,h=Object.keys(r);if(!0===e.sortKeys)h.sort();else if("function"==typeof e.sortKeys)h.sort(e.sortKeys);else if(e.sortKeys)throw new ee("sortKeys must be a boolean or a function");for(i=0,o=h.length;i1024)&&(e.dump&&10===e.dump.charCodeAt(0)?c+="?":c+="? "),c+=e.dump,u&&(c+=generateNextLine(e,t)),writeNode(e,t+1,s,!0,u)&&(e.dump&&10===e.dump.charCodeAt(0)?c+=":":c+=": ",f+=c+=e.dump));e.tag=l,e.dump=f||"{}"}(e,t,e.dump,i),l&&(e.dump="&ref_"+f+e.dump)):(!function writeFlowMapping(e,t,r){var n,i,o,a,s,u="",c=e.tag,f=Object.keys(r);for(n=0,i=f.length;n1024&&(s+="? "),s+=e.dump+(e.condenseFlow?'"':"")+":"+(e.condenseFlow?"":" "),writeNode(e,t,a,!1,!1)&&(u+=s+=e.dump));e.tag=c,e.dump="{"+u+"}"}(e,t,e.dump),l&&(e.dump="&ref_"+f+" "+e.dump));else if("[object Array]"===u)n&&0!==e.dump.length?(e.noArrayIndent&&!a&&t>0?writeBlockSequence(e,t-1,e.dump,i):writeBlockSequence(e,t,e.dump,i),l&&(e.dump="&ref_"+f+e.dump)):(!function writeFlowSequence(e,t,r){var n,i,o,a="",s=e.tag;for(n=0,i=r.length;n",e.dump=s+" "+e.dump)}return!0}function getDuplicateReferences(e,t){var r,n,i=[],o=[];for(inspectNode(e,i,o),r=0,n=o.length;r()=>{},downloadConfig=e=>t=>{const{fn:{fetch:r}}=t;return r(e)},getConfigByUrl=(e,t)=>r=>{const{specActions:n,configsActions:i}=r;if(e)return i.downloadConfig(e).then(next,next);function next(i){i instanceof Error||i.status>=400?(n.updateLoadingStatus("failedConfig"),n.updateLoadingStatus("failedConfig"),n.updateUrl(""),console.error(i.statusText+" "+e.url),t(null)):t(((e,t)=>{try{return Ze.load(e)}catch(e){return t&&t.errActions.newThrownErr(new Error(e)),{}}})(i.text,r))}},get=(e,t)=>e.getIn(Array.isArray(t)?t:[t]),Qe={[Ge]:(e,t)=>e.merge((0,o.fromJS)(t.payload)),[Je]:(e,t)=>{const r=t.payload,n=e.get(r);return e.set(r,!n)}};var Xe=__webpack_require__(7248),et=__webpack_require__.n(Xe),tt=__webpack_require__(7666),rt=__webpack_require__.n(tt);const nt=console.error,withErrorBoundary=e=>t=>{const{getComponent:r,fn:i}=e(),o=r("ErrorBoundary"),a=i.getDisplayName(t);class WithErrorBoundary extends n.Component{render(){return n.createElement(o,{targetName:a,getComponent:r,fn:i},n.createElement(t,rt()({},this.props,this.context)))}}var s;return WithErrorBoundary.displayName=`WithErrorBoundary(${a})`,(s=t).prototype&&s.prototype.isReactComponent&&(WithErrorBoundary.prototype.mapStateToProps=t.prototype.mapStateToProps),WithErrorBoundary},fallback=({name:e})=>n.createElement("div",{className:"fallback"},"😱 ",n.createElement("i",null,"Could not render ","t"===e?"this component":e,", see the console."));class ErrorBoundary extends n.Component{static defaultProps={targetName:"this component",getComponent:()=>fallback,fn:{componentDidCatch:nt},children:null};static getDerivedStateFromError(e){return{hasError:!0,error:e}}constructor(...e){super(...e),this.state={hasError:!1,error:null}}componentDidCatch(e,t){this.props.fn.componentDidCatch(e,t)}render(){const{getComponent:e,targetName:t,children:r}=this.props;if(this.state.hasError){const r=e("Fallback");return n.createElement(r,{name:t})}return r}}const it=ErrorBoundary,ot=[top_bar,function configsPlugin(){return{statePlugins:{configs:{reducers:Qe,actions:e,selectors:t}}}},stadalone_layout,(({componentList:e=[],fullOverride:t=!1}={})=>({getSystem:r})=>{const n=t?e:["App","BaseLayout","VersionPragmaFilter","InfoContainer","ServersContainer","SchemesContainer","AuthorizeBtnContainer","FilterContainer","Operations","OperationContainer","parameters","responses","OperationServers","Models","ModelWrapper",...e],i=et()(n,Array(n.length).fill(((e,{fn:t})=>t.withErrorBoundary(e))));return{fn:{componentDidCatch:nt,withErrorBoundary:withErrorBoundary(r)},components:{ErrorBoundary:it,Fallback:fallback},wrapComponents:i}})({fullOverride:!0,componentList:["Topbar","StandaloneLayout","onlineValidatorBadge"]})]})(),r=r.default})())); \ No newline at end of file diff --git a/server/internal/httpapi/docs/swagger-ui/swagger-ui.css b/server/internal/httpapi/docs/swagger-ui/swagger-ui.css new file mode 100644 index 0000000..27ffa53 --- /dev/null +++ b/server/internal/httpapi/docs/swagger-ui/swagger-ui.css @@ -0,0 +1,3 @@ +.swagger-ui{color:#3b4151;font-family:sans-serif/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */}.swagger-ui html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}.swagger-ui body{margin:0}.swagger-ui article,.swagger-ui aside,.swagger-ui footer,.swagger-ui header,.swagger-ui nav,.swagger-ui section{display:block}.swagger-ui h1{font-size:2em;margin:.67em 0}.swagger-ui figcaption,.swagger-ui figure,.swagger-ui main{display:block}.swagger-ui figure{margin:1em 40px}.swagger-ui hr{box-sizing:content-box;height:0;overflow:visible}.swagger-ui pre{font-family:monospace,monospace;font-size:1em}.swagger-ui a{background-color:transparent;-webkit-text-decoration-skip:objects}.swagger-ui abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}.swagger-ui b,.swagger-ui strong{font-weight:inherit;font-weight:bolder}.swagger-ui code,.swagger-ui kbd,.swagger-ui samp{font-family:monospace,monospace;font-size:1em}.swagger-ui dfn{font-style:italic}.swagger-ui mark{background-color:#ff0;color:#000}.swagger-ui small{font-size:80%}.swagger-ui sub,.swagger-ui sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}.swagger-ui sub{bottom:-.25em}.swagger-ui sup{top:-.5em}.swagger-ui audio,.swagger-ui video{display:inline-block}.swagger-ui audio:not([controls]){display:none;height:0}.swagger-ui img{border-style:none}.swagger-ui svg:not(:root){overflow:hidden}.swagger-ui button,.swagger-ui input,.swagger-ui optgroup,.swagger-ui select,.swagger-ui textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}.swagger-ui button,.swagger-ui input{overflow:visible}.swagger-ui button,.swagger-ui select{text-transform:none}.swagger-ui [type=reset],.swagger-ui [type=submit],.swagger-ui button,.swagger-ui html [type=button]{-webkit-appearance:button}.swagger-ui [type=button]::-moz-focus-inner,.swagger-ui [type=reset]::-moz-focus-inner,.swagger-ui [type=submit]::-moz-focus-inner,.swagger-ui button::-moz-focus-inner{border-style:none;padding:0}.swagger-ui [type=button]:-moz-focusring,.swagger-ui [type=reset]:-moz-focusring,.swagger-ui [type=submit]:-moz-focusring,.swagger-ui button:-moz-focusring{outline:1px dotted ButtonText}.swagger-ui fieldset{padding:.35em .75em .625em}.swagger-ui legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}.swagger-ui progress{display:inline-block;vertical-align:baseline}.swagger-ui textarea{overflow:auto}.swagger-ui [type=checkbox],.swagger-ui [type=radio]{box-sizing:border-box;padding:0}.swagger-ui [type=number]::-webkit-inner-spin-button,.swagger-ui [type=number]::-webkit-outer-spin-button{height:auto}.swagger-ui [type=search]{-webkit-appearance:textfield;outline-offset:-2px}.swagger-ui [type=search]::-webkit-search-cancel-button,.swagger-ui [type=search]::-webkit-search-decoration{-webkit-appearance:none}.swagger-ui ::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}.swagger-ui details,.swagger-ui menu{display:block}.swagger-ui summary{display:list-item}.swagger-ui canvas{display:inline-block}.swagger-ui [hidden],.swagger-ui template{display:none}.swagger-ui .debug *{outline:1px solid gold}.swagger-ui .debug-white *{outline:1px solid #fff}.swagger-ui .debug-black *{outline:1px solid #000}.swagger-ui .debug-grid{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTRDOTY4N0U2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTRDOTY4N0Q2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3NjY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3NzY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PsBS+GMAAAAjSURBVHjaYvz//z8DLsD4gcGXiYEAGBIKGBne//fFpwAgwAB98AaF2pjlUQAAAABJRU5ErkJggg==) repeat 0 0}.swagger-ui .debug-grid-16{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODYyRjhERDU2N0YyMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODYyRjhERDQ2N0YyMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3QTY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3QjY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PvCS01IAAABMSURBVHjaYmR4/5+BFPBfAMFm/MBgx8RAGWCn1AAmSg34Q6kBDKMGMDCwICeMIemF/5QawEipAWwUhwEjMDvbAWlWkvVBwu8vQIABAEwBCph8U6c0AAAAAElFTkSuQmCC) repeat 0 0}.swagger-ui .debug-grid-8-solid{background:#fff url(data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAAAAAD/4QMxaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzExMSA3OS4xNTgzMjUsIDIwMTUvMDkvMTAtMDE6MTA6MjAgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE1IChNYWNpbnRvc2gpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkIxMjI0OTczNjdCMzExRTZCMkJDRTI0MDgxMDAyMTcxIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkIxMjI0OTc0NjdCMzExRTZCMkJDRTI0MDgxMDAyMTcxIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6QjEyMjQ5NzE2N0IzMTFFNkIyQkNFMjQwODEwMDIxNzEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6QjEyMjQ5NzI2N0IzMTFFNkIyQkNFMjQwODEwMDIxNzEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7/7gAOQWRvYmUAZMAAAAAB/9sAhAAbGhopHSlBJiZBQi8vL0JHPz4+P0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHAR0pKTQmND8oKD9HPzU/R0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0f/wAARCAAIAAgDASIAAhEBAxEB/8QAWQABAQAAAAAAAAAAAAAAAAAAAAYBAQEAAAAAAAAAAAAAAAAAAAIEEAEBAAMBAAAAAAAAAAAAAAABADECA0ERAAEDBQAAAAAAAAAAAAAAAAARITFBUWESIv/aAAwDAQACEQMRAD8AoOnTV1QTD7JJshP3vSM3P//Z) repeat 0 0}.swagger-ui .debug-grid-16-solid{background:#fff url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NzY3MkJEN0U2N0M1MTFFNkIyQkNFMjQwODEwMDIxNzEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NzY3MkJEN0Y2N0M1MTFFNkIyQkNFMjQwODEwMDIxNzEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3QzY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3RDY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pve6J3kAAAAzSURBVHjaYvz//z8D0UDsMwMjSRoYP5Gq4SPNbRjVMEQ1fCRDg+in/6+J1AJUxsgAEGAA31BAJMS0GYEAAAAASUVORK5CYII=) repeat 0 0}.swagger-ui .border-box,.swagger-ui a,.swagger-ui article,.swagger-ui body,.swagger-ui code,.swagger-ui dd,.swagger-ui div,.swagger-ui dl,.swagger-ui dt,.swagger-ui fieldset,.swagger-ui footer,.swagger-ui form,.swagger-ui h1,.swagger-ui h2,.swagger-ui h3,.swagger-ui h4,.swagger-ui h5,.swagger-ui h6,.swagger-ui header,.swagger-ui html,.swagger-ui input[type=email],.swagger-ui input[type=number],.swagger-ui input[type=password],.swagger-ui input[type=tel],.swagger-ui input[type=text],.swagger-ui input[type=url],.swagger-ui legend,.swagger-ui li,.swagger-ui main,.swagger-ui ol,.swagger-ui p,.swagger-ui pre,.swagger-ui section,.swagger-ui table,.swagger-ui td,.swagger-ui textarea,.swagger-ui th,.swagger-ui tr,.swagger-ui ul{box-sizing:border-box}.swagger-ui .aspect-ratio{height:0;position:relative}.swagger-ui .aspect-ratio--16x9{padding-bottom:56.25%}.swagger-ui .aspect-ratio--9x16{padding-bottom:177.77%}.swagger-ui .aspect-ratio--4x3{padding-bottom:75%}.swagger-ui .aspect-ratio--3x4{padding-bottom:133.33%}.swagger-ui .aspect-ratio--6x4{padding-bottom:66.6%}.swagger-ui .aspect-ratio--4x6{padding-bottom:150%}.swagger-ui .aspect-ratio--8x5{padding-bottom:62.5%}.swagger-ui .aspect-ratio--5x8{padding-bottom:160%}.swagger-ui .aspect-ratio--7x5{padding-bottom:71.42%}.swagger-ui .aspect-ratio--5x7{padding-bottom:140%}.swagger-ui .aspect-ratio--1x1{padding-bottom:100%}.swagger-ui .aspect-ratio--object{bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%;z-index:100}@media screen and (min-width:30em){.swagger-ui .aspect-ratio-ns{height:0;position:relative}.swagger-ui .aspect-ratio--16x9-ns{padding-bottom:56.25%}.swagger-ui .aspect-ratio--9x16-ns{padding-bottom:177.77%}.swagger-ui .aspect-ratio--4x3-ns{padding-bottom:75%}.swagger-ui .aspect-ratio--3x4-ns{padding-bottom:133.33%}.swagger-ui .aspect-ratio--6x4-ns{padding-bottom:66.6%}.swagger-ui .aspect-ratio--4x6-ns{padding-bottom:150%}.swagger-ui .aspect-ratio--8x5-ns{padding-bottom:62.5%}.swagger-ui .aspect-ratio--5x8-ns{padding-bottom:160%}.swagger-ui .aspect-ratio--7x5-ns{padding-bottom:71.42%}.swagger-ui .aspect-ratio--5x7-ns{padding-bottom:140%}.swagger-ui .aspect-ratio--1x1-ns{padding-bottom:100%}.swagger-ui .aspect-ratio--object-ns{bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%;z-index:100}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .aspect-ratio-m{height:0;position:relative}.swagger-ui .aspect-ratio--16x9-m{padding-bottom:56.25%}.swagger-ui .aspect-ratio--9x16-m{padding-bottom:177.77%}.swagger-ui .aspect-ratio--4x3-m{padding-bottom:75%}.swagger-ui .aspect-ratio--3x4-m{padding-bottom:133.33%}.swagger-ui .aspect-ratio--6x4-m{padding-bottom:66.6%}.swagger-ui .aspect-ratio--4x6-m{padding-bottom:150%}.swagger-ui .aspect-ratio--8x5-m{padding-bottom:62.5%}.swagger-ui .aspect-ratio--5x8-m{padding-bottom:160%}.swagger-ui .aspect-ratio--7x5-m{padding-bottom:71.42%}.swagger-ui .aspect-ratio--5x7-m{padding-bottom:140%}.swagger-ui .aspect-ratio--1x1-m{padding-bottom:100%}.swagger-ui .aspect-ratio--object-m{bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%;z-index:100}}@media screen and (min-width:60em){.swagger-ui .aspect-ratio-l{height:0;position:relative}.swagger-ui .aspect-ratio--16x9-l{padding-bottom:56.25%}.swagger-ui .aspect-ratio--9x16-l{padding-bottom:177.77%}.swagger-ui .aspect-ratio--4x3-l{padding-bottom:75%}.swagger-ui .aspect-ratio--3x4-l{padding-bottom:133.33%}.swagger-ui .aspect-ratio--6x4-l{padding-bottom:66.6%}.swagger-ui .aspect-ratio--4x6-l{padding-bottom:150%}.swagger-ui .aspect-ratio--8x5-l{padding-bottom:62.5%}.swagger-ui .aspect-ratio--5x8-l{padding-bottom:160%}.swagger-ui .aspect-ratio--7x5-l{padding-bottom:71.42%}.swagger-ui .aspect-ratio--5x7-l{padding-bottom:140%}.swagger-ui .aspect-ratio--1x1-l{padding-bottom:100%}.swagger-ui .aspect-ratio--object-l{bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%;z-index:100}}.swagger-ui img{max-width:100%}.swagger-ui .cover{background-size:cover!important}.swagger-ui .contain{background-size:contain!important}@media screen and (min-width:30em){.swagger-ui .cover-ns{background-size:cover!important}.swagger-ui .contain-ns{background-size:contain!important}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .cover-m{background-size:cover!important}.swagger-ui .contain-m{background-size:contain!important}}@media screen and (min-width:60em){.swagger-ui .cover-l{background-size:cover!important}.swagger-ui .contain-l{background-size:contain!important}}.swagger-ui .bg-center{background-position:50%;background-repeat:no-repeat}.swagger-ui .bg-top{background-position:top;background-repeat:no-repeat}.swagger-ui .bg-right{background-position:100%;background-repeat:no-repeat}.swagger-ui .bg-bottom{background-position:bottom;background-repeat:no-repeat}.swagger-ui .bg-left{background-position:0;background-repeat:no-repeat}@media screen and (min-width:30em){.swagger-ui .bg-center-ns{background-position:50%;background-repeat:no-repeat}.swagger-ui .bg-top-ns{background-position:top;background-repeat:no-repeat}.swagger-ui .bg-right-ns{background-position:100%;background-repeat:no-repeat}.swagger-ui .bg-bottom-ns{background-position:bottom;background-repeat:no-repeat}.swagger-ui .bg-left-ns{background-position:0;background-repeat:no-repeat}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .bg-center-m{background-position:50%;background-repeat:no-repeat}.swagger-ui .bg-top-m{background-position:top;background-repeat:no-repeat}.swagger-ui .bg-right-m{background-position:100%;background-repeat:no-repeat}.swagger-ui .bg-bottom-m{background-position:bottom;background-repeat:no-repeat}.swagger-ui .bg-left-m{background-position:0;background-repeat:no-repeat}}@media screen and (min-width:60em){.swagger-ui .bg-center-l{background-position:50%;background-repeat:no-repeat}.swagger-ui .bg-top-l{background-position:top;background-repeat:no-repeat}.swagger-ui .bg-right-l{background-position:100%;background-repeat:no-repeat}.swagger-ui .bg-bottom-l{background-position:bottom;background-repeat:no-repeat}.swagger-ui .bg-left-l{background-position:0;background-repeat:no-repeat}}.swagger-ui .outline{outline:1px solid}.swagger-ui .outline-transparent{outline:1px solid transparent}.swagger-ui .outline-0{outline:0}@media screen and (min-width:30em){.swagger-ui .outline-ns{outline:1px solid}.swagger-ui .outline-transparent-ns{outline:1px solid transparent}.swagger-ui .outline-0-ns{outline:0}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .outline-m{outline:1px solid}.swagger-ui .outline-transparent-m{outline:1px solid transparent}.swagger-ui .outline-0-m{outline:0}}@media screen and (min-width:60em){.swagger-ui .outline-l{outline:1px solid}.swagger-ui .outline-transparent-l{outline:1px solid transparent}.swagger-ui .outline-0-l{outline:0}}.swagger-ui .ba{border-style:solid;border-width:1px}.swagger-ui .bt{border-top-style:solid;border-top-width:1px}.swagger-ui .br{border-right-style:solid;border-right-width:1px}.swagger-ui .bb{border-bottom-style:solid;border-bottom-width:1px}.swagger-ui .bl{border-left-style:solid;border-left-width:1px}.swagger-ui .bn{border-style:none;border-width:0}@media screen and (min-width:30em){.swagger-ui .ba-ns{border-style:solid;border-width:1px}.swagger-ui .bt-ns{border-top-style:solid;border-top-width:1px}.swagger-ui .br-ns{border-right-style:solid;border-right-width:1px}.swagger-ui .bb-ns{border-bottom-style:solid;border-bottom-width:1px}.swagger-ui .bl-ns{border-left-style:solid;border-left-width:1px}.swagger-ui .bn-ns{border-style:none;border-width:0}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .ba-m{border-style:solid;border-width:1px}.swagger-ui .bt-m{border-top-style:solid;border-top-width:1px}.swagger-ui .br-m{border-right-style:solid;border-right-width:1px}.swagger-ui .bb-m{border-bottom-style:solid;border-bottom-width:1px}.swagger-ui .bl-m{border-left-style:solid;border-left-width:1px}.swagger-ui .bn-m{border-style:none;border-width:0}}@media screen and (min-width:60em){.swagger-ui .ba-l{border-style:solid;border-width:1px}.swagger-ui .bt-l{border-top-style:solid;border-top-width:1px}.swagger-ui .br-l{border-right-style:solid;border-right-width:1px}.swagger-ui .bb-l{border-bottom-style:solid;border-bottom-width:1px}.swagger-ui .bl-l{border-left-style:solid;border-left-width:1px}.swagger-ui .bn-l{border-style:none;border-width:0}}.swagger-ui .b--black{border-color:#000}.swagger-ui .b--near-black{border-color:#111}.swagger-ui .b--dark-gray{border-color:#333}.swagger-ui .b--mid-gray{border-color:#555}.swagger-ui .b--gray{border-color:#777}.swagger-ui .b--silver{border-color:#999}.swagger-ui .b--light-silver{border-color:#aaa}.swagger-ui .b--moon-gray{border-color:#ccc}.swagger-ui .b--light-gray{border-color:#eee}.swagger-ui .b--near-white{border-color:#f4f4f4}.swagger-ui .b--white{border-color:#fff}.swagger-ui .b--white-90{border-color:hsla(0,0%,100%,.9)}.swagger-ui .b--white-80{border-color:hsla(0,0%,100%,.8)}.swagger-ui .b--white-70{border-color:hsla(0,0%,100%,.7)}.swagger-ui .b--white-60{border-color:hsla(0,0%,100%,.6)}.swagger-ui .b--white-50{border-color:hsla(0,0%,100%,.5)}.swagger-ui .b--white-40{border-color:hsla(0,0%,100%,.4)}.swagger-ui .b--white-30{border-color:hsla(0,0%,100%,.3)}.swagger-ui .b--white-20{border-color:hsla(0,0%,100%,.2)}.swagger-ui .b--white-10{border-color:hsla(0,0%,100%,.1)}.swagger-ui .b--white-05{border-color:hsla(0,0%,100%,.05)}.swagger-ui .b--white-025{border-color:hsla(0,0%,100%,.025)}.swagger-ui .b--white-0125{border-color:hsla(0,0%,100%,.013)}.swagger-ui .b--black-90{border-color:rgba(0,0,0,.9)}.swagger-ui .b--black-80{border-color:rgba(0,0,0,.8)}.swagger-ui .b--black-70{border-color:rgba(0,0,0,.7)}.swagger-ui .b--black-60{border-color:rgba(0,0,0,.6)}.swagger-ui .b--black-50{border-color:rgba(0,0,0,.5)}.swagger-ui .b--black-40{border-color:rgba(0,0,0,.4)}.swagger-ui .b--black-30{border-color:rgba(0,0,0,.3)}.swagger-ui .b--black-20{border-color:rgba(0,0,0,.2)}.swagger-ui .b--black-10{border-color:rgba(0,0,0,.1)}.swagger-ui .b--black-05{border-color:rgba(0,0,0,.05)}.swagger-ui .b--black-025{border-color:rgba(0,0,0,.025)}.swagger-ui .b--black-0125{border-color:rgba(0,0,0,.013)}.swagger-ui .b--dark-red{border-color:#e7040f}.swagger-ui .b--red{border-color:#ff4136}.swagger-ui .b--light-red{border-color:#ff725c}.swagger-ui .b--orange{border-color:#ff6300}.swagger-ui .b--gold{border-color:#ffb700}.swagger-ui .b--yellow{border-color:gold}.swagger-ui .b--light-yellow{border-color:#fbf1a9}.swagger-ui .b--purple{border-color:#5e2ca5}.swagger-ui .b--light-purple{border-color:#a463f2}.swagger-ui .b--dark-pink{border-color:#d5008f}.swagger-ui .b--hot-pink{border-color:#ff41b4}.swagger-ui .b--pink{border-color:#ff80cc}.swagger-ui .b--light-pink{border-color:#ffa3d7}.swagger-ui .b--dark-green{border-color:#137752}.swagger-ui .b--green{border-color:#19a974}.swagger-ui .b--light-green{border-color:#9eebcf}.swagger-ui .b--navy{border-color:#001b44}.swagger-ui .b--dark-blue{border-color:#00449e}.swagger-ui .b--blue{border-color:#357edd}.swagger-ui .b--light-blue{border-color:#96ccff}.swagger-ui .b--lightest-blue{border-color:#cdecff}.swagger-ui .b--washed-blue{border-color:#f6fffe}.swagger-ui .b--washed-green{border-color:#e8fdf5}.swagger-ui .b--washed-yellow{border-color:#fffceb}.swagger-ui .b--washed-red{border-color:#ffdfdf}.swagger-ui .b--transparent{border-color:transparent}.swagger-ui .b--inherit{border-color:inherit}.swagger-ui .br0{border-radius:0}.swagger-ui .br1{border-radius:.125rem}.swagger-ui .br2{border-radius:.25rem}.swagger-ui .br3{border-radius:.5rem}.swagger-ui .br4{border-radius:1rem}.swagger-ui .br-100{border-radius:100%}.swagger-ui .br-pill{border-radius:9999px}.swagger-ui .br--bottom{border-top-left-radius:0;border-top-right-radius:0}.swagger-ui .br--top{border-bottom-left-radius:0;border-bottom-right-radius:0}.swagger-ui .br--right{border-bottom-left-radius:0;border-top-left-radius:0}.swagger-ui .br--left{border-bottom-right-radius:0;border-top-right-radius:0}@media screen and (min-width:30em){.swagger-ui .br0-ns{border-radius:0}.swagger-ui .br1-ns{border-radius:.125rem}.swagger-ui .br2-ns{border-radius:.25rem}.swagger-ui .br3-ns{border-radius:.5rem}.swagger-ui .br4-ns{border-radius:1rem}.swagger-ui .br-100-ns{border-radius:100%}.swagger-ui .br-pill-ns{border-radius:9999px}.swagger-ui .br--bottom-ns{border-top-left-radius:0;border-top-right-radius:0}.swagger-ui .br--top-ns{border-bottom-left-radius:0;border-bottom-right-radius:0}.swagger-ui .br--right-ns{border-bottom-left-radius:0;border-top-left-radius:0}.swagger-ui .br--left-ns{border-bottom-right-radius:0;border-top-right-radius:0}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .br0-m{border-radius:0}.swagger-ui .br1-m{border-radius:.125rem}.swagger-ui .br2-m{border-radius:.25rem}.swagger-ui .br3-m{border-radius:.5rem}.swagger-ui .br4-m{border-radius:1rem}.swagger-ui .br-100-m{border-radius:100%}.swagger-ui .br-pill-m{border-radius:9999px}.swagger-ui .br--bottom-m{border-top-left-radius:0;border-top-right-radius:0}.swagger-ui .br--top-m{border-bottom-left-radius:0;border-bottom-right-radius:0}.swagger-ui .br--right-m{border-bottom-left-radius:0;border-top-left-radius:0}.swagger-ui .br--left-m{border-bottom-right-radius:0;border-top-right-radius:0}}@media screen and (min-width:60em){.swagger-ui .br0-l{border-radius:0}.swagger-ui .br1-l{border-radius:.125rem}.swagger-ui .br2-l{border-radius:.25rem}.swagger-ui .br3-l{border-radius:.5rem}.swagger-ui .br4-l{border-radius:1rem}.swagger-ui .br-100-l{border-radius:100%}.swagger-ui .br-pill-l{border-radius:9999px}.swagger-ui .br--bottom-l{border-top-left-radius:0;border-top-right-radius:0}.swagger-ui .br--top-l{border-bottom-left-radius:0;border-bottom-right-radius:0}.swagger-ui .br--right-l{border-bottom-left-radius:0;border-top-left-radius:0}.swagger-ui .br--left-l{border-bottom-right-radius:0;border-top-right-radius:0}}.swagger-ui .b--dotted{border-style:dotted}.swagger-ui .b--dashed{border-style:dashed}.swagger-ui .b--solid{border-style:solid}.swagger-ui .b--none{border-style:none}@media screen and (min-width:30em){.swagger-ui .b--dotted-ns{border-style:dotted}.swagger-ui .b--dashed-ns{border-style:dashed}.swagger-ui .b--solid-ns{border-style:solid}.swagger-ui .b--none-ns{border-style:none}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .b--dotted-m{border-style:dotted}.swagger-ui .b--dashed-m{border-style:dashed}.swagger-ui .b--solid-m{border-style:solid}.swagger-ui .b--none-m{border-style:none}}@media screen and (min-width:60em){.swagger-ui .b--dotted-l{border-style:dotted}.swagger-ui .b--dashed-l{border-style:dashed}.swagger-ui .b--solid-l{border-style:solid}.swagger-ui .b--none-l{border-style:none}}.swagger-ui .bw0{border-width:0}.swagger-ui .bw1{border-width:.125rem}.swagger-ui .bw2{border-width:.25rem}.swagger-ui .bw3{border-width:.5rem}.swagger-ui .bw4{border-width:1rem}.swagger-ui .bw5{border-width:2rem}.swagger-ui .bt-0{border-top-width:0}.swagger-ui .br-0{border-right-width:0}.swagger-ui .bb-0{border-bottom-width:0}.swagger-ui .bl-0{border-left-width:0}@media screen and (min-width:30em){.swagger-ui .bw0-ns{border-width:0}.swagger-ui .bw1-ns{border-width:.125rem}.swagger-ui .bw2-ns{border-width:.25rem}.swagger-ui .bw3-ns{border-width:.5rem}.swagger-ui .bw4-ns{border-width:1rem}.swagger-ui .bw5-ns{border-width:2rem}.swagger-ui .bt-0-ns{border-top-width:0}.swagger-ui .br-0-ns{border-right-width:0}.swagger-ui .bb-0-ns{border-bottom-width:0}.swagger-ui .bl-0-ns{border-left-width:0}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .bw0-m{border-width:0}.swagger-ui .bw1-m{border-width:.125rem}.swagger-ui .bw2-m{border-width:.25rem}.swagger-ui .bw3-m{border-width:.5rem}.swagger-ui .bw4-m{border-width:1rem}.swagger-ui .bw5-m{border-width:2rem}.swagger-ui .bt-0-m{border-top-width:0}.swagger-ui .br-0-m{border-right-width:0}.swagger-ui .bb-0-m{border-bottom-width:0}.swagger-ui .bl-0-m{border-left-width:0}}@media screen and (min-width:60em){.swagger-ui .bw0-l{border-width:0}.swagger-ui .bw1-l{border-width:.125rem}.swagger-ui .bw2-l{border-width:.25rem}.swagger-ui .bw3-l{border-width:.5rem}.swagger-ui .bw4-l{border-width:1rem}.swagger-ui .bw5-l{border-width:2rem}.swagger-ui .bt-0-l{border-top-width:0}.swagger-ui .br-0-l{border-right-width:0}.swagger-ui .bb-0-l{border-bottom-width:0}.swagger-ui .bl-0-l{border-left-width:0}}.swagger-ui .shadow-1{box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-2{box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-3{box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-4{box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.swagger-ui .shadow-5{box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}@media screen and (min-width:30em){.swagger-ui .shadow-1-ns{box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-2-ns{box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-3-ns{box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-4-ns{box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.swagger-ui .shadow-5-ns{box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .shadow-1-m{box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-2-m{box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-3-m{box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-4-m{box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.swagger-ui .shadow-5-m{box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}}@media screen and (min-width:60em){.swagger-ui .shadow-1-l{box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-2-l{box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-3-l{box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-4-l{box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.swagger-ui .shadow-5-l{box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}}.swagger-ui .pre{overflow-x:auto;overflow-y:hidden;overflow:scroll}.swagger-ui .top-0{top:0}.swagger-ui .right-0{right:0}.swagger-ui .bottom-0{bottom:0}.swagger-ui .left-0{left:0}.swagger-ui .top-1{top:1rem}.swagger-ui .right-1{right:1rem}.swagger-ui .bottom-1{bottom:1rem}.swagger-ui .left-1{left:1rem}.swagger-ui .top-2{top:2rem}.swagger-ui .right-2{right:2rem}.swagger-ui .bottom-2{bottom:2rem}.swagger-ui .left-2{left:2rem}.swagger-ui .top--1{top:-1rem}.swagger-ui .right--1{right:-1rem}.swagger-ui .bottom--1{bottom:-1rem}.swagger-ui .left--1{left:-1rem}.swagger-ui .top--2{top:-2rem}.swagger-ui .right--2{right:-2rem}.swagger-ui .bottom--2{bottom:-2rem}.swagger-ui .left--2{left:-2rem}.swagger-ui .absolute--fill{bottom:0;left:0;right:0;top:0}@media screen and (min-width:30em){.swagger-ui .top-0-ns{top:0}.swagger-ui .left-0-ns{left:0}.swagger-ui .right-0-ns{right:0}.swagger-ui .bottom-0-ns{bottom:0}.swagger-ui .top-1-ns{top:1rem}.swagger-ui .left-1-ns{left:1rem}.swagger-ui .right-1-ns{right:1rem}.swagger-ui .bottom-1-ns{bottom:1rem}.swagger-ui .top-2-ns{top:2rem}.swagger-ui .left-2-ns{left:2rem}.swagger-ui .right-2-ns{right:2rem}.swagger-ui .bottom-2-ns{bottom:2rem}.swagger-ui .top--1-ns{top:-1rem}.swagger-ui .right--1-ns{right:-1rem}.swagger-ui .bottom--1-ns{bottom:-1rem}.swagger-ui .left--1-ns{left:-1rem}.swagger-ui .top--2-ns{top:-2rem}.swagger-ui .right--2-ns{right:-2rem}.swagger-ui .bottom--2-ns{bottom:-2rem}.swagger-ui .left--2-ns{left:-2rem}.swagger-ui .absolute--fill-ns{bottom:0;left:0;right:0;top:0}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .top-0-m{top:0}.swagger-ui .left-0-m{left:0}.swagger-ui .right-0-m{right:0}.swagger-ui .bottom-0-m{bottom:0}.swagger-ui .top-1-m{top:1rem}.swagger-ui .left-1-m{left:1rem}.swagger-ui .right-1-m{right:1rem}.swagger-ui .bottom-1-m{bottom:1rem}.swagger-ui .top-2-m{top:2rem}.swagger-ui .left-2-m{left:2rem}.swagger-ui .right-2-m{right:2rem}.swagger-ui .bottom-2-m{bottom:2rem}.swagger-ui .top--1-m{top:-1rem}.swagger-ui .right--1-m{right:-1rem}.swagger-ui .bottom--1-m{bottom:-1rem}.swagger-ui .left--1-m{left:-1rem}.swagger-ui .top--2-m{top:-2rem}.swagger-ui .right--2-m{right:-2rem}.swagger-ui .bottom--2-m{bottom:-2rem}.swagger-ui .left--2-m{left:-2rem}.swagger-ui .absolute--fill-m{bottom:0;left:0;right:0;top:0}}@media screen and (min-width:60em){.swagger-ui .top-0-l{top:0}.swagger-ui .left-0-l{left:0}.swagger-ui .right-0-l{right:0}.swagger-ui .bottom-0-l{bottom:0}.swagger-ui .top-1-l{top:1rem}.swagger-ui .left-1-l{left:1rem}.swagger-ui .right-1-l{right:1rem}.swagger-ui .bottom-1-l{bottom:1rem}.swagger-ui .top-2-l{top:2rem}.swagger-ui .left-2-l{left:2rem}.swagger-ui .right-2-l{right:2rem}.swagger-ui .bottom-2-l{bottom:2rem}.swagger-ui .top--1-l{top:-1rem}.swagger-ui .right--1-l{right:-1rem}.swagger-ui .bottom--1-l{bottom:-1rem}.swagger-ui .left--1-l{left:-1rem}.swagger-ui .top--2-l{top:-2rem}.swagger-ui .right--2-l{right:-2rem}.swagger-ui .bottom--2-l{bottom:-2rem}.swagger-ui .left--2-l{left:-2rem}.swagger-ui .absolute--fill-l{bottom:0;left:0;right:0;top:0}}.swagger-ui .cf:after,.swagger-ui .cf:before{content:" ";display:table}.swagger-ui .cf:after{clear:both}.swagger-ui .cf{zoom:1}.swagger-ui .cl{clear:left}.swagger-ui .cr{clear:right}.swagger-ui .cb{clear:both}.swagger-ui .cn{clear:none}@media screen and (min-width:30em){.swagger-ui .cl-ns{clear:left}.swagger-ui .cr-ns{clear:right}.swagger-ui .cb-ns{clear:both}.swagger-ui .cn-ns{clear:none}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .cl-m{clear:left}.swagger-ui .cr-m{clear:right}.swagger-ui .cb-m{clear:both}.swagger-ui .cn-m{clear:none}}@media screen and (min-width:60em){.swagger-ui .cl-l{clear:left}.swagger-ui .cr-l{clear:right}.swagger-ui .cb-l{clear:both}.swagger-ui .cn-l{clear:none}}.swagger-ui .flex{display:flex}.swagger-ui .inline-flex{display:inline-flex}.swagger-ui .flex-auto{flex:1 1 auto;min-height:0;min-width:0}.swagger-ui .flex-none{flex:none}.swagger-ui .flex-column{flex-direction:column}.swagger-ui .flex-row{flex-direction:row}.swagger-ui .flex-wrap{flex-wrap:wrap}.swagger-ui .flex-nowrap{flex-wrap:nowrap}.swagger-ui .flex-wrap-reverse{flex-wrap:wrap-reverse}.swagger-ui .flex-column-reverse{flex-direction:column-reverse}.swagger-ui .flex-row-reverse{flex-direction:row-reverse}.swagger-ui .items-start{align-items:flex-start}.swagger-ui .items-end{align-items:flex-end}.swagger-ui .items-center{align-items:center}.swagger-ui .items-baseline{align-items:baseline}.swagger-ui .items-stretch{align-items:stretch}.swagger-ui .self-start{align-self:flex-start}.swagger-ui .self-end{align-self:flex-end}.swagger-ui .self-center{align-self:center}.swagger-ui .self-baseline{align-self:baseline}.swagger-ui .self-stretch{align-self:stretch}.swagger-ui .justify-start{justify-content:flex-start}.swagger-ui .justify-end{justify-content:flex-end}.swagger-ui .justify-center{justify-content:center}.swagger-ui .justify-between{justify-content:space-between}.swagger-ui .justify-around{justify-content:space-around}.swagger-ui .content-start{align-content:flex-start}.swagger-ui .content-end{align-content:flex-end}.swagger-ui .content-center{align-content:center}.swagger-ui .content-between{align-content:space-between}.swagger-ui .content-around{align-content:space-around}.swagger-ui .content-stretch{align-content:stretch}.swagger-ui .order-0{order:0}.swagger-ui .order-1{order:1}.swagger-ui .order-2{order:2}.swagger-ui .order-3{order:3}.swagger-ui .order-4{order:4}.swagger-ui .order-5{order:5}.swagger-ui .order-6{order:6}.swagger-ui .order-7{order:7}.swagger-ui .order-8{order:8}.swagger-ui .order-last{order:99999}.swagger-ui .flex-grow-0{flex-grow:0}.swagger-ui .flex-grow-1{flex-grow:1}.swagger-ui .flex-shrink-0{flex-shrink:0}.swagger-ui .flex-shrink-1{flex-shrink:1}@media screen and (min-width:30em){.swagger-ui .flex-ns{display:flex}.swagger-ui .inline-flex-ns{display:inline-flex}.swagger-ui .flex-auto-ns{flex:1 1 auto;min-height:0;min-width:0}.swagger-ui .flex-none-ns{flex:none}.swagger-ui .flex-column-ns{flex-direction:column}.swagger-ui .flex-row-ns{flex-direction:row}.swagger-ui .flex-wrap-ns{flex-wrap:wrap}.swagger-ui .flex-nowrap-ns{flex-wrap:nowrap}.swagger-ui .flex-wrap-reverse-ns{flex-wrap:wrap-reverse}.swagger-ui .flex-column-reverse-ns{flex-direction:column-reverse}.swagger-ui .flex-row-reverse-ns{flex-direction:row-reverse}.swagger-ui .items-start-ns{align-items:flex-start}.swagger-ui .items-end-ns{align-items:flex-end}.swagger-ui .items-center-ns{align-items:center}.swagger-ui .items-baseline-ns{align-items:baseline}.swagger-ui .items-stretch-ns{align-items:stretch}.swagger-ui .self-start-ns{align-self:flex-start}.swagger-ui .self-end-ns{align-self:flex-end}.swagger-ui .self-center-ns{align-self:center}.swagger-ui .self-baseline-ns{align-self:baseline}.swagger-ui .self-stretch-ns{align-self:stretch}.swagger-ui .justify-start-ns{justify-content:flex-start}.swagger-ui .justify-end-ns{justify-content:flex-end}.swagger-ui .justify-center-ns{justify-content:center}.swagger-ui .justify-between-ns{justify-content:space-between}.swagger-ui .justify-around-ns{justify-content:space-around}.swagger-ui .content-start-ns{align-content:flex-start}.swagger-ui .content-end-ns{align-content:flex-end}.swagger-ui .content-center-ns{align-content:center}.swagger-ui .content-between-ns{align-content:space-between}.swagger-ui .content-around-ns{align-content:space-around}.swagger-ui .content-stretch-ns{align-content:stretch}.swagger-ui .order-0-ns{order:0}.swagger-ui .order-1-ns{order:1}.swagger-ui .order-2-ns{order:2}.swagger-ui .order-3-ns{order:3}.swagger-ui .order-4-ns{order:4}.swagger-ui .order-5-ns{order:5}.swagger-ui .order-6-ns{order:6}.swagger-ui .order-7-ns{order:7}.swagger-ui .order-8-ns{order:8}.swagger-ui .order-last-ns{order:99999}.swagger-ui .flex-grow-0-ns{flex-grow:0}.swagger-ui .flex-grow-1-ns{flex-grow:1}.swagger-ui .flex-shrink-0-ns{flex-shrink:0}.swagger-ui .flex-shrink-1-ns{flex-shrink:1}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .flex-m{display:flex}.swagger-ui .inline-flex-m{display:inline-flex}.swagger-ui .flex-auto-m{flex:1 1 auto;min-height:0;min-width:0}.swagger-ui .flex-none-m{flex:none}.swagger-ui .flex-column-m{flex-direction:column}.swagger-ui .flex-row-m{flex-direction:row}.swagger-ui .flex-wrap-m{flex-wrap:wrap}.swagger-ui .flex-nowrap-m{flex-wrap:nowrap}.swagger-ui .flex-wrap-reverse-m{flex-wrap:wrap-reverse}.swagger-ui .flex-column-reverse-m{flex-direction:column-reverse}.swagger-ui .flex-row-reverse-m{flex-direction:row-reverse}.swagger-ui .items-start-m{align-items:flex-start}.swagger-ui .items-end-m{align-items:flex-end}.swagger-ui .items-center-m{align-items:center}.swagger-ui .items-baseline-m{align-items:baseline}.swagger-ui .items-stretch-m{align-items:stretch}.swagger-ui .self-start-m{align-self:flex-start}.swagger-ui .self-end-m{align-self:flex-end}.swagger-ui .self-center-m{align-self:center}.swagger-ui .self-baseline-m{align-self:baseline}.swagger-ui .self-stretch-m{align-self:stretch}.swagger-ui .justify-start-m{justify-content:flex-start}.swagger-ui .justify-end-m{justify-content:flex-end}.swagger-ui .justify-center-m{justify-content:center}.swagger-ui .justify-between-m{justify-content:space-between}.swagger-ui .justify-around-m{justify-content:space-around}.swagger-ui .content-start-m{align-content:flex-start}.swagger-ui .content-end-m{align-content:flex-end}.swagger-ui .content-center-m{align-content:center}.swagger-ui .content-between-m{align-content:space-between}.swagger-ui .content-around-m{align-content:space-around}.swagger-ui .content-stretch-m{align-content:stretch}.swagger-ui .order-0-m{order:0}.swagger-ui .order-1-m{order:1}.swagger-ui .order-2-m{order:2}.swagger-ui .order-3-m{order:3}.swagger-ui .order-4-m{order:4}.swagger-ui .order-5-m{order:5}.swagger-ui .order-6-m{order:6}.swagger-ui .order-7-m{order:7}.swagger-ui .order-8-m{order:8}.swagger-ui .order-last-m{order:99999}.swagger-ui .flex-grow-0-m{flex-grow:0}.swagger-ui .flex-grow-1-m{flex-grow:1}.swagger-ui .flex-shrink-0-m{flex-shrink:0}.swagger-ui .flex-shrink-1-m{flex-shrink:1}}@media screen and (min-width:60em){.swagger-ui .flex-l{display:flex}.swagger-ui .inline-flex-l{display:inline-flex}.swagger-ui .flex-auto-l{flex:1 1 auto;min-height:0;min-width:0}.swagger-ui .flex-none-l{flex:none}.swagger-ui .flex-column-l{flex-direction:column}.swagger-ui .flex-row-l{flex-direction:row}.swagger-ui .flex-wrap-l{flex-wrap:wrap}.swagger-ui .flex-nowrap-l{flex-wrap:nowrap}.swagger-ui .flex-wrap-reverse-l{flex-wrap:wrap-reverse}.swagger-ui .flex-column-reverse-l{flex-direction:column-reverse}.swagger-ui .flex-row-reverse-l{flex-direction:row-reverse}.swagger-ui .items-start-l{align-items:flex-start}.swagger-ui .items-end-l{align-items:flex-end}.swagger-ui .items-center-l{align-items:center}.swagger-ui .items-baseline-l{align-items:baseline}.swagger-ui .items-stretch-l{align-items:stretch}.swagger-ui .self-start-l{align-self:flex-start}.swagger-ui .self-end-l{align-self:flex-end}.swagger-ui .self-center-l{align-self:center}.swagger-ui .self-baseline-l{align-self:baseline}.swagger-ui .self-stretch-l{align-self:stretch}.swagger-ui .justify-start-l{justify-content:flex-start}.swagger-ui .justify-end-l{justify-content:flex-end}.swagger-ui .justify-center-l{justify-content:center}.swagger-ui .justify-between-l{justify-content:space-between}.swagger-ui .justify-around-l{justify-content:space-around}.swagger-ui .content-start-l{align-content:flex-start}.swagger-ui .content-end-l{align-content:flex-end}.swagger-ui .content-center-l{align-content:center}.swagger-ui .content-between-l{align-content:space-between}.swagger-ui .content-around-l{align-content:space-around}.swagger-ui .content-stretch-l{align-content:stretch}.swagger-ui .order-0-l{order:0}.swagger-ui .order-1-l{order:1}.swagger-ui .order-2-l{order:2}.swagger-ui .order-3-l{order:3}.swagger-ui .order-4-l{order:4}.swagger-ui .order-5-l{order:5}.swagger-ui .order-6-l{order:6}.swagger-ui .order-7-l{order:7}.swagger-ui .order-8-l{order:8}.swagger-ui .order-last-l{order:99999}.swagger-ui .flex-grow-0-l{flex-grow:0}.swagger-ui .flex-grow-1-l{flex-grow:1}.swagger-ui .flex-shrink-0-l{flex-shrink:0}.swagger-ui .flex-shrink-1-l{flex-shrink:1}}.swagger-ui .dn{display:none}.swagger-ui .di{display:inline}.swagger-ui .db{display:block}.swagger-ui .dib{display:inline-block}.swagger-ui .dit{display:inline-table}.swagger-ui .dt{display:table}.swagger-ui .dtc{display:table-cell}.swagger-ui .dt-row{display:table-row}.swagger-ui .dt-row-group{display:table-row-group}.swagger-ui .dt-column{display:table-column}.swagger-ui .dt-column-group{display:table-column-group}.swagger-ui .dt--fixed{table-layout:fixed;width:100%}@media screen and (min-width:30em){.swagger-ui .dn-ns{display:none}.swagger-ui .di-ns{display:inline}.swagger-ui .db-ns{display:block}.swagger-ui .dib-ns{display:inline-block}.swagger-ui .dit-ns{display:inline-table}.swagger-ui .dt-ns{display:table}.swagger-ui .dtc-ns{display:table-cell}.swagger-ui .dt-row-ns{display:table-row}.swagger-ui .dt-row-group-ns{display:table-row-group}.swagger-ui .dt-column-ns{display:table-column}.swagger-ui .dt-column-group-ns{display:table-column-group}.swagger-ui .dt--fixed-ns{table-layout:fixed;width:100%}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .dn-m{display:none}.swagger-ui .di-m{display:inline}.swagger-ui .db-m{display:block}.swagger-ui .dib-m{display:inline-block}.swagger-ui .dit-m{display:inline-table}.swagger-ui .dt-m{display:table}.swagger-ui .dtc-m{display:table-cell}.swagger-ui .dt-row-m{display:table-row}.swagger-ui .dt-row-group-m{display:table-row-group}.swagger-ui .dt-column-m{display:table-column}.swagger-ui .dt-column-group-m{display:table-column-group}.swagger-ui .dt--fixed-m{table-layout:fixed;width:100%}}@media screen and (min-width:60em){.swagger-ui .dn-l{display:none}.swagger-ui .di-l{display:inline}.swagger-ui .db-l{display:block}.swagger-ui .dib-l{display:inline-block}.swagger-ui .dit-l{display:inline-table}.swagger-ui .dt-l{display:table}.swagger-ui .dtc-l{display:table-cell}.swagger-ui .dt-row-l{display:table-row}.swagger-ui .dt-row-group-l{display:table-row-group}.swagger-ui .dt-column-l{display:table-column}.swagger-ui .dt-column-group-l{display:table-column-group}.swagger-ui .dt--fixed-l{table-layout:fixed;width:100%}}.swagger-ui .fl{_display:inline;float:left}.swagger-ui .fr{_display:inline;float:right}.swagger-ui .fn{float:none}@media screen and (min-width:30em){.swagger-ui .fl-ns{_display:inline;float:left}.swagger-ui .fr-ns{_display:inline;float:right}.swagger-ui .fn-ns{float:none}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .fl-m{_display:inline;float:left}.swagger-ui .fr-m{_display:inline;float:right}.swagger-ui .fn-m{float:none}}@media screen and (min-width:60em){.swagger-ui .fl-l{_display:inline;float:left}.swagger-ui .fr-l{_display:inline;float:right}.swagger-ui .fn-l{float:none}}.swagger-ui .sans-serif{font-family:-apple-system,BlinkMacSystemFont,avenir next,avenir,helvetica,helvetica neue,ubuntu,roboto,noto,segoe ui,arial,sans-serif}.swagger-ui .serif{font-family:georgia,serif}.swagger-ui .system-sans-serif{font-family:sans-serif}.swagger-ui .system-serif{font-family:serif}.swagger-ui .code,.swagger-ui code{font-family:Consolas,monaco,monospace}.swagger-ui .courier{font-family:Courier Next,courier,monospace}.swagger-ui .helvetica{font-family:helvetica neue,helvetica,sans-serif}.swagger-ui .avenir{font-family:avenir next,avenir,sans-serif}.swagger-ui .athelas{font-family:athelas,georgia,serif}.swagger-ui .georgia{font-family:georgia,serif}.swagger-ui .times{font-family:times,serif}.swagger-ui .bodoni{font-family:Bodoni MT,serif}.swagger-ui .calisto{font-family:Calisto MT,serif}.swagger-ui .garamond{font-family:garamond,serif}.swagger-ui .baskerville{font-family:baskerville,serif}.swagger-ui .i{font-style:italic}.swagger-ui .fs-normal{font-style:normal}@media screen and (min-width:30em){.swagger-ui .i-ns{font-style:italic}.swagger-ui .fs-normal-ns{font-style:normal}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .i-m{font-style:italic}.swagger-ui .fs-normal-m{font-style:normal}}@media screen and (min-width:60em){.swagger-ui .i-l{font-style:italic}.swagger-ui .fs-normal-l{font-style:normal}}.swagger-ui .normal{font-weight:400}.swagger-ui .b{font-weight:700}.swagger-ui .fw1{font-weight:100}.swagger-ui .fw2{font-weight:200}.swagger-ui .fw3{font-weight:300}.swagger-ui .fw4{font-weight:400}.swagger-ui .fw5{font-weight:500}.swagger-ui .fw6{font-weight:600}.swagger-ui .fw7{font-weight:700}.swagger-ui .fw8{font-weight:800}.swagger-ui .fw9{font-weight:900}@media screen and (min-width:30em){.swagger-ui .normal-ns{font-weight:400}.swagger-ui .b-ns{font-weight:700}.swagger-ui .fw1-ns{font-weight:100}.swagger-ui .fw2-ns{font-weight:200}.swagger-ui .fw3-ns{font-weight:300}.swagger-ui .fw4-ns{font-weight:400}.swagger-ui .fw5-ns{font-weight:500}.swagger-ui .fw6-ns{font-weight:600}.swagger-ui .fw7-ns{font-weight:700}.swagger-ui .fw8-ns{font-weight:800}.swagger-ui .fw9-ns{font-weight:900}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .normal-m{font-weight:400}.swagger-ui .b-m{font-weight:700}.swagger-ui .fw1-m{font-weight:100}.swagger-ui .fw2-m{font-weight:200}.swagger-ui .fw3-m{font-weight:300}.swagger-ui .fw4-m{font-weight:400}.swagger-ui .fw5-m{font-weight:500}.swagger-ui .fw6-m{font-weight:600}.swagger-ui .fw7-m{font-weight:700}.swagger-ui .fw8-m{font-weight:800}.swagger-ui .fw9-m{font-weight:900}}@media screen and (min-width:60em){.swagger-ui .normal-l{font-weight:400}.swagger-ui .b-l{font-weight:700}.swagger-ui .fw1-l{font-weight:100}.swagger-ui .fw2-l{font-weight:200}.swagger-ui .fw3-l{font-weight:300}.swagger-ui .fw4-l{font-weight:400}.swagger-ui .fw5-l{font-weight:500}.swagger-ui .fw6-l{font-weight:600}.swagger-ui .fw7-l{font-weight:700}.swagger-ui .fw8-l{font-weight:800}.swagger-ui .fw9-l{font-weight:900}}.swagger-ui .input-reset{-webkit-appearance:none;-moz-appearance:none}.swagger-ui .button-reset::-moz-focus-inner,.swagger-ui .input-reset::-moz-focus-inner{border:0;padding:0}.swagger-ui .h1{height:1rem}.swagger-ui .h2{height:2rem}.swagger-ui .h3{height:4rem}.swagger-ui .h4{height:8rem}.swagger-ui .h5{height:16rem}.swagger-ui .h-25{height:25%}.swagger-ui .h-50{height:50%}.swagger-ui .h-75{height:75%}.swagger-ui .h-100{height:100%}.swagger-ui .min-h-100{min-height:100%}.swagger-ui .vh-25{height:25vh}.swagger-ui .vh-50{height:50vh}.swagger-ui .vh-75{height:75vh}.swagger-ui .vh-100{height:100vh}.swagger-ui .min-vh-100{min-height:100vh}.swagger-ui .h-auto{height:auto}.swagger-ui .h-inherit{height:inherit}@media screen and (min-width:30em){.swagger-ui .h1-ns{height:1rem}.swagger-ui .h2-ns{height:2rem}.swagger-ui .h3-ns{height:4rem}.swagger-ui .h4-ns{height:8rem}.swagger-ui .h5-ns{height:16rem}.swagger-ui .h-25-ns{height:25%}.swagger-ui .h-50-ns{height:50%}.swagger-ui .h-75-ns{height:75%}.swagger-ui .h-100-ns{height:100%}.swagger-ui .min-h-100-ns{min-height:100%}.swagger-ui .vh-25-ns{height:25vh}.swagger-ui .vh-50-ns{height:50vh}.swagger-ui .vh-75-ns{height:75vh}.swagger-ui .vh-100-ns{height:100vh}.swagger-ui .min-vh-100-ns{min-height:100vh}.swagger-ui .h-auto-ns{height:auto}.swagger-ui .h-inherit-ns{height:inherit}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .h1-m{height:1rem}.swagger-ui .h2-m{height:2rem}.swagger-ui .h3-m{height:4rem}.swagger-ui .h4-m{height:8rem}.swagger-ui .h5-m{height:16rem}.swagger-ui .h-25-m{height:25%}.swagger-ui .h-50-m{height:50%}.swagger-ui .h-75-m{height:75%}.swagger-ui .h-100-m{height:100%}.swagger-ui .min-h-100-m{min-height:100%}.swagger-ui .vh-25-m{height:25vh}.swagger-ui .vh-50-m{height:50vh}.swagger-ui .vh-75-m{height:75vh}.swagger-ui .vh-100-m{height:100vh}.swagger-ui .min-vh-100-m{min-height:100vh}.swagger-ui .h-auto-m{height:auto}.swagger-ui .h-inherit-m{height:inherit}}@media screen and (min-width:60em){.swagger-ui .h1-l{height:1rem}.swagger-ui .h2-l{height:2rem}.swagger-ui .h3-l{height:4rem}.swagger-ui .h4-l{height:8rem}.swagger-ui .h5-l{height:16rem}.swagger-ui .h-25-l{height:25%}.swagger-ui .h-50-l{height:50%}.swagger-ui .h-75-l{height:75%}.swagger-ui .h-100-l{height:100%}.swagger-ui .min-h-100-l{min-height:100%}.swagger-ui .vh-25-l{height:25vh}.swagger-ui .vh-50-l{height:50vh}.swagger-ui .vh-75-l{height:75vh}.swagger-ui .vh-100-l{height:100vh}.swagger-ui .min-vh-100-l{min-height:100vh}.swagger-ui .h-auto-l{height:auto}.swagger-ui .h-inherit-l{height:inherit}}.swagger-ui .tracked{letter-spacing:.1em}.swagger-ui .tracked-tight{letter-spacing:-.05em}.swagger-ui .tracked-mega{letter-spacing:.25em}@media screen and (min-width:30em){.swagger-ui .tracked-ns{letter-spacing:.1em}.swagger-ui .tracked-tight-ns{letter-spacing:-.05em}.swagger-ui .tracked-mega-ns{letter-spacing:.25em}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .tracked-m{letter-spacing:.1em}.swagger-ui .tracked-tight-m{letter-spacing:-.05em}.swagger-ui .tracked-mega-m{letter-spacing:.25em}}@media screen and (min-width:60em){.swagger-ui .tracked-l{letter-spacing:.1em}.swagger-ui .tracked-tight-l{letter-spacing:-.05em}.swagger-ui .tracked-mega-l{letter-spacing:.25em}}.swagger-ui .lh-solid{line-height:1}.swagger-ui .lh-title{line-height:1.25}.swagger-ui .lh-copy{line-height:1.5}@media screen and (min-width:30em){.swagger-ui .lh-solid-ns{line-height:1}.swagger-ui .lh-title-ns{line-height:1.25}.swagger-ui .lh-copy-ns{line-height:1.5}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .lh-solid-m{line-height:1}.swagger-ui .lh-title-m{line-height:1.25}.swagger-ui .lh-copy-m{line-height:1.5}}@media screen and (min-width:60em){.swagger-ui .lh-solid-l{line-height:1}.swagger-ui .lh-title-l{line-height:1.25}.swagger-ui .lh-copy-l{line-height:1.5}}.swagger-ui .link{-webkit-text-decoration:none;text-decoration:none}.swagger-ui .link,.swagger-ui .link:active,.swagger-ui .link:focus,.swagger-ui .link:hover,.swagger-ui .link:link,.swagger-ui .link:visited{transition:color .15s ease-in}.swagger-ui .link:focus{outline:1px dotted currentColor}.swagger-ui .list{list-style-type:none}.swagger-ui .mw-100{max-width:100%}.swagger-ui .mw1{max-width:1rem}.swagger-ui .mw2{max-width:2rem}.swagger-ui .mw3{max-width:4rem}.swagger-ui .mw4{max-width:8rem}.swagger-ui .mw5{max-width:16rem}.swagger-ui .mw6{max-width:32rem}.swagger-ui .mw7{max-width:48rem}.swagger-ui .mw8{max-width:64rem}.swagger-ui .mw9{max-width:96rem}.swagger-ui .mw-none{max-width:none}@media screen and (min-width:30em){.swagger-ui .mw-100-ns{max-width:100%}.swagger-ui .mw1-ns{max-width:1rem}.swagger-ui .mw2-ns{max-width:2rem}.swagger-ui .mw3-ns{max-width:4rem}.swagger-ui .mw4-ns{max-width:8rem}.swagger-ui .mw5-ns{max-width:16rem}.swagger-ui .mw6-ns{max-width:32rem}.swagger-ui .mw7-ns{max-width:48rem}.swagger-ui .mw8-ns{max-width:64rem}.swagger-ui .mw9-ns{max-width:96rem}.swagger-ui .mw-none-ns{max-width:none}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .mw-100-m{max-width:100%}.swagger-ui .mw1-m{max-width:1rem}.swagger-ui .mw2-m{max-width:2rem}.swagger-ui .mw3-m{max-width:4rem}.swagger-ui .mw4-m{max-width:8rem}.swagger-ui .mw5-m{max-width:16rem}.swagger-ui .mw6-m{max-width:32rem}.swagger-ui .mw7-m{max-width:48rem}.swagger-ui .mw8-m{max-width:64rem}.swagger-ui .mw9-m{max-width:96rem}.swagger-ui .mw-none-m{max-width:none}}@media screen and (min-width:60em){.swagger-ui .mw-100-l{max-width:100%}.swagger-ui .mw1-l{max-width:1rem}.swagger-ui .mw2-l{max-width:2rem}.swagger-ui .mw3-l{max-width:4rem}.swagger-ui .mw4-l{max-width:8rem}.swagger-ui .mw5-l{max-width:16rem}.swagger-ui .mw6-l{max-width:32rem}.swagger-ui .mw7-l{max-width:48rem}.swagger-ui .mw8-l{max-width:64rem}.swagger-ui .mw9-l{max-width:96rem}.swagger-ui .mw-none-l{max-width:none}}.swagger-ui .w1{width:1rem}.swagger-ui .w2{width:2rem}.swagger-ui .w3{width:4rem}.swagger-ui .w4{width:8rem}.swagger-ui .w5{width:16rem}.swagger-ui .w-10{width:10%}.swagger-ui .w-20{width:20%}.swagger-ui .w-25{width:25%}.swagger-ui .w-30{width:30%}.swagger-ui .w-33{width:33%}.swagger-ui .w-34{width:34%}.swagger-ui .w-40{width:40%}.swagger-ui .w-50{width:50%}.swagger-ui .w-60{width:60%}.swagger-ui .w-70{width:70%}.swagger-ui .w-75{width:75%}.swagger-ui .w-80{width:80%}.swagger-ui .w-90{width:90%}.swagger-ui .w-100{width:100%}.swagger-ui .w-third{width:33.3333333333%}.swagger-ui .w-two-thirds{width:66.6666666667%}.swagger-ui .w-auto{width:auto}@media screen and (min-width:30em){.swagger-ui .w1-ns{width:1rem}.swagger-ui .w2-ns{width:2rem}.swagger-ui .w3-ns{width:4rem}.swagger-ui .w4-ns{width:8rem}.swagger-ui .w5-ns{width:16rem}.swagger-ui .w-10-ns{width:10%}.swagger-ui .w-20-ns{width:20%}.swagger-ui .w-25-ns{width:25%}.swagger-ui .w-30-ns{width:30%}.swagger-ui .w-33-ns{width:33%}.swagger-ui .w-34-ns{width:34%}.swagger-ui .w-40-ns{width:40%}.swagger-ui .w-50-ns{width:50%}.swagger-ui .w-60-ns{width:60%}.swagger-ui .w-70-ns{width:70%}.swagger-ui .w-75-ns{width:75%}.swagger-ui .w-80-ns{width:80%}.swagger-ui .w-90-ns{width:90%}.swagger-ui .w-100-ns{width:100%}.swagger-ui .w-third-ns{width:33.3333333333%}.swagger-ui .w-two-thirds-ns{width:66.6666666667%}.swagger-ui .w-auto-ns{width:auto}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .w1-m{width:1rem}.swagger-ui .w2-m{width:2rem}.swagger-ui .w3-m{width:4rem}.swagger-ui .w4-m{width:8rem}.swagger-ui .w5-m{width:16rem}.swagger-ui .w-10-m{width:10%}.swagger-ui .w-20-m{width:20%}.swagger-ui .w-25-m{width:25%}.swagger-ui .w-30-m{width:30%}.swagger-ui .w-33-m{width:33%}.swagger-ui .w-34-m{width:34%}.swagger-ui .w-40-m{width:40%}.swagger-ui .w-50-m{width:50%}.swagger-ui .w-60-m{width:60%}.swagger-ui .w-70-m{width:70%}.swagger-ui .w-75-m{width:75%}.swagger-ui .w-80-m{width:80%}.swagger-ui .w-90-m{width:90%}.swagger-ui .w-100-m{width:100%}.swagger-ui .w-third-m{width:33.3333333333%}.swagger-ui .w-two-thirds-m{width:66.6666666667%}.swagger-ui .w-auto-m{width:auto}}@media screen and (min-width:60em){.swagger-ui .w1-l{width:1rem}.swagger-ui .w2-l{width:2rem}.swagger-ui .w3-l{width:4rem}.swagger-ui .w4-l{width:8rem}.swagger-ui .w5-l{width:16rem}.swagger-ui .w-10-l{width:10%}.swagger-ui .w-20-l{width:20%}.swagger-ui .w-25-l{width:25%}.swagger-ui .w-30-l{width:30%}.swagger-ui .w-33-l{width:33%}.swagger-ui .w-34-l{width:34%}.swagger-ui .w-40-l{width:40%}.swagger-ui .w-50-l{width:50%}.swagger-ui .w-60-l{width:60%}.swagger-ui .w-70-l{width:70%}.swagger-ui .w-75-l{width:75%}.swagger-ui .w-80-l{width:80%}.swagger-ui .w-90-l{width:90%}.swagger-ui .w-100-l{width:100%}.swagger-ui .w-third-l{width:33.3333333333%}.swagger-ui .w-two-thirds-l{width:66.6666666667%}.swagger-ui .w-auto-l{width:auto}}.swagger-ui .overflow-visible{overflow:visible}.swagger-ui .overflow-hidden{overflow:hidden}.swagger-ui .overflow-scroll{overflow:scroll}.swagger-ui .overflow-auto{overflow:auto}.swagger-ui .overflow-x-visible{overflow-x:visible}.swagger-ui .overflow-x-hidden{overflow-x:hidden}.swagger-ui .overflow-x-scroll{overflow-x:scroll}.swagger-ui .overflow-x-auto{overflow-x:auto}.swagger-ui .overflow-y-visible{overflow-y:visible}.swagger-ui .overflow-y-hidden{overflow-y:hidden}.swagger-ui .overflow-y-scroll{overflow-y:scroll}.swagger-ui .overflow-y-auto{overflow-y:auto}@media screen and (min-width:30em){.swagger-ui .overflow-visible-ns{overflow:visible}.swagger-ui .overflow-hidden-ns{overflow:hidden}.swagger-ui .overflow-scroll-ns{overflow:scroll}.swagger-ui .overflow-auto-ns{overflow:auto}.swagger-ui .overflow-x-visible-ns{overflow-x:visible}.swagger-ui .overflow-x-hidden-ns{overflow-x:hidden}.swagger-ui .overflow-x-scroll-ns{overflow-x:scroll}.swagger-ui .overflow-x-auto-ns{overflow-x:auto}.swagger-ui .overflow-y-visible-ns{overflow-y:visible}.swagger-ui .overflow-y-hidden-ns{overflow-y:hidden}.swagger-ui .overflow-y-scroll-ns{overflow-y:scroll}.swagger-ui .overflow-y-auto-ns{overflow-y:auto}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .overflow-visible-m{overflow:visible}.swagger-ui .overflow-hidden-m{overflow:hidden}.swagger-ui .overflow-scroll-m{overflow:scroll}.swagger-ui .overflow-auto-m{overflow:auto}.swagger-ui .overflow-x-visible-m{overflow-x:visible}.swagger-ui .overflow-x-hidden-m{overflow-x:hidden}.swagger-ui .overflow-x-scroll-m{overflow-x:scroll}.swagger-ui .overflow-x-auto-m{overflow-x:auto}.swagger-ui .overflow-y-visible-m{overflow-y:visible}.swagger-ui .overflow-y-hidden-m{overflow-y:hidden}.swagger-ui .overflow-y-scroll-m{overflow-y:scroll}.swagger-ui .overflow-y-auto-m{overflow-y:auto}}@media screen and (min-width:60em){.swagger-ui .overflow-visible-l{overflow:visible}.swagger-ui .overflow-hidden-l{overflow:hidden}.swagger-ui .overflow-scroll-l{overflow:scroll}.swagger-ui .overflow-auto-l{overflow:auto}.swagger-ui .overflow-x-visible-l{overflow-x:visible}.swagger-ui .overflow-x-hidden-l{overflow-x:hidden}.swagger-ui .overflow-x-scroll-l{overflow-x:scroll}.swagger-ui .overflow-x-auto-l{overflow-x:auto}.swagger-ui .overflow-y-visible-l{overflow-y:visible}.swagger-ui .overflow-y-hidden-l{overflow-y:hidden}.swagger-ui .overflow-y-scroll-l{overflow-y:scroll}.swagger-ui .overflow-y-auto-l{overflow-y:auto}}.swagger-ui .static{position:static}.swagger-ui .relative{position:relative}.swagger-ui .absolute{position:absolute}.swagger-ui .fixed{position:fixed}@media screen and (min-width:30em){.swagger-ui .static-ns{position:static}.swagger-ui .relative-ns{position:relative}.swagger-ui .absolute-ns{position:absolute}.swagger-ui .fixed-ns{position:fixed}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .static-m{position:static}.swagger-ui .relative-m{position:relative}.swagger-ui .absolute-m{position:absolute}.swagger-ui .fixed-m{position:fixed}}@media screen and (min-width:60em){.swagger-ui .static-l{position:static}.swagger-ui .relative-l{position:relative}.swagger-ui .absolute-l{position:absolute}.swagger-ui .fixed-l{position:fixed}}.swagger-ui .o-100{opacity:1}.swagger-ui .o-90{opacity:.9}.swagger-ui .o-80{opacity:.8}.swagger-ui .o-70{opacity:.7}.swagger-ui .o-60{opacity:.6}.swagger-ui .o-50{opacity:.5}.swagger-ui .o-40{opacity:.4}.swagger-ui .o-30{opacity:.3}.swagger-ui .o-20{opacity:.2}.swagger-ui .o-10{opacity:.1}.swagger-ui .o-05{opacity:.05}.swagger-ui .o-025{opacity:.025}.swagger-ui .o-0{opacity:0}.swagger-ui .rotate-45{transform:rotate(45deg)}.swagger-ui .rotate-90{transform:rotate(90deg)}.swagger-ui .rotate-135{transform:rotate(135deg)}.swagger-ui .rotate-180{transform:rotate(180deg)}.swagger-ui .rotate-225{transform:rotate(225deg)}.swagger-ui .rotate-270{transform:rotate(270deg)}.swagger-ui .rotate-315{transform:rotate(315deg)}@media screen and (min-width:30em){.swagger-ui .rotate-45-ns{transform:rotate(45deg)}.swagger-ui .rotate-90-ns{transform:rotate(90deg)}.swagger-ui .rotate-135-ns{transform:rotate(135deg)}.swagger-ui .rotate-180-ns{transform:rotate(180deg)}.swagger-ui .rotate-225-ns{transform:rotate(225deg)}.swagger-ui .rotate-270-ns{transform:rotate(270deg)}.swagger-ui .rotate-315-ns{transform:rotate(315deg)}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .rotate-45-m{transform:rotate(45deg)}.swagger-ui .rotate-90-m{transform:rotate(90deg)}.swagger-ui .rotate-135-m{transform:rotate(135deg)}.swagger-ui .rotate-180-m{transform:rotate(180deg)}.swagger-ui .rotate-225-m{transform:rotate(225deg)}.swagger-ui .rotate-270-m{transform:rotate(270deg)}.swagger-ui .rotate-315-m{transform:rotate(315deg)}}@media screen and (min-width:60em){.swagger-ui .rotate-45-l{transform:rotate(45deg)}.swagger-ui .rotate-90-l{transform:rotate(90deg)}.swagger-ui .rotate-135-l{transform:rotate(135deg)}.swagger-ui .rotate-180-l{transform:rotate(180deg)}.swagger-ui .rotate-225-l{transform:rotate(225deg)}.swagger-ui .rotate-270-l{transform:rotate(270deg)}.swagger-ui .rotate-315-l{transform:rotate(315deg)}}.swagger-ui .black-90{color:rgba(0,0,0,.9)}.swagger-ui .black-80{color:rgba(0,0,0,.8)}.swagger-ui .black-70{color:rgba(0,0,0,.7)}.swagger-ui .black-60{color:rgba(0,0,0,.6)}.swagger-ui .black-50{color:rgba(0,0,0,.5)}.swagger-ui .black-40{color:rgba(0,0,0,.4)}.swagger-ui .black-30{color:rgba(0,0,0,.3)}.swagger-ui .black-20{color:rgba(0,0,0,.2)}.swagger-ui .black-10{color:rgba(0,0,0,.1)}.swagger-ui .black-05{color:rgba(0,0,0,.05)}.swagger-ui .white-90{color:hsla(0,0%,100%,.9)}.swagger-ui .white-80{color:hsla(0,0%,100%,.8)}.swagger-ui .white-70{color:hsla(0,0%,100%,.7)}.swagger-ui .white-60{color:hsla(0,0%,100%,.6)}.swagger-ui .white-50{color:hsla(0,0%,100%,.5)}.swagger-ui .white-40{color:hsla(0,0%,100%,.4)}.swagger-ui .white-30{color:hsla(0,0%,100%,.3)}.swagger-ui .white-20{color:hsla(0,0%,100%,.2)}.swagger-ui .white-10{color:hsla(0,0%,100%,.1)}.swagger-ui .black{color:#000}.swagger-ui .near-black{color:#111}.swagger-ui .dark-gray{color:#333}.swagger-ui .mid-gray{color:#555}.swagger-ui .gray{color:#777}.swagger-ui .silver{color:#999}.swagger-ui .light-silver{color:#aaa}.swagger-ui .moon-gray{color:#ccc}.swagger-ui .light-gray{color:#eee}.swagger-ui .near-white{color:#f4f4f4}.swagger-ui .white{color:#fff}.swagger-ui .dark-red{color:#e7040f}.swagger-ui .red{color:#ff4136}.swagger-ui .light-red{color:#ff725c}.swagger-ui .orange{color:#ff6300}.swagger-ui .gold{color:#ffb700}.swagger-ui .yellow{color:gold}.swagger-ui .light-yellow{color:#fbf1a9}.swagger-ui .purple{color:#5e2ca5}.swagger-ui .light-purple{color:#a463f2}.swagger-ui .dark-pink{color:#d5008f}.swagger-ui .hot-pink{color:#ff41b4}.swagger-ui .pink{color:#ff80cc}.swagger-ui .light-pink{color:#ffa3d7}.swagger-ui .dark-green{color:#137752}.swagger-ui .green{color:#19a974}.swagger-ui .light-green{color:#9eebcf}.swagger-ui .navy{color:#001b44}.swagger-ui .dark-blue{color:#00449e}.swagger-ui .blue{color:#357edd}.swagger-ui .light-blue{color:#96ccff}.swagger-ui .lightest-blue{color:#cdecff}.swagger-ui .washed-blue{color:#f6fffe}.swagger-ui .washed-green{color:#e8fdf5}.swagger-ui .washed-yellow{color:#fffceb}.swagger-ui .washed-red{color:#ffdfdf}.swagger-ui .color-inherit{color:inherit}.swagger-ui .bg-black-90{background-color:rgba(0,0,0,.9)}.swagger-ui .bg-black-80{background-color:rgba(0,0,0,.8)}.swagger-ui .bg-black-70{background-color:rgba(0,0,0,.7)}.swagger-ui .bg-black-60{background-color:rgba(0,0,0,.6)}.swagger-ui .bg-black-50{background-color:rgba(0,0,0,.5)}.swagger-ui .bg-black-40{background-color:rgba(0,0,0,.4)}.swagger-ui .bg-black-30{background-color:rgba(0,0,0,.3)}.swagger-ui .bg-black-20{background-color:rgba(0,0,0,.2)}.swagger-ui .bg-black-10{background-color:rgba(0,0,0,.1)}.swagger-ui .bg-black-05{background-color:rgba(0,0,0,.05)}.swagger-ui .bg-white-90{background-color:hsla(0,0%,100%,.9)}.swagger-ui .bg-white-80{background-color:hsla(0,0%,100%,.8)}.swagger-ui .bg-white-70{background-color:hsla(0,0%,100%,.7)}.swagger-ui .bg-white-60{background-color:hsla(0,0%,100%,.6)}.swagger-ui .bg-white-50{background-color:hsla(0,0%,100%,.5)}.swagger-ui .bg-white-40{background-color:hsla(0,0%,100%,.4)}.swagger-ui .bg-white-30{background-color:hsla(0,0%,100%,.3)}.swagger-ui .bg-white-20{background-color:hsla(0,0%,100%,.2)}.swagger-ui .bg-white-10{background-color:hsla(0,0%,100%,.1)}.swagger-ui .bg-black{background-color:#000}.swagger-ui .bg-near-black{background-color:#111}.swagger-ui .bg-dark-gray{background-color:#333}.swagger-ui .bg-mid-gray{background-color:#555}.swagger-ui .bg-gray{background-color:#777}.swagger-ui .bg-silver{background-color:#999}.swagger-ui .bg-light-silver{background-color:#aaa}.swagger-ui .bg-moon-gray{background-color:#ccc}.swagger-ui .bg-light-gray{background-color:#eee}.swagger-ui .bg-near-white{background-color:#f4f4f4}.swagger-ui .bg-white{background-color:#fff}.swagger-ui .bg-transparent{background-color:transparent}.swagger-ui .bg-dark-red{background-color:#e7040f}.swagger-ui .bg-red{background-color:#ff4136}.swagger-ui .bg-light-red{background-color:#ff725c}.swagger-ui .bg-orange{background-color:#ff6300}.swagger-ui .bg-gold{background-color:#ffb700}.swagger-ui .bg-yellow{background-color:gold}.swagger-ui .bg-light-yellow{background-color:#fbf1a9}.swagger-ui .bg-purple{background-color:#5e2ca5}.swagger-ui .bg-light-purple{background-color:#a463f2}.swagger-ui .bg-dark-pink{background-color:#d5008f}.swagger-ui .bg-hot-pink{background-color:#ff41b4}.swagger-ui .bg-pink{background-color:#ff80cc}.swagger-ui .bg-light-pink{background-color:#ffa3d7}.swagger-ui .bg-dark-green{background-color:#137752}.swagger-ui .bg-green{background-color:#19a974}.swagger-ui .bg-light-green{background-color:#9eebcf}.swagger-ui .bg-navy{background-color:#001b44}.swagger-ui .bg-dark-blue{background-color:#00449e}.swagger-ui .bg-blue{background-color:#357edd}.swagger-ui .bg-light-blue{background-color:#96ccff}.swagger-ui .bg-lightest-blue{background-color:#cdecff}.swagger-ui .bg-washed-blue{background-color:#f6fffe}.swagger-ui .bg-washed-green{background-color:#e8fdf5}.swagger-ui .bg-washed-yellow{background-color:#fffceb}.swagger-ui .bg-washed-red{background-color:#ffdfdf}.swagger-ui .bg-inherit{background-color:inherit}.swagger-ui .hover-black:focus,.swagger-ui .hover-black:hover{color:#000}.swagger-ui .hover-near-black:focus,.swagger-ui .hover-near-black:hover{color:#111}.swagger-ui .hover-dark-gray:focus,.swagger-ui .hover-dark-gray:hover{color:#333}.swagger-ui .hover-mid-gray:focus,.swagger-ui .hover-mid-gray:hover{color:#555}.swagger-ui .hover-gray:focus,.swagger-ui .hover-gray:hover{color:#777}.swagger-ui .hover-silver:focus,.swagger-ui .hover-silver:hover{color:#999}.swagger-ui .hover-light-silver:focus,.swagger-ui .hover-light-silver:hover{color:#aaa}.swagger-ui .hover-moon-gray:focus,.swagger-ui .hover-moon-gray:hover{color:#ccc}.swagger-ui .hover-light-gray:focus,.swagger-ui .hover-light-gray:hover{color:#eee}.swagger-ui .hover-near-white:focus,.swagger-ui .hover-near-white:hover{color:#f4f4f4}.swagger-ui .hover-white:focus,.swagger-ui .hover-white:hover{color:#fff}.swagger-ui .hover-black-90:focus,.swagger-ui .hover-black-90:hover{color:rgba(0,0,0,.9)}.swagger-ui .hover-black-80:focus,.swagger-ui .hover-black-80:hover{color:rgba(0,0,0,.8)}.swagger-ui .hover-black-70:focus,.swagger-ui .hover-black-70:hover{color:rgba(0,0,0,.7)}.swagger-ui .hover-black-60:focus,.swagger-ui .hover-black-60:hover{color:rgba(0,0,0,.6)}.swagger-ui .hover-black-50:focus,.swagger-ui .hover-black-50:hover{color:rgba(0,0,0,.5)}.swagger-ui .hover-black-40:focus,.swagger-ui .hover-black-40:hover{color:rgba(0,0,0,.4)}.swagger-ui .hover-black-30:focus,.swagger-ui .hover-black-30:hover{color:rgba(0,0,0,.3)}.swagger-ui .hover-black-20:focus,.swagger-ui .hover-black-20:hover{color:rgba(0,0,0,.2)}.swagger-ui .hover-black-10:focus,.swagger-ui .hover-black-10:hover{color:rgba(0,0,0,.1)}.swagger-ui .hover-white-90:focus,.swagger-ui .hover-white-90:hover{color:hsla(0,0%,100%,.9)}.swagger-ui .hover-white-80:focus,.swagger-ui .hover-white-80:hover{color:hsla(0,0%,100%,.8)}.swagger-ui .hover-white-70:focus,.swagger-ui .hover-white-70:hover{color:hsla(0,0%,100%,.7)}.swagger-ui .hover-white-60:focus,.swagger-ui .hover-white-60:hover{color:hsla(0,0%,100%,.6)}.swagger-ui .hover-white-50:focus,.swagger-ui .hover-white-50:hover{color:hsla(0,0%,100%,.5)}.swagger-ui .hover-white-40:focus,.swagger-ui .hover-white-40:hover{color:hsla(0,0%,100%,.4)}.swagger-ui .hover-white-30:focus,.swagger-ui .hover-white-30:hover{color:hsla(0,0%,100%,.3)}.swagger-ui .hover-white-20:focus,.swagger-ui .hover-white-20:hover{color:hsla(0,0%,100%,.2)}.swagger-ui .hover-white-10:focus,.swagger-ui .hover-white-10:hover{color:hsla(0,0%,100%,.1)}.swagger-ui .hover-inherit:focus,.swagger-ui .hover-inherit:hover{color:inherit}.swagger-ui .hover-bg-black:focus,.swagger-ui .hover-bg-black:hover{background-color:#000}.swagger-ui .hover-bg-near-black:focus,.swagger-ui .hover-bg-near-black:hover{background-color:#111}.swagger-ui .hover-bg-dark-gray:focus,.swagger-ui .hover-bg-dark-gray:hover{background-color:#333}.swagger-ui .hover-bg-mid-gray:focus,.swagger-ui .hover-bg-mid-gray:hover{background-color:#555}.swagger-ui .hover-bg-gray:focus,.swagger-ui .hover-bg-gray:hover{background-color:#777}.swagger-ui .hover-bg-silver:focus,.swagger-ui .hover-bg-silver:hover{background-color:#999}.swagger-ui .hover-bg-light-silver:focus,.swagger-ui .hover-bg-light-silver:hover{background-color:#aaa}.swagger-ui .hover-bg-moon-gray:focus,.swagger-ui .hover-bg-moon-gray:hover{background-color:#ccc}.swagger-ui .hover-bg-light-gray:focus,.swagger-ui .hover-bg-light-gray:hover{background-color:#eee}.swagger-ui .hover-bg-near-white:focus,.swagger-ui .hover-bg-near-white:hover{background-color:#f4f4f4}.swagger-ui .hover-bg-white:focus,.swagger-ui .hover-bg-white:hover{background-color:#fff}.swagger-ui .hover-bg-transparent:focus,.swagger-ui .hover-bg-transparent:hover{background-color:transparent}.swagger-ui .hover-bg-black-90:focus,.swagger-ui .hover-bg-black-90:hover{background-color:rgba(0,0,0,.9)}.swagger-ui .hover-bg-black-80:focus,.swagger-ui .hover-bg-black-80:hover{background-color:rgba(0,0,0,.8)}.swagger-ui .hover-bg-black-70:focus,.swagger-ui .hover-bg-black-70:hover{background-color:rgba(0,0,0,.7)}.swagger-ui .hover-bg-black-60:focus,.swagger-ui .hover-bg-black-60:hover{background-color:rgba(0,0,0,.6)}.swagger-ui .hover-bg-black-50:focus,.swagger-ui .hover-bg-black-50:hover{background-color:rgba(0,0,0,.5)}.swagger-ui .hover-bg-black-40:focus,.swagger-ui .hover-bg-black-40:hover{background-color:rgba(0,0,0,.4)}.swagger-ui .hover-bg-black-30:focus,.swagger-ui .hover-bg-black-30:hover{background-color:rgba(0,0,0,.3)}.swagger-ui .hover-bg-black-20:focus,.swagger-ui .hover-bg-black-20:hover{background-color:rgba(0,0,0,.2)}.swagger-ui .hover-bg-black-10:focus,.swagger-ui .hover-bg-black-10:hover{background-color:rgba(0,0,0,.1)}.swagger-ui .hover-bg-white-90:focus,.swagger-ui .hover-bg-white-90:hover{background-color:hsla(0,0%,100%,.9)}.swagger-ui .hover-bg-white-80:focus,.swagger-ui .hover-bg-white-80:hover{background-color:hsla(0,0%,100%,.8)}.swagger-ui .hover-bg-white-70:focus,.swagger-ui .hover-bg-white-70:hover{background-color:hsla(0,0%,100%,.7)}.swagger-ui .hover-bg-white-60:focus,.swagger-ui .hover-bg-white-60:hover{background-color:hsla(0,0%,100%,.6)}.swagger-ui .hover-bg-white-50:focus,.swagger-ui .hover-bg-white-50:hover{background-color:hsla(0,0%,100%,.5)}.swagger-ui .hover-bg-white-40:focus,.swagger-ui .hover-bg-white-40:hover{background-color:hsla(0,0%,100%,.4)}.swagger-ui .hover-bg-white-30:focus,.swagger-ui .hover-bg-white-30:hover{background-color:hsla(0,0%,100%,.3)}.swagger-ui .hover-bg-white-20:focus,.swagger-ui .hover-bg-white-20:hover{background-color:hsla(0,0%,100%,.2)}.swagger-ui .hover-bg-white-10:focus,.swagger-ui .hover-bg-white-10:hover{background-color:hsla(0,0%,100%,.1)}.swagger-ui .hover-dark-red:focus,.swagger-ui .hover-dark-red:hover{color:#e7040f}.swagger-ui .hover-red:focus,.swagger-ui .hover-red:hover{color:#ff4136}.swagger-ui .hover-light-red:focus,.swagger-ui .hover-light-red:hover{color:#ff725c}.swagger-ui .hover-orange:focus,.swagger-ui .hover-orange:hover{color:#ff6300}.swagger-ui .hover-gold:focus,.swagger-ui .hover-gold:hover{color:#ffb700}.swagger-ui .hover-yellow:focus,.swagger-ui .hover-yellow:hover{color:gold}.swagger-ui .hover-light-yellow:focus,.swagger-ui .hover-light-yellow:hover{color:#fbf1a9}.swagger-ui .hover-purple:focus,.swagger-ui .hover-purple:hover{color:#5e2ca5}.swagger-ui .hover-light-purple:focus,.swagger-ui .hover-light-purple:hover{color:#a463f2}.swagger-ui .hover-dark-pink:focus,.swagger-ui .hover-dark-pink:hover{color:#d5008f}.swagger-ui .hover-hot-pink:focus,.swagger-ui .hover-hot-pink:hover{color:#ff41b4}.swagger-ui .hover-pink:focus,.swagger-ui .hover-pink:hover{color:#ff80cc}.swagger-ui .hover-light-pink:focus,.swagger-ui .hover-light-pink:hover{color:#ffa3d7}.swagger-ui .hover-dark-green:focus,.swagger-ui .hover-dark-green:hover{color:#137752}.swagger-ui .hover-green:focus,.swagger-ui .hover-green:hover{color:#19a974}.swagger-ui .hover-light-green:focus,.swagger-ui .hover-light-green:hover{color:#9eebcf}.swagger-ui .hover-navy:focus,.swagger-ui .hover-navy:hover{color:#001b44}.swagger-ui .hover-dark-blue:focus,.swagger-ui .hover-dark-blue:hover{color:#00449e}.swagger-ui .hover-blue:focus,.swagger-ui .hover-blue:hover{color:#357edd}.swagger-ui .hover-light-blue:focus,.swagger-ui .hover-light-blue:hover{color:#96ccff}.swagger-ui .hover-lightest-blue:focus,.swagger-ui .hover-lightest-blue:hover{color:#cdecff}.swagger-ui .hover-washed-blue:focus,.swagger-ui .hover-washed-blue:hover{color:#f6fffe}.swagger-ui .hover-washed-green:focus,.swagger-ui .hover-washed-green:hover{color:#e8fdf5}.swagger-ui .hover-washed-yellow:focus,.swagger-ui .hover-washed-yellow:hover{color:#fffceb}.swagger-ui .hover-washed-red:focus,.swagger-ui .hover-washed-red:hover{color:#ffdfdf}.swagger-ui .hover-bg-dark-red:focus,.swagger-ui .hover-bg-dark-red:hover{background-color:#e7040f}.swagger-ui .hover-bg-red:focus,.swagger-ui .hover-bg-red:hover{background-color:#ff4136}.swagger-ui .hover-bg-light-red:focus,.swagger-ui .hover-bg-light-red:hover{background-color:#ff725c}.swagger-ui .hover-bg-orange:focus,.swagger-ui .hover-bg-orange:hover{background-color:#ff6300}.swagger-ui .hover-bg-gold:focus,.swagger-ui .hover-bg-gold:hover{background-color:#ffb700}.swagger-ui .hover-bg-yellow:focus,.swagger-ui .hover-bg-yellow:hover{background-color:gold}.swagger-ui .hover-bg-light-yellow:focus,.swagger-ui .hover-bg-light-yellow:hover{background-color:#fbf1a9}.swagger-ui .hover-bg-purple:focus,.swagger-ui .hover-bg-purple:hover{background-color:#5e2ca5}.swagger-ui .hover-bg-light-purple:focus,.swagger-ui .hover-bg-light-purple:hover{background-color:#a463f2}.swagger-ui .hover-bg-dark-pink:focus,.swagger-ui .hover-bg-dark-pink:hover{background-color:#d5008f}.swagger-ui .hover-bg-hot-pink:focus,.swagger-ui .hover-bg-hot-pink:hover{background-color:#ff41b4}.swagger-ui .hover-bg-pink:focus,.swagger-ui .hover-bg-pink:hover{background-color:#ff80cc}.swagger-ui .hover-bg-light-pink:focus,.swagger-ui .hover-bg-light-pink:hover{background-color:#ffa3d7}.swagger-ui .hover-bg-dark-green:focus,.swagger-ui .hover-bg-dark-green:hover{background-color:#137752}.swagger-ui .hover-bg-green:focus,.swagger-ui .hover-bg-green:hover{background-color:#19a974}.swagger-ui .hover-bg-light-green:focus,.swagger-ui .hover-bg-light-green:hover{background-color:#9eebcf}.swagger-ui .hover-bg-navy:focus,.swagger-ui .hover-bg-navy:hover{background-color:#001b44}.swagger-ui .hover-bg-dark-blue:focus,.swagger-ui .hover-bg-dark-blue:hover{background-color:#00449e}.swagger-ui .hover-bg-blue:focus,.swagger-ui .hover-bg-blue:hover{background-color:#357edd}.swagger-ui .hover-bg-light-blue:focus,.swagger-ui .hover-bg-light-blue:hover{background-color:#96ccff}.swagger-ui .hover-bg-lightest-blue:focus,.swagger-ui .hover-bg-lightest-blue:hover{background-color:#cdecff}.swagger-ui .hover-bg-washed-blue:focus,.swagger-ui .hover-bg-washed-blue:hover{background-color:#f6fffe}.swagger-ui .hover-bg-washed-green:focus,.swagger-ui .hover-bg-washed-green:hover{background-color:#e8fdf5}.swagger-ui .hover-bg-washed-yellow:focus,.swagger-ui .hover-bg-washed-yellow:hover{background-color:#fffceb}.swagger-ui .hover-bg-washed-red:focus,.swagger-ui .hover-bg-washed-red:hover{background-color:#ffdfdf}.swagger-ui .hover-bg-inherit:focus,.swagger-ui .hover-bg-inherit:hover{background-color:inherit}.swagger-ui .pa0{padding:0}.swagger-ui .pa1{padding:.25rem}.swagger-ui .pa2{padding:.5rem}.swagger-ui .pa3{padding:1rem}.swagger-ui .pa4{padding:2rem}.swagger-ui .pa5{padding:4rem}.swagger-ui .pa6{padding:8rem}.swagger-ui .pa7{padding:16rem}.swagger-ui .pl0{padding-left:0}.swagger-ui .pl1{padding-left:.25rem}.swagger-ui .pl2{padding-left:.5rem}.swagger-ui .pl3{padding-left:1rem}.swagger-ui .pl4{padding-left:2rem}.swagger-ui .pl5{padding-left:4rem}.swagger-ui .pl6{padding-left:8rem}.swagger-ui .pl7{padding-left:16rem}.swagger-ui .pr0{padding-right:0}.swagger-ui .pr1{padding-right:.25rem}.swagger-ui .pr2{padding-right:.5rem}.swagger-ui .pr3{padding-right:1rem}.swagger-ui .pr4{padding-right:2rem}.swagger-ui .pr5{padding-right:4rem}.swagger-ui .pr6{padding-right:8rem}.swagger-ui .pr7{padding-right:16rem}.swagger-ui .pb0{padding-bottom:0}.swagger-ui .pb1{padding-bottom:.25rem}.swagger-ui .pb2{padding-bottom:.5rem}.swagger-ui .pb3{padding-bottom:1rem}.swagger-ui .pb4{padding-bottom:2rem}.swagger-ui .pb5{padding-bottom:4rem}.swagger-ui .pb6{padding-bottom:8rem}.swagger-ui .pb7{padding-bottom:16rem}.swagger-ui .pt0{padding-top:0}.swagger-ui .pt1{padding-top:.25rem}.swagger-ui .pt2{padding-top:.5rem}.swagger-ui .pt3{padding-top:1rem}.swagger-ui .pt4{padding-top:2rem}.swagger-ui .pt5{padding-top:4rem}.swagger-ui .pt6{padding-top:8rem}.swagger-ui .pt7{padding-top:16rem}.swagger-ui .pv0{padding-bottom:0;padding-top:0}.swagger-ui .pv1{padding-bottom:.25rem;padding-top:.25rem}.swagger-ui .pv2{padding-bottom:.5rem;padding-top:.5rem}.swagger-ui .pv3{padding-bottom:1rem;padding-top:1rem}.swagger-ui .pv4{padding-bottom:2rem;padding-top:2rem}.swagger-ui .pv5{padding-bottom:4rem;padding-top:4rem}.swagger-ui .pv6{padding-bottom:8rem;padding-top:8rem}.swagger-ui .pv7{padding-bottom:16rem;padding-top:16rem}.swagger-ui .ph0{padding-left:0;padding-right:0}.swagger-ui .ph1{padding-left:.25rem;padding-right:.25rem}.swagger-ui .ph2{padding-left:.5rem;padding-right:.5rem}.swagger-ui .ph3{padding-left:1rem;padding-right:1rem}.swagger-ui .ph4{padding-left:2rem;padding-right:2rem}.swagger-ui .ph5{padding-left:4rem;padding-right:4rem}.swagger-ui .ph6{padding-left:8rem;padding-right:8rem}.swagger-ui .ph7{padding-left:16rem;padding-right:16rem}.swagger-ui .ma0{margin:0}.swagger-ui .ma1{margin:.25rem}.swagger-ui .ma2{margin:.5rem}.swagger-ui .ma3{margin:1rem}.swagger-ui .ma4{margin:2rem}.swagger-ui .ma5{margin:4rem}.swagger-ui .ma6{margin:8rem}.swagger-ui .ma7{margin:16rem}.swagger-ui .ml0{margin-left:0}.swagger-ui .ml1{margin-left:.25rem}.swagger-ui .ml2{margin-left:.5rem}.swagger-ui .ml3{margin-left:1rem}.swagger-ui .ml4{margin-left:2rem}.swagger-ui .ml5{margin-left:4rem}.swagger-ui .ml6{margin-left:8rem}.swagger-ui .ml7{margin-left:16rem}.swagger-ui .mr0{margin-right:0}.swagger-ui .mr1{margin-right:.25rem}.swagger-ui .mr2{margin-right:.5rem}.swagger-ui .mr3{margin-right:1rem}.swagger-ui .mr4{margin-right:2rem}.swagger-ui .mr5{margin-right:4rem}.swagger-ui .mr6{margin-right:8rem}.swagger-ui .mr7{margin-right:16rem}.swagger-ui .mb0{margin-bottom:0}.swagger-ui .mb1{margin-bottom:.25rem}.swagger-ui .mb2{margin-bottom:.5rem}.swagger-ui .mb3{margin-bottom:1rem}.swagger-ui .mb4{margin-bottom:2rem}.swagger-ui .mb5{margin-bottom:4rem}.swagger-ui .mb6{margin-bottom:8rem}.swagger-ui .mb7{margin-bottom:16rem}.swagger-ui .mt0{margin-top:0}.swagger-ui .mt1{margin-top:.25rem}.swagger-ui .mt2{margin-top:.5rem}.swagger-ui .mt3{margin-top:1rem}.swagger-ui .mt4{margin-top:2rem}.swagger-ui .mt5{margin-top:4rem}.swagger-ui .mt6{margin-top:8rem}.swagger-ui .mt7{margin-top:16rem}.swagger-ui .mv0{margin-bottom:0;margin-top:0}.swagger-ui .mv1{margin-bottom:.25rem;margin-top:.25rem}.swagger-ui .mv2{margin-bottom:.5rem;margin-top:.5rem}.swagger-ui .mv3{margin-bottom:1rem;margin-top:1rem}.swagger-ui .mv4{margin-bottom:2rem;margin-top:2rem}.swagger-ui .mv5{margin-bottom:4rem;margin-top:4rem}.swagger-ui .mv6{margin-bottom:8rem;margin-top:8rem}.swagger-ui .mv7{margin-bottom:16rem;margin-top:16rem}.swagger-ui .mh0{margin-left:0;margin-right:0}.swagger-ui .mh1{margin-left:.25rem;margin-right:.25rem}.swagger-ui .mh2{margin-left:.5rem;margin-right:.5rem}.swagger-ui .mh3{margin-left:1rem;margin-right:1rem}.swagger-ui .mh4{margin-left:2rem;margin-right:2rem}.swagger-ui .mh5{margin-left:4rem;margin-right:4rem}.swagger-ui .mh6{margin-left:8rem;margin-right:8rem}.swagger-ui .mh7{margin-left:16rem;margin-right:16rem}@media screen and (min-width:30em){.swagger-ui .pa0-ns{padding:0}.swagger-ui .pa1-ns{padding:.25rem}.swagger-ui .pa2-ns{padding:.5rem}.swagger-ui .pa3-ns{padding:1rem}.swagger-ui .pa4-ns{padding:2rem}.swagger-ui .pa5-ns{padding:4rem}.swagger-ui .pa6-ns{padding:8rem}.swagger-ui .pa7-ns{padding:16rem}.swagger-ui .pl0-ns{padding-left:0}.swagger-ui .pl1-ns{padding-left:.25rem}.swagger-ui .pl2-ns{padding-left:.5rem}.swagger-ui .pl3-ns{padding-left:1rem}.swagger-ui .pl4-ns{padding-left:2rem}.swagger-ui .pl5-ns{padding-left:4rem}.swagger-ui .pl6-ns{padding-left:8rem}.swagger-ui .pl7-ns{padding-left:16rem}.swagger-ui .pr0-ns{padding-right:0}.swagger-ui .pr1-ns{padding-right:.25rem}.swagger-ui .pr2-ns{padding-right:.5rem}.swagger-ui .pr3-ns{padding-right:1rem}.swagger-ui .pr4-ns{padding-right:2rem}.swagger-ui .pr5-ns{padding-right:4rem}.swagger-ui .pr6-ns{padding-right:8rem}.swagger-ui .pr7-ns{padding-right:16rem}.swagger-ui .pb0-ns{padding-bottom:0}.swagger-ui .pb1-ns{padding-bottom:.25rem}.swagger-ui .pb2-ns{padding-bottom:.5rem}.swagger-ui .pb3-ns{padding-bottom:1rem}.swagger-ui .pb4-ns{padding-bottom:2rem}.swagger-ui .pb5-ns{padding-bottom:4rem}.swagger-ui .pb6-ns{padding-bottom:8rem}.swagger-ui .pb7-ns{padding-bottom:16rem}.swagger-ui .pt0-ns{padding-top:0}.swagger-ui .pt1-ns{padding-top:.25rem}.swagger-ui .pt2-ns{padding-top:.5rem}.swagger-ui .pt3-ns{padding-top:1rem}.swagger-ui .pt4-ns{padding-top:2rem}.swagger-ui .pt5-ns{padding-top:4rem}.swagger-ui .pt6-ns{padding-top:8rem}.swagger-ui .pt7-ns{padding-top:16rem}.swagger-ui .pv0-ns{padding-bottom:0;padding-top:0}.swagger-ui .pv1-ns{padding-bottom:.25rem;padding-top:.25rem}.swagger-ui .pv2-ns{padding-bottom:.5rem;padding-top:.5rem}.swagger-ui .pv3-ns{padding-bottom:1rem;padding-top:1rem}.swagger-ui .pv4-ns{padding-bottom:2rem;padding-top:2rem}.swagger-ui .pv5-ns{padding-bottom:4rem;padding-top:4rem}.swagger-ui .pv6-ns{padding-bottom:8rem;padding-top:8rem}.swagger-ui .pv7-ns{padding-bottom:16rem;padding-top:16rem}.swagger-ui .ph0-ns{padding-left:0;padding-right:0}.swagger-ui .ph1-ns{padding-left:.25rem;padding-right:.25rem}.swagger-ui .ph2-ns{padding-left:.5rem;padding-right:.5rem}.swagger-ui .ph3-ns{padding-left:1rem;padding-right:1rem}.swagger-ui .ph4-ns{padding-left:2rem;padding-right:2rem}.swagger-ui .ph5-ns{padding-left:4rem;padding-right:4rem}.swagger-ui .ph6-ns{padding-left:8rem;padding-right:8rem}.swagger-ui .ph7-ns{padding-left:16rem;padding-right:16rem}.swagger-ui .ma0-ns{margin:0}.swagger-ui .ma1-ns{margin:.25rem}.swagger-ui .ma2-ns{margin:.5rem}.swagger-ui .ma3-ns{margin:1rem}.swagger-ui .ma4-ns{margin:2rem}.swagger-ui .ma5-ns{margin:4rem}.swagger-ui .ma6-ns{margin:8rem}.swagger-ui .ma7-ns{margin:16rem}.swagger-ui .ml0-ns{margin-left:0}.swagger-ui .ml1-ns{margin-left:.25rem}.swagger-ui .ml2-ns{margin-left:.5rem}.swagger-ui .ml3-ns{margin-left:1rem}.swagger-ui .ml4-ns{margin-left:2rem}.swagger-ui .ml5-ns{margin-left:4rem}.swagger-ui .ml6-ns{margin-left:8rem}.swagger-ui .ml7-ns{margin-left:16rem}.swagger-ui .mr0-ns{margin-right:0}.swagger-ui .mr1-ns{margin-right:.25rem}.swagger-ui .mr2-ns{margin-right:.5rem}.swagger-ui .mr3-ns{margin-right:1rem}.swagger-ui .mr4-ns{margin-right:2rem}.swagger-ui .mr5-ns{margin-right:4rem}.swagger-ui .mr6-ns{margin-right:8rem}.swagger-ui .mr7-ns{margin-right:16rem}.swagger-ui .mb0-ns{margin-bottom:0}.swagger-ui .mb1-ns{margin-bottom:.25rem}.swagger-ui .mb2-ns{margin-bottom:.5rem}.swagger-ui .mb3-ns{margin-bottom:1rem}.swagger-ui .mb4-ns{margin-bottom:2rem}.swagger-ui .mb5-ns{margin-bottom:4rem}.swagger-ui .mb6-ns{margin-bottom:8rem}.swagger-ui .mb7-ns{margin-bottom:16rem}.swagger-ui .mt0-ns{margin-top:0}.swagger-ui .mt1-ns{margin-top:.25rem}.swagger-ui .mt2-ns{margin-top:.5rem}.swagger-ui .mt3-ns{margin-top:1rem}.swagger-ui .mt4-ns{margin-top:2rem}.swagger-ui .mt5-ns{margin-top:4rem}.swagger-ui .mt6-ns{margin-top:8rem}.swagger-ui .mt7-ns{margin-top:16rem}.swagger-ui .mv0-ns{margin-bottom:0;margin-top:0}.swagger-ui .mv1-ns{margin-bottom:.25rem;margin-top:.25rem}.swagger-ui .mv2-ns{margin-bottom:.5rem;margin-top:.5rem}.swagger-ui .mv3-ns{margin-bottom:1rem;margin-top:1rem}.swagger-ui .mv4-ns{margin-bottom:2rem;margin-top:2rem}.swagger-ui .mv5-ns{margin-bottom:4rem;margin-top:4rem}.swagger-ui .mv6-ns{margin-bottom:8rem;margin-top:8rem}.swagger-ui .mv7-ns{margin-bottom:16rem;margin-top:16rem}.swagger-ui .mh0-ns{margin-left:0;margin-right:0}.swagger-ui .mh1-ns{margin-left:.25rem;margin-right:.25rem}.swagger-ui .mh2-ns{margin-left:.5rem;margin-right:.5rem}.swagger-ui .mh3-ns{margin-left:1rem;margin-right:1rem}.swagger-ui .mh4-ns{margin-left:2rem;margin-right:2rem}.swagger-ui .mh5-ns{margin-left:4rem;margin-right:4rem}.swagger-ui .mh6-ns{margin-left:8rem;margin-right:8rem}.swagger-ui .mh7-ns{margin-left:16rem;margin-right:16rem}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .pa0-m{padding:0}.swagger-ui .pa1-m{padding:.25rem}.swagger-ui .pa2-m{padding:.5rem}.swagger-ui .pa3-m{padding:1rem}.swagger-ui .pa4-m{padding:2rem}.swagger-ui .pa5-m{padding:4rem}.swagger-ui .pa6-m{padding:8rem}.swagger-ui .pa7-m{padding:16rem}.swagger-ui .pl0-m{padding-left:0}.swagger-ui .pl1-m{padding-left:.25rem}.swagger-ui .pl2-m{padding-left:.5rem}.swagger-ui .pl3-m{padding-left:1rem}.swagger-ui .pl4-m{padding-left:2rem}.swagger-ui .pl5-m{padding-left:4rem}.swagger-ui .pl6-m{padding-left:8rem}.swagger-ui .pl7-m{padding-left:16rem}.swagger-ui .pr0-m{padding-right:0}.swagger-ui .pr1-m{padding-right:.25rem}.swagger-ui .pr2-m{padding-right:.5rem}.swagger-ui .pr3-m{padding-right:1rem}.swagger-ui .pr4-m{padding-right:2rem}.swagger-ui .pr5-m{padding-right:4rem}.swagger-ui .pr6-m{padding-right:8rem}.swagger-ui .pr7-m{padding-right:16rem}.swagger-ui .pb0-m{padding-bottom:0}.swagger-ui .pb1-m{padding-bottom:.25rem}.swagger-ui .pb2-m{padding-bottom:.5rem}.swagger-ui .pb3-m{padding-bottom:1rem}.swagger-ui .pb4-m{padding-bottom:2rem}.swagger-ui .pb5-m{padding-bottom:4rem}.swagger-ui .pb6-m{padding-bottom:8rem}.swagger-ui .pb7-m{padding-bottom:16rem}.swagger-ui .pt0-m{padding-top:0}.swagger-ui .pt1-m{padding-top:.25rem}.swagger-ui .pt2-m{padding-top:.5rem}.swagger-ui .pt3-m{padding-top:1rem}.swagger-ui .pt4-m{padding-top:2rem}.swagger-ui .pt5-m{padding-top:4rem}.swagger-ui .pt6-m{padding-top:8rem}.swagger-ui .pt7-m{padding-top:16rem}.swagger-ui .pv0-m{padding-bottom:0;padding-top:0}.swagger-ui .pv1-m{padding-bottom:.25rem;padding-top:.25rem}.swagger-ui .pv2-m{padding-bottom:.5rem;padding-top:.5rem}.swagger-ui .pv3-m{padding-bottom:1rem;padding-top:1rem}.swagger-ui .pv4-m{padding-bottom:2rem;padding-top:2rem}.swagger-ui .pv5-m{padding-bottom:4rem;padding-top:4rem}.swagger-ui .pv6-m{padding-bottom:8rem;padding-top:8rem}.swagger-ui .pv7-m{padding-bottom:16rem;padding-top:16rem}.swagger-ui .ph0-m{padding-left:0;padding-right:0}.swagger-ui .ph1-m{padding-left:.25rem;padding-right:.25rem}.swagger-ui .ph2-m{padding-left:.5rem;padding-right:.5rem}.swagger-ui .ph3-m{padding-left:1rem;padding-right:1rem}.swagger-ui .ph4-m{padding-left:2rem;padding-right:2rem}.swagger-ui .ph5-m{padding-left:4rem;padding-right:4rem}.swagger-ui .ph6-m{padding-left:8rem;padding-right:8rem}.swagger-ui .ph7-m{padding-left:16rem;padding-right:16rem}.swagger-ui .ma0-m{margin:0}.swagger-ui .ma1-m{margin:.25rem}.swagger-ui .ma2-m{margin:.5rem}.swagger-ui .ma3-m{margin:1rem}.swagger-ui .ma4-m{margin:2rem}.swagger-ui .ma5-m{margin:4rem}.swagger-ui .ma6-m{margin:8rem}.swagger-ui .ma7-m{margin:16rem}.swagger-ui .ml0-m{margin-left:0}.swagger-ui .ml1-m{margin-left:.25rem}.swagger-ui .ml2-m{margin-left:.5rem}.swagger-ui .ml3-m{margin-left:1rem}.swagger-ui .ml4-m{margin-left:2rem}.swagger-ui .ml5-m{margin-left:4rem}.swagger-ui .ml6-m{margin-left:8rem}.swagger-ui .ml7-m{margin-left:16rem}.swagger-ui .mr0-m{margin-right:0}.swagger-ui .mr1-m{margin-right:.25rem}.swagger-ui .mr2-m{margin-right:.5rem}.swagger-ui .mr3-m{margin-right:1rem}.swagger-ui .mr4-m{margin-right:2rem}.swagger-ui .mr5-m{margin-right:4rem}.swagger-ui .mr6-m{margin-right:8rem}.swagger-ui .mr7-m{margin-right:16rem}.swagger-ui .mb0-m{margin-bottom:0}.swagger-ui .mb1-m{margin-bottom:.25rem}.swagger-ui .mb2-m{margin-bottom:.5rem}.swagger-ui .mb3-m{margin-bottom:1rem}.swagger-ui .mb4-m{margin-bottom:2rem}.swagger-ui .mb5-m{margin-bottom:4rem}.swagger-ui .mb6-m{margin-bottom:8rem}.swagger-ui .mb7-m{margin-bottom:16rem}.swagger-ui .mt0-m{margin-top:0}.swagger-ui .mt1-m{margin-top:.25rem}.swagger-ui .mt2-m{margin-top:.5rem}.swagger-ui .mt3-m{margin-top:1rem}.swagger-ui .mt4-m{margin-top:2rem}.swagger-ui .mt5-m{margin-top:4rem}.swagger-ui .mt6-m{margin-top:8rem}.swagger-ui .mt7-m{margin-top:16rem}.swagger-ui .mv0-m{margin-bottom:0;margin-top:0}.swagger-ui .mv1-m{margin-bottom:.25rem;margin-top:.25rem}.swagger-ui .mv2-m{margin-bottom:.5rem;margin-top:.5rem}.swagger-ui .mv3-m{margin-bottom:1rem;margin-top:1rem}.swagger-ui .mv4-m{margin-bottom:2rem;margin-top:2rem}.swagger-ui .mv5-m{margin-bottom:4rem;margin-top:4rem}.swagger-ui .mv6-m{margin-bottom:8rem;margin-top:8rem}.swagger-ui .mv7-m{margin-bottom:16rem;margin-top:16rem}.swagger-ui .mh0-m{margin-left:0;margin-right:0}.swagger-ui .mh1-m{margin-left:.25rem;margin-right:.25rem}.swagger-ui .mh2-m{margin-left:.5rem;margin-right:.5rem}.swagger-ui .mh3-m{margin-left:1rem;margin-right:1rem}.swagger-ui .mh4-m{margin-left:2rem;margin-right:2rem}.swagger-ui .mh5-m{margin-left:4rem;margin-right:4rem}.swagger-ui .mh6-m{margin-left:8rem;margin-right:8rem}.swagger-ui .mh7-m{margin-left:16rem;margin-right:16rem}}@media screen and (min-width:60em){.swagger-ui .pa0-l{padding:0}.swagger-ui .pa1-l{padding:.25rem}.swagger-ui .pa2-l{padding:.5rem}.swagger-ui .pa3-l{padding:1rem}.swagger-ui .pa4-l{padding:2rem}.swagger-ui .pa5-l{padding:4rem}.swagger-ui .pa6-l{padding:8rem}.swagger-ui .pa7-l{padding:16rem}.swagger-ui .pl0-l{padding-left:0}.swagger-ui .pl1-l{padding-left:.25rem}.swagger-ui .pl2-l{padding-left:.5rem}.swagger-ui .pl3-l{padding-left:1rem}.swagger-ui .pl4-l{padding-left:2rem}.swagger-ui .pl5-l{padding-left:4rem}.swagger-ui .pl6-l{padding-left:8rem}.swagger-ui .pl7-l{padding-left:16rem}.swagger-ui .pr0-l{padding-right:0}.swagger-ui .pr1-l{padding-right:.25rem}.swagger-ui .pr2-l{padding-right:.5rem}.swagger-ui .pr3-l{padding-right:1rem}.swagger-ui .pr4-l{padding-right:2rem}.swagger-ui .pr5-l{padding-right:4rem}.swagger-ui .pr6-l{padding-right:8rem}.swagger-ui .pr7-l{padding-right:16rem}.swagger-ui .pb0-l{padding-bottom:0}.swagger-ui .pb1-l{padding-bottom:.25rem}.swagger-ui .pb2-l{padding-bottom:.5rem}.swagger-ui .pb3-l{padding-bottom:1rem}.swagger-ui .pb4-l{padding-bottom:2rem}.swagger-ui .pb5-l{padding-bottom:4rem}.swagger-ui .pb6-l{padding-bottom:8rem}.swagger-ui .pb7-l{padding-bottom:16rem}.swagger-ui .pt0-l{padding-top:0}.swagger-ui .pt1-l{padding-top:.25rem}.swagger-ui .pt2-l{padding-top:.5rem}.swagger-ui .pt3-l{padding-top:1rem}.swagger-ui .pt4-l{padding-top:2rem}.swagger-ui .pt5-l{padding-top:4rem}.swagger-ui .pt6-l{padding-top:8rem}.swagger-ui .pt7-l{padding-top:16rem}.swagger-ui .pv0-l{padding-bottom:0;padding-top:0}.swagger-ui .pv1-l{padding-bottom:.25rem;padding-top:.25rem}.swagger-ui .pv2-l{padding-bottom:.5rem;padding-top:.5rem}.swagger-ui .pv3-l{padding-bottom:1rem;padding-top:1rem}.swagger-ui .pv4-l{padding-bottom:2rem;padding-top:2rem}.swagger-ui .pv5-l{padding-bottom:4rem;padding-top:4rem}.swagger-ui .pv6-l{padding-bottom:8rem;padding-top:8rem}.swagger-ui .pv7-l{padding-bottom:16rem;padding-top:16rem}.swagger-ui .ph0-l{padding-left:0;padding-right:0}.swagger-ui .ph1-l{padding-left:.25rem;padding-right:.25rem}.swagger-ui .ph2-l{padding-left:.5rem;padding-right:.5rem}.swagger-ui .ph3-l{padding-left:1rem;padding-right:1rem}.swagger-ui .ph4-l{padding-left:2rem;padding-right:2rem}.swagger-ui .ph5-l{padding-left:4rem;padding-right:4rem}.swagger-ui .ph6-l{padding-left:8rem;padding-right:8rem}.swagger-ui .ph7-l{padding-left:16rem;padding-right:16rem}.swagger-ui .ma0-l{margin:0}.swagger-ui .ma1-l{margin:.25rem}.swagger-ui .ma2-l{margin:.5rem}.swagger-ui .ma3-l{margin:1rem}.swagger-ui .ma4-l{margin:2rem}.swagger-ui .ma5-l{margin:4rem}.swagger-ui .ma6-l{margin:8rem}.swagger-ui .ma7-l{margin:16rem}.swagger-ui .ml0-l{margin-left:0}.swagger-ui .ml1-l{margin-left:.25rem}.swagger-ui .ml2-l{margin-left:.5rem}.swagger-ui .ml3-l{margin-left:1rem}.swagger-ui .ml4-l{margin-left:2rem}.swagger-ui .ml5-l{margin-left:4rem}.swagger-ui .ml6-l{margin-left:8rem}.swagger-ui .ml7-l{margin-left:16rem}.swagger-ui .mr0-l{margin-right:0}.swagger-ui .mr1-l{margin-right:.25rem}.swagger-ui .mr2-l{margin-right:.5rem}.swagger-ui .mr3-l{margin-right:1rem}.swagger-ui .mr4-l{margin-right:2rem}.swagger-ui .mr5-l{margin-right:4rem}.swagger-ui .mr6-l{margin-right:8rem}.swagger-ui .mr7-l{margin-right:16rem}.swagger-ui .mb0-l{margin-bottom:0}.swagger-ui .mb1-l{margin-bottom:.25rem}.swagger-ui .mb2-l{margin-bottom:.5rem}.swagger-ui .mb3-l{margin-bottom:1rem}.swagger-ui .mb4-l{margin-bottom:2rem}.swagger-ui .mb5-l{margin-bottom:4rem}.swagger-ui .mb6-l{margin-bottom:8rem}.swagger-ui .mb7-l{margin-bottom:16rem}.swagger-ui .mt0-l{margin-top:0}.swagger-ui .mt1-l{margin-top:.25rem}.swagger-ui .mt2-l{margin-top:.5rem}.swagger-ui .mt3-l{margin-top:1rem}.swagger-ui .mt4-l{margin-top:2rem}.swagger-ui .mt5-l{margin-top:4rem}.swagger-ui .mt6-l{margin-top:8rem}.swagger-ui .mt7-l{margin-top:16rem}.swagger-ui .mv0-l{margin-bottom:0;margin-top:0}.swagger-ui .mv1-l{margin-bottom:.25rem;margin-top:.25rem}.swagger-ui .mv2-l{margin-bottom:.5rem;margin-top:.5rem}.swagger-ui .mv3-l{margin-bottom:1rem;margin-top:1rem}.swagger-ui .mv4-l{margin-bottom:2rem;margin-top:2rem}.swagger-ui .mv5-l{margin-bottom:4rem;margin-top:4rem}.swagger-ui .mv6-l{margin-bottom:8rem;margin-top:8rem}.swagger-ui .mv7-l{margin-bottom:16rem;margin-top:16rem}.swagger-ui .mh0-l{margin-left:0;margin-right:0}.swagger-ui .mh1-l{margin-left:.25rem;margin-right:.25rem}.swagger-ui .mh2-l{margin-left:.5rem;margin-right:.5rem}.swagger-ui .mh3-l{margin-left:1rem;margin-right:1rem}.swagger-ui .mh4-l{margin-left:2rem;margin-right:2rem}.swagger-ui .mh5-l{margin-left:4rem;margin-right:4rem}.swagger-ui .mh6-l{margin-left:8rem;margin-right:8rem}.swagger-ui .mh7-l{margin-left:16rem;margin-right:16rem}}.swagger-ui .na1{margin:-.25rem}.swagger-ui .na2{margin:-.5rem}.swagger-ui .na3{margin:-1rem}.swagger-ui .na4{margin:-2rem}.swagger-ui .na5{margin:-4rem}.swagger-ui .na6{margin:-8rem}.swagger-ui .na7{margin:-16rem}.swagger-ui .nl1{margin-left:-.25rem}.swagger-ui .nl2{margin-left:-.5rem}.swagger-ui .nl3{margin-left:-1rem}.swagger-ui .nl4{margin-left:-2rem}.swagger-ui .nl5{margin-left:-4rem}.swagger-ui .nl6{margin-left:-8rem}.swagger-ui .nl7{margin-left:-16rem}.swagger-ui .nr1{margin-right:-.25rem}.swagger-ui .nr2{margin-right:-.5rem}.swagger-ui .nr3{margin-right:-1rem}.swagger-ui .nr4{margin-right:-2rem}.swagger-ui .nr5{margin-right:-4rem}.swagger-ui .nr6{margin-right:-8rem}.swagger-ui .nr7{margin-right:-16rem}.swagger-ui .nb1{margin-bottom:-.25rem}.swagger-ui .nb2{margin-bottom:-.5rem}.swagger-ui .nb3{margin-bottom:-1rem}.swagger-ui .nb4{margin-bottom:-2rem}.swagger-ui .nb5{margin-bottom:-4rem}.swagger-ui .nb6{margin-bottom:-8rem}.swagger-ui .nb7{margin-bottom:-16rem}.swagger-ui .nt1{margin-top:-.25rem}.swagger-ui .nt2{margin-top:-.5rem}.swagger-ui .nt3{margin-top:-1rem}.swagger-ui .nt4{margin-top:-2rem}.swagger-ui .nt5{margin-top:-4rem}.swagger-ui .nt6{margin-top:-8rem}.swagger-ui .nt7{margin-top:-16rem}@media screen and (min-width:30em){.swagger-ui .na1-ns{margin:-.25rem}.swagger-ui .na2-ns{margin:-.5rem}.swagger-ui .na3-ns{margin:-1rem}.swagger-ui .na4-ns{margin:-2rem}.swagger-ui .na5-ns{margin:-4rem}.swagger-ui .na6-ns{margin:-8rem}.swagger-ui .na7-ns{margin:-16rem}.swagger-ui .nl1-ns{margin-left:-.25rem}.swagger-ui .nl2-ns{margin-left:-.5rem}.swagger-ui .nl3-ns{margin-left:-1rem}.swagger-ui .nl4-ns{margin-left:-2rem}.swagger-ui .nl5-ns{margin-left:-4rem}.swagger-ui .nl6-ns{margin-left:-8rem}.swagger-ui .nl7-ns{margin-left:-16rem}.swagger-ui .nr1-ns{margin-right:-.25rem}.swagger-ui .nr2-ns{margin-right:-.5rem}.swagger-ui .nr3-ns{margin-right:-1rem}.swagger-ui .nr4-ns{margin-right:-2rem}.swagger-ui .nr5-ns{margin-right:-4rem}.swagger-ui .nr6-ns{margin-right:-8rem}.swagger-ui .nr7-ns{margin-right:-16rem}.swagger-ui .nb1-ns{margin-bottom:-.25rem}.swagger-ui .nb2-ns{margin-bottom:-.5rem}.swagger-ui .nb3-ns{margin-bottom:-1rem}.swagger-ui .nb4-ns{margin-bottom:-2rem}.swagger-ui .nb5-ns{margin-bottom:-4rem}.swagger-ui .nb6-ns{margin-bottom:-8rem}.swagger-ui .nb7-ns{margin-bottom:-16rem}.swagger-ui .nt1-ns{margin-top:-.25rem}.swagger-ui .nt2-ns{margin-top:-.5rem}.swagger-ui .nt3-ns{margin-top:-1rem}.swagger-ui .nt4-ns{margin-top:-2rem}.swagger-ui .nt5-ns{margin-top:-4rem}.swagger-ui .nt6-ns{margin-top:-8rem}.swagger-ui .nt7-ns{margin-top:-16rem}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .na1-m{margin:-.25rem}.swagger-ui .na2-m{margin:-.5rem}.swagger-ui .na3-m{margin:-1rem}.swagger-ui .na4-m{margin:-2rem}.swagger-ui .na5-m{margin:-4rem}.swagger-ui .na6-m{margin:-8rem}.swagger-ui .na7-m{margin:-16rem}.swagger-ui .nl1-m{margin-left:-.25rem}.swagger-ui .nl2-m{margin-left:-.5rem}.swagger-ui .nl3-m{margin-left:-1rem}.swagger-ui .nl4-m{margin-left:-2rem}.swagger-ui .nl5-m{margin-left:-4rem}.swagger-ui .nl6-m{margin-left:-8rem}.swagger-ui .nl7-m{margin-left:-16rem}.swagger-ui .nr1-m{margin-right:-.25rem}.swagger-ui .nr2-m{margin-right:-.5rem}.swagger-ui .nr3-m{margin-right:-1rem}.swagger-ui .nr4-m{margin-right:-2rem}.swagger-ui .nr5-m{margin-right:-4rem}.swagger-ui .nr6-m{margin-right:-8rem}.swagger-ui .nr7-m{margin-right:-16rem}.swagger-ui .nb1-m{margin-bottom:-.25rem}.swagger-ui .nb2-m{margin-bottom:-.5rem}.swagger-ui .nb3-m{margin-bottom:-1rem}.swagger-ui .nb4-m{margin-bottom:-2rem}.swagger-ui .nb5-m{margin-bottom:-4rem}.swagger-ui .nb6-m{margin-bottom:-8rem}.swagger-ui .nb7-m{margin-bottom:-16rem}.swagger-ui .nt1-m{margin-top:-.25rem}.swagger-ui .nt2-m{margin-top:-.5rem}.swagger-ui .nt3-m{margin-top:-1rem}.swagger-ui .nt4-m{margin-top:-2rem}.swagger-ui .nt5-m{margin-top:-4rem}.swagger-ui .nt6-m{margin-top:-8rem}.swagger-ui .nt7-m{margin-top:-16rem}}@media screen and (min-width:60em){.swagger-ui .na1-l{margin:-.25rem}.swagger-ui .na2-l{margin:-.5rem}.swagger-ui .na3-l{margin:-1rem}.swagger-ui .na4-l{margin:-2rem}.swagger-ui .na5-l{margin:-4rem}.swagger-ui .na6-l{margin:-8rem}.swagger-ui .na7-l{margin:-16rem}.swagger-ui .nl1-l{margin-left:-.25rem}.swagger-ui .nl2-l{margin-left:-.5rem}.swagger-ui .nl3-l{margin-left:-1rem}.swagger-ui .nl4-l{margin-left:-2rem}.swagger-ui .nl5-l{margin-left:-4rem}.swagger-ui .nl6-l{margin-left:-8rem}.swagger-ui .nl7-l{margin-left:-16rem}.swagger-ui .nr1-l{margin-right:-.25rem}.swagger-ui .nr2-l{margin-right:-.5rem}.swagger-ui .nr3-l{margin-right:-1rem}.swagger-ui .nr4-l{margin-right:-2rem}.swagger-ui .nr5-l{margin-right:-4rem}.swagger-ui .nr6-l{margin-right:-8rem}.swagger-ui .nr7-l{margin-right:-16rem}.swagger-ui .nb1-l{margin-bottom:-.25rem}.swagger-ui .nb2-l{margin-bottom:-.5rem}.swagger-ui .nb3-l{margin-bottom:-1rem}.swagger-ui .nb4-l{margin-bottom:-2rem}.swagger-ui .nb5-l{margin-bottom:-4rem}.swagger-ui .nb6-l{margin-bottom:-8rem}.swagger-ui .nb7-l{margin-bottom:-16rem}.swagger-ui .nt1-l{margin-top:-.25rem}.swagger-ui .nt2-l{margin-top:-.5rem}.swagger-ui .nt3-l{margin-top:-1rem}.swagger-ui .nt4-l{margin-top:-2rem}.swagger-ui .nt5-l{margin-top:-4rem}.swagger-ui .nt6-l{margin-top:-8rem}.swagger-ui .nt7-l{margin-top:-16rem}}.swagger-ui .collapse{border-collapse:collapse;border-spacing:0}.swagger-ui .striped--light-silver:nth-child(odd){background-color:#aaa}.swagger-ui .striped--moon-gray:nth-child(odd){background-color:#ccc}.swagger-ui .striped--light-gray:nth-child(odd){background-color:#eee}.swagger-ui .striped--near-white:nth-child(odd){background-color:#f4f4f4}.swagger-ui .stripe-light:nth-child(odd){background-color:hsla(0,0%,100%,.1)}.swagger-ui .stripe-dark:nth-child(odd){background-color:rgba(0,0,0,.1)}.swagger-ui .strike{-webkit-text-decoration:line-through;text-decoration:line-through}.swagger-ui .underline{-webkit-text-decoration:underline;text-decoration:underline}.swagger-ui .no-underline{-webkit-text-decoration:none;text-decoration:none}@media screen and (min-width:30em){.swagger-ui .strike-ns{-webkit-text-decoration:line-through;text-decoration:line-through}.swagger-ui .underline-ns{-webkit-text-decoration:underline;text-decoration:underline}.swagger-ui .no-underline-ns{-webkit-text-decoration:none;text-decoration:none}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .strike-m{-webkit-text-decoration:line-through;text-decoration:line-through}.swagger-ui .underline-m{-webkit-text-decoration:underline;text-decoration:underline}.swagger-ui .no-underline-m{-webkit-text-decoration:none;text-decoration:none}}@media screen and (min-width:60em){.swagger-ui .strike-l{-webkit-text-decoration:line-through;text-decoration:line-through}.swagger-ui .underline-l{-webkit-text-decoration:underline;text-decoration:underline}.swagger-ui .no-underline-l{-webkit-text-decoration:none;text-decoration:none}}.swagger-ui .tl{text-align:left}.swagger-ui .tr{text-align:right}.swagger-ui .tc{text-align:center}.swagger-ui .tj{text-align:justify}@media screen and (min-width:30em){.swagger-ui .tl-ns{text-align:left}.swagger-ui .tr-ns{text-align:right}.swagger-ui .tc-ns{text-align:center}.swagger-ui .tj-ns{text-align:justify}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .tl-m{text-align:left}.swagger-ui .tr-m{text-align:right}.swagger-ui .tc-m{text-align:center}.swagger-ui .tj-m{text-align:justify}}@media screen and (min-width:60em){.swagger-ui .tl-l{text-align:left}.swagger-ui .tr-l{text-align:right}.swagger-ui .tc-l{text-align:center}.swagger-ui .tj-l{text-align:justify}}.swagger-ui .ttc{text-transform:capitalize}.swagger-ui .ttl{text-transform:lowercase}.swagger-ui .ttu{text-transform:uppercase}.swagger-ui .ttn{text-transform:none}@media screen and (min-width:30em){.swagger-ui .ttc-ns{text-transform:capitalize}.swagger-ui .ttl-ns{text-transform:lowercase}.swagger-ui .ttu-ns{text-transform:uppercase}.swagger-ui .ttn-ns{text-transform:none}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .ttc-m{text-transform:capitalize}.swagger-ui .ttl-m{text-transform:lowercase}.swagger-ui .ttu-m{text-transform:uppercase}.swagger-ui .ttn-m{text-transform:none}}@media screen and (min-width:60em){.swagger-ui .ttc-l{text-transform:capitalize}.swagger-ui .ttl-l{text-transform:lowercase}.swagger-ui .ttu-l{text-transform:uppercase}.swagger-ui .ttn-l{text-transform:none}}.swagger-ui .f-6,.swagger-ui .f-headline{font-size:6rem}.swagger-ui .f-5,.swagger-ui .f-subheadline{font-size:5rem}.swagger-ui .f1{font-size:3rem}.swagger-ui .f2{font-size:2.25rem}.swagger-ui .f3{font-size:1.5rem}.swagger-ui .f4{font-size:1.25rem}.swagger-ui .f5{font-size:1rem}.swagger-ui .f6{font-size:.875rem}.swagger-ui .f7{font-size:.75rem}@media screen and (min-width:30em){.swagger-ui .f-6-ns,.swagger-ui .f-headline-ns{font-size:6rem}.swagger-ui .f-5-ns,.swagger-ui .f-subheadline-ns{font-size:5rem}.swagger-ui .f1-ns{font-size:3rem}.swagger-ui .f2-ns{font-size:2.25rem}.swagger-ui .f3-ns{font-size:1.5rem}.swagger-ui .f4-ns{font-size:1.25rem}.swagger-ui .f5-ns{font-size:1rem}.swagger-ui .f6-ns{font-size:.875rem}.swagger-ui .f7-ns{font-size:.75rem}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .f-6-m,.swagger-ui .f-headline-m{font-size:6rem}.swagger-ui .f-5-m,.swagger-ui .f-subheadline-m{font-size:5rem}.swagger-ui .f1-m{font-size:3rem}.swagger-ui .f2-m{font-size:2.25rem}.swagger-ui .f3-m{font-size:1.5rem}.swagger-ui .f4-m{font-size:1.25rem}.swagger-ui .f5-m{font-size:1rem}.swagger-ui .f6-m{font-size:.875rem}.swagger-ui .f7-m{font-size:.75rem}}@media screen and (min-width:60em){.swagger-ui .f-6-l,.swagger-ui .f-headline-l{font-size:6rem}.swagger-ui .f-5-l,.swagger-ui .f-subheadline-l{font-size:5rem}.swagger-ui .f1-l{font-size:3rem}.swagger-ui .f2-l{font-size:2.25rem}.swagger-ui .f3-l{font-size:1.5rem}.swagger-ui .f4-l{font-size:1.25rem}.swagger-ui .f5-l{font-size:1rem}.swagger-ui .f6-l{font-size:.875rem}.swagger-ui .f7-l{font-size:.75rem}}.swagger-ui .measure{max-width:30em}.swagger-ui .measure-wide{max-width:34em}.swagger-ui .measure-narrow{max-width:20em}.swagger-ui .indent{margin-bottom:0;margin-top:0;text-indent:1em}.swagger-ui .small-caps{font-feature-settings:"smcp";font-variant:small-caps}.swagger-ui .truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media screen and (min-width:30em){.swagger-ui .measure-ns{max-width:30em}.swagger-ui .measure-wide-ns{max-width:34em}.swagger-ui .measure-narrow-ns{max-width:20em}.swagger-ui .indent-ns{margin-bottom:0;margin-top:0;text-indent:1em}.swagger-ui .small-caps-ns{font-feature-settings:"smcp";font-variant:small-caps}.swagger-ui .truncate-ns{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .measure-m{max-width:30em}.swagger-ui .measure-wide-m{max-width:34em}.swagger-ui .measure-narrow-m{max-width:20em}.swagger-ui .indent-m{margin-bottom:0;margin-top:0;text-indent:1em}.swagger-ui .small-caps-m{font-feature-settings:"smcp";font-variant:small-caps}.swagger-ui .truncate-m{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}}@media screen and (min-width:60em){.swagger-ui .measure-l{max-width:30em}.swagger-ui .measure-wide-l{max-width:34em}.swagger-ui .measure-narrow-l{max-width:20em}.swagger-ui .indent-l{margin-bottom:0;margin-top:0;text-indent:1em}.swagger-ui .small-caps-l{font-feature-settings:"smcp";font-variant:small-caps}.swagger-ui .truncate-l{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}}.swagger-ui .overflow-container{overflow-y:scroll}.swagger-ui .center{margin-left:auto;margin-right:auto}.swagger-ui .mr-auto{margin-right:auto}.swagger-ui .ml-auto{margin-left:auto}@media screen and (min-width:30em){.swagger-ui .center-ns{margin-left:auto;margin-right:auto}.swagger-ui .mr-auto-ns{margin-right:auto}.swagger-ui .ml-auto-ns{margin-left:auto}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .center-m{margin-left:auto;margin-right:auto}.swagger-ui .mr-auto-m{margin-right:auto}.swagger-ui .ml-auto-m{margin-left:auto}}@media screen and (min-width:60em){.swagger-ui .center-l{margin-left:auto;margin-right:auto}.swagger-ui .mr-auto-l{margin-right:auto}.swagger-ui .ml-auto-l{margin-left:auto}}.swagger-ui .clip{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}@media screen and (min-width:30em){.swagger-ui .clip-ns{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .clip-m{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}}@media screen and (min-width:60em){.swagger-ui .clip-l{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}}.swagger-ui .ws-normal{white-space:normal}.swagger-ui .nowrap{white-space:nowrap}.swagger-ui .pre{white-space:pre}@media screen and (min-width:30em){.swagger-ui .ws-normal-ns{white-space:normal}.swagger-ui .nowrap-ns{white-space:nowrap}.swagger-ui .pre-ns{white-space:pre}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .ws-normal-m{white-space:normal}.swagger-ui .nowrap-m{white-space:nowrap}.swagger-ui .pre-m{white-space:pre}}@media screen and (min-width:60em){.swagger-ui .ws-normal-l{white-space:normal}.swagger-ui .nowrap-l{white-space:nowrap}.swagger-ui .pre-l{white-space:pre}}.swagger-ui .v-base{vertical-align:baseline}.swagger-ui .v-mid{vertical-align:middle}.swagger-ui .v-top{vertical-align:top}.swagger-ui .v-btm{vertical-align:bottom}@media screen and (min-width:30em){.swagger-ui .v-base-ns{vertical-align:baseline}.swagger-ui .v-mid-ns{vertical-align:middle}.swagger-ui .v-top-ns{vertical-align:top}.swagger-ui .v-btm-ns{vertical-align:bottom}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .v-base-m{vertical-align:baseline}.swagger-ui .v-mid-m{vertical-align:middle}.swagger-ui .v-top-m{vertical-align:top}.swagger-ui .v-btm-m{vertical-align:bottom}}@media screen and (min-width:60em){.swagger-ui .v-base-l{vertical-align:baseline}.swagger-ui .v-mid-l{vertical-align:middle}.swagger-ui .v-top-l{vertical-align:top}.swagger-ui .v-btm-l{vertical-align:bottom}}.swagger-ui .dim{opacity:1;transition:opacity .15s ease-in}.swagger-ui .dim:focus,.swagger-ui .dim:hover{opacity:.5;transition:opacity .15s ease-in}.swagger-ui .dim:active{opacity:.8;transition:opacity .15s ease-out}.swagger-ui .glow{transition:opacity .15s ease-in}.swagger-ui .glow:focus,.swagger-ui .glow:hover{opacity:1;transition:opacity .15s ease-in}.swagger-ui .hide-child .child{opacity:0;transition:opacity .15s ease-in}.swagger-ui .hide-child:active .child,.swagger-ui .hide-child:focus .child,.swagger-ui .hide-child:hover .child{opacity:1;transition:opacity .15s ease-in}.swagger-ui .underline-hover:focus,.swagger-ui .underline-hover:hover{-webkit-text-decoration:underline;text-decoration:underline}.swagger-ui .grow{-moz-osx-font-smoothing:grayscale;backface-visibility:hidden;transform:translateZ(0);transition:transform .25s ease-out}.swagger-ui .grow:focus,.swagger-ui .grow:hover{transform:scale(1.05)}.swagger-ui .grow:active{transform:scale(.9)}.swagger-ui .grow-large{-moz-osx-font-smoothing:grayscale;backface-visibility:hidden;transform:translateZ(0);transition:transform .25s ease-in-out}.swagger-ui .grow-large:focus,.swagger-ui .grow-large:hover{transform:scale(1.2)}.swagger-ui .grow-large:active{transform:scale(.95)}.swagger-ui .pointer:hover{cursor:pointer}.swagger-ui .shadow-hover{cursor:pointer;position:relative;transition:all .5s cubic-bezier(.165,.84,.44,1)}.swagger-ui .shadow-hover:after{border-radius:inherit;box-shadow:0 0 16px 2px rgba(0,0,0,.2);content:"";height:100%;left:0;opacity:0;position:absolute;top:0;transition:opacity .5s cubic-bezier(.165,.84,.44,1);width:100%;z-index:-1}.swagger-ui .shadow-hover:focus:after,.swagger-ui .shadow-hover:hover:after{opacity:1}.swagger-ui .bg-animate,.swagger-ui .bg-animate:focus,.swagger-ui .bg-animate:hover{transition:background-color .15s ease-in-out}.swagger-ui .z-0{z-index:0}.swagger-ui .z-1{z-index:1}.swagger-ui .z-2{z-index:2}.swagger-ui .z-3{z-index:3}.swagger-ui .z-4{z-index:4}.swagger-ui .z-5{z-index:5}.swagger-ui .z-999{z-index:999}.swagger-ui .z-9999{z-index:9999}.swagger-ui .z-max{z-index:2147483647}.swagger-ui .z-inherit{z-index:inherit}.swagger-ui .z-initial,.swagger-ui .z-unset{z-index:auto}.swagger-ui .nested-copy-line-height ol,.swagger-ui .nested-copy-line-height p,.swagger-ui .nested-copy-line-height ul{line-height:1.5}.swagger-ui .nested-headline-line-height h1,.swagger-ui .nested-headline-line-height h2,.swagger-ui .nested-headline-line-height h3,.swagger-ui .nested-headline-line-height h4,.swagger-ui .nested-headline-line-height h5,.swagger-ui .nested-headline-line-height h6{line-height:1.25}.swagger-ui .nested-list-reset ol,.swagger-ui .nested-list-reset ul{list-style-type:none;margin-left:0;padding-left:0}.swagger-ui .nested-copy-indent p+p{margin-bottom:0;margin-top:0;text-indent:.1em}.swagger-ui .nested-copy-seperator p+p{margin-top:1.5em}.swagger-ui .nested-img img{display:block;max-width:100%;width:100%}.swagger-ui .nested-links a{color:#357edd;transition:color .15s ease-in}.swagger-ui .nested-links a:focus,.swagger-ui .nested-links a:hover{color:#96ccff;transition:color .15s ease-in}.swagger-ui .wrapper{box-sizing:border-box;margin:0 auto;max-width:1460px;padding:0 20px;width:100%}.swagger-ui .opblock-tag-section{display:flex;flex-direction:column}.swagger-ui .try-out.btn-group{display:flex;flex:.1 2 auto;padding:0}.swagger-ui .try-out__btn{margin-left:1.25rem}.swagger-ui .opblock-tag{align-items:center;border-bottom:1px solid rgba(59,65,81,.3);cursor:pointer;display:flex;padding:10px 20px 10px 10px;transition:all .2s}.swagger-ui .opblock-tag:hover{background:rgba(0,0,0,.02)}.swagger-ui .opblock-tag{color:#3b4151;font-family:sans-serif;font-size:24px;margin:0 0 5px}.swagger-ui .opblock-tag.no-desc span{flex:1}.swagger-ui .opblock-tag svg{transition:all .4s}.swagger-ui .opblock-tag small{color:#3b4151;flex:2;font-family:sans-serif;font-size:14px;font-weight:400;padding:0 10px}.swagger-ui .opblock-tag>div{flex:1 1 150px;font-weight:400;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media(max-width:640px){.swagger-ui .opblock-tag small,.swagger-ui .opblock-tag>div{flex:1}}.swagger-ui .opblock-tag .info__externaldocs{text-align:right}.swagger-ui .parameter__type{color:#3b4151;font-family:monospace;font-size:12px;font-weight:600;padding:5px 0}.swagger-ui .parameter-controls{margin-top:.75em}.swagger-ui .examples__title{display:block;font-size:1.1em;font-weight:700;margin-bottom:.75em}.swagger-ui .examples__section{margin-top:1.5em}.swagger-ui .examples__section-header{font-size:.9rem;font-weight:700;margin-bottom:.5rem}.swagger-ui .examples-select{display:inline-block;margin-bottom:.75em}.swagger-ui .examples-select .examples-select-element{width:100%}.swagger-ui .examples-select__section-label{font-size:.9rem;font-weight:700;margin-right:.5rem}.swagger-ui .example__section{margin-top:1.5em}.swagger-ui .example__section-header{font-size:.9rem;font-weight:700;margin-bottom:.5rem}.swagger-ui .view-line-link{cursor:pointer;margin:0 5px;position:relative;top:3px;transition:all .5s;width:20px}.swagger-ui .opblock{border:1px solid #000;border-radius:4px;box-shadow:0 0 3px rgba(0,0,0,.19);margin:0 0 15px}.swagger-ui .opblock .tab-header{display:flex;flex:1}.swagger-ui .opblock .tab-header .tab-item{cursor:pointer;padding:0 40px}.swagger-ui .opblock .tab-header .tab-item:first-of-type{padding:0 40px 0 0}.swagger-ui .opblock .tab-header .tab-item.active h4 span{position:relative}.swagger-ui .opblock .tab-header .tab-item.active h4 span:after{background:grey;bottom:-15px;content:"";height:4px;left:50%;position:absolute;transform:translateX(-50%);width:120%}.swagger-ui .opblock.is-open .opblock-summary{border-bottom:1px solid #000}.swagger-ui .opblock .opblock-section-header{align-items:center;background:hsla(0,0%,100%,.8);box-shadow:0 1px 2px rgba(0,0,0,.1);display:flex;min-height:50px;padding:8px 20px}.swagger-ui .opblock .opblock-section-header>label{align-items:center;color:#3b4151;display:flex;font-family:sans-serif;font-size:12px;font-weight:700;margin:0 0 0 auto}.swagger-ui .opblock .opblock-section-header>label>span{padding:0 10px 0 0}.swagger-ui .opblock .opblock-section-header h4{color:#3b4151;flex:1;font-family:sans-serif;font-size:14px;margin:0}.swagger-ui .opblock .opblock-summary-method{background:#000;border-radius:3px;color:#fff;font-family:sans-serif;font-size:14px;font-weight:700;min-width:80px;padding:6px 0;text-align:center;text-shadow:0 1px 0 rgba(0,0,0,.1)}@media(max-width:768px){.swagger-ui .opblock .opblock-summary-method{font-size:12px}}.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{align-items:center;color:#3b4151;display:flex;font-family:monospace;font-size:16px;font-weight:600;word-break:break-word}@media(max-width:768px){.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{font-size:12px}}.swagger-ui .opblock .opblock-summary-path{flex-shrink:1}@media(max-width:640px){.swagger-ui .opblock .opblock-summary-path{max-width:100%}}.swagger-ui .opblock .opblock-summary-path__deprecated{-webkit-text-decoration:line-through;text-decoration:line-through}.swagger-ui .opblock .opblock-summary-operation-id{font-size:14px}.swagger-ui .opblock .opblock-summary-description{color:#3b4151;font-family:sans-serif;font-size:13px;word-break:break-word}.swagger-ui .opblock .opblock-summary-path-description-wrapper{align-items:center;display:flex;flex-direction:row;flex-wrap:wrap;gap:0 10px;padding:0 10px;width:100%}@media(max-width:550px){.swagger-ui .opblock .opblock-summary-path-description-wrapper{align-items:flex-start;flex-direction:column}}.swagger-ui .opblock .opblock-summary{align-items:center;cursor:pointer;display:flex;padding:5px}.swagger-ui .opblock .opblock-summary .view-line-link{cursor:pointer;margin:0;position:relative;top:2px;transition:all .5s;width:0}.swagger-ui .opblock .opblock-summary:hover .view-line-link{margin:0 5px;width:18px}.swagger-ui .opblock .opblock-summary:hover .view-line-link.copy-to-clipboard{width:24px}.swagger-ui .opblock.opblock-post{background:rgba(73,204,144,.1);border-color:#49cc90}.swagger-ui .opblock.opblock-post .opblock-summary-method{background:#49cc90}.swagger-ui .opblock.opblock-post .opblock-summary{border-color:#49cc90}.swagger-ui .opblock.opblock-post .tab-header .tab-item.active h4 span:after{background:#49cc90}.swagger-ui .opblock.opblock-put{background:rgba(252,161,48,.1);border-color:#fca130}.swagger-ui .opblock.opblock-put .opblock-summary-method{background:#fca130}.swagger-ui .opblock.opblock-put .opblock-summary{border-color:#fca130}.swagger-ui .opblock.opblock-put .tab-header .tab-item.active h4 span:after{background:#fca130}.swagger-ui .opblock.opblock-delete{background:rgba(249,62,62,.1);border-color:#f93e3e}.swagger-ui .opblock.opblock-delete .opblock-summary-method{background:#f93e3e}.swagger-ui .opblock.opblock-delete .opblock-summary{border-color:#f93e3e}.swagger-ui .opblock.opblock-delete .tab-header .tab-item.active h4 span:after{background:#f93e3e}.swagger-ui .opblock.opblock-get{background:rgba(97,175,254,.1);border-color:#61affe}.swagger-ui .opblock.opblock-get .opblock-summary-method{background:#61affe}.swagger-ui .opblock.opblock-get .opblock-summary{border-color:#61affe}.swagger-ui .opblock.opblock-get .tab-header .tab-item.active h4 span:after{background:#61affe}.swagger-ui .opblock.opblock-patch{background:rgba(80,227,194,.1);border-color:#50e3c2}.swagger-ui .opblock.opblock-patch .opblock-summary-method{background:#50e3c2}.swagger-ui .opblock.opblock-patch .opblock-summary{border-color:#50e3c2}.swagger-ui .opblock.opblock-patch .tab-header .tab-item.active h4 span:after{background:#50e3c2}.swagger-ui .opblock.opblock-head{background:rgba(144,18,254,.1);border-color:#9012fe}.swagger-ui .opblock.opblock-head .opblock-summary-method{background:#9012fe}.swagger-ui .opblock.opblock-head .opblock-summary{border-color:#9012fe}.swagger-ui .opblock.opblock-head .tab-header .tab-item.active h4 span:after{background:#9012fe}.swagger-ui .opblock.opblock-options{background:rgba(13,90,167,.1);border-color:#0d5aa7}.swagger-ui .opblock.opblock-options .opblock-summary-method{background:#0d5aa7}.swagger-ui .opblock.opblock-options .opblock-summary{border-color:#0d5aa7}.swagger-ui .opblock.opblock-options .tab-header .tab-item.active h4 span:after{background:#0d5aa7}.swagger-ui .opblock.opblock-deprecated{background:hsla(0,0%,92%,.1);border-color:#ebebeb;opacity:.6}.swagger-ui .opblock.opblock-deprecated .opblock-summary-method{background:#ebebeb}.swagger-ui .opblock.opblock-deprecated .opblock-summary{border-color:#ebebeb}.swagger-ui .opblock.opblock-deprecated .tab-header .tab-item.active h4 span:after{background:#ebebeb}.swagger-ui .opblock .opblock-schemes{padding:8px 20px}.swagger-ui .opblock .opblock-schemes .schemes-title{padding:0 10px 0 0}.swagger-ui .filter .operation-filter-input{border:2px solid #d8dde7;margin:20px 0;padding:10px;width:100%}.swagger-ui .download-url-wrapper .failed,.swagger-ui .filter .failed{color:red}.swagger-ui .download-url-wrapper .loading,.swagger-ui .filter .loading{color:#aaa}.swagger-ui .model-example{margin-top:1em}.swagger-ui .tab{display:flex;list-style:none;padding:0}.swagger-ui .tab li{color:#3b4151;cursor:pointer;font-family:sans-serif;font-size:12px;min-width:60px;padding:0}.swagger-ui .tab li:first-of-type{padding-left:0;padding-right:12px;position:relative}.swagger-ui .tab li:first-of-type:after{background:rgba(0,0,0,.2);content:"";height:100%;position:absolute;right:6px;top:0;width:1px}.swagger-ui .tab li.active{font-weight:700}.swagger-ui .tab li button.tablinks{background:none;border:0;color:inherit;font-family:inherit;font-weight:inherit;padding:0}.swagger-ui .opblock-description-wrapper,.swagger-ui .opblock-external-docs-wrapper,.swagger-ui .opblock-title_normal{color:#3b4151;font-family:sans-serif;font-size:12px;margin:0 0 5px;padding:15px 20px}.swagger-ui .opblock-description-wrapper h4,.swagger-ui .opblock-external-docs-wrapper h4,.swagger-ui .opblock-title_normal h4{color:#3b4151;font-family:sans-serif;font-size:12px;margin:0 0 5px}.swagger-ui .opblock-description-wrapper p,.swagger-ui .opblock-external-docs-wrapper p,.swagger-ui .opblock-title_normal p{color:#3b4151;font-family:sans-serif;font-size:14px;margin:0}.swagger-ui .opblock-external-docs-wrapper h4{padding-left:0}.swagger-ui .execute-wrapper{padding:20px;text-align:right}.swagger-ui .execute-wrapper .btn{padding:8px 40px;width:100%}.swagger-ui .body-param-options{display:flex;flex-direction:column}.swagger-ui .body-param-options .body-param-edit{padding:10px 0}.swagger-ui .body-param-options label{padding:8px 0}.swagger-ui .body-param-options label select{margin:3px 0 0}.swagger-ui .responses-inner{padding:20px}.swagger-ui .responses-inner h4,.swagger-ui .responses-inner h5{color:#3b4151;font-family:sans-serif;font-size:12px;margin:10px 0 5px}.swagger-ui .responses-inner .curl{max-height:400px;min-height:6em;overflow-y:auto}.swagger-ui .response-col_status{color:#3b4151;font-family:sans-serif;font-size:14px}.swagger-ui .response-col_status .response-undocumented{color:#909090;font-family:monospace;font-size:11px;font-weight:600}.swagger-ui .response-col_links{color:#3b4151;font-family:sans-serif;font-size:14px;max-width:40em;padding-left:2em}.swagger-ui .response-col_links .response-undocumented{color:#909090;font-family:monospace;font-size:11px;font-weight:600}.swagger-ui .response-col_links .operation-link{margin-bottom:1.5em}.swagger-ui .response-col_links .operation-link .description{margin-bottom:.5em}.swagger-ui .opblock-body .opblock-loading-animation{display:block;margin:3em auto}.swagger-ui .opblock-body pre.microlight{background:#333;border-radius:4px;font-size:12px;hyphens:auto;margin:0;padding:10px;white-space:pre-wrap;word-break:break-all;word-break:break-word;word-wrap:break-word;color:#fff;font-family:monospace;font-weight:600}.swagger-ui .opblock-body pre.microlight .headerline{display:block}.swagger-ui .highlight-code{position:relative}.swagger-ui .highlight-code>.microlight{max-height:400px;min-height:6em;overflow-y:auto}.swagger-ui .highlight-code>.microlight code{white-space:pre-wrap!important;word-break:break-all}.swagger-ui .curl-command{position:relative}.swagger-ui .download-contents{align-items:center;background:#7d8293;border:none;border-radius:4px;bottom:10px;color:#fff;display:flex;font-family:sans-serif;font-size:14px;font-weight:600;height:30px;justify-content:center;padding:5px;position:absolute;right:10px;text-align:center}.swagger-ui .scheme-container{background:#fff;box-shadow:0 1px 2px 0 rgba(0,0,0,.15);margin:0 0 20px;padding:30px 0}.swagger-ui .scheme-container .schemes{align-items:flex-end;display:flex;flex-wrap:wrap;gap:10px;justify-content:space-between}.swagger-ui .scheme-container .schemes>.schemes-server-container{display:flex;flex-wrap:wrap;gap:10px}.swagger-ui .scheme-container .schemes>.schemes-server-container>label{color:#3b4151;display:flex;flex-direction:column;font-family:sans-serif;font-size:12px;font-weight:700;margin:-20px 15px 0 0}.swagger-ui .scheme-container .schemes>.schemes-server-container>label select{min-width:130px;text-transform:uppercase}.swagger-ui .scheme-container .schemes:not(:has(.schemes-server-container)){justify-content:flex-end}.swagger-ui .scheme-container .schemes .auth-wrapper{flex:none;justify-content:start}.swagger-ui .scheme-container .schemes .auth-wrapper .authorize{display:flex;flex-wrap:nowrap;margin:0;padding-right:20px}.swagger-ui .loading-container{align-items:center;display:flex;flex-direction:column;justify-content:center;margin-top:1em;min-height:1px;padding:40px 0 60px}.swagger-ui .loading-container .loading{position:relative}.swagger-ui .loading-container .loading:after{color:#3b4151;content:"loading";font-family:sans-serif;font-size:10px;font-weight:700;left:50%;position:absolute;text-transform:uppercase;top:50%;transform:translate(-50%,-50%)}.swagger-ui .loading-container .loading:before{animation:rotation 1s linear infinite,opacity .5s;backface-visibility:hidden;border:2px solid rgba(85,85,85,.1);border-radius:100%;border-top-color:rgba(0,0,0,.6);content:"";display:block;height:60px;left:50%;margin:-30px;opacity:1;position:absolute;top:50%;width:60px}@keyframes rotation{to{transform:rotate(1turn)}}.swagger-ui .response-controls{display:flex;padding-top:1em}.swagger-ui .response-control-media-type{margin-right:1em}.swagger-ui .response-control-media-type--accept-controller select{border-color:green}.swagger-ui .response-control-media-type__accept-message{color:green;font-size:.7em}.swagger-ui .response-control-examples__title,.swagger-ui .response-control-media-type__title{display:block;font-size:.7em;margin-bottom:.2em}@keyframes blinker{50%{opacity:0}}.swagger-ui .hidden{display:none}.swagger-ui .no-margin{border:none;height:auto;margin:0;padding:0}.swagger-ui .float-right{float:right}.swagger-ui .svg-assets{height:0;position:absolute;width:0}.swagger-ui section h3{color:#3b4151;font-family:sans-serif}.swagger-ui a.nostyle{display:inline}.swagger-ui a.nostyle,.swagger-ui a.nostyle:visited{color:inherit;cursor:pointer;text-decoration:inherit}.swagger-ui .fallback{color:#aaa;padding:1em}.swagger-ui .version-pragma{height:100%;padding:5em 0}.swagger-ui .version-pragma__message{display:flex;font-size:1.2em;height:100%;justify-content:center;line-height:1.5em;padding:0 .6em;text-align:center}.swagger-ui .version-pragma__message>div{flex:1;max-width:55ch}.swagger-ui .version-pragma__message code{background-color:#dedede;padding:4px 4px 2px;white-space:pre}.swagger-ui .opblock-link{font-weight:400}.swagger-ui .opblock-link.shown{font-weight:700}.swagger-ui span.token-string{color:#555}.swagger-ui span.token-not-formatted{color:#555;font-weight:700}.swagger-ui .btn{background:transparent;border:2px solid grey;border-radius:4px;box-shadow:0 1px 2px rgba(0,0,0,.1);color:#3b4151;font-family:sans-serif;font-size:14px;font-weight:700;padding:5px 23px;transition:all .3s}.swagger-ui .btn.btn-sm{font-size:12px;padding:4px 23px}.swagger-ui .btn[disabled]{cursor:not-allowed;opacity:.3}.swagger-ui .btn:hover{box-shadow:0 0 5px rgba(0,0,0,.3)}.swagger-ui .btn.cancel{background-color:transparent;border-color:#ff6060;color:#ff6060;font-family:sans-serif}.swagger-ui .btn.authorize{background-color:transparent;border-color:#49cc90;color:#49cc90;display:inline;line-height:1}.swagger-ui .btn.authorize span{float:left;padding:4px 20px 0 0}.swagger-ui .btn.authorize svg{fill:#49cc90}.swagger-ui .btn.execute{background-color:#4990e2;border-color:#4990e2;color:#fff}.swagger-ui .btn-group{display:flex;padding:30px}.swagger-ui .btn-group .btn{flex:1}.swagger-ui .btn-group .btn:first-child{border-radius:4px 0 0 4px}.swagger-ui .btn-group .btn:last-child{border-radius:0 4px 4px 0}.swagger-ui .authorization__btn{background:none;border:none;padding:0 0 0 10px}.swagger-ui .authorization__btn .locked{opacity:1}.swagger-ui .authorization__btn .unlocked{opacity:.4}.swagger-ui .model-box-control,.swagger-ui .models-control,.swagger-ui .opblock-summary-control{all:inherit;border-bottom:0;cursor:pointer;flex:1;padding:0}.swagger-ui .model-box-control:focus,.swagger-ui .models-control:focus,.swagger-ui .opblock-summary-control:focus{outline:auto}.swagger-ui .expand-methods,.swagger-ui .expand-operation{background:none;border:none}.swagger-ui .expand-methods svg,.swagger-ui .expand-operation svg{height:20px;width:20px}.swagger-ui .expand-methods{padding:0 10px}.swagger-ui .expand-methods:hover svg{fill:#404040}.swagger-ui .expand-methods svg{transition:all .3s;fill:#707070}.swagger-ui button{cursor:pointer}.swagger-ui button.invalid{animation:shake .4s 1;background:#feebeb;border-color:#f93e3e}.swagger-ui .copy-to-clipboard{align-items:center;background:#7d8293;border:none;border-radius:4px;bottom:10px;display:flex;height:30px;justify-content:center;position:absolute;right:100px;width:30px}.swagger-ui .copy-to-clipboard button{background:url("data:image/svg+xml;charset=utf-8,") 50% no-repeat;border:none;flex-grow:1;flex-shrink:1;height:25px}.swagger-ui .copy-to-clipboard:active{background:#5e626f}.swagger-ui .opblock-control-arrow{background:none;border:none;text-align:center}.swagger-ui .curl-command .copy-to-clipboard{bottom:5px;height:20px;right:10px;width:20px}.swagger-ui .curl-command .copy-to-clipboard button{height:18px}.swagger-ui .opblock .opblock-summary .view-line-link.copy-to-clipboard{height:26px;position:static}.swagger-ui select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f7f7 url("data:image/svg+xml;charset=utf-8,") right 10px center no-repeat;background-size:20px;border:2px solid #41444e;border-radius:4px;box-shadow:0 1px 2px 0 rgba(0,0,0,.25);color:#3b4151;font-family:sans-serif;font-size:14px;font-weight:700;padding:5px 40px 5px 10px}.swagger-ui select[multiple]{background:#f7f7f7;margin:5px 0;padding:5px}.swagger-ui select.invalid{animation:shake .4s 1;background:#feebeb;border-color:#f93e3e}.swagger-ui .opblock-body select{min-width:230px}@media(max-width:768px){.swagger-ui .opblock-body select{min-width:180px}}@media(max-width:640px){.swagger-ui .opblock-body select{min-width:100%;width:100%}}.swagger-ui label{color:#3b4151;font-family:sans-serif;font-size:12px;font-weight:700;margin:0 0 5px}.swagger-ui input[type=email],.swagger-ui input[type=file],.swagger-ui input[type=password],.swagger-ui input[type=search],.swagger-ui input[type=text]{line-height:1}@media(max-width:768px){.swagger-ui input[type=email],.swagger-ui input[type=file],.swagger-ui input[type=password],.swagger-ui input[type=search],.swagger-ui input[type=text]{max-width:175px}}.swagger-ui input[type=email],.swagger-ui input[type=file],.swagger-ui input[type=password],.swagger-ui input[type=search],.swagger-ui input[type=text],.swagger-ui textarea{background:#fff;border:1px solid #d9d9d9;border-radius:4px;margin:5px 0;min-width:100px;padding:8px 10px}.swagger-ui input[type=email].invalid,.swagger-ui input[type=file].invalid,.swagger-ui input[type=password].invalid,.swagger-ui input[type=search].invalid,.swagger-ui input[type=text].invalid,.swagger-ui textarea.invalid{animation:shake .4s 1;background:#feebeb;border-color:#f93e3e}.swagger-ui input[disabled],.swagger-ui select[disabled],.swagger-ui textarea[disabled]{background-color:#fafafa;color:#888;cursor:not-allowed}.swagger-ui select[disabled]{border-color:#888}.swagger-ui textarea[disabled]{background-color:#41444e;color:#fff}@keyframes shake{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}.swagger-ui textarea{background:hsla(0,0%,100%,.8);border:none;border-radius:4px;color:#3b4151;font-family:monospace;font-size:12px;font-weight:600;min-height:280px;outline:none;padding:10px;width:100%}.swagger-ui textarea:focus{border:2px solid #61affe}.swagger-ui textarea.curl{background:#41444e;border-radius:4px;color:#fff;font-family:monospace;font-size:12px;font-weight:600;margin:0;min-height:100px;padding:10px;resize:none}.swagger-ui .checkbox{color:#303030;padding:5px 0 10px;transition:opacity .5s}.swagger-ui .checkbox label{display:flex}.swagger-ui .checkbox p{color:#3b4151;font-family:monospace;font-style:italic;font-weight:400!important;font-weight:600;margin:0!important}.swagger-ui .checkbox input[type=checkbox]{display:none}.swagger-ui .checkbox input[type=checkbox]+label>.item{background:#e8e8e8;border-radius:1px;box-shadow:0 0 0 2px #e8e8e8;cursor:pointer;display:inline-block;flex:none;height:16px;margin:0 8px 0 0;padding:5px;position:relative;top:3px;width:16px}.swagger-ui .checkbox input[type=checkbox]+label>.item:active{transform:scale(.9)}.swagger-ui .checkbox input[type=checkbox]:checked+label>.item{background:#e8e8e8 url("data:image/svg+xml;charset=utf-8,") 50% no-repeat}.swagger-ui .dialog-ux{bottom:0;left:0;position:fixed;right:0;top:0;z-index:9999}.swagger-ui .dialog-ux .backdrop-ux{background:rgba(0,0,0,.8);bottom:0;left:0;position:fixed;right:0;top:0}.swagger-ui .dialog-ux .modal-ux{background:#fff;border:1px solid #ebebeb;border-radius:4px;box-shadow:0 10px 30px 0 rgba(0,0,0,.2);left:50%;max-width:650px;min-width:300px;position:absolute;top:50%;transform:translate(-50%,-50%);width:100%;z-index:9999}.swagger-ui .dialog-ux .modal-ux-content{max-height:540px;overflow-y:auto;padding:20px}.swagger-ui .dialog-ux .modal-ux-content p{color:#41444e;color:#3b4151;font-family:sans-serif;font-size:12px;margin:0 0 5px}.swagger-ui .dialog-ux .modal-ux-content h4{color:#3b4151;font-family:sans-serif;font-size:18px;font-weight:600;margin:15px 0 0}.swagger-ui .dialog-ux .modal-ux-header{align-items:center;border-bottom:1px solid #ebebeb;display:flex;padding:12px 0}.swagger-ui .dialog-ux .modal-ux-header .close-modal{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:none;padding:0 10px}.swagger-ui .dialog-ux .modal-ux-header h3{color:#3b4151;flex:1;font-family:sans-serif;font-size:20px;font-weight:600;margin:0;padding:0 20px}.swagger-ui .model{color:#3b4151;font-family:monospace;font-size:12px;font-weight:300;font-weight:600}.swagger-ui .model .deprecated span,.swagger-ui .model .deprecated td{color:#a0a0a0!important}.swagger-ui .model .deprecated>td:first-of-type{-webkit-text-decoration:line-through;text-decoration:line-through}.swagger-ui .model-toggle{cursor:pointer;display:inline-block;font-size:10px;margin:auto .3em;position:relative;top:6px;transform:rotate(90deg);transform-origin:50% 50%;transition:transform .15s ease-in}.swagger-ui .model-toggle.collapsed{transform:rotate(0deg)}.swagger-ui .model-toggle:after{background:url("data:image/svg+xml;charset=utf-8,") 50% no-repeat;background-size:100%;content:"";display:block;height:20px;width:20px}.swagger-ui .model-jump-to-path{cursor:pointer;position:relative}.swagger-ui .model-jump-to-path .view-line-link{cursor:pointer;position:absolute;top:-.4em}.swagger-ui .model-title{position:relative}.swagger-ui .model-title:hover .model-hint{visibility:visible}.swagger-ui .model-hint{background:rgba(0,0,0,.7);border-radius:4px;color:#ebebeb;padding:.1em .5em;position:absolute;top:-1.8em;visibility:hidden;white-space:nowrap}.swagger-ui .model p{margin:0 0 1em}.swagger-ui .model .property{color:#999;font-style:italic}.swagger-ui .model .property.primitive{color:#6b6b6b}.swagger-ui .model .external-docs,.swagger-ui table.model tr.description{color:#666;font-weight:400}.swagger-ui table.model tr.description td:first-child,.swagger-ui table.model tr.property-row.required td:first-child{font-weight:700}.swagger-ui table.model tr.property-row td{vertical-align:top}.swagger-ui table.model tr.property-row td:first-child{padding-right:.2em}.swagger-ui table.model tr.property-row .star{color:red}.swagger-ui table.model tr.extension{color:#777}.swagger-ui table.model tr.extension td:last-child{vertical-align:top}.swagger-ui table.model tr.external-docs td:first-child{font-weight:700}.swagger-ui table.model tr .renderedMarkdown p:first-child{margin-top:0}.swagger-ui section.models{border:1px solid rgba(59,65,81,.3);border-radius:4px;margin:30px 0}.swagger-ui section.models .pointer{cursor:pointer}.swagger-ui section.models.is-open{padding:0 0 20px}.swagger-ui section.models.is-open h4{border-bottom:1px solid rgba(59,65,81,.3);margin:0 0 5px}.swagger-ui section.models h4{align-items:center;color:#606060;cursor:pointer;display:flex;font-family:sans-serif;font-size:16px;margin:0;padding:10px 20px 10px 10px;transition:all .2s}.swagger-ui section.models h4 svg{transition:all .4s}.swagger-ui section.models h4 span{flex:1}.swagger-ui section.models h4:hover{background:rgba(0,0,0,.02)}.swagger-ui section.models h5{color:#707070;font-family:sans-serif;font-size:16px;margin:0 0 10px}.swagger-ui section.models .model-jump-to-path{position:relative;top:5px}.swagger-ui section.models .model-container{background:rgba(0,0,0,.05);border-radius:4px;margin:0 20px 15px;position:relative;transition:all .5s}.swagger-ui section.models .model-container:hover{background:rgba(0,0,0,.07)}.swagger-ui section.models .model-container:first-of-type{margin:20px}.swagger-ui section.models .model-container:last-of-type{margin:0 20px}.swagger-ui section.models .model-container .models-jump-to-path{opacity:.65;position:absolute;right:5px;top:8px}.swagger-ui section.models .model-box{background:none}.swagger-ui .model-box{background:rgba(0,0,0,.1);border-radius:4px;display:inline-block;padding:10px}.swagger-ui .model-box .model-jump-to-path{position:relative;top:4px}.swagger-ui .model-box.deprecated{opacity:.5}.swagger-ui .model-title{color:#505050;font-family:sans-serif;font-size:16px}.swagger-ui .model-title img{bottom:0;margin-left:1em;position:relative}.swagger-ui .model-deprecated-warning{color:#f93e3e;font-family:sans-serif;font-size:16px;font-weight:600;margin-right:1em}.swagger-ui span>span.model .brace-close{padding:0 0 0 10px}.swagger-ui .prop-name{display:inline-block;margin-right:1em}.swagger-ui .prop-type{color:#55a}.swagger-ui .prop-enum{display:block}.swagger-ui .prop-format{color:#606060}.swagger-ui .servers>label{color:#3b4151;font-family:sans-serif;font-size:12px;margin:-20px 15px 0 0}.swagger-ui .servers>label select{max-width:100%;min-width:130px;width:100%}.swagger-ui .servers h4.message{padding-bottom:2em}.swagger-ui .servers table tr{width:30em}.swagger-ui .servers table td{display:inline-block;max-width:15em;padding-bottom:10px;padding-top:10px;vertical-align:middle}.swagger-ui .servers table td:first-of-type{padding-right:1em}.swagger-ui .servers table td input{height:100%;width:100%}.swagger-ui .servers .computed-url{margin:2em 0}.swagger-ui .servers .computed-url code{display:inline-block;font-size:16px;margin:0 1em;padding:4px}.swagger-ui .servers-title{font-size:12px;font-weight:700}.swagger-ui .operation-servers h4.message{margin-bottom:2em}.swagger-ui table{border-collapse:collapse;padding:0 10px;width:100%}.swagger-ui table.model tbody tr td{padding:0;vertical-align:top}.swagger-ui table.model tbody tr td:first-of-type{padding:0 0 0 2em;width:174px}.swagger-ui table.headers td{color:#3b4151;font-family:monospace;font-size:12px;font-weight:300;font-weight:600;vertical-align:middle}.swagger-ui table.headers .header-example{color:#999;font-style:italic}.swagger-ui table tbody tr td{padding:10px 0 0;vertical-align:top}.swagger-ui table tbody tr td:first-of-type{min-width:6em;padding:10px 0}.swagger-ui table thead tr td,.swagger-ui table thead tr th{border-bottom:1px solid rgba(59,65,81,.2);color:#3b4151;font-family:sans-serif;font-size:12px;font-weight:700;padding:12px 0;text-align:left}.swagger-ui .parameters-col_description{margin-bottom:2em;width:99%}.swagger-ui .parameters-col_description input{max-width:340px;width:100%}.swagger-ui .parameters-col_description select{border-width:1px}.swagger-ui .parameters-col_description .markdown p,.swagger-ui .parameters-col_description .renderedMarkdown p{margin:0}.swagger-ui .parameter__name{color:#3b4151;font-family:sans-serif;font-size:16px;font-weight:400;margin-right:.75em}.swagger-ui .parameter__name.required{font-weight:700}.swagger-ui .parameter__name.required span{color:red}.swagger-ui .parameter__name.required:after{color:rgba(255,0,0,.6);content:"required";font-size:10px;padding:5px;position:relative;top:-6px}.swagger-ui .parameter__extension,.swagger-ui .parameter__in{color:grey;font-family:monospace;font-size:12px;font-style:italic;font-weight:600}.swagger-ui .parameter__deprecated{color:red;font-family:monospace;font-size:12px;font-style:italic;font-weight:600}.swagger-ui .parameter__empty_value_toggle{display:block;font-size:13px;padding-bottom:12px;padding-top:5px}.swagger-ui .parameter__empty_value_toggle input{margin-right:7px;width:auto}.swagger-ui .parameter__empty_value_toggle.disabled{opacity:.7}.swagger-ui .table-container{padding:20px}.swagger-ui .response-col_description{width:99%}.swagger-ui .response-col_description .markdown p,.swagger-ui .response-col_description .renderedMarkdown p{margin:0}.swagger-ui .response-col_links{min-width:6em}.swagger-ui .response__extension{color:grey;font-family:monospace;font-size:12px;font-style:italic;font-weight:600}.swagger-ui .topbar{background-color:#1b1b1b;padding:10px 0}.swagger-ui .topbar .topbar-wrapper{align-items:center;display:flex;flex-wrap:wrap;gap:10px}@media(max-width:550px){.swagger-ui .topbar .topbar-wrapper{align-items:start;flex-direction:column}}.swagger-ui .topbar a{align-items:center;color:#fff;display:flex;flex:1;font-family:sans-serif;font-size:1.5em;font-weight:700;max-width:300px;-webkit-text-decoration:none;text-decoration:none}.swagger-ui .topbar a span{margin:0;padding:0 10px}.swagger-ui .topbar .download-url-wrapper{display:flex;flex:3;justify-content:flex-end}.swagger-ui .topbar .download-url-wrapper input[type=text]{border:2px solid #62a03f;border-radius:4px 0 0 4px;margin:0;max-width:100%;outline:none;width:100%}.swagger-ui .topbar .download-url-wrapper .select-label{align-items:center;color:#f0f0f0;display:flex;margin:0;max-width:600px;width:100%}.swagger-ui .topbar .download-url-wrapper .select-label span{flex:1;font-size:16px;padding:0 10px 0 0;text-align:right}.swagger-ui .topbar .download-url-wrapper .select-label select{border:2px solid #62a03f;box-shadow:none;flex:2;outline:none;width:100%}.swagger-ui .topbar .download-url-wrapper .download-url-button{background:#62a03f;border:none;border-radius:0 4px 4px 0;color:#fff;font-family:sans-serif;font-size:16px;font-weight:700;padding:4px 30px}@media(max-width:550px){.swagger-ui .topbar .download-url-wrapper{width:100%}}.swagger-ui .info{margin:50px 0}.swagger-ui .info.failed-config{margin-left:auto;margin-right:auto;max-width:880px;text-align:center}.swagger-ui .info hgroup.main{margin:0 0 20px}.swagger-ui .info hgroup.main a{font-size:12px}.swagger-ui .info pre{font-size:14px}.swagger-ui .info li,.swagger-ui .info p,.swagger-ui .info table{color:#3b4151;font-family:sans-serif;font-size:14px}.swagger-ui .info h1,.swagger-ui .info h2,.swagger-ui .info h3,.swagger-ui .info h4,.swagger-ui .info h5{color:#3b4151;font-family:sans-serif}.swagger-ui .info a{color:#4990e2;font-family:sans-serif;font-size:14px;transition:all .4s}.swagger-ui .info a:hover{color:#1f69c0}.swagger-ui .info>div{margin:0 0 5px}.swagger-ui .info .base-url{color:#3b4151;font-family:monospace;font-size:12px;font-weight:300!important;font-weight:600;margin:0}.swagger-ui .info .title{color:#3b4151;font-family:sans-serif;font-size:36px;margin:0}.swagger-ui .info .title small{background:#7d8492;border-radius:57px;display:inline-block;font-size:10px;margin:0 0 0 5px;padding:2px 4px;position:relative;top:-5px;vertical-align:super}.swagger-ui .info .title small.version-stamp{background-color:#89bf04}.swagger-ui .info .title small pre{color:#fff;font-family:sans-serif;margin:0;padding:0}.swagger-ui .auth-btn-wrapper{display:flex;justify-content:center;padding:10px 0}.swagger-ui .auth-btn-wrapper .btn-done{margin-right:1em}.swagger-ui .auth-wrapper{display:flex;flex:1;justify-content:flex-end}.swagger-ui .auth-wrapper .authorize{margin-left:10px;margin-right:10px;padding-right:20px}.swagger-ui .auth-container{border-bottom:1px solid #ebebeb;margin:0 0 10px;padding:10px 20px}.swagger-ui .auth-container:last-of-type{border:0;margin:0;padding:10px 20px}.swagger-ui .auth-container h4{margin:5px 0 15px!important}.swagger-ui .auth-container .wrapper{margin:0;padding:0}.swagger-ui .auth-container input[type=password],.swagger-ui .auth-container input[type=text]{min-width:230px}.swagger-ui .auth-container .errors{background-color:#fee;border-radius:4px;color:red;color:#3b4151;font-family:monospace;font-size:12px;font-weight:600;margin:1em;padding:10px}.swagger-ui .auth-container .errors b{margin-right:1em;text-transform:capitalize}.swagger-ui .scopes h2{color:#3b4151;font-family:sans-serif;font-size:14px}.swagger-ui .scopes h2 a{color:#4990e2;cursor:pointer;font-size:12px;padding-left:10px;-webkit-text-decoration:underline;text-decoration:underline}.swagger-ui .scope-def{padding:0 0 20px}.swagger-ui .errors-wrapper{animation:scaleUp .5s;background:rgba(249,62,62,.1);border:2px solid #f93e3e;border-radius:4px;margin:20px;padding:10px 20px}.swagger-ui .errors-wrapper .error-wrapper{margin:0 0 10px}.swagger-ui .errors-wrapper .errors h4{color:#3b4151;font-family:monospace;font-size:14px;font-weight:600;margin:0}.swagger-ui .errors-wrapper .errors small{color:#606060}.swagger-ui .errors-wrapper .errors .message{white-space:pre-line}.swagger-ui .errors-wrapper .errors .message.thrown{max-width:100%}.swagger-ui .errors-wrapper .errors .error-line{cursor:pointer;-webkit-text-decoration:underline;text-decoration:underline}.swagger-ui .errors-wrapper hgroup{align-items:center;display:flex}.swagger-ui .errors-wrapper hgroup h4{color:#3b4151;flex:1;font-family:sans-serif;font-size:20px;margin:0}@keyframes scaleUp{0%{opacity:0;transform:scale(.8)}to{opacity:1;transform:scale(1)}}.swagger-ui .Resizer.vertical.disabled{display:none}.swagger-ui .markdown p,.swagger-ui .markdown pre,.swagger-ui .renderedMarkdown p,.swagger-ui .renderedMarkdown pre{margin:1em auto;word-break:break-all;word-break:break-word}.swagger-ui .markdown pre,.swagger-ui .renderedMarkdown pre{background:none;color:#000;font-weight:400;padding:0;white-space:pre-wrap}.swagger-ui .markdown code,.swagger-ui .renderedMarkdown code{background:rgba(0,0,0,.05);border-radius:4px;color:#9012fe;font-family:monospace;font-size:14px;font-weight:600;padding:5px 7px}.swagger-ui .markdown pre>code,.swagger-ui .renderedMarkdown pre>code{display:block}.swagger-ui .json-schema-2020-12{background-color:rgba(0,0,0,.05);border-radius:4px;margin:0 20px 15px;padding:12px 0 12px 20px}.swagger-ui .json-schema-2020-12:first-of-type{margin:20px}.swagger-ui .json-schema-2020-12:last-of-type{margin:0 20px}.swagger-ui .json-schema-2020-12--embedded{background-color:inherit;padding-bottom:0;padding-left:inherit;padding-right:inherit;padding-top:0}.swagger-ui .json-schema-2020-12-body{border-left:1px dashed rgba(0,0,0,.1);margin:2px 0}.swagger-ui .json-schema-2020-12-body--collapsed{display:none}.swagger-ui .json-schema-2020-12-accordion{border:none;outline:none;padding-left:0}.swagger-ui .json-schema-2020-12-accordion__children{display:inline-block}.swagger-ui .json-schema-2020-12-accordion__icon{display:inline-block;height:18px;vertical-align:bottom;width:18px}.swagger-ui .json-schema-2020-12-accordion__icon--expanded{transform:rotate(-90deg);transform-origin:50% 50%;transition:transform .15s ease-in}.swagger-ui .json-schema-2020-12-accordion__icon--collapsed{transform:rotate(0deg);transform-origin:50% 50%;transition:transform .15s ease-in}.swagger-ui .json-schema-2020-12-accordion__icon svg{height:20px;width:20px}.swagger-ui .json-schema-2020-12-expand-deep-button{border:none;color:#505050;color:#afaeae;font-family:sans-serif;font-size:12px;padding-right:0}.swagger-ui .json-schema-2020-12-keyword{margin:5px 0}.swagger-ui .json-schema-2020-12-keyword__children{border-left:1px dashed rgba(0,0,0,.1);margin:0 0 0 20px;padding:0}.swagger-ui .json-schema-2020-12-keyword__children--collapsed{display:none}.swagger-ui .json-schema-2020-12-keyword__name{font-size:12px;font-weight:700;margin-left:20px}.swagger-ui .json-schema-2020-12-keyword__name--primary{color:#3b4151;font-style:normal}.swagger-ui .json-schema-2020-12-keyword__name--secondary{color:#6b6b6b;font-style:italic}.swagger-ui .json-schema-2020-12-keyword__value{color:#6b6b6b;font-size:12px;font-style:italic;font-weight:400}.swagger-ui .json-schema-2020-12-keyword__value--primary{color:#3b4151;font-style:normal}.swagger-ui .json-schema-2020-12-keyword__value--secondary{color:#6b6b6b;font-style:italic}.swagger-ui .json-schema-2020-12-keyword__value--const,.swagger-ui .json-schema-2020-12-keyword__value--warning{border:1px dashed #6b6b6b;border-radius:4px;color:#3b4151;color:#6b6b6b;display:inline-block;font-family:monospace;font-style:normal;font-weight:600;line-height:1.5;margin-left:10px;padding:1px 4px}.swagger-ui .json-schema-2020-12-keyword__value--warning{border:1px dashed red;color:red}.swagger-ui .json-schema-2020-12-keyword__name--secondary+.json-schema-2020-12-keyword__value--secondary:before{content:"="}.swagger-ui .json-schema-2020-12__attribute{color:#3b4151;font-family:monospace;font-size:12px;padding-left:10px;text-transform:lowercase}.swagger-ui .json-schema-2020-12__attribute--primary{color:#55a}.swagger-ui .json-schema-2020-12__attribute--muted{color:gray}.swagger-ui .json-schema-2020-12__attribute--warning{color:red}.swagger-ui .json-schema-2020-12-keyword--\$vocabulary ul{border-left:1px dashed rgba(0,0,0,.1);margin:0 0 0 20px}.swagger-ui .json-schema-2020-12-\$vocabulary-uri{margin-left:35px}.swagger-ui .json-schema-2020-12-\$vocabulary-uri--disabled{-webkit-text-decoration:line-through;text-decoration:line-through}.swagger-ui .json-schema-2020-12-keyword--description{color:#6b6b6b;font-size:12px;margin-left:20px}.swagger-ui .json-schema-2020-12-keyword--description p{margin:0}.swagger-ui .json-schema-2020-12__title{color:#505050;display:inline-block;font-family:sans-serif;font-size:12px;font-weight:700;line-height:normal}.swagger-ui .json-schema-2020-12__title .json-schema-2020-12-keyword__name{margin:0}.swagger-ui .json-schema-2020-12-property{margin:7px 0}.swagger-ui .json-schema-2020-12-property .json-schema-2020-12__title{color:#3b4151;font-family:monospace;font-size:12px;font-weight:600;vertical-align:middle}.swagger-ui .json-schema-2020-12-keyword--properties>ul{border:none;margin:0;padding:0}.swagger-ui .json-schema-2020-12-property{list-style-type:none}.swagger-ui .json-schema-2020-12-property--required>.json-schema-2020-12:first-of-type>.json-schema-2020-12-head .json-schema-2020-12__title:after{color:red;content:"*";font-weight:700}.swagger-ui .json-schema-2020-12-keyword--patternProperties ul{border:none;margin:0;padding:0}.swagger-ui .json-schema-2020-12-keyword--patternProperties .json-schema-2020-12__title:first-of-type:after,.swagger-ui .json-schema-2020-12-keyword--patternProperties .json-schema-2020-12__title:first-of-type:before{color:#55a;content:"/"}.swagger-ui .json-schema-2020-12-keyword--enum>ul{display:inline-block;margin:0;padding:0}.swagger-ui .json-schema-2020-12-keyword--enum>ul li{display:inline;list-style-type:none}.swagger-ui .json-schema-2020-12__constraint{background-color:#805ad5;border-radius:4px;color:#3b4151;color:#fff;font-family:monospace;font-weight:600;line-height:1.5;margin-left:10px;padding:1px 3px}.swagger-ui .json-schema-2020-12__constraint--string{background-color:#d69e2e;color:#fff}.swagger-ui .json-schema-2020-12-keyword--dependentRequired>ul{display:inline-block;margin:0;padding:0}.swagger-ui .json-schema-2020-12-keyword--dependentRequired>ul li{display:inline;list-style-type:none}.swagger-ui .model-box .json-schema-2020-12:not(.json-schema-2020-12--embedded)>.json-schema-2020-12-head .json-schema-2020-12__title:first-of-type{font-size:16px}.swagger-ui .model-box>.json-schema-2020-12{margin:0}.swagger-ui .model-box .json-schema-2020-12{background-color:transparent;padding:0}.swagger-ui .model-box .json-schema-2020-12-accordion,.swagger-ui .model-box .json-schema-2020-12-expand-deep-button{background-color:transparent}.swagger-ui .models .json-schema-2020-12:not(.json-schema-2020-12--embedded)>.json-schema-2020-12-head .json-schema-2020-12__title:first-of-type{font-size:16px} + +/*# sourceMappingURL=swagger-ui.css.map*/ \ No newline at end of file diff --git a/server/internal/httpapi/docs_test.go b/server/internal/httpapi/docs_test.go new file mode 100644 index 0000000..fb24adc --- /dev/null +++ b/server/internal/httpapi/docs_test.go @@ -0,0 +1,144 @@ +package httpapi + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/dvcdsys/code-index/server/internal/apikeys" + apidb "github.com/dvcdsys/code-index/server/internal/db" + "github.com/dvcdsys/code-index/server/internal/sessions" + "github.com/dvcdsys/code-index/server/internal/users" +) + +// newDocsTestServer wires a router with full auth services so we can +// verify that the docs endpoints are reachable WITHOUT credentials — +// otherwise a passing test could just be the dev-mode skip in +// requireAuth. +func newDocsTestServer(t *testing.T) http.Handler { + t.Helper() + database, err := apidb.Open(":memory:") + if err != nil { + t.Fatalf("open db: %v", err) + } + t.Cleanup(func() { _ = database.Close() }) + return NewRouter(Deps{ + DB: database, + ServerVersion: "0.0.0-test", + APIVersion: "v1", + Users: users.New(database), + Sessions: sessions.New(database), + APIKeys: apikeys.New(database), + }) +} + +// TestDocs_IndexServesHTML verifies GET /docs returns the Swagger UI shell +// without requiring an Authorization header. +func TestDocs_IndexServesHTML(t *testing.T) { + srv := newDocsTestServer(t) + req := httptest.NewRequest(http.MethodGet, "/docs", nil) + rr := httptest.NewRecorder() + srv.ServeHTTP(rr, req) + if rr.Code != http.StatusOK { + t.Fatalf("status = %d, want 200 (docs must be public)", rr.Code) + } + if ct := rr.Header().Get("Content-Type"); !strings.HasPrefix(ct, "text/html") { + t.Errorf("Content-Type = %q, want text/html prefix", ct) + } + body := rr.Body.String() + if !strings.Contains(body, "Swagger UI") { + t.Errorf("body missing 'Swagger UI' marker; first 200B: %q", body[:min(200, len(body))]) + } + if !strings.Contains(body, "/openapi.json") { + t.Errorf("body should reference /openapi.json as spec source") + } +} + +// TestDocs_StaticAssetServed verifies the JS bundle is reachable under +// /docs/. Pulls swagger-ui-bundle.js because it's the largest +// asset and the most-likely-to-break in any future refactor. +func TestDocs_StaticAssetServed(t *testing.T) { + srv := newDocsTestServer(t) + req := httptest.NewRequest(http.MethodGet, "/docs/swagger-ui-bundle.js", nil) + rr := httptest.NewRecorder() + srv.ServeHTTP(rr, req) + if rr.Code != http.StatusOK { + t.Fatalf("status = %d, want 200", rr.Code) + } + if ct := rr.Header().Get("Content-Type"); !strings.HasPrefix(ct, "application/javascript") { + t.Errorf("Content-Type = %q, want application/javascript prefix", ct) + } + if rr.Body.Len() < 1000 { + t.Errorf("bundle body too small (%d bytes) — embed may have failed", rr.Body.Len()) + } +} + +// TestDocs_StaticAssetNotFound verifies that requests for a non-existent +// asset return 404 rather than panicking or echoing back unrelated content. +func TestDocs_StaticAssetNotFound(t *testing.T) { + srv := newDocsTestServer(t) + req := httptest.NewRequest(http.MethodGet, "/docs/does-not-exist.js", nil) + rr := httptest.NewRecorder() + srv.ServeHTTP(rr, req) + if rr.Code != http.StatusNotFound { + t.Errorf("status = %d, want 404", rr.Code) + } +} + +// TestOpenAPISpec_ServesValidJSON verifies the spec endpoint returns valid +// JSON with the expected info.title and info.version. This catches both +// embed regressions (spec missing from binary) and accidental contract +// drift (info section overwritten). +func TestOpenAPISpec_ServesValidJSON(t *testing.T) { + srv := newDocsTestServer(t) + req := httptest.NewRequest(http.MethodGet, "/openapi.json", nil) + rr := httptest.NewRecorder() + srv.ServeHTTP(rr, req) + if rr.Code != http.StatusOK { + t.Fatalf("status = %d, want 200 (openapi.json must be public)", rr.Code) + } + if ct := rr.Header().Get("Content-Type"); !strings.HasPrefix(ct, "application/json") { + t.Errorf("Content-Type = %q, want application/json prefix", ct) + } + var doc struct { + OpenAPI string `json:"openapi"` + Info struct { + Title string `json:"title"` + Version string `json:"version"` + } `json:"info"` + Paths map[string]any `json:"paths"` + } + if err := json.Unmarshal(rr.Body.Bytes(), &doc); err != nil { + t.Fatalf("invalid JSON: %v", err) + } + if !strings.HasPrefix(doc.OpenAPI, "3.") { + t.Errorf("openapi version = %q, want 3.x", doc.OpenAPI) + } + if doc.Info.Title != "cix-server API" { + t.Errorf("info.title = %q, want %q", doc.Info.Title, "cix-server API") + } + if doc.Info.Version != "v1" { + t.Errorf("info.version = %q, want v1", doc.Info.Version) + } + if len(doc.Paths) < 13 { + t.Errorf("paths count = %d, expected at least 13", len(doc.Paths)) + } +} + +// TestDocs_IsPublic — defense-in-depth: explicitly verify the three docs +// endpoints work WITHOUT an Authorization header, even though +// TestAuth_StatusRejectsMissingKey already covers the inverse case for the +// API routes. +func TestDocs_IsPublic(t *testing.T) { + srv := newDocsTestServer(t) + for _, p := range []string{"/docs", "/docs/swagger-ui-bundle.js", "/openapi.json"} { + req := httptest.NewRequest(http.MethodGet, p, nil) + rr := httptest.NewRecorder() + srv.ServeHTTP(rr, req) + if rr.Code == http.StatusUnauthorized { + t.Errorf("%s returned 401 — must be public", p) + } + } +} diff --git a/server/internal/httpapi/health.go b/server/internal/httpapi/health.go index 43eeed9..ef9dee9 100644 --- a/server/internal/httpapi/health.go +++ b/server/internal/httpapi/health.go @@ -1,73 +1,22 @@ package httpapi import ( - "context" "encoding/json" "net/http" - "time" ) -// healthHandler mirrors api/app/routers/health.py: returns {"status":"ok"}. -// Unauthenticated — used by probes. -// -// m6 — the probe now verifies the DB is reachable within 1 second. A stuck -// SQLite file (e.g. a locked WAL writer or a full disk) surfaces as HTTP 503 -// instead of a silently-healthy 200. -func healthHandler(d Deps) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - if d.DB != nil { - pingCtx, cancel := context.WithTimeout(r.Context(), time.Second) - defer cancel() - if err := d.DB.PingContext(pingCtx); err != nil { - writeJSON(w, http.StatusServiceUnavailable, map[string]any{ - "status": "unhealthy", - "reason": "db unreachable", - }) - return - } - } - writeJSON(w, http.StatusOK, map[string]any{"status": "ok"}) - } -} - -// statusHandler mirrors api/app/routers/health.py:status(). -// m5 — model_loaded reflects the actual embeddings service state rather than -// being hard-coded to true; this way operators can see when the sidecar is -// still warming up or has crashed. -func statusHandler(d Deps) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - projectCount := 0 - activeJobs := 0 - - if d.DB != nil { - _ = d.DB.QueryRowContext(r.Context(), - `SELECT COUNT(*) FROM projects`).Scan(&projectCount) - _ = d.DB.QueryRowContext(r.Context(), - `SELECT COUNT(*) FROM index_runs WHERE status = 'running'`).Scan(&activeJobs) - } - - modelLoaded := false - if d.EmbeddingSvc != nil { - readyCtx, cancel := context.WithTimeout(r.Context(), 500*time.Millisecond) - modelLoaded = d.EmbeddingSvc.Ready(readyCtx) == nil - cancel() - } - - writeJSON(w, http.StatusOK, map[string]any{ - "status": "ok", - "backend": d.Backend, - "server_version": d.ServerVersion, - "api_version": d.APIVersion, - "model_loaded": modelLoaded, - "embedding_model": d.EmbeddingModel, - "projects": projectCount, - "active_indexing_jobs": activeJobs, - }) - } -} - +// writeJSON encodes body as JSON and writes it with the given status code. +// Shared by every handler in this package; lives here because health.go is +// the smallest non-generated file and feels like the right home. func writeJSON(w http.ResponseWriter, code int, body any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) _ = json.NewEncoder(w).Encode(body) } + +// writeError emits the canonical {"detail": "..."} error body. The shape is +// byte-identical to the Python FastAPI default and matches every error +// schema declared in doc/openapi.yaml. +func writeError(w http.ResponseWriter, code int, msg string) { + writeJSON(w, code, map[string]any{"detail": msg}) +} diff --git a/server/internal/httpapi/health_test.go b/server/internal/httpapi/health_test.go index d5a52c1..9c2bd76 100644 --- a/server/internal/httpapi/health_test.go +++ b/server/internal/httpapi/health_test.go @@ -24,6 +24,7 @@ func newTestServer(t *testing.T) http.Handler { APIVersion: "v1", Backend: "go", EmbeddingModel: "test-model", + AuthDisabled: true, }) } diff --git a/server/internal/httpapi/indexing.go b/server/internal/httpapi/indexing.go index 4065a1e..7ad8835 100644 --- a/server/internal/httpapi/indexing.go +++ b/server/internal/httpapi/indexing.go @@ -3,19 +3,21 @@ package httpapi import ( "context" "encoding/json" - "errors" "net/http" - "strconv" "strings" "time" - "github.com/dvcdsys/code-index/server/internal/embeddings" "github.com/dvcdsys/code-index/server/internal/indexer" "github.com/dvcdsys/code-index/server/internal/projects" ) // --------------------------------------------------------------------------- -// Request / response types — match api/app/schemas/indexing.py exactly. +// Wire-format types kept as test fixtures. +// +// The Server methods in server.go construct openapi.* equivalents; the +// types below are byte-compatible JSON shapes that *_test.go files +// unmarshal into. Removing them would force every indexing test to be +// rewritten — keeping them costs nothing. // --------------------------------------------------------------------------- type indexBeginRequest struct { @@ -63,129 +65,41 @@ type indexProgressResponse struct { Progress map[string]any `json:"progress,omitempty"` } -// --------------------------------------------------------------------------- -// POST /api/v1/projects/{path}/index/begin -// --------------------------------------------------------------------------- - -func indexBeginHandler(d Deps) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - p := resolveProjectFromHash(w, r, d) - if p == nil { - return - } - if d.Indexer == nil { - writeError(w, http.StatusServiceUnavailable, "indexer not configured") - return - } - - var body indexBeginRequest - // Body is optional — accept empty request. - if r.ContentLength > 0 { - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - writeError(w, http.StatusUnprocessableEntity, "invalid request body") - return - } - } - - runID, stored, err := d.Indexer.BeginIndexing(r.Context(), p.HostPath, body.Full) - if err != nil { - // C2 — another session is already active for this project. - if errors.Is(err, indexer.ErrSessionConflict) { - writeError(w, http.StatusConflict, err.Error()) - return - } - writeError(w, http.StatusInternalServerError, err.Error()) - return - } - if stored == nil { - stored = map[string]string{} - } - writeJSON(w, http.StatusOK, indexBeginResponse{RunID: runID, StoredHashes: stored}) - } +type indexCancelResponse struct { + Cancelled bool `json:"cancelled"` } +// Suppress "declared but not used" warnings for the request shapes — they +// are populated only via JSON unmarshal in tests, so static analysis cannot +// see the writes. +var ( + _ = indexBeginRequest{} + _ = indexFilesRequest{} + _ = indexFinishRequest{} +) + // --------------------------------------------------------------------------- -// POST /api/v1/projects/{path}/index/files +// Constants + helpers shared with server.go (Server.IndexFiles). // --------------------------------------------------------------------------- // maxFilesPerBatch matches Python schemas.IndexFilesRequest max_length=50. const maxFilesPerBatch = 50 -func indexFilesHandler(d Deps) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - p := resolveProjectFromHash(w, r, d) - if p == nil { - return - } - if d.Indexer == nil { - writeError(w, http.StatusServiceUnavailable, "indexer not configured") - return - } - - var body indexFilesRequest - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - writeError(w, http.StatusUnprocessableEntity, "invalid request body") - return - } - if body.RunID == "" { - writeError(w, http.StatusUnprocessableEntity, "run_id is required") - return - } - if len(body.Files) > maxFilesPerBatch { - writeError(w, http.StatusUnprocessableEntity, "too many files in batch (max 50)") - return - } - - files := make([]indexer.FilePayload, len(body.Files)) - for i, f := range body.Files { - files[i] = indexer.FilePayload{ - Path: f.Path, - Content: f.Content, - ContentHash: f.ContentHash, - Language: f.Language, - Size: f.Size, - } - } - - // Negotiate streaming vs single-JSON via Accept header. Old CLIs do - // not advertise application/x-ndjson, so they keep getting the legacy - // blocking response. New CLIs explicitly request the stream and get - // per-file progress + heartbeats. - if acceptsNDJSON(r.Header.Get("Accept")) { - indexFilesStreamingHandler(d, p, body.RunID, files, w, r) - return - } - - accepted, chunks, total, err := d.Indexer.ProcessFiles(r.Context(), p.HostPath, body.RunID, files) - if err != nil { - if retry, busy := embeddings.IsBusy(err); busy { - w.Header().Set("Retry-After", strconv.Itoa(retry)) - writeError(w, http.StatusServiceUnavailable, - "GPU is busy processing another embedding request, retry after "+strconv.Itoa(retry)+"s") - return - } - if errors.Is(err, indexer.ErrNoSession) || errors.Is(err, indexer.ErrProjectMismatch) { - writeError(w, http.StatusNotFound, err.Error()) - return - } - writeError(w, http.StatusInternalServerError, err.Error()) - return - } +// streamingHeartbeatInterval is how often we emit a heartbeat event when no +// file-level progress has been sent. Idle on the wire ≤ heartbeatInterval + +// embedder slack, well under the client's default 30s read deadline. Var +// (not const) so tests can shrink it to keep the suite fast. +var streamingHeartbeatInterval = 10 * time.Second - writeJSON(w, http.StatusOK, indexFilesResponse{ - FilesAccepted: accepted, - ChunksCreated: chunks, - FilesProcessedTotal: total, - }) - } -} +// streamingDisconnectCancelTimeout bounds how long we spend cleaning up a +// session after the client disconnects. +const streamingDisconnectCancelTimeout = 5 * time.Second // acceptsNDJSON returns true when the Accept header advertises // application/x-ndjson. Comma-separated values are inspected; q-values are // ignored (presence is sufficient — the client opted in). func acceptsNDJSON(accept string) bool { for _, part := range strings.Split(accept, ",") { - // Strip parameters (q=…) and surrounding whitespace. mediaType := strings.TrimSpace(part) if i := strings.IndexByte(mediaType, ';'); i >= 0 { mediaType = strings.TrimSpace(mediaType[:i]) @@ -197,20 +111,14 @@ func acceptsNDJSON(accept string) bool { return false } -// streamingHeartbeatInterval is how often we emit a heartbeat event when no -// file-level progress has been sent. Idle on the wire ≤ heartbeatInterval + -// embedder slack, well under the client's default 30s read deadline. Var -// (not const) so tests can shrink it to keep the suite fast. -var streamingHeartbeatInterval = 10 * time.Second - -// streamingDisconnectCancelTimeout bounds how long we spend cleaning up a -// session after the client disconnects. -const streamingDisconnectCancelTimeout = 5 * time.Second - // indexFilesStreamingHandler writes one NDJSON event per line with per-file // progress and 10-second heartbeats. When the client disconnects mid-batch // we call CancelIndexing so the session lock is released immediately rather // than lingering until the 1-hour TTL. +// +// Called from Server.IndexFiles when the client requests the streaming +// content type. Lives in this file because the JSON wire format and the +// indexer-channel plumbing belong together. func indexFilesStreamingHandler( d Deps, p *projects.Project, @@ -221,27 +129,28 @@ func indexFilesStreamingHandler( ) { flusher, ok := w.(http.Flusher) if !ok { - // httptest.ResponseRecorder and a few mock servers don't implement - // Flusher. Falling back to writeError keeps tests readable while - // still pointing at the misuse. writeError(w, http.StatusInternalServerError, "streaming not supported by HTTP transport") return } + // Indexing batches dominate on GPU embed time and routinely exceed the + // global http.Server.WriteTimeout (60s). The zero deadline disables it + // for this request only — the streamCtx + heartbeat watchdog still bound + // runaway sessions. + rc := http.NewResponseController(w) + if err := rc.SetWriteDeadline(time.Time{}); err != nil { + d.Logger.Warn("streaming: clearing write deadline failed (continuing)", + "run_id", runID, "err", err) + } + w.Header().Set("Content-Type", "application/x-ndjson") w.Header().Set("Cache-Control", "no-cache") - // X-Accel-Buffering disables proxy buffering on nginx; harmless elsewhere. w.Header().Set("X-Accel-Buffering", "no") w.WriteHeader(http.StatusOK) flusher.Flush() progress := make(chan indexer.ProgressEvent, 32) - // streamCtx is a child of r.Context() so client-disconnect propagation - // works automatically, but we keep our own cancel handle so a *write* - // failure (broken pipe before Go's read goroutine notices the FIN) can - // also unblock the indexer goroutine immediately. Otherwise the embedder - // would keep computing wasted GPU work until r.Context() eventually fires. streamCtx, cancelStream := context.WithCancel(r.Context()) defer cancelStream() @@ -256,10 +165,6 @@ func indexFilesStreamingHandler( encoder := json.NewEncoder(w) clientGone := false - // markClientGone is the single place where we transition into the "drain - // progress until the indexer exits" mode. Cancelling streamCtx makes the - // embedder's ctx.Done() select fire so the indexer returns within ms - // rather than completing wasted work. markClientGone := func() { if clientGone { return @@ -272,9 +177,6 @@ func indexFilesStreamingHandler( select { case ev, open := <-progress: if !open { - // ProcessFilesStreaming has returned and closed the channel. - // If the client disconnected mid-flight, free the session - // lock immediately so a follow-up reindex doesn't hit 409. if clientGone { d.Logger.Warn("streaming: client disconnected mid-batch, cancelling session", "run_id", runID, "project", p.HostPath) @@ -286,7 +188,7 @@ func indexFilesStreamingHandler( return } if clientGone { - continue // drain to let ProcessFilesStreaming finish + continue } if err := encoder.Encode(ev); err != nil { markClientGone() @@ -306,148 +208,14 @@ func indexFilesStreamingHandler( } flusher.Flush() case <-r.Context().Done(): - // Client disconnected (or request context cancelled by router). - // Set clientGone and cancel the indexer's ctx so it returns now. d.Logger.Debug("streaming: r.Context() done", "run_id", runID, "err", r.Context().Err()) markClientGone() } } } -// --------------------------------------------------------------------------- -// POST /api/v1/projects/{path}/index/finish -// --------------------------------------------------------------------------- - -func indexFinishHandler(d Deps) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - p := resolveProjectFromHash(w, r, d) - if p == nil { - return - } - if d.Indexer == nil { - writeError(w, http.StatusServiceUnavailable, "indexer not configured") - return - } - - var body indexFinishRequest - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - writeError(w, http.StatusUnprocessableEntity, "invalid request body") - return - } - if body.RunID == "" { - writeError(w, http.StatusUnprocessableEntity, "run_id is required") - return - } - - status, files, chunks, err := d.Indexer.FinishIndexing( - r.Context(), p.HostPath, body.RunID, body.DeletedPaths, body.TotalFilesDiscovered, - ) - if err != nil { - if errors.Is(err, indexer.ErrNoSession) || errors.Is(err, indexer.ErrProjectMismatch) { - writeError(w, http.StatusNotFound, err.Error()) - return - } - writeError(w, http.StatusInternalServerError, err.Error()) - return - } - - writeJSON(w, http.StatusOK, indexFinishResponse{ - Status: status, - FilesProcessed: files, - ChunksCreated: chunks, - }) - } -} - -// --------------------------------------------------------------------------- -// POST /api/v1/projects/{path}/index/cancel -// --------------------------------------------------------------------------- - -type indexCancelResponse struct { - Cancelled bool `json:"cancelled"` -} - -// indexCancelHandler terminates any in-flight session for the project. -// Idempotent: returns {cancelled: false} when no session is active, so the -// CLI stale-session guard at startup can call this unconditionally. -func indexCancelHandler(d Deps) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - p := resolveProjectFromHash(w, r, d) - if p == nil { - return - } - if d.Indexer == nil { - writeJSON(w, http.StatusOK, indexCancelResponse{Cancelled: false}) - return - } - - cancelled, err := d.Indexer.CancelIndexing(r.Context(), p.HostPath) - if err != nil { - writeError(w, http.StatusInternalServerError, err.Error()) - return - } - writeJSON(w, http.StatusOK, indexCancelResponse{Cancelled: cancelled}) - } -} - -// --------------------------------------------------------------------------- -// GET /api/v1/projects/{path}/index/status -// --------------------------------------------------------------------------- - -func indexStatusHandler(d Deps) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - p := resolveProjectFromHash(w, r, d) - if p == nil { - return - } - if d.Indexer == nil { - writeJSON(w, http.StatusOK, indexProgressResponse{Status: "idle"}) - return - } - - progress := d.Indexer.GetProgress(p.HostPath) - if progress != nil { - // m4 — match Python's progress payload. Python emits - // files_discovered alongside files_processed (routers/indexing.py). - writeJSON(w, http.StatusOK, indexProgressResponse{ - Status: progress.Status, - Progress: map[string]any{ - "phase": progress.Phase, - "files_discovered": progress.FilesDiscovered, - "files_processed": progress.FilesProcessed, - "files_total": progress.FilesTotal, - "chunks_created": progress.ChunksCreated, - "elapsed_seconds": roundFloat1(progress.ElapsedSeconds), - "run_id": progress.RunID, - }, - }) - return - } - - // Fall back to last run row. - row := d.DB.QueryRowContext(r.Context(), - `SELECT status, files_processed, files_total, chunks_created - FROM index_runs WHERE project_path = ? ORDER BY started_at DESC LIMIT 1`, - p.HostPath, - ) - var status string - var filesProcessed, filesTotal, chunks int - if err := row.Scan(&status, &filesProcessed, &filesTotal, &chunks); err != nil { - writeJSON(w, http.StatusOK, indexProgressResponse{Status: "idle"}) - return - } - writeJSON(w, http.StatusOK, indexProgressResponse{ - Status: status, - Progress: map[string]any{ - "files_processed": filesProcessed, - "files_total": filesTotal, - "chunks_created": chunks, - }, - }) - } -} - // roundFloat1 rounds to 1 decimal place — matches Python round(x, 1). +// Used by Server.IndexStatus for elapsed_seconds. func roundFloat1(f float64) float64 { return float64(int(f*10+0.5)) / 10 } diff --git a/server/internal/httpapi/loginlimiter.go b/server/internal/httpapi/loginlimiter.go new file mode 100644 index 0000000..5c72b32 --- /dev/null +++ b/server/internal/httpapi/loginlimiter.go @@ -0,0 +1,120 @@ +package httpapi + +// Login rate limiter — protects POST /api/v1/auth/login from brute-force +// credential attacks. Two sliding-window counters live in memory: +// +// - per (IP, lower-cased email): keyLimit attempts within keyWindow. +// Slows password guessing against a known account. +// - per IP: ipLimit attempts within ipWindow. Slows horizontal sweeps +// across many emails from a single source. +// +// On a successful login the per-(IP, email) counter is cleared so a user +// who fat-fingered their password a few times then succeeds is not stuck +// behind their own counter. The per-IP counter is intentionally NOT +// cleared — otherwise an attacker could mix one valid login into a +// horizontal sweep to lift the global cap. +// +// The implementation is a single mutex over two maps. At the rates we +// permit (~600/hr peak per IP) contention is irrelevant for an admin +// tool. State is in-process; restarts wipe the counters, which is fine — +// the attacker has to re-establish the connection state anyway. + +import ( + "net/http" + "strconv" + "strings" + "sync" + "time" +) + +type loginLimiter struct { + mu sync.Mutex + perKey map[string][]time.Time // "ip|lower(email)" → recent attempt timestamps + perIP map[string][]time.Time // ip → recent attempt timestamps + + keyLimit int + keyWindow time.Duration + ipLimit int + ipWindow time.Duration + + now func() time.Time // overridable in tests +} + +// newLoginLimiter returns a limiter with the production defaults: +// 5 attempts / 15 min per (IP, email) and 60 attempts / minute per IP. +func newLoginLimiter() *loginLimiter { + return &loginLimiter{ + perKey: map[string][]time.Time{}, + perIP: map[string][]time.Time{}, + keyLimit: 5, + keyWindow: 15 * time.Minute, + ipLimit: 60, + ipWindow: time.Minute, + now: time.Now, + } +} + +func loginLimiterKey(ip, email string) string { + return ip + "|" + strings.ToLower(strings.TrimSpace(email)) +} + +// allow returns (true, 0) when the caller may proceed with authentication +// and records the attempt against both windows. Returns (false, retry) +// when either window is full; the caller must respond with 429 and +// `Retry-After: retry`. +func (l *loginLimiter) allow(ip, email string) (bool, time.Duration) { + l.mu.Lock() + defer l.mu.Unlock() + now := l.now() + + if pruned, retry, blocked := checkSlidingWindow(l.perIP[ip], now, l.ipWindow, l.ipLimit); blocked { + l.perIP[ip] = pruned + return false, retry + } + + key := loginLimiterKey(ip, email) + if pruned, retry, blocked := checkSlidingWindow(l.perKey[key], now, l.keyWindow, l.keyLimit); blocked { + l.perKey[key] = pruned + return false, retry + } + + l.perIP[ip] = append(pruneOlder(l.perIP[ip], now.Add(-l.ipWindow)), now) + l.perKey[key] = append(pruneOlder(l.perKey[key], now.Add(-l.keyWindow)), now) + return true, 0 +} + +// reset clears the per-(IP, email) counter after a successful login. The +// per-IP counter is left in place by design — see file-level comment. +func (l *loginLimiter) reset(ip, email string) { + l.mu.Lock() + defer l.mu.Unlock() + delete(l.perKey, loginLimiterKey(ip, email)) +} + +func checkSlidingWindow(ts []time.Time, now time.Time, window time.Duration, limit int) ([]time.Time, time.Duration, bool) { + pruned := pruneOlder(ts, now.Add(-window)) + if len(pruned) >= limit { + retry := max(window-now.Sub(pruned[0]), time.Second) + return pruned, retry, true + } + return pruned, 0, false +} + +func pruneOlder(ts []time.Time, cutoff time.Time) []time.Time { + i := 0 + for i < len(ts) && ts[i].Before(cutoff) { + i++ + } + if i == 0 { + return ts + } + return append(ts[:0:0], ts[i:]...) +} + +// writeRateLimited emits a 429 response with the Retry-After header set +// to the number of seconds until at least one slot frees up. +func writeRateLimited(w http.ResponseWriter, retry time.Duration) { + secs := max(int(retry.Seconds()), 1) + w.Header().Set("Retry-After", strconv.Itoa(secs)) + writeError(w, http.StatusTooManyRequests, "Too many login attempts; try again later.") +} diff --git a/server/internal/httpapi/loginlimiter_test.go b/server/internal/httpapi/loginlimiter_test.go new file mode 100644 index 0000000..80000c9 --- /dev/null +++ b/server/internal/httpapi/loginlimiter_test.go @@ -0,0 +1,118 @@ +package httpapi + +import ( + "testing" + "time" +) + +// fakeClock returns a closure satisfying loginLimiter.now. Mutating *t between +// calls advances time deterministically. +func fakeClock(t *time.Time) func() time.Time { + return func() time.Time { return *t } +} + +func TestLoginLimiter_PerEmailWindow(t *testing.T) { + now := time.Date(2026, 5, 4, 12, 0, 0, 0, time.UTC) + l := newLoginLimiter() + l.now = fakeClock(&now) + l.keyLimit = 3 + l.keyWindow = time.Minute + + for i := range 3 { + if ok, _ := l.allow("1.2.3.4", "alice@example.com"); !ok { + t.Fatalf("attempt %d unexpectedly blocked", i+1) + } + } + // Fourth in the same window must block. + ok, retry := l.allow("1.2.3.4", "alice@example.com") + if ok { + t.Fatalf("expected 4th attempt to be blocked") + } + if retry < time.Second || retry > time.Minute { + t.Errorf("retry = %v, want between 1s and 1m", retry) + } + // Different email from the same IP must still pass — per-email window + // is keyed independently. (Per-IP cap is left at default 60/min so it + // does not interfere here.) + if ok, _ := l.allow("1.2.3.4", "bob@example.com"); !ok { + t.Errorf("different email from same IP should pass") + } + // After the window slides past, the original counter resets. + now = now.Add(time.Minute + time.Second) + if ok, _ := l.allow("1.2.3.4", "alice@example.com"); !ok { + t.Errorf("after window expiry, attempt should pass") + } +} + +func TestLoginLimiter_Reset(t *testing.T) { + now := time.Date(2026, 5, 4, 12, 0, 0, 0, time.UTC) + l := newLoginLimiter() + l.now = fakeClock(&now) + l.keyLimit = 2 + l.keyWindow = time.Minute + + for range 2 { + _, _ = l.allow("1.2.3.4", "alice@example.com") + } + // On the boundary now: a 3rd attempt would block. + if ok, _ := l.allow("1.2.3.4", "alice@example.com"); ok { + t.Fatalf("expected 3rd attempt to block before reset") + } + // Successful login → reset → next attempt admitted. + l.reset("1.2.3.4", "alice@example.com") + if ok, _ := l.allow("1.2.3.4", "alice@example.com"); !ok { + t.Errorf("post-reset attempt should be admitted") + } +} + +func TestLoginLimiter_PerIPCap(t *testing.T) { + now := time.Date(2026, 5, 4, 12, 0, 0, 0, time.UTC) + l := newLoginLimiter() + l.now = fakeClock(&now) + l.ipLimit = 3 + l.ipWindow = time.Minute + l.keyLimit = 100 // lift the per-email cap so we exercise per-IP only + + // Three attempts across different emails — all admitted. + for i, email := range []string{"a@x", "b@x", "c@x"} { + if ok, _ := l.allow("1.2.3.4", email); !ok { + t.Fatalf("attempt %d (%s) unexpectedly blocked", i+1, email) + } + } + // Fourth from same IP, different email — blocked by per-IP cap. + if ok, _ := l.allow("1.2.3.4", "d@x"); ok { + t.Errorf("4th attempt from same IP should hit per-IP cap") + } + // A different IP is unaffected. + if ok, _ := l.allow("5.6.7.8", "a@x"); !ok { + t.Errorf("different IP should not be blocked") + } +} + +func TestLoginLimiter_EmailCaseInsensitive(t *testing.T) { + l := newLoginLimiter() + l.keyLimit = 1 + l.keyWindow = time.Minute + + if ok, _ := l.allow("1.2.3.4", "Alice@example.com"); !ok { + t.Fatalf("first attempt unexpectedly blocked") + } + // Same email, different case — must hit the same bucket. + if ok, _ := l.allow("1.2.3.4", "alice@EXAMPLE.com"); ok { + t.Errorf("case-different email should share the per-email counter") + } +} + +func TestPruneOlder(t *testing.T) { + base := time.Date(2026, 5, 4, 12, 0, 0, 0, time.UTC) + ts := []time.Time{ + base.Add(-3 * time.Minute), + base.Add(-2 * time.Minute), + base.Add(-30 * time.Second), + base, + } + got := pruneOlder(ts, base.Add(-time.Minute)) + if len(got) != 2 { + t.Fatalf("len(got) = %d, want 2 (kept entries within last minute)", len(got)) + } +} diff --git a/server/internal/httpapi/middleware.go b/server/internal/httpapi/middleware.go index e605d5d..1a909b4 100644 --- a/server/internal/httpapi/middleware.go +++ b/server/internal/httpapi/middleware.go @@ -1,11 +1,17 @@ package httpapi import ( + "context" + "errors" "log/slog" + "net" "net/http" "strings" "time" + "github.com/dvcdsys/code-index/server/internal/apikeys" + "github.com/dvcdsys/code-index/server/internal/sessions" + "github.com/dvcdsys/code-index/server/internal/users" "github.com/go-chi/chi/v5/middleware" ) @@ -19,27 +25,156 @@ func serverVersionHeader(version string) func(http.Handler) http.Handler { } } -// requireAPIKey enforces Bearer-token auth matching api/app/auth.py. +// Request body size caps. The default of 1 MiB covers every auth/admin/ +// search/project endpoint generously — JSON payloads on those are kilobytes +// at most. /index/files is the one outlier: at default config (batch=20, +// max-file=512 KiB) a real payload is ~11 MiB. The 64 MiB cap also fits +// operator-tuned worst case (batch=50 × max-file=1 MiB ≈ 55 MiB) with +// headroom; pathological configs above that fail loud with HTTP 413. +const ( + defaultMaxBodyBytes int64 = 1 << 20 // 1 MiB + indexingMaxBodyBytes int64 = 64 << 20 // 64 MiB +) + +// bodySizeFor picks the right cap for a request path. The indexing endpoint +// is the only one that legitimately receives multi-megabyte JSON. +func bodySizeFor(path string) int64 { + if strings.Contains(path, "/index/files") { + return indexingMaxBodyBytes + } + return defaultMaxBodyBytes +} + +// limitBodySize wraps r.Body with http.MaxBytesReader so handlers cannot +// be forced to read unbounded JSON, and rejects oversize requests up-front +// when the client honestly declared Content-Length. The fast path keeps +// CPU off the bcrypt and parser code paths during a flood. +func limitBodySize() func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + limit := bodySizeFor(r.URL.Path) + if r.ContentLength > limit { + writeError(w, http.StatusRequestEntityTooLarge, "request body too large") + return + } + r.Body = http.MaxBytesReader(w, r.Body, limit) + next.ServeHTTP(w, r) + }) + } +} + +// publicPaths is the set of HTTP paths that bypass the auth check. +// Includes the bootstrap probe + login (callers MUST be able to reach +// these without a valid session) plus the documentation, health, and +// dashboard static-asset endpoints. The dashboard's API calls still go +// through the auth gate — only the SPA shell + Vite-built assets are +// public so the login form can render. +var publicPaths = map[string]struct{}{ + "/health": {}, + "/docs": {}, + "/openapi.json": {}, + "/dashboard": {}, + "/api/v1/auth/bootstrap-status": {}, + "/api/v1/auth/login": {}, +} + +// authContextKey is the context key under which the authenticated user +// is stashed by requireAuth. Handlers retrieve it via userFromCtx; the +// "session" or "api_key" auth method is recorded alongside so /auth/me +// can report which path the caller arrived through. +type authContextKey struct{} + +type authContext struct { + User users.User + Method string // "session" | "api_key" + Session *sessions.Session + APIKey *apikeys.ApiKey +} + +func withAuth(ctx context.Context, ac *authContext) context.Context { + return context.WithValue(ctx, authContextKey{}, ac) +} + +func authFromCtx(ctx context.Context) (*authContext, bool) { + v, ok := ctx.Value(authContextKey{}).(*authContext) + return v, ok +} + +// requireAuth gates every non-public route. Order of checks: session +// cookie first (most common for browsers), then Bearer API key. // -// Behaviour: -// - `GET /health` is public (probe endpoint) — it is wired outside this -// middleware in NewRouter. -// - All other routes require `Authorization: Bearer `. -// - Missing or mismatched tokens return 401 with -// `{"detail":"Invalid or missing API key"}` — byte-identical to Python. -// - If apiKey is empty the check is skipped (dev mode); cmd/cix-server/main.go -// logs a warning on startup. -func requireAPIKey(apiKey string) func(http.Handler) http.Handler { +// Either path attaches the resolved user to the request context. Hands +// off to next on success; writes 401 with `{"detail":"..."}` on failure. +func requireAuth(d Deps) func(http.Handler) http.Handler { + if d.Users == nil || d.Sessions == nil || d.APIKeys == nil { + // Defensive panic: if a deployment forgets to wire any of the + // three services, every request would 401 silently. Fail loud + // at startup instead. + panic("httpapi: requireAuth installed without Users+Sessions+APIKeys services — set Deps.AuthDisabled=true to opt out") + } return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if apiKey == "" { + if isPublicPath(r.URL.Path) { next.ServeHTTP(w, r) return } - authz := r.Header.Get("Authorization") - const prefix = "Bearer " - if !strings.HasPrefix(authz, prefix) || authz[len(prefix):] != apiKey { - writeError(w, http.StatusUnauthorized, "Invalid or missing API key") + + ip := clientIP(r) + ua := r.UserAgent() + + // 1. Session cookie. The cookie is HttpOnly + SameSite=Strict + // so any browser sending it has the right origin; we still + // validate the id against the sessions table. + if c, err := r.Cookie(sessions.CookieName); err == nil { + sess, u, sErr := d.Sessions.Get(r.Context(), c.Value) + if sErr == nil { + _ = d.Sessions.Touch(r.Context(), sess.ID, ip, ua) + ac := &authContext{User: u, Method: "session", Session: &sess} + next.ServeHTTP(w, r.WithContext(withAuth(r.Context(), ac))) + return + } + // If the cookie was present but invalid (expired, deleted, + // user-disabled), fall through to Bearer auth — some CLI + // clients also set a cookie for unrelated reasons. + _ = sErr + } + + // 2. Bearer API key. + if authz := r.Header.Get("Authorization"); strings.HasPrefix(authz, "Bearer ") { + key := strings.TrimSpace(authz[len("Bearer "):]) + if key != "" { + u, ak, aErr := d.APIKeys.Authenticate(r.Context(), key) + if aErr == nil { + _ = d.APIKeys.Touch(r.Context(), ak.ID, ip, ua) + ac := &authContext{User: u, Method: "api_key", APIKey: &ak} + next.ServeHTTP(w, r.WithContext(withAuth(r.Context(), ac))) + return + } + if errors.Is(aErr, apikeys.ErrUserDisabled) { + writeError(w, http.StatusUnauthorized, "API key owner is disabled") + return + } + } + } + + writeError(w, http.StatusUnauthorized, "Authentication required") + }) + } +} + +// requireRole rejects callers whose attached user does not have the +// expected role. Always paired with requireAuth — must be installed +// further down the chain. +func requireRole(role string) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ac, ok := authFromCtx(r.Context()) + if !ok { + writeError(w, http.StatusUnauthorized, "Authentication required") + return + } + if ac.User.Role != role { + writeError(w, http.StatusForbidden, "This action requires role: "+role) return } next.ServeHTTP(w, r) @@ -47,6 +182,42 @@ func requireAPIKey(apiKey string) func(http.Handler) http.Handler { } } +// isPublicPath returns true when the path is exempt from auth. +func isPublicPath(p string) bool { + if _, ok := publicPaths[p]; ok { + return true + } + if strings.HasPrefix(p, "/docs/") { + return true + } + if strings.HasPrefix(p, "/dashboard/") { + return true + } + return false +} + +// clientIP returns the best-effort remote IP. Honours X-Forwarded-For +// (first hop) when present, otherwise falls back to the raw RemoteAddr. +// +// Used for audit metadata (sessions.last_seen_ip, api_keys.last_used_ip) +// AND as the per-IP key for the login rate limiter. Production deployments +// MUST sit behind a reverse proxy that replaces (not appends to) the +// inbound XFF — otherwise an attacker can rotate a forged header per +// request to bypass the per-IP cap. See doc/SECURITY_DEPLOYMENT.md. +func clientIP(r *http.Request) string { + if xff := r.Header.Get("X-Forwarded-For"); xff != "" { + if i := strings.IndexByte(xff, ','); i > 0 { + return strings.TrimSpace(xff[:i]) + } + return strings.TrimSpace(xff) + } + host, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + return r.RemoteAddr + } + return host +} + // structuredLogger logs one line per request via slog at INFO level. func structuredLogger(logger *slog.Logger) func(http.Handler) http.Handler { if logger == nil { diff --git a/server/internal/httpapi/middleware_test.go b/server/internal/httpapi/middleware_test.go index 95e1e98..6b18866 100644 --- a/server/internal/httpapi/middleware_test.go +++ b/server/internal/httpapi/middleware_test.go @@ -1,18 +1,34 @@ package httpapi import ( + "context" "encoding/json" "net/http" "net/http/httptest" "testing" + "github.com/dvcdsys/code-index/server/internal/apikeys" apidb "github.com/dvcdsys/code-index/server/internal/db" + "github.com/dvcdsys/code-index/server/internal/sessions" + "github.com/dvcdsys/code-index/server/internal/users" ) -// newAuthTestServer builds a router wired with the given API key. A nil key -// argument keeps dev-mode behaviour (auth disabled) so existing tests are -// unaffected. -func newAuthTestServer(t *testing.T, apiKey string) http.Handler { +// authTestFixture bundles a router plus the seeded admin user + a fresh +// API key for that user. Used by every test that needs to exercise the +// real auth path (cookie OR Bearer) instead of bypassing via +// AuthDisabled=true. +// +// Deps is exposed so tests can poke directly at the services to seed +// extra fixtures (other users, extra keys, etc.) without going through +// HTTP for setup-time arrangements. +type authTestFixture struct { + Router http.Handler + Deps Deps + UserID string + FullKey string +} + +func newAuthFixture(t *testing.T) *authTestFixture { t.Helper() database, err := apidb.Open(":memory:") if err != nil { @@ -20,30 +36,65 @@ func newAuthTestServer(t *testing.T, apiKey string) http.Handler { } t.Cleanup(func() { _ = database.Close() }) + usrSvc := users.New(database) + sessSvc := sessions.New(database) + akSvc := apikeys.New(database) + + u, err := usrSvc.Create(context.Background(), "admin@example.com", "secret-password", users.RoleAdmin, false) + if err != nil { + t.Fatalf("seed admin: %v", err) + } + full, _, err := akSvc.Generate(context.Background(), u.ID, "test-key") + if err != nil { + t.Fatalf("seed key: %v", err) + } + + deps := Deps{ + DB: database, + ServerVersion: "0.0.0-test", + APIVersion: "v1", + EmbeddingModel: "test-model", + Users: usrSvc, + Sessions: sessSvc, + APIKeys: akSvc, + } + return &authTestFixture{Router: NewRouter(deps), Deps: deps, UserID: u.ID, FullKey: full} +} + +// newAuthDisabledServer mirrors the old "empty key + AuthDisabled" path. +// Some legacy tests still want a router that lets every request through +// without any wiring — this is the single helper that supports it. +func newAuthDisabledServer(t *testing.T) http.Handler { + t.Helper() + database, err := apidb.Open(":memory:") + if err != nil { + t.Fatalf("open db: %v", err) + } + t.Cleanup(func() { _ = database.Close() }) return NewRouter(Deps{ DB: database, ServerVersion: "0.0.0-test", APIVersion: "v1", EmbeddingModel: "test-model", - APIKey: apiKey, + AuthDisabled: true, }) } func TestAuth_HealthIsPublic(t *testing.T) { - srv := newAuthTestServer(t, "secret-key") + f := newAuthFixture(t) req := httptest.NewRequest(http.MethodGet, "/health", nil) rr := httptest.NewRecorder() - srv.ServeHTTP(rr, req) + f.Router.ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("status = %d, want 200 (health must be public)", rr.Code) } } func TestAuth_StatusRejectsMissingKey(t *testing.T) { - srv := newAuthTestServer(t, "secret-key") + f := newAuthFixture(t) req := httptest.NewRequest(http.MethodGet, "/api/v1/status", nil) rr := httptest.NewRecorder() - srv.ServeHTTP(rr, req) + f.Router.ServeHTTP(rr, req) if rr.Code != http.StatusUnauthorized { t.Fatalf("status = %d, want 401", rr.Code) } @@ -51,40 +102,77 @@ func TestAuth_StatusRejectsMissingKey(t *testing.T) { if err := json.Unmarshal(rr.Body.Bytes(), &body); err != nil { t.Fatalf("json: %v (body=%s)", err, rr.Body.String()) } - if body["detail"] != "Invalid or missing API key" { - t.Errorf("detail = %v, want %q", body["detail"], "Invalid or missing API key") + if body["detail"] != "Authentication required" { + t.Errorf("detail = %v, want 'Authentication required'", body["detail"]) } } func TestAuth_StatusRejectsWrongKey(t *testing.T) { - srv := newAuthTestServer(t, "secret-key") + f := newAuthFixture(t) req := httptest.NewRequest(http.MethodGet, "/api/v1/status", nil) - req.Header.Set("Authorization", "Bearer not-the-right-key") + req.Header.Set("Authorization", "Bearer cix_not-the-right-key-at-all-1234567890ab") rr := httptest.NewRecorder() - srv.ServeHTTP(rr, req) + f.Router.ServeHTTP(rr, req) if rr.Code != http.StatusUnauthorized { t.Fatalf("status = %d, want 401", rr.Code) } } func TestAuth_StatusAcceptsCorrectKey(t *testing.T) { - srv := newAuthTestServer(t, "secret-key") + f := newAuthFixture(t) req := httptest.NewRequest(http.MethodGet, "/api/v1/status", nil) - req.Header.Set("Authorization", "Bearer secret-key") + req.Header.Set("Authorization", "Bearer "+f.FullKey) rr := httptest.NewRecorder() - srv.ServeHTTP(rr, req) + f.Router.ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("status = %d, want 200; body=%s", rr.Code, rr.Body.String()) } } -func TestAuth_EmptyKeySkipsCheck(t *testing.T) { - // Dev mode: no key configured => auth middleware passes through. - srv := newAuthTestServer(t, "") +// TestAuth_DisabledFlagSkipsCheck — explicit dev-mode opt-out via +// AuthDisabled. With the flag on, NewRouter omits the requireAuth +// middleware entirely so every endpoint succeeds without credentials. +func TestAuth_DisabledFlagSkipsCheck(t *testing.T) { + srv := newAuthDisabledServer(t) req := httptest.NewRequest(http.MethodGet, "/api/v1/status", nil) rr := httptest.NewRecorder() srv.ServeHTTP(rr, req) if rr.Code != http.StatusOK { - t.Fatalf("status = %d, want 200 in dev mode", rr.Code) + t.Fatalf("status = %d, want 200 with AuthDisabled=true", rr.Code) + } +} + +// TestLimitBodySize_RejectsLargePayloadAtLogin sends a request with a +// declared Content-Length above the default 1 MiB cap and expects 413 +// before the login handler ever runs. Crucially, this fires at the +// public /auth/login path so an unauthenticated attacker cannot force +// the server to read an unbounded body. +func TestLimitBodySize_RejectsLargePayloadAtLogin(t *testing.T) { + f := newAuthFixture(t) + // The body itself doesn't matter — the middleware checks + // Content-Length first and returns 413 without reading. + req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", http.NoBody) + req.ContentLength = (2 << 20) // 2 MiB > 1 MiB default + req.Header.Set("Content-Type", "application/json") + rr := httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + if rr.Code != http.StatusRequestEntityTooLarge { + t.Fatalf("status = %d, want 413 (body=%s)", rr.Code, rr.Body.String()) + } +} + +// TestLimitBodySize_AllowsLargerIndexingPayload confirms the per-route +// override: the indexing endpoint accepts payloads up to 32 MiB, well +// past the 1 MiB default. Sending exactly 2 MiB should pass the size +// check and reach the auth handler (which 401s for an unauthenticated +// request — but past the 413 gate, which is what we're testing). +func TestLimitBodySize_AllowsLargerIndexingPayload(t *testing.T) { + f := newAuthFixture(t) + req := httptest.NewRequest(http.MethodPost, "/api/v1/projects/abc123/index/files", http.NoBody) + req.ContentLength = (2 << 20) // 2 MiB + rr := httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + if rr.Code == http.StatusRequestEntityTooLarge { + t.Fatalf("indexing endpoint should not 413 at 2 MiB (got %d)", rr.Code) } } diff --git a/server/internal/httpapi/openapi/gen.go b/server/internal/httpapi/openapi/gen.go new file mode 100644 index 0000000..f9d926d --- /dev/null +++ b/server/internal/httpapi/openapi/gen.go @@ -0,0 +1,9 @@ +// Package openapi contains the generated server types and chi-compatible +// ServerInterface for the cix-server HTTP API. The single source of truth is +// doc/openapi.yaml at the repo root; this directory holds nothing but the +// generator config (oapi.yaml) and the generated output (openapi.gen.go). +// +// Regenerate with `make openapi-gen` (from server/) or `go generate ./...`. +package openapi + +//go:generate go tool github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=oapi.yaml ../../../../doc/openapi.yaml diff --git a/server/internal/httpapi/openapi/oapi.yaml b/server/internal/httpapi/openapi/oapi.yaml new file mode 100644 index 0000000..5997eeb --- /dev/null +++ b/server/internal/httpapi/openapi/oapi.yaml @@ -0,0 +1,12 @@ +# oapi-codegen v2 config — see https://github.com/oapi-codegen/oapi-codegen +# Run via `go generate` from gen.go (or `make openapi-gen` from server/). +package: openapi +output: openapi.gen.go +generate: + models: true + chi-server: true + embedded-spec: true +output-options: + skip-prune: true +compatibility: + always-prefix-enum-values: false diff --git a/server/internal/httpapi/openapi/openapi.gen.go b/server/internal/httpapi/openapi/openapi.gen.go new file mode 100644 index 0000000..b2cf5cf --- /dev/null +++ b/server/internal/httpapi/openapi/openapi.gen.go @@ -0,0 +1,2997 @@ +// Package openapi provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.7.0 DO NOT EDIT. +package openapi + +import ( + "bytes" + "compress/flate" + "context" + "encoding/base64" + "errors" + "fmt" + "net/http" + "net/url" + "path" + "strings" + "time" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/go-chi/chi/v5" + "github.com/oapi-codegen/runtime" + openapi_types "github.com/oapi-codegen/runtime/types" +) + +const ( + BearerAuthScopes bearerAuthContextKey = "bearerAuth.Scopes" +) + +// Defines values for CreateUserRequestRole. +const ( + CreateUserRequestRoleAdmin CreateUserRequestRole = "admin" + CreateUserRequestRoleViewer CreateUserRequestRole = "viewer" +) + +// Valid indicates whether the value is a known member of the CreateUserRequestRole enum. +func (e CreateUserRequestRole) Valid() bool { + switch e { + case CreateUserRequestRoleAdmin: + return true + case CreateUserRequestRoleViewer: + return true + default: + return false + } +} + +// Defines values for HealthResponseStatus. +const ( + HealthResponseStatusOk HealthResponseStatus = "ok" + HealthResponseStatusUnhealthy HealthResponseStatus = "unhealthy" +) + +// Valid indicates whether the value is a known member of the HealthResponseStatus enum. +func (e HealthResponseStatus) Valid() bool { + switch e { + case HealthResponseStatusOk: + return true + case HealthResponseStatusUnhealthy: + return true + default: + return false + } +} + +// Defines values for IndexFinishResponseStatus. +const ( + IndexFinishResponseStatusCompleted IndexFinishResponseStatus = "completed" +) + +// Valid indicates whether the value is a known member of the IndexFinishResponseStatus enum. +func (e IndexFinishResponseStatus) Valid() bool { + switch e { + case IndexFinishResponseStatusCompleted: + return true + default: + return false + } +} + +// Defines values for IndexProgressEventEvent. +const ( + IndexProgressEventEventBatchDone IndexProgressEventEvent = "batch_done" + IndexProgressEventEventError IndexProgressEventEvent = "error" + IndexProgressEventEventFileChunked IndexProgressEventEvent = "file_chunked" + IndexProgressEventEventFileDone IndexProgressEventEvent = "file_done" + IndexProgressEventEventFileEmbedded IndexProgressEventEvent = "file_embedded" + IndexProgressEventEventFileError IndexProgressEventEvent = "file_error" + IndexProgressEventEventFileStarted IndexProgressEventEvent = "file_started" + IndexProgressEventEventHeartbeat IndexProgressEventEvent = "heartbeat" +) + +// Valid indicates whether the value is a known member of the IndexProgressEventEvent enum. +func (e IndexProgressEventEvent) Valid() bool { + switch e { + case IndexProgressEventEventBatchDone: + return true + case IndexProgressEventEventError: + return true + case IndexProgressEventEventFileChunked: + return true + case IndexProgressEventEventFileDone: + return true + case IndexProgressEventEventFileEmbedded: + return true + case IndexProgressEventEventFileError: + return true + case IndexProgressEventEventFileStarted: + return true + case IndexProgressEventEventHeartbeat: + return true + default: + return false + } +} + +// Defines values for IndexProgressInfoPhase. +const ( + IndexProgressInfoPhaseCompleted IndexProgressInfoPhase = "completed" + IndexProgressInfoPhaseReceiving IndexProgressInfoPhase = "receiving" +) + +// Valid indicates whether the value is a known member of the IndexProgressInfoPhase enum. +func (e IndexProgressInfoPhase) Valid() bool { + switch e { + case IndexProgressInfoPhaseCompleted: + return true + case IndexProgressInfoPhaseReceiving: + return true + default: + return false + } +} + +// Defines values for IndexProgressResponseStatus. +const ( + IndexProgressResponseStatusCancelled IndexProgressResponseStatus = "cancelled" + IndexProgressResponseStatusCompleted IndexProgressResponseStatus = "completed" + IndexProgressResponseStatusFailed IndexProgressResponseStatus = "failed" + IndexProgressResponseStatusIdle IndexProgressResponseStatus = "idle" + IndexProgressResponseStatusIndexing IndexProgressResponseStatus = "indexing" + IndexProgressResponseStatusRunning IndexProgressResponseStatus = "running" +) + +// Valid indicates whether the value is a known member of the IndexProgressResponseStatus enum. +func (e IndexProgressResponseStatus) Valid() bool { + switch e { + case IndexProgressResponseStatusCancelled: + return true + case IndexProgressResponseStatusCompleted: + return true + case IndexProgressResponseStatusFailed: + return true + case IndexProgressResponseStatusIdle: + return true + case IndexProgressResponseStatusIndexing: + return true + case IndexProgressResponseStatusRunning: + return true + default: + return false + } +} + +// Defines values for MeResponseAuthMethod. +const ( + MeResponseAuthMethodApiKey MeResponseAuthMethod = "api_key" + MeResponseAuthMethodSession MeResponseAuthMethod = "session" +) + +// Valid indicates whether the value is a known member of the MeResponseAuthMethod enum. +func (e MeResponseAuthMethod) Valid() bool { + switch e { + case MeResponseAuthMethodApiKey: + return true + case MeResponseAuthMethodSession: + return true + default: + return false + } +} + +// Defines values for ProjectStatus. +const ( + ProjectStatusCreated ProjectStatus = "created" + ProjectStatusError ProjectStatus = "error" + ProjectStatusIndexed ProjectStatus = "indexed" + ProjectStatusIndexing ProjectStatus = "indexing" +) + +// Valid indicates whether the value is a known member of the ProjectStatus enum. +func (e ProjectStatus) Valid() bool { + switch e { + case ProjectStatusCreated: + return true + case ProjectStatusError: + return true + case ProjectStatusIndexed: + return true + case ProjectStatusIndexing: + return true + default: + return false + } +} + +// Defines values for ReferenceItemChunkType. +const ( + Reference ReferenceItemChunkType = "reference" +) + +// Valid indicates whether the value is a known member of the ReferenceItemChunkType enum. +func (e ReferenceItemChunkType) Valid() bool { + switch e { + case Reference: + return true + default: + return false + } +} + +// Defines values for RuntimeConfigSource. +const ( + Db RuntimeConfigSource = "db" + Env RuntimeConfigSource = "env" + Recommended RuntimeConfigSource = "recommended" +) + +// Valid indicates whether the value is a known member of the RuntimeConfigSource enum. +func (e RuntimeConfigSource) Valid() bool { + switch e { + case Db: + return true + case Env: + return true + case Recommended: + return true + default: + return false + } +} + +// Defines values for SidecarStatusState. +const ( + SidecarStatusStateDisabled SidecarStatusState = "disabled" + SidecarStatusStateFailed SidecarStatusState = "failed" + SidecarStatusStateRestarting SidecarStatusState = "restarting" + SidecarStatusStateRunning SidecarStatusState = "running" + SidecarStatusStateStarting SidecarStatusState = "starting" +) + +// Valid indicates whether the value is a known member of the SidecarStatusState enum. +func (e SidecarStatusState) Valid() bool { + switch e { + case SidecarStatusStateDisabled: + return true + case SidecarStatusStateFailed: + return true + case SidecarStatusStateRestarting: + return true + case SidecarStatusStateRunning: + return true + case SidecarStatusStateStarting: + return true + default: + return false + } +} + +// Defines values for StatusResponseStatus. +const ( + StatusResponseStatusOk StatusResponseStatus = "ok" +) + +// Valid indicates whether the value is a known member of the StatusResponseStatus enum. +func (e StatusResponseStatus) Valid() bool { + switch e { + case StatusResponseStatusOk: + return true + default: + return false + } +} + +// Defines values for UpdateUserRequestRole. +const ( + UpdateUserRequestRoleAdmin UpdateUserRequestRole = "admin" + UpdateUserRequestRoleViewer UpdateUserRequestRole = "viewer" +) + +// Valid indicates whether the value is a known member of the UpdateUserRequestRole enum. +func (e UpdateUserRequestRole) Valid() bool { + switch e { + case UpdateUserRequestRoleAdmin: + return true + case UpdateUserRequestRoleViewer: + return true + default: + return false + } +} + +// Defines values for UserRole. +const ( + UserRoleAdmin UserRole = "admin" + UserRoleViewer UserRole = "viewer" +) + +// Valid indicates whether the value is a known member of the UserRole enum. +func (e UserRole) Valid() bool { + switch e { + case UserRoleAdmin: + return true + case UserRoleViewer: + return true + default: + return false + } +} + +// Defines values for UserWithStatsRole. +const ( + UserWithStatsRoleAdmin UserWithStatsRole = "admin" + UserWithStatsRoleViewer UserWithStatsRole = "viewer" +) + +// Valid indicates whether the value is a known member of the UserWithStatsRole enum. +func (e UserWithStatsRole) Valid() bool { + switch e { + case UserWithStatsRoleAdmin: + return true + case UserWithStatsRoleViewer: + return true + default: + return false + } +} + +// Defines values for ListApiKeysParamsOwner. +const ( + All ListApiKeysParamsOwner = "all" +) + +// Valid indicates whether the value is a known member of the ListApiKeysParamsOwner enum. +func (e ListApiKeysParamsOwner) Valid() bool { + switch e { + case All: + return true + default: + return false + } +} + +// Defines values for IndexFilesParamsAccept. +const ( + Applicationjson IndexFilesParamsAccept = "application/json" + ApplicationxNdjson IndexFilesParamsAccept = "application/x-ndjson" +) + +// Valid indicates whether the value is a known member of the IndexFilesParamsAccept enum. +func (e IndexFilesParamsAccept) Valid() bool { + switch e { + case Applicationjson: + return true + case ApplicationxNdjson: + return true + default: + return false + } +} + +// ApiKey defines model for ApiKey. +type ApiKey struct { + CreatedAt time.Time `json:"created_at"` + Id string `json:"id"` + LastUsedAt *time.Time `json:"last_used_at,omitempty"` + LastUsedIp *string `json:"last_used_ip,omitempty"` + LastUsedUa *string `json:"last_used_ua,omitempty"` + Name string `json:"name"` + OwnerUserId string `json:"owner_user_id"` + + // Prefix Display-only prefix of the full key (e.g. `cix_a1b2c3d4`). + // Long enough to recognise in lists, short enough that it + // cannot reconstruct the original. + Prefix string `json:"prefix"` + Revoked bool `json:"revoked"` + RevokedAt *time.Time `json:"revoked_at,omitempty"` +} + +// ApiKeyCreated defines model for ApiKeyCreated. +type ApiKeyCreated struct { + ApiKey ApiKey `json:"api_key"` + + // FullKey The plaintext key value. **Returned exactly once.** Store it + // securely — there is no way to retrieve it later. + FullKey string `json:"full_key"` +} + +// ApiKeyListResponse defines model for ApiKeyListResponse. +type ApiKeyListResponse struct { + ApiKeys []ApiKey `json:"api_keys"` + Total int `json:"total"` +} + +// BootstrapStatusResponse defines model for BootstrapStatusResponse. +type BootstrapStatusResponse struct { + // NeedsBootstrap True when the users table is empty. + NeedsBootstrap bool `json:"needs_bootstrap"` +} + +// ChangePasswordRequest defines model for ChangePasswordRequest. +type ChangePasswordRequest struct { + CurrentPassword string `json:"current_password"` + + // NewPassword Minimum 8 characters. No upper bound. + NewPassword string `json:"new_password"` +} + +// CreateApiKeyRequest defines model for CreateApiKeyRequest. +type CreateApiKeyRequest struct { + // Name Human-friendly label shown in the dashboard. The full key + // value is generated server-side and returned exactly once. + Name string `json:"name"` +} + +// CreateProjectRequest defines model for CreateProjectRequest. +type CreateProjectRequest struct { + HostPath string `json:"host_path"` +} + +// CreateUserRequest defines model for CreateUserRequest. +type CreateUserRequest struct { + Email openapi_types.Email `json:"email"` + + // InitialPassword One-time password the new user must change on first login. + // The admin shares this out-of-band. + InitialPassword string `json:"initial_password"` + Role CreateUserRequestRole `json:"role"` +} + +// CreateUserRequestRole defines model for CreateUserRequest.Role. +type CreateUserRequestRole string + +// DefinitionItem defines model for DefinitionItem. +type DefinitionItem struct { + EndLine int `json:"end_line"` + FilePath string `json:"file_path"` + Kind string `json:"kind"` + Language string `json:"language"` + Line int `json:"line"` + Name string `json:"name"` + ParentName *string `json:"parent_name,omitempty"` + Signature *string `json:"signature,omitempty"` +} + +// DefinitionRequest defines model for DefinitionRequest. +type DefinitionRequest struct { + FilePath *string `json:"file_path,omitempty"` + Kind *string `json:"kind,omitempty"` + Limit *int `json:"limit,omitempty"` + Symbol string `json:"symbol"` +} + +// DefinitionResponse defines model for DefinitionResponse. +type DefinitionResponse struct { + Results []DefinitionItem `json:"results"` + Total int `json:"total"` +} + +// DirEntry defines model for DirEntry. +type DirEntry struct { + FileCount int `json:"file_count"` + Path string `json:"path"` +} + +// Error defines model for Error. +type Error struct { + Detail string `json:"detail"` +} + +// FileGroupResult defines model for FileGroupResult. +type FileGroupResult struct { + BestScore float32 `json:"best_score"` + FilePath string `json:"file_path"` + Language *string `json:"language,omitempty"` + Matches []FileMatch `json:"matches"` +} + +// FileMatch defines model for FileMatch. +type FileMatch struct { + ChunkType string `json:"chunk_type"` + Content string `json:"content"` + EndLine int `json:"end_line"` + NestedHits *[]NestedHit `json:"nested_hits,omitempty"` + Score float32 `json:"score"` + StartLine int `json:"start_line"` + SymbolName *string `json:"symbol_name,omitempty"` +} + +// FilePayload defines model for FilePayload. +type FilePayload struct { + // Content UTF-8 text. Binary files should not be submitted. + Content string `json:"content"` + + // ContentHash SHA-256 hex digest of `content`. + ContentHash string `json:"content_hash"` + Language *string `json:"language,omitempty"` + Path string `json:"path"` + Size int `json:"size"` +} + +// FileResultItem defines model for FileResultItem. +type FileResultItem struct { + FilePath string `json:"file_path"` + + // Language Detected language, or null if undetected. + Language *string `json:"language"` +} + +// FileSearchRequest defines model for FileSearchRequest. +type FileSearchRequest struct { + Limit *int `json:"limit,omitempty"` + + // Query Substring matched against `file_path`. + Query string `json:"query"` +} + +// FileSearchResponse defines model for FileSearchResponse. +type FileSearchResponse struct { + Results []FileResultItem `json:"results"` + Total int `json:"total"` +} + +// HealthResponse defines model for HealthResponse. +type HealthResponse struct { + // Reason Set only when `status` is `unhealthy`. + Reason *string `json:"reason,omitempty"` + Status HealthResponseStatus `json:"status"` +} + +// HealthResponseStatus defines model for HealthResponse.Status. +type HealthResponseStatus string + +// IndexBeginRequest defines model for IndexBeginRequest. +type IndexBeginRequest struct { + // Full When true, wipes existing project state before opening the session. + Full *bool `json:"full,omitempty"` +} + +// IndexBeginResponse defines model for IndexBeginResponse. +type IndexBeginResponse struct { + RunId string `json:"run_id"` + + // StoredHashes Map from file path → SHA-256 of currently-stored content. Empty + // when the project has never been indexed (or `full:true` was passed). + StoredHashes map[string]string `json:"stored_hashes"` +} + +// IndexCancelResponse defines model for IndexCancelResponse. +type IndexCancelResponse struct { + Cancelled bool `json:"cancelled"` +} + +// IndexFilesRequest defines model for IndexFilesRequest. +type IndexFilesRequest struct { + Files []FilePayload `json:"files"` + RunId string `json:"run_id"` +} + +// IndexFilesResponse defines model for IndexFilesResponse. +type IndexFilesResponse struct { + ChunksCreated int `json:"chunks_created"` + FilesAccepted int `json:"files_accepted"` + FilesProcessedTotal int `json:"files_processed_total"` +} + +// IndexFinishRequest defines model for IndexFinishRequest. +type IndexFinishRequest struct { + DeletedPaths *[]string `json:"deleted_paths,omitempty"` + RunId string `json:"run_id"` + TotalFilesDiscovered *int `json:"total_files_discovered,omitempty"` +} + +// IndexFinishResponse defines model for IndexFinishResponse. +type IndexFinishResponse struct { + ChunksCreated int `json:"chunks_created"` + FilesProcessed int `json:"files_processed"` + Status IndexFinishResponseStatus `json:"status"` +} + +// IndexFinishResponseStatus defines model for IndexFinishResponse.Status. +type IndexFinishResponseStatus string + +// IndexProgressEvent One event in the NDJSON stream emitted by `POST /index/files` when +// the client sends `Accept: application/x-ndjson`. The `event` field +// discriminates the variant; other fields are populated as relevant. +type IndexProgressEvent struct { + BatchSize *int `json:"batch_size,omitempty"` + Chunks *int `json:"chunks,omitempty"` + ChunksCreated *int `json:"chunks_created,omitempty"` + EmbedMs *int64 `json:"embed_ms,omitempty"` + Event IndexProgressEventEvent `json:"event"` + Fatal *bool `json:"fatal,omitempty"` + FileIndex *int `json:"file_index,omitempty"` + FilesAccepted *int `json:"files_accepted,omitempty"` + FilesProcessedTotal *int `json:"files_processed_total,omitempty"` + Message *string `json:"message,omitempty"` + Path *string `json:"path,omitempty"` + RunId *string `json:"run_id,omitempty"` + Ts *time.Time `json:"ts,omitempty"` +} + +// IndexProgressEventEvent defines model for IndexProgressEvent.Event. +type IndexProgressEventEvent string + +// IndexProgressInfo Progress payload. The active-session variant carries every field; +// the historical-fallback variant only carries `files_processed`, +// `files_total`, and `chunks_created`. +type IndexProgressInfo struct { + ChunksCreated *int `json:"chunks_created,omitempty"` + ElapsedSeconds *float64 `json:"elapsed_seconds,omitempty"` + FilesDiscovered *int `json:"files_discovered,omitempty"` + FilesProcessed *int `json:"files_processed,omitempty"` + FilesTotal *int `json:"files_total,omitempty"` + Phase *IndexProgressInfoPhase `json:"phase,omitempty"` + RunId *string `json:"run_id,omitempty"` +} + +// IndexProgressInfoPhase defines model for IndexProgressInfo.Phase. +type IndexProgressInfoPhase string + +// IndexProgressResponse defines model for IndexProgressResponse. +type IndexProgressResponse struct { + // Progress Progress payload. The active-session variant carries every field; + // the historical-fallback variant only carries `files_processed`, + // `files_total`, and `chunks_created`. + Progress *IndexProgressInfo `json:"progress,omitempty"` + + // Status `idle` — no session ever / fallback unavailable. + // `indexing` — session active. + // `completed`/`cancelled`/`failed`/`running` — last-run status from `index_runs`. + Status IndexProgressResponseStatus `json:"status"` +} + +// IndexProgressResponseStatus `idle` — no session ever / fallback unavailable. +// `indexing` — session active. +// `completed`/`cancelled`/`failed`/`running` — last-run status from `index_runs`. +type IndexProgressResponseStatus string + +// LoginRequest defines model for LoginRequest. +type LoginRequest struct { + Email openapi_types.Email `json:"email"` + Password string `json:"password"` +} + +// LoginResponse defines model for LoginResponse. +type LoginResponse struct { + User User `json:"user"` +} + +// MeResponse defines model for MeResponse. +type MeResponse struct { + // AuthMethod Tells the dashboard whether to surface "logout" (session) or + // hide it (api_key access — there's nothing to log out of). + AuthMethod MeResponseAuthMethod `json:"auth_method"` + User User `json:"user"` +} + +// MeResponseAuthMethod Tells the dashboard whether to surface "logout" (session) or +// hide it (api_key access — there's nothing to log out of). +type MeResponseAuthMethod string + +// ModelEntry defines model for ModelEntry. +type ModelEntry struct { + // Id HF repo ID derived from the cache directory name (e.g. owner/model). + Id string `json:"id"` + + // Path Absolute path to the .gguf file on disk. + Path string `json:"path"` + SizeBytes int64 `json:"size_bytes"` +} + +// ModelList defines model for ModelList. +type ModelList struct { + // CacheDir The CIX_GGUF_CACHE_DIR that was scanned. Empty list with non-empty cache_dir = no .gguf files found. + CacheDir string `json:"cache_dir"` + Models []ModelEntry `json:"models"` +} + +// NestedHit defines model for NestedHit. +type NestedHit struct { + ChunkType string `json:"chunk_type"` + EndLine int `json:"end_line"` + Score float32 `json:"score"` + StartLine int `json:"start_line"` + SymbolName *string `json:"symbol_name,omitempty"` +} + +// Project defines model for Project. +type Project struct { + // ChromaPath Resolved chromem-go collection directory for this project. NULL when not computed. + ChromaPath *string `json:"chroma_path,omitempty"` + ChromaSizeBytes *int64 `json:"chroma_size_bytes,omitempty"` + + // ContainerPath Path inside the container (often equal to host_path). + ContainerPath string `json:"container_path"` + CreatedAt time.Time `json:"created_at"` + + // HostPath Absolute filesystem path on the operator's machine. + HostPath string `json:"host_path"` + + // IndexedWithModel Embedding model identifier active when this project was last + // (re)indexed. NULL on rows that pre-date drift tracking — the + // dashboard treats NULL as "Unknown" rather than as drift. + IndexedWithModel *string `json:"indexed_with_model,omitempty"` + Languages []string `json:"languages"` + LastIndexedAt *time.Time `json:"last_indexed_at"` + + // PathHash First 16 hex chars of SHA1(host_path) — stable URL identifier. + PathHash string `json:"path_hash"` + Settings ProjectSettings `json:"settings"` + + // SqlitePath Resolved SQLite database path for the active model. NULL on dashboards that don't expose storage info. + SqlitePath *string `json:"sqlite_path,omitempty"` + SqliteSizeBytes *int64 `json:"sqlite_size_bytes,omitempty"` + Stats ProjectStats `json:"stats"` + Status ProjectStatus `json:"status"` + UpdatedAt time.Time `json:"updated_at"` +} + +// ProjectStatus defines model for Project.Status. +type ProjectStatus string + +// ProjectListResponse defines model for ProjectListResponse. +type ProjectListResponse struct { + Projects []Project `json:"projects"` + Total int `json:"total"` +} + +// ProjectSettings defines model for ProjectSettings. +type ProjectSettings struct { + ExcludePatterns []string `json:"exclude_patterns"` + MaxFileSize int `json:"max_file_size"` +} + +// ProjectStats defines model for ProjectStats. +type ProjectStats struct { + IndexedFiles int `json:"indexed_files"` + TotalChunks int `json:"total_chunks"` + TotalFiles int `json:"total_files"` + TotalSymbols int `json:"total_symbols"` +} + +// ProjectSummary defines model for ProjectSummary. +type ProjectSummary struct { + HostPath string `json:"host_path"` + Languages []string `json:"languages"` + + // PathHash First 16 hex chars of SHA1(host_path) — stable URL identifier. + PathHash string `json:"path_hash"` + RecentSymbols []SymbolEntry `json:"recent_symbols"` + Status string `json:"status"` + TopDirectories []DirEntry `json:"top_directories"` + TotalChunks int `json:"total_chunks"` + TotalFiles int `json:"total_files"` + TotalSymbols int `json:"total_symbols"` +} + +// ReferenceItem defines model for ReferenceItem. +type ReferenceItem struct { + ChunkType ReferenceItemChunkType `json:"chunk_type"` + + // Content Always empty — see endpoint description. + Content string `json:"content"` + + // EndLine Always equal to `start_line` (refs table stores tokens, not ranges). + EndLine int `json:"end_line"` + FilePath string `json:"file_path"` + Language string `json:"language"` + StartLine int `json:"start_line"` + SymbolName string `json:"symbol_name"` +} + +// ReferenceItemChunkType defines model for ReferenceItem.ChunkType. +type ReferenceItemChunkType string + +// ReferenceRequest defines model for ReferenceRequest. +type ReferenceRequest struct { + FilePath *string `json:"file_path,omitempty"` + Limit *int `json:"limit,omitempty"` + Symbol string `json:"symbol"` +} + +// ReferenceResponse defines model for ReferenceResponse. +type ReferenceResponse struct { + Results []ReferenceItem `json:"results"` + Total int `json:"total"` +} + +// RestartAccepted defines model for RestartAccepted. +type RestartAccepted struct { + // RestartId Opaque ID; future versions may expose per-restart progress under this id. + RestartId string `json:"restart_id"` +} + +// RuntimeConfig defines model for RuntimeConfig. +type RuntimeConfig struct { + // EmbeddingModel HF repo ID or absolute filesystem path to a .gguf file. + EmbeddingModel string `json:"embedding_model"` + LlamaBatchSize int `json:"llama_batch_size"` + LlamaCtxSize int `json:"llama_ctx_size"` + + // LlamaNGpuLayers -1 = all layers (Metal/CUDA), 0 = CPU only. + LlamaNGpuLayers int `json:"llama_n_gpu_layers"` + + // LlamaNThreads 0 = let llama-server auto-detect. + LlamaNThreads int `json:"llama_n_threads"` + MaxEmbeddingConcurrency int `json:"max_embedding_concurrency"` + Recommended *RuntimeConfigRecommended `json:"recommended,omitempty"` + + // Source Per-field origin label so the dashboard can render a "DB" / + // "Env" / "Recommended" pill next to each value. Keys match the + // other field names: `embedding_model`, `llama_ctx_size`, ... + Source map[string]RuntimeConfigSource `json:"source"` + + // UpdatedAt When the runtime_settings row was last written, or null when only env/recommended are in effect. + UpdatedAt *time.Time `json:"updated_at,omitempty"` + + // UpdatedBy Who issued the last PUT, captured from the active session. + UpdatedBy *string `json:"updated_by,omitempty"` +} + +// RuntimeConfigSource defines model for RuntimeConfig.Source. +type RuntimeConfigSource string + +// RuntimeConfigRecommended defines model for RuntimeConfigRecommended. +type RuntimeConfigRecommended struct { + EmbeddingModel string `json:"embedding_model"` + LlamaBatchSize int `json:"llama_batch_size"` + LlamaCtxSize int `json:"llama_ctx_size"` + LlamaNGpuLayers int `json:"llama_n_gpu_layers"` + LlamaNThreads int `json:"llama_n_threads"` + MaxEmbeddingConcurrency int `json:"max_embedding_concurrency"` +} + +// RuntimeConfigUpdate All fields optional. Send a value to set/replace the override for +// that field, send `""` (string fields) or `0` (numeric fields) to +// CLEAR the override (next read falls back to env / recommended). +// Omitted fields keep their current value. +type RuntimeConfigUpdate struct { + EmbeddingModel *string `json:"embedding_model,omitempty"` + LlamaBatchSize *int `json:"llama_batch_size,omitempty"` + LlamaCtxSize *int `json:"llama_ctx_size,omitempty"` + LlamaNGpuLayers *int `json:"llama_n_gpu_layers,omitempty"` + LlamaNThreads *int `json:"llama_n_threads,omitempty"` + MaxEmbeddingConcurrency *int `json:"max_embedding_concurrency,omitempty"` +} + +// SemanticSearchRequest defines model for SemanticSearchRequest. +type SemanticSearchRequest struct { + // Excludes Blacklist — drop results whose path matches any prefix or substring. + Excludes *[]string `json:"excludes,omitempty"` + Languages *[]string `json:"languages,omitempty"` + + // Limit Maximum number of FILE groups (not chunks) to return. + Limit *int `json:"limit,omitempty"` + + // MinScore Minimum cosine similarity. Omit for server default (0.4 for + // CodeRankEmbed-Q8). Send `0` explicitly to disable the floor. + MinScore *float32 `json:"min_score,omitempty"` + + // Paths Whitelist — keep only results whose path matches any prefix or substring. + Paths *[]string `json:"paths,omitempty"` + Query string `json:"query"` +} + +// SemanticSearchResponse defines model for SemanticSearchResponse. +type SemanticSearchResponse struct { + // QueryTimeMs Wall-clock query latency, rounded to 1 decimal place. + QueryTimeMs float64 `json:"query_time_ms"` + Results []FileGroupResult `json:"results"` + Total int `json:"total"` +} + +// Session defines model for Session. +type Session struct { + CreatedAt time.Time `json:"created_at"` + ExpiresAt time.Time `json:"expires_at"` + Id string `json:"id"` + + // IsCurrent True for the session carrying this request. + IsCurrent bool `json:"is_current"` + LastSeenAt time.Time `json:"last_seen_at"` + LastSeenIp *string `json:"last_seen_ip,omitempty"` + LastSeenUa *string `json:"last_seen_ua,omitempty"` +} + +// SessionListResponse defines model for SessionListResponse. +type SessionListResponse struct { + Sessions []Session `json:"sessions"` + Total int `json:"total"` +} + +// SidecarStatus defines model for SidecarStatus. +type SidecarStatus struct { + // InFlight Embedding queue depth at the moment of sampling. + InFlight int `json:"in_flight"` + LastError *string `json:"last_error,omitempty"` + Model *string `json:"model,omitempty"` + + // Pid 0 when no child process is alive (failed / disabled). + Pid *int `json:"pid,omitempty"` + Ready bool `json:"ready"` + + // RestartInFlight True between accept of POST /sidecar/restart and respawn completion. + RestartInFlight *bool `json:"restart_in_flight,omitempty"` + State SidecarStatusState `json:"state"` + UptimeSeconds *int `json:"uptime_seconds,omitempty"` +} + +// SidecarStatusState defines model for SidecarStatus.State. +type SidecarStatusState string + +// StatusResponse defines model for StatusResponse. +type StatusResponse struct { + // ActiveIndexingJobs Currently-running `index_runs` rows. + ActiveIndexingJobs int `json:"active_indexing_jobs"` + ApiVersion string `json:"api_version"` + + // Backend Backend identifier (e.g. `go`). + Backend string `json:"backend"` + + // EmbeddingModel Hugging Face model id (e.g. `awhiteside/CodeRankEmbed-Q8_0-GGUF`). + EmbeddingModel string `json:"embedding_model"` + + // ModelLoaded Whether the llama-server sidecar reports ready within 500 ms. + // False when the sidecar is starting or has crashed. + ModelLoaded bool `json:"model_loaded"` + + // Projects Total registered projects. + Projects int `json:"projects"` + ServerVersion string `json:"server_version"` + Status StatusResponseStatus `json:"status"` +} + +// StatusResponseStatus defines model for StatusResponse.Status. +type StatusResponseStatus string + +// SymbolEntry defines model for SymbolEntry. +type SymbolEntry struct { + FilePath string `json:"file_path"` + Kind string `json:"kind"` + Language string `json:"language"` + Name string `json:"name"` +} + +// SymbolResultItem defines model for SymbolResultItem. +type SymbolResultItem struct { + EndLine int `json:"end_line"` + FilePath string `json:"file_path"` + Kind string `json:"kind"` + Language string `json:"language"` + Line int `json:"line"` + Name string `json:"name"` + ParentName *string `json:"parent_name,omitempty"` + Signature *string `json:"signature,omitempty"` +} + +// SymbolSearchRequest defines model for SymbolSearchRequest. +type SymbolSearchRequest struct { + Kinds *[]string `json:"kinds,omitempty"` + Limit *int `json:"limit,omitempty"` + Query string `json:"query"` +} + +// SymbolSearchResponse defines model for SymbolSearchResponse. +type SymbolSearchResponse struct { + Results []SymbolResultItem `json:"results"` + Total int `json:"total"` +} + +// UpdateProjectRequest defines model for UpdateProjectRequest. +type UpdateProjectRequest struct { + Settings *ProjectSettings `json:"settings,omitempty"` +} + +// UpdateUserRequest defines model for UpdateUserRequest. +type UpdateUserRequest struct { + // Disabled When true, the user can no longer authenticate. Refused for + // the last enabled admin when set to true. + Disabled *bool `json:"disabled,omitempty"` + + // Role New role for the user. Refused for the last enabled admin + // when set to `viewer`. + Role *UpdateUserRequestRole `json:"role,omitempty"` +} + +// UpdateUserRequestRole New role for the user. Refused for the last enabled admin +// when set to `viewer`. +type UpdateUserRequestRole string + +// User defines model for User. +type User struct { + CreatedAt time.Time `json:"created_at"` + + // Disabled True when `disabled_at` is set. Disabled users cannot + // authenticate via password OR API key. + Disabled bool `json:"disabled"` + DisabledAt *time.Time `json:"disabled_at,omitempty"` + Email openapi_types.Email `json:"email"` + Id string `json:"id"` + MustChangePassword bool `json:"must_change_password"` + Role UserRole `json:"role"` + UpdatedAt time.Time `json:"updated_at"` +} + +// UserRole defines model for User.Role. +type UserRole string + +// UserListResponse defines model for UserListResponse. +type UserListResponse struct { + Total int `json:"total"` + Users []UserWithStats `json:"users"` +} + +// UserWithStats defines model for UserWithStats. +type UserWithStats struct { + // ActiveSessionsCount Count of non-expired sessions for this user. + ActiveSessionsCount int `json:"active_sessions_count"` + + // ApiKeysCount Count of non-revoked API keys owned by this user. + ApiKeysCount int `json:"api_keys_count"` + CreatedAt time.Time `json:"created_at"` + + // Disabled True when `disabled_at` is set. Disabled users cannot + // authenticate via password OR API key. + Disabled bool `json:"disabled"` + DisabledAt *time.Time `json:"disabled_at,omitempty"` + Email openapi_types.Email `json:"email"` + Id string `json:"id"` + + // LastLoginAt Most recent session creation timestamp (RFC3339). + // Null if the user has never logged in. + LastLoginAt *time.Time `json:"last_login_at,omitempty"` + MustChangePassword bool `json:"must_change_password"` + Role UserWithStatsRole `json:"role"` + UpdatedAt time.Time `json:"updated_at"` +} + +// UserWithStatsRole defines model for UserWithStats.Role. +type UserWithStatsRole string + +// ProjectHash defines model for ProjectHash. +type ProjectHash = string + +// Conflict defines model for Conflict. +type Conflict = Error + +// Forbidden defines model for Forbidden. +type Forbidden = Error + +// IndexerUnavailable defines model for IndexerUnavailable. +type IndexerUnavailable = Error + +// InternalError defines model for InternalError. +type InternalError = Error + +// NotFound defines model for NotFound. +type NotFound = Error + +// Unauthorized defines model for Unauthorized. +type Unauthorized = Error + +// Unprocessable defines model for Unprocessable. +type Unprocessable = Error + +// bearerAuthContextKey is the context key for bearerAuth security scheme +type bearerAuthContextKey string + +// ListApiKeysParams defines parameters for ListApiKeys. +type ListApiKeysParams struct { + // Owner `all` — admin-only, returns every key in the system. + // Anything else (or unset) returns the caller's keys. + Owner *ListApiKeysParamsOwner `form:"owner,omitempty" json:"owner,omitempty"` +} + +// ListApiKeysParamsOwner defines parameters for ListApiKeys. +type ListApiKeysParamsOwner string + +// IndexFilesParams defines parameters for IndexFiles. +type IndexFilesParams struct { + // Accept `application/x-ndjson` switches to a streamed response + // (one `IndexProgressEvent` per line). Default: `application/json`. + Accept *IndexFilesParamsAccept `json:"Accept,omitempty"` +} + +// IndexFilesParamsAccept defines parameters for IndexFiles. +type IndexFilesParamsAccept string + +// PutRuntimeConfigJSONRequestBody defines body for PutRuntimeConfig for application/json ContentType. +type PutRuntimeConfigJSONRequestBody = RuntimeConfigUpdate + +// CreateUserJSONRequestBody defines body for CreateUser for application/json ContentType. +type CreateUserJSONRequestBody = CreateUserRequest + +// UpdateUserJSONRequestBody defines body for UpdateUser for application/json ContentType. +type UpdateUserJSONRequestBody = UpdateUserRequest + +// CreateApiKeyJSONRequestBody defines body for CreateApiKey for application/json ContentType. +type CreateApiKeyJSONRequestBody = CreateApiKeyRequest + +// ChangePasswordJSONRequestBody defines body for ChangePassword for application/json ContentType. +type ChangePasswordJSONRequestBody = ChangePasswordRequest + +// LoginJSONRequestBody defines body for Login for application/json ContentType. +type LoginJSONRequestBody = LoginRequest + +// CreateProjectJSONRequestBody defines body for CreateProject for application/json ContentType. +type CreateProjectJSONRequestBody = CreateProjectRequest + +// UpdateProjectJSONRequestBody defines body for UpdateProject for application/json ContentType. +type UpdateProjectJSONRequestBody = UpdateProjectRequest + +// IndexBeginJSONRequestBody defines body for IndexBegin for application/json ContentType. +type IndexBeginJSONRequestBody = IndexBeginRequest + +// IndexFilesJSONRequestBody defines body for IndexFiles for application/json ContentType. +type IndexFilesJSONRequestBody = IndexFilesRequest + +// IndexFinishJSONRequestBody defines body for IndexFinish for application/json ContentType. +type IndexFinishJSONRequestBody = IndexFinishRequest + +// SemanticSearchJSONRequestBody defines body for SemanticSearch for application/json ContentType. +type SemanticSearchJSONRequestBody = SemanticSearchRequest + +// SearchDefinitionsJSONRequestBody defines body for SearchDefinitions for application/json ContentType. +type SearchDefinitionsJSONRequestBody = DefinitionRequest + +// SearchFilesJSONRequestBody defines body for SearchFiles for application/json ContentType. +type SearchFilesJSONRequestBody = FileSearchRequest + +// SearchReferencesJSONRequestBody defines body for SearchReferences for application/json ContentType. +type SearchReferencesJSONRequestBody = ReferenceRequest + +// SearchSymbolsJSONRequestBody defines body for SearchSymbols for application/json ContentType. +type SearchSymbolsJSONRequestBody = SymbolSearchRequest + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // List GGUF model files cached on disk (admin only) + // (GET /api/v1/admin/models) + ListModels(w http.ResponseWriter, r *http.Request) + // Read effective runtime config (admin only) + // (GET /api/v1/admin/runtime-config) + GetRuntimeConfig(w http.ResponseWriter, r *http.Request) + // Save runtime config overrides (admin only) + // (PUT /api/v1/admin/runtime-config) + PutRuntimeConfig(w http.ResponseWriter, r *http.Request) + // Restart the llama-server sidecar (admin only) + // (POST /api/v1/admin/sidecar/restart) + RestartSidecar(w http.ResponseWriter, r *http.Request) + // Sidecar process status (admin only) + // (GET /api/v1/admin/sidecar/status) + GetSidecarStatus(w http.ResponseWriter, r *http.Request) + // List all users (admin only) + // (GET /api/v1/admin/users) + ListUsers(w http.ResponseWriter, r *http.Request) + // Invite a new user (admin only) + // (POST /api/v1/admin/users) + CreateUser(w http.ResponseWriter, r *http.Request) + // Delete a user (admin only) + // (DELETE /api/v1/admin/users/{id}) + DeleteUser(w http.ResponseWriter, r *http.Request, id string) + // Change role or disabled flag (admin only) + // (PATCH /api/v1/admin/users/{id}) + UpdateUser(w http.ResponseWriter, r *http.Request, id string) + // List my API keys (or all keys if admin) + // (GET /api/v1/api-keys) + ListApiKeys(w http.ResponseWriter, r *http.Request, params ListApiKeysParams) + // Issue a new API key + // (POST /api/v1/api-keys) + CreateApiKey(w http.ResponseWriter, r *http.Request) + // Revoke an API key + // (DELETE /api/v1/api-keys/{id}) + RevokeApiKey(w http.ResponseWriter, r *http.Request, id string) + // Whether the dashboard needs first-run bootstrap (public) + // (GET /api/v1/auth/bootstrap-status) + GetBootstrapStatus(w http.ResponseWriter, r *http.Request) + // Change the current user's password + // (POST /api/v1/auth/change-password) + ChangePassword(w http.ResponseWriter, r *http.Request) + // Exchange email + password for a session cookie (public) + // (POST /api/v1/auth/login) + Login(w http.ResponseWriter, r *http.Request) + // End the current session + // (POST /api/v1/auth/logout) + Logout(w http.ResponseWriter, r *http.Request) + // Current authenticated user + // (GET /api/v1/auth/me) + GetMe(w http.ResponseWriter, r *http.Request) + // Active sessions of the current user + // (GET /api/v1/auth/sessions) + ListMySessions(w http.ResponseWriter, r *http.Request) + // End one of my sessions (sign out a single device) + // (DELETE /api/v1/auth/sessions/{id}) + DeleteMySession(w http.ResponseWriter, r *http.Request, id string) + // List all registered projects + // (GET /api/v1/projects) + ListProjects(w http.ResponseWriter, r *http.Request) + // Register a new project + // (POST /api/v1/projects) + CreateProject(w http.ResponseWriter, r *http.Request) + // Delete a project and all its indexed data (admin only) + // (DELETE /api/v1/projects/{path}) + DeleteProject(w http.ResponseWriter, r *http.Request, path ProjectHash) + // Get one project by hash + // (GET /api/v1/projects/{path}) + GetProject(w http.ResponseWriter, r *http.Request, path ProjectHash) + // Patch project settings (admin only) + // (PATCH /api/v1/projects/{path}) + UpdateProject(w http.ResponseWriter, r *http.Request, path ProjectHash) + // Open an indexing session + // (POST /api/v1/projects/{path}/index/begin) + IndexBegin(w http.ResponseWriter, r *http.Request, path ProjectHash) + // Cancel any active indexing session (idempotent) + // (POST /api/v1/projects/{path}/index/cancel) + IndexCancel(w http.ResponseWriter, r *http.Request, path ProjectHash) + // Submit a batch of files (max 50) + // (POST /api/v1/projects/{path}/index/files) + IndexFiles(w http.ResponseWriter, r *http.Request, path ProjectHash, params IndexFilesParams) + // Commit the indexing session + // (POST /api/v1/projects/{path}/index/finish) + IndexFinish(w http.ResponseWriter, r *http.Request, path ProjectHash) + // Live progress of the current run, or last completed run + // (GET /api/v1/projects/{path}/index/status) + IndexStatus(w http.ResponseWriter, r *http.Request, path ProjectHash) + // Semantic (vector) search + // (POST /api/v1/projects/{path}/search) + SemanticSearch(w http.ResponseWriter, r *http.Request, path ProjectHash) + // Go-to-definition by symbol name + // (POST /api/v1/projects/{path}/search/definitions) + SearchDefinitions(w http.ResponseWriter, r *http.Request, path ProjectHash) + // File-path substring search + // (POST /api/v1/projects/{path}/search/files) + SearchFiles(w http.ResponseWriter, r *http.Request, path ProjectHash) + // Find references to a symbol + // (POST /api/v1/projects/{path}/search/references) + SearchReferences(w http.ResponseWriter, r *http.Request, path ProjectHash) + // Symbol search by name (prefix/substring) + // (POST /api/v1/projects/{path}/search/symbols) + SearchSymbols(w http.ResponseWriter, r *http.Request, path ProjectHash) + // Project overview (top dirs, recent symbols, totals) + // (GET /api/v1/projects/{path}/summary) + GetProjectSummary(w http.ResponseWriter, r *http.Request, path ProjectHash) + // Server / sidecar status (authenticated) + // (GET /api/v1/status) + GetStatus(w http.ResponseWriter, r *http.Request) + // Liveness probe (public) + // (GET /health) + GetHealth(w http.ResponseWriter, r *http.Request) +} + +// Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. + +type Unimplemented struct{} + +// List GGUF model files cached on disk (admin only) +// (GET /api/v1/admin/models) +func (_ Unimplemented) ListModels(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Read effective runtime config (admin only) +// (GET /api/v1/admin/runtime-config) +func (_ Unimplemented) GetRuntimeConfig(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Save runtime config overrides (admin only) +// (PUT /api/v1/admin/runtime-config) +func (_ Unimplemented) PutRuntimeConfig(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Restart the llama-server sidecar (admin only) +// (POST /api/v1/admin/sidecar/restart) +func (_ Unimplemented) RestartSidecar(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Sidecar process status (admin only) +// (GET /api/v1/admin/sidecar/status) +func (_ Unimplemented) GetSidecarStatus(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// List all users (admin only) +// (GET /api/v1/admin/users) +func (_ Unimplemented) ListUsers(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Invite a new user (admin only) +// (POST /api/v1/admin/users) +func (_ Unimplemented) CreateUser(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Delete a user (admin only) +// (DELETE /api/v1/admin/users/{id}) +func (_ Unimplemented) DeleteUser(w http.ResponseWriter, r *http.Request, id string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Change role or disabled flag (admin only) +// (PATCH /api/v1/admin/users/{id}) +func (_ Unimplemented) UpdateUser(w http.ResponseWriter, r *http.Request, id string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// List my API keys (or all keys if admin) +// (GET /api/v1/api-keys) +func (_ Unimplemented) ListApiKeys(w http.ResponseWriter, r *http.Request, params ListApiKeysParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Issue a new API key +// (POST /api/v1/api-keys) +func (_ Unimplemented) CreateApiKey(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Revoke an API key +// (DELETE /api/v1/api-keys/{id}) +func (_ Unimplemented) RevokeApiKey(w http.ResponseWriter, r *http.Request, id string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Whether the dashboard needs first-run bootstrap (public) +// (GET /api/v1/auth/bootstrap-status) +func (_ Unimplemented) GetBootstrapStatus(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Change the current user's password +// (POST /api/v1/auth/change-password) +func (_ Unimplemented) ChangePassword(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Exchange email + password for a session cookie (public) +// (POST /api/v1/auth/login) +func (_ Unimplemented) Login(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// End the current session +// (POST /api/v1/auth/logout) +func (_ Unimplemented) Logout(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Current authenticated user +// (GET /api/v1/auth/me) +func (_ Unimplemented) GetMe(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Active sessions of the current user +// (GET /api/v1/auth/sessions) +func (_ Unimplemented) ListMySessions(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// End one of my sessions (sign out a single device) +// (DELETE /api/v1/auth/sessions/{id}) +func (_ Unimplemented) DeleteMySession(w http.ResponseWriter, r *http.Request, id string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// List all registered projects +// (GET /api/v1/projects) +func (_ Unimplemented) ListProjects(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Register a new project +// (POST /api/v1/projects) +func (_ Unimplemented) CreateProject(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Delete a project and all its indexed data (admin only) +// (DELETE /api/v1/projects/{path}) +func (_ Unimplemented) DeleteProject(w http.ResponseWriter, r *http.Request, path ProjectHash) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Get one project by hash +// (GET /api/v1/projects/{path}) +func (_ Unimplemented) GetProject(w http.ResponseWriter, r *http.Request, path ProjectHash) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Patch project settings (admin only) +// (PATCH /api/v1/projects/{path}) +func (_ Unimplemented) UpdateProject(w http.ResponseWriter, r *http.Request, path ProjectHash) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Open an indexing session +// (POST /api/v1/projects/{path}/index/begin) +func (_ Unimplemented) IndexBegin(w http.ResponseWriter, r *http.Request, path ProjectHash) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Cancel any active indexing session (idempotent) +// (POST /api/v1/projects/{path}/index/cancel) +func (_ Unimplemented) IndexCancel(w http.ResponseWriter, r *http.Request, path ProjectHash) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Submit a batch of files (max 50) +// (POST /api/v1/projects/{path}/index/files) +func (_ Unimplemented) IndexFiles(w http.ResponseWriter, r *http.Request, path ProjectHash, params IndexFilesParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Commit the indexing session +// (POST /api/v1/projects/{path}/index/finish) +func (_ Unimplemented) IndexFinish(w http.ResponseWriter, r *http.Request, path ProjectHash) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Live progress of the current run, or last completed run +// (GET /api/v1/projects/{path}/index/status) +func (_ Unimplemented) IndexStatus(w http.ResponseWriter, r *http.Request, path ProjectHash) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Semantic (vector) search +// (POST /api/v1/projects/{path}/search) +func (_ Unimplemented) SemanticSearch(w http.ResponseWriter, r *http.Request, path ProjectHash) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Go-to-definition by symbol name +// (POST /api/v1/projects/{path}/search/definitions) +func (_ Unimplemented) SearchDefinitions(w http.ResponseWriter, r *http.Request, path ProjectHash) { + w.WriteHeader(http.StatusNotImplemented) +} + +// File-path substring search +// (POST /api/v1/projects/{path}/search/files) +func (_ Unimplemented) SearchFiles(w http.ResponseWriter, r *http.Request, path ProjectHash) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Find references to a symbol +// (POST /api/v1/projects/{path}/search/references) +func (_ Unimplemented) SearchReferences(w http.ResponseWriter, r *http.Request, path ProjectHash) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Symbol search by name (prefix/substring) +// (POST /api/v1/projects/{path}/search/symbols) +func (_ Unimplemented) SearchSymbols(w http.ResponseWriter, r *http.Request, path ProjectHash) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Project overview (top dirs, recent symbols, totals) +// (GET /api/v1/projects/{path}/summary) +func (_ Unimplemented) GetProjectSummary(w http.ResponseWriter, r *http.Request, path ProjectHash) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Server / sidecar status (authenticated) +// (GET /api/v1/status) +func (_ Unimplemented) GetStatus(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Liveness probe (public) +// (GET /health) +func (_ Unimplemented) GetHealth(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// ListModels operation middleware +func (siw *ServerInterfaceWrapper) ListModels(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ListModels(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// GetRuntimeConfig operation middleware +func (siw *ServerInterfaceWrapper) GetRuntimeConfig(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetRuntimeConfig(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// PutRuntimeConfig operation middleware +func (siw *ServerInterfaceWrapper) PutRuntimeConfig(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.PutRuntimeConfig(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// RestartSidecar operation middleware +func (siw *ServerInterfaceWrapper) RestartSidecar(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.RestartSidecar(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// GetSidecarStatus operation middleware +func (siw *ServerInterfaceWrapper) GetSidecarStatus(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetSidecarStatus(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// ListUsers operation middleware +func (siw *ServerInterfaceWrapper) ListUsers(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ListUsers(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// CreateUser operation middleware +func (siw *ServerInterfaceWrapper) CreateUser(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.CreateUser(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// DeleteUser operation middleware +func (siw *ServerInterfaceWrapper) DeleteUser(w http.ResponseWriter, r *http.Request) { + + var err error + _ = err + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DeleteUser(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// UpdateUser operation middleware +func (siw *ServerInterfaceWrapper) UpdateUser(w http.ResponseWriter, r *http.Request) { + + var err error + _ = err + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.UpdateUser(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// ListApiKeys operation middleware +func (siw *ServerInterfaceWrapper) ListApiKeys(w http.ResponseWriter, r *http.Request) { + + var err error + _ = err + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + // Parameter object where we will unmarshal all parameters from the context + var params ListApiKeysParams + + // ------------- Optional query parameter "owner" ------------- + + err = runtime.BindQueryParameterWithOptions("form", true, false, "owner", r.URL.Query(), ¶ms.Owner, runtime.BindQueryParameterOptions{Type: "string", Format: ""}) + if err != nil { + var requiredError *runtime.RequiredParameterError + if errors.As(err, &requiredError) { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "owner"}) + } else { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "owner", Err: err}) + } + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ListApiKeys(w, r, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// CreateApiKey operation middleware +func (siw *ServerInterfaceWrapper) CreateApiKey(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.CreateApiKey(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// RevokeApiKey operation middleware +func (siw *ServerInterfaceWrapper) RevokeApiKey(w http.ResponseWriter, r *http.Request) { + + var err error + _ = err + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.RevokeApiKey(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// GetBootstrapStatus operation middleware +func (siw *ServerInterfaceWrapper) GetBootstrapStatus(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetBootstrapStatus(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// ChangePassword operation middleware +func (siw *ServerInterfaceWrapper) ChangePassword(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ChangePassword(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// Login operation middleware +func (siw *ServerInterfaceWrapper) Login(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.Login(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// Logout operation middleware +func (siw *ServerInterfaceWrapper) Logout(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.Logout(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// GetMe operation middleware +func (siw *ServerInterfaceWrapper) GetMe(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetMe(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// ListMySessions operation middleware +func (siw *ServerInterfaceWrapper) ListMySessions(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ListMySessions(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// DeleteMySession operation middleware +func (siw *ServerInterfaceWrapper) DeleteMySession(w http.ResponseWriter, r *http.Request) { + + var err error + _ = err + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DeleteMySession(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// ListProjects operation middleware +func (siw *ServerInterfaceWrapper) ListProjects(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ListProjects(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// CreateProject operation middleware +func (siw *ServerInterfaceWrapper) CreateProject(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.CreateProject(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// DeleteProject operation middleware +func (siw *ServerInterfaceWrapper) DeleteProject(w http.ResponseWriter, r *http.Request) { + + var err error + _ = err + + // ------------- Path parameter "path" ------------- + var path ProjectHash + + err = runtime.BindStyledParameterWithOptions("simple", "path", chi.URLParam(r, "path"), &path, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DeleteProject(w, r, path) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// GetProject operation middleware +func (siw *ServerInterfaceWrapper) GetProject(w http.ResponseWriter, r *http.Request) { + + var err error + _ = err + + // ------------- Path parameter "path" ------------- + var path ProjectHash + + err = runtime.BindStyledParameterWithOptions("simple", "path", chi.URLParam(r, "path"), &path, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetProject(w, r, path) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// UpdateProject operation middleware +func (siw *ServerInterfaceWrapper) UpdateProject(w http.ResponseWriter, r *http.Request) { + + var err error + _ = err + + // ------------- Path parameter "path" ------------- + var path ProjectHash + + err = runtime.BindStyledParameterWithOptions("simple", "path", chi.URLParam(r, "path"), &path, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.UpdateProject(w, r, path) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// IndexBegin operation middleware +func (siw *ServerInterfaceWrapper) IndexBegin(w http.ResponseWriter, r *http.Request) { + + var err error + _ = err + + // ------------- Path parameter "path" ------------- + var path ProjectHash + + err = runtime.BindStyledParameterWithOptions("simple", "path", chi.URLParam(r, "path"), &path, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.IndexBegin(w, r, path) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// IndexCancel operation middleware +func (siw *ServerInterfaceWrapper) IndexCancel(w http.ResponseWriter, r *http.Request) { + + var err error + _ = err + + // ------------- Path parameter "path" ------------- + var path ProjectHash + + err = runtime.BindStyledParameterWithOptions("simple", "path", chi.URLParam(r, "path"), &path, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.IndexCancel(w, r, path) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// IndexFiles operation middleware +func (siw *ServerInterfaceWrapper) IndexFiles(w http.ResponseWriter, r *http.Request) { + + var err error + _ = err + + // ------------- Path parameter "path" ------------- + var path ProjectHash + + err = runtime.BindStyledParameterWithOptions("simple", "path", chi.URLParam(r, "path"), &path, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + // Parameter object where we will unmarshal all parameters from the context + var params IndexFilesParams + + headers := r.Header + + // ------------- Optional header parameter "Accept" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("Accept")]; found { + var Accept IndexFilesParamsAccept + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "Accept", Count: n}) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "Accept", valueList[0], &Accept, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "Accept", Err: err}) + return + } + + params.Accept = &Accept + + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.IndexFiles(w, r, path, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// IndexFinish operation middleware +func (siw *ServerInterfaceWrapper) IndexFinish(w http.ResponseWriter, r *http.Request) { + + var err error + _ = err + + // ------------- Path parameter "path" ------------- + var path ProjectHash + + err = runtime.BindStyledParameterWithOptions("simple", "path", chi.URLParam(r, "path"), &path, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.IndexFinish(w, r, path) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// IndexStatus operation middleware +func (siw *ServerInterfaceWrapper) IndexStatus(w http.ResponseWriter, r *http.Request) { + + var err error + _ = err + + // ------------- Path parameter "path" ------------- + var path ProjectHash + + err = runtime.BindStyledParameterWithOptions("simple", "path", chi.URLParam(r, "path"), &path, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.IndexStatus(w, r, path) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// SemanticSearch operation middleware +func (siw *ServerInterfaceWrapper) SemanticSearch(w http.ResponseWriter, r *http.Request) { + + var err error + _ = err + + // ------------- Path parameter "path" ------------- + var path ProjectHash + + err = runtime.BindStyledParameterWithOptions("simple", "path", chi.URLParam(r, "path"), &path, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.SemanticSearch(w, r, path) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// SearchDefinitions operation middleware +func (siw *ServerInterfaceWrapper) SearchDefinitions(w http.ResponseWriter, r *http.Request) { + + var err error + _ = err + + // ------------- Path parameter "path" ------------- + var path ProjectHash + + err = runtime.BindStyledParameterWithOptions("simple", "path", chi.URLParam(r, "path"), &path, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.SearchDefinitions(w, r, path) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// SearchFiles operation middleware +func (siw *ServerInterfaceWrapper) SearchFiles(w http.ResponseWriter, r *http.Request) { + + var err error + _ = err + + // ------------- Path parameter "path" ------------- + var path ProjectHash + + err = runtime.BindStyledParameterWithOptions("simple", "path", chi.URLParam(r, "path"), &path, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.SearchFiles(w, r, path) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// SearchReferences operation middleware +func (siw *ServerInterfaceWrapper) SearchReferences(w http.ResponseWriter, r *http.Request) { + + var err error + _ = err + + // ------------- Path parameter "path" ------------- + var path ProjectHash + + err = runtime.BindStyledParameterWithOptions("simple", "path", chi.URLParam(r, "path"), &path, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.SearchReferences(w, r, path) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// SearchSymbols operation middleware +func (siw *ServerInterfaceWrapper) SearchSymbols(w http.ResponseWriter, r *http.Request) { + + var err error + _ = err + + // ------------- Path parameter "path" ------------- + var path ProjectHash + + err = runtime.BindStyledParameterWithOptions("simple", "path", chi.URLParam(r, "path"), &path, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.SearchSymbols(w, r, path) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// GetProjectSummary operation middleware +func (siw *ServerInterfaceWrapper) GetProjectSummary(w http.ResponseWriter, r *http.Request) { + + var err error + _ = err + + // ------------- Path parameter "path" ------------- + var path ProjectHash + + err = runtime.BindStyledParameterWithOptions("simple", "path", chi.URLParam(r, "path"), &path, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetProjectSummary(w, r, path) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// GetStatus operation middleware +func (siw *ServerInterfaceWrapper) GetStatus(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetStatus(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// GetHealth operation middleware +func (siw *ServerInterfaceWrapper) GetHealth(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetHealth(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{}) +} + +type ChiServerOptions struct { + BaseURL string + BaseRouter chi.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r chi.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = chi.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/api/v1/admin/models", wrapper.ListModels) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/api/v1/admin/runtime-config", wrapper.GetRuntimeConfig) + }) + r.Group(func(r chi.Router) { + r.Put(options.BaseURL+"/api/v1/admin/runtime-config", wrapper.PutRuntimeConfig) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/api/v1/admin/sidecar/restart", wrapper.RestartSidecar) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/api/v1/admin/sidecar/status", wrapper.GetSidecarStatus) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/api/v1/admin/users", wrapper.ListUsers) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/api/v1/admin/users", wrapper.CreateUser) + }) + r.Group(func(r chi.Router) { + r.Delete(options.BaseURL+"/api/v1/admin/users/{id}", wrapper.DeleteUser) + }) + r.Group(func(r chi.Router) { + r.Patch(options.BaseURL+"/api/v1/admin/users/{id}", wrapper.UpdateUser) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/api/v1/api-keys", wrapper.ListApiKeys) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/api/v1/api-keys", wrapper.CreateApiKey) + }) + r.Group(func(r chi.Router) { + r.Delete(options.BaseURL+"/api/v1/api-keys/{id}", wrapper.RevokeApiKey) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/api/v1/auth/bootstrap-status", wrapper.GetBootstrapStatus) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/api/v1/auth/change-password", wrapper.ChangePassword) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/api/v1/auth/login", wrapper.Login) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/api/v1/auth/logout", wrapper.Logout) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/api/v1/auth/me", wrapper.GetMe) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/api/v1/auth/sessions", wrapper.ListMySessions) + }) + r.Group(func(r chi.Router) { + r.Delete(options.BaseURL+"/api/v1/auth/sessions/{id}", wrapper.DeleteMySession) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/api/v1/projects", wrapper.ListProjects) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/api/v1/projects", wrapper.CreateProject) + }) + r.Group(func(r chi.Router) { + r.Delete(options.BaseURL+"/api/v1/projects/{path}", wrapper.DeleteProject) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/api/v1/projects/{path}", wrapper.GetProject) + }) + r.Group(func(r chi.Router) { + r.Patch(options.BaseURL+"/api/v1/projects/{path}", wrapper.UpdateProject) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/api/v1/projects/{path}/index/begin", wrapper.IndexBegin) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/api/v1/projects/{path}/index/cancel", wrapper.IndexCancel) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/api/v1/projects/{path}/index/files", wrapper.IndexFiles) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/api/v1/projects/{path}/index/finish", wrapper.IndexFinish) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/api/v1/projects/{path}/index/status", wrapper.IndexStatus) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/api/v1/projects/{path}/search", wrapper.SemanticSearch) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/api/v1/projects/{path}/search/definitions", wrapper.SearchDefinitions) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/api/v1/projects/{path}/search/files", wrapper.SearchFiles) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/api/v1/projects/{path}/search/references", wrapper.SearchReferences) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/api/v1/projects/{path}/search/symbols", wrapper.SearchSymbols) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/api/v1/projects/{path}/summary", wrapper.GetProjectSummary) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/api/v1/status", wrapper.GetStatus) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/health", wrapper.GetHealth) + }) + + return r +} + +// Base64 encoded, compressed with deflate, json marshaled OpenAPI spec. +// Stored as a slice of fixed-width chunks rather than one concatenated +// const string: with thousands of chunks the chained `+` fold is several +// times slower for the Go compiler than parsing a slice literal. +var swaggerSpec = []string{ + "7L3rchs5kij8KhncjWjJS1Ky2z0XdfQP2bLb/sbd9kr2N3vOVB8WWJUkMSoC1QBKEtfhiP21DzCxTzhP", + "cgIJoC4kiqRsSe6ZOL8kklW4JDITec+Pg0wuSylQGD04+TgomWJLNKjo0zsl/4qZecX0wn7MUWeKl4ZL", + "MTgZvORKG3j8O1jgDWQLpjTIGaQXr04fHyykNpOSmcVhOoYLxESkXBhUghVHpRtUj+2w75hZpONEDIYD", + "bge17wyGA8GW2HxS+GvFFeaDE6MqHA50tsAlsyvCG7YsC/vod9Pf50+yP+Jj9u3sD8dPnwyG9m075eBk", + "8H/+wkaz49Eff/n4+Hef/nUwHJhVaV/SRnExH3z69MlOokspNNLGn0sxK3hm7P+ZFAYF/cvKsuAZswA4", + "+qu2UPjYWsy/KpwNTgb/ctSA9Mj9qo9eKCWVm6gLxXPUslIZAisUsnwFeMO10XCA4/kYcMl4AYZdojgc", + "fBoOXko15XmO4v4XdlqZBQpjR8V8CNPKQMGySw1mgRBOBJQs0C7stcjxBtUHwa4YL9jUnsl9r5Dm5GIO", + "GtUVzxCENJBJMePzymILLcshnRvj3lf0QSyYyAvMaUmoAN2Tw8HP0ryUlcgfEKEsNGY056fh4INglVlI", + "xf8TH2ANP3Gt7cFIBVxcsYLncPruNVziyq2lVDJDrR8GTX5ixUyqpUVW/LVCbWAq85Vd29Ivs8bmGcci", + "1wM7hh/Wznpa8j/hirijkiUqwx2TyBRa2pgwWrmdw/43yJnBkeFL3OQzwwEn6G98XTBtJpXePpioCk9Z", + "jg1uGYWXdpRbvFCxvV5wfDmyAXktUNmh1KRni6XCGb/ZvEbOuC4LthpJUazAPWTvEctlZlVRWKTxzDDN", + "+M2EPZ4+yb7Nn6aH40S8kWIOKGQ1X4CRoDCTc8E1AhdQWDY6BL2QytTPLJgBbhKRMWHJw74gtFFVZmhC", + "qficC1a4C2ljCwqv5CW2tzeVskAmWj9+wQF+at90f7Gosg5XfwA1MIdtHGzW90s9tJzaq9YuzyHxc/f4", + "Ji6zkk8uHZJvIzJPCp+GA3s24Y3ugb5fIJQFs/f9jaHju2JFhWN49OgcTaUE5oA3LDPFCqTIcPzoEVwY", + "qZBORmNWKSxW8Pf/+h97JvZrDULCNVu5MzaK45V9GApmUEXPag2UYXetZffD6A3X5twLA72Aov+5waXe", + "H2R+PqYUc5+lYUULmSzE5qj6Vq8H4ZXY2p9JabRRrLwwzFS6fwMCMdeTaXg8cn6qQrheoCCSsKinwVi0", + "tQeBy9Ksxg3AawJYW/P6LLElP18wMcd3TOtrqfJzx5wjbLZSCoUVJ92D9rslF29QzM1icPI4xqbwuvP4", + "+u0k+LJawh9IamWZlXbH8LOEqixRwdTemXaLrUn+sAvDNha5tojo/okYHX707j5w3O4WXlVLJkYzxVHk", + "xQoKNsXCsrprYVmfPbec6cVUMpWP4X2LlSaCiNEe5RwFKssNvLAy0jxHYMLekzEyJTrbCvh1HLBL79+4", + "Vy56d17rEJHrZG2m5tH+6T5oVL1zkZzd4dvum9gNLrjhrNiCX2+F4/gQHqEDEXhNxATLShuLeWKOIAXM", + "SI0q5JyLcSLsWbF8yQXoBVNopW2uQVZmJGejKRP5xjH8IXZRSSdYoaiWxEHsiIPh4IrjNaoWkHrgGTa/", + "sVc/dAzKZzijx6V4bXAZAbHIJwUXGGN4w8GMF9h32MPBJRd9cpOYV2wel0n6Z+sVY0pGVNz7u+ZzwUyl", + "cDdO+pualt7en1/XsAFIaxvbAduLvp8LPb7kxuHvjFWFGZw8PibcsuxxcHI8jIBOr5ZTWezkwWvA8G/t", + "2l7fnaVQV4XZ/85dw8Vtd++23a5tIqxi2zV8xtULYdSq54wyWTk9ZzuQ9+N6Hp1aA8dWVKu+3eXkaDzL", + "2z6Jfy428kte4I9KVuU5AWZzjilqM9GZdORSs9ZZIUlc9QOKajndhwlspfUlM9kC98cQu/af7DubyLEG", + "gDbltjbUTNkHGjf8pjizqMTlxL0R2UhLF974bTsLFaitJrDgtyCUn+mdV9zEaOQWJ6cNU2bL2hz99/HV", + "dWbRDNbhkgE0YWXDNiz7TuEdWxWSRTSeFqDXjDjvX47+AFZ5GcMzLphagcUBbeWrqsjJrjJF0NV0yY3B", + "fByTEvzok0XUdHrx6nT05DtnOc35HLUh06l/KY2OuBX9e4lG8//EW7I5j+sNtDt78UP2gduxgrgEsD95", + "r1kI0GBm5dTwyBCkAqtMA59BJXL/+/jWKnbnVt52B9utXSBT2aL3Dt68TJ/svEx/rVBFNOiLauoWDI7H", + "5MDmjAttIK1XnI5vKY27uXZt7q5u4DVceMAb+BWywmzdCfN2xTWgowGyQZHqm2pSo1OrKKWVWNCgqzhl", + "ukfb8ra8HAwH9Vu75W0/Qmw7ZOR+hnO+RfqriqKDeDNWaFw3g/6ZNHpLFHDNS9TO0WCRzDtkwK4CYYoz", + "qRBkicL+aFUXjVpzKXrU/q1L7j2FSvQZCrWRyt5jTPsLneU5yXKseNcZY+PNdbtvCTMll8S9wdIM/P2/", + "/waB98oZeKW9WI3cnOA53RheLEuzSkRtBQkgWjANAq9QwRTR6to53mAOB1JBao+BuE4K10yT8of5Ycc8", + "FWC0jtYOGOtb70WH50xkWPQDN6Pfi7ilct1wUT/bO52lZb1V97gdXwhXMoltN6/da98db7KIBkluw+hq", + "aLqV7dpWLxCtbKEnWWMx3c7LabYJyzIsb/G894RgPvkcfrg253B90X2zbIGJ4Lr/jsuxQCtjWmLqnvkG", + "LX7mWXrOPnHrzrnO5BWq3fCM48DOfd7t4ddg3v3C5p1hiYWgu/d1sTntBgL0AuCdknOFWr+4isrAbwUC", + "2p+COfHns//v4u3PoI1CtgR0ki9MV5C+e3vxHo6IEx7RelK6QRNhX8sKbgfRKHIN6Skh6gm0nXw3I5H/", + "VUuROjtlSrOmzhOXCIsAii+5YAad5/mKKc6E+R6kWaDyHjtgCqGUZVWQPZNpUFjgFRPGsd81tdQKVZMg", + "GW+ejYPhtt/aiLH5DC6nmE8cXdSqExfmd08HMVTAcAQBE0jGIyWoJuEJzdt8pCny5nMuSUNyv5HCPxws", + "kCkzRVLY3Jb9U+6BXyK0N2Ndj0TLvUVD0yn3G/C67O8WLG/z0SVqfWtlZ4tQYfS+Ptp1Uyidzk46ei1m", + "cpOMwq9QuivP4TjLDL/CkZeqAkZDxpTiVi67QlI5sci/d1S04FYw4BkrRjNWFFOWXdZvkcgaXk3XIJwO", + "E+G/I1inQ7Lvp10sTmNEclsOiAUr7ZlqzKTI16AtK6uS9Vh8bsPmP4PTtra/h+FtwXTHcq4wQ35lEWO4", + "lUNvQb5Pu3Cn/xoq/RO7pKpNVOxcMV2kTHleYEr+VSGDbE9YB0dQ41fVRPKMKXrLxdq498JLDpPt7zVs", + "0qO0FinTo3TGuPtHVULU7xdMm5GqBLg1OjHdzTFRldAeI8Mh2AWTN8KtoXMUw5YEaxkYd//46b5I9Xoj", + "t2ldt3AZ7e257HHDbPUi+lX2oVClUe1Cnw86IkLRi7EJf8It3vHKLCZLNAsZ8Yu9x6LQXeekFRXoHjcS", + "dKVmLENIBoWcy8okAzjwiHYIUiViwXNy+x94hzjY20brJlLgGw1CmgWprRIKOQdZGZCzwy46+UEHwzou", + "IEbPXwa4YQcUUTDKHIseXwGPQO/VS1BYSnh9BjkqfoW5IxsSs1hmocoVZkaqFQi2RB80QwEkR0s72eE4", + "jpwmYqE8nWpZVMbrzUbSNOP5vJo5dVoKyLm+jNtD+H/iZLoyGJeAbiHGkxrn7XOtUXvB+YZHAwgsdCY5", + "V/FYleev/2Py448fXk6enz5/9WJy9vrcxQlZJV5nTAjMvUGAYorgmpsFCClGFAsB9ejwg+WnDYy0i76L", + "gojOY3+tuYUru9wVfuRha9cxcDWG/9s6KLY7IX5zPoNmM2FxMXD4WIQYMJRcskmcSM5Ry8ISIj2Fy9Fc", + "QiaLAjP7QIseZ1I5R763I43h5w9v3jhLowtaXZbVfhbsYVjSLaisZ8i2WiOFYVyg6tnpO8sFuKAIEWI4", + "4Xk4kDODAvDXihWWTzSR33G/yGfETHYCQXrYFBHcShtcOo4lndpqj5IZqb7RsGTZggscx2M6yI43saQ9", + "IQranOoFqVxklLcPAM9RGD7jqLwYFGKmmmMmFmJlnUQcKDz0s/jDlwKUvNaO15QKRxYGkCs+M2AUyy7t", + "VP5qS0RzY1oN3Gg3BtOQDD6ISyGvRTIAxdxdumDC/kRjuatvj0hQ5/24pVWHAkgD9L4kdNUeWo+zLJ5n", + "sJZm4MRSF6b24fxN63TGt8oEGA40GsPFfCdP9izjIjxuX/214AZ3MYuLf3/D7Ukzw6ZM+xvWcYigGzoU", + "axClPn2PLrkU3xjAm1JqBKsbsjkCFzO5FwPxy7xTBmLF6L1BRs/GjWC14bIl7Hv82mq5qMr8lnwl4vUM", + "Hs6G4WxwxjaltHAlAGDY2OY6kbmt5W0SzZYLaXssasik2VuOCNfcHbnj6vm3+ePW6WRTkbrJiionsrFE", + "eksOtGQ3E2cwu72ne2Pm9eG27Scg/Jrk7o+19opstzc4Y3djcNzn6VsN7YQofUvAtCcaru1pbdHrE20D", + "WbVcspi6sy3S87Ovpt/OjaIwQ2HaR7EXsV7Q8z1Sf5t5Rlwo5SQIn/wWzrk6eK2PP/z2MLWPbddsuM2u", + "u2i9FY03gbhxjjFMP8cZKhQZxiNguqpVY2P0L0Vvtt44pdPimq18RL63yiGgyEvJhYHWs1GRt63GxccN", + "En3a6FYpHCichXQA8lZrMPIShR6SIqOYmKNui/57x/hujW+6U12xHfWzO9asq0G25tkRMlSjwmdG7W7G", + "E3338MG5rU3cVWRQl0QeMDDoHOmwT1suqo2dEDbErG9vS/ZrhfD67HuYVaZSCFeoNJfCKparIIqXqEZ+", + "FAi2ewpQ8+o/j1mDNrcSVhHdRSWsPPuc0lxjVmmvpfapsS0zolTA+vRnI4G1bFnxiMSCLdmk602tz+xx", + "DD/dG5m5udXzYjIvq0nBVj4rvbuh0WP4AVhRgHsADn5Cw4qj5x/OTg+HcAw/wPN3H8hNFudKYQ6zUMjy", + "yAR2iAIN0IMjn9jLKiNHLvBwPNhFllaobA4mk8IFHmWr3RBQmMnlEkXuEHYrYbUx47z1nmUMlBK8LZgq", + "XEb5lBjh1aA7d+xmWjMRoRqR09JnUYakJLlm8c+YAIVEEwySwdmzZABHiUgGL8SV/ReSQWvxyQBKXhQg", + "8MZYpESWLUI+4Z9wpV2EpLORtCICyAKuTyBdo4d0CGkXCdMhjMfRIK11pTIWTrdAUA7sk6ALgpLXteEH", + "rhU3BkUTsUpGInLaorg6aoGYYhi4AJzNPFJ9niUlLHq6ii1aAte6QpeSRCt89+H9EDJWWqbWcil4Q0Qr", + "9O92obXrjGiD+KPUvUmO26gnwoJqVN/JO8+7lLWTje7F/vZhefuyub1Y1S2ZzS6F+Osc2s6z+kA4HRNV", + "ixABJEvH1cZwgSIH5pgE+RXRHCksC5Y527W8QqV4jjCTKhFkT6MxhhSmBGkySAYpHPgIbDf8oaXf9DiF", + "A1EtUfGs/t7IRDx/8+L0vDv2ATEsCw1yqWsgp7plYOIKjqBF94fjRLz18VR+L5eIpR2OqxCi6lleJE4j", + "gqm7jb0RzN1t4tvE5H3fWcfs/d9rYfrul7Zi/q7XY1EaF7hkwvBsR+S/NyNFRIdnBcsuyWlo9bNcyRK8", + "pArXCxlsvz6RCJhoCiAo0CEJwPLe21jkP8+QH00FXA+ovqG8aefCAzmDl6/fvIC5klWp4YAcWaRNH/pE", + "/UqJPYQjLpocsXiidiY1FwiaL3nBFDerMViKIaO5l8f8suHgePzUEfZzmeM5E5fktxn9+x8OPWewVIw3", + "ZcEzbgoqKZBzqkTiSk4UUvqSArs9mHUY7Potyw3Wp07ETBf+fR99nU1yN2kh69jfpwDSCBOSgZYxaLCi", + "GGWFzC6BnqSqDSJbDUHJigQfI+Ex5JjxJSuA+HRX+umNHvucpJR2wuI9KZ/DNZDEgetiUO6kqAzelFyh", + "votCNFxP/JXTUxgiOKpCCFjGlFq5RBGuQ4WdWKaI93toRHGrhTZv3aaoDb2wV1GbWMhJx3fTgu7aHjrg", + "2nLK2704HpK3MAx73PmCkiL1nNsMJhc8x4ypi9rQvO7qmMwKPl+Ybb7yXyusEHIszQKYK7KzlEsr0cgZ", + "aLYsC8/mtl8SBHYMmczxcJp4rHDMmHMcIi8gW/AiBx9NClwDK6zWc+DiCOEo3A354e41Uvm2vtpA3qbT", + "DzIirimaa0QBLozagshF12t3EkfBtuSKdOiSXQvwoZA9+VnOEt41N/vQSG/8dP/6kd2HOooy7L7H5er1", + "3jrg9xY8060qAG3YQqYoJu6oaeOU1UnwGU/+KqeRm+h5ne7lQdAJOaVojN2nzEo+8aa/bgHCq8cx7mXF", + "fhQRHHzmfmiHkfgqV3OZxmNndtv2qvncbuulVXVClEoYll1bscRi0tG6aDQ5Hv3444eXPdPSQJNCMq8p", + "b9hBfNAJdi1kHmfJ2qiMBlfd8JqbBRfw3fExLPU4ES9ZoVvVhsJLXEPARysQLZiGTDG9wLxjqmmhedsX", + "vkZZlsGBwjnXBhXmUBeg3HnYbivt894v+XP/DJ6AHxtzdTFt7RSGEU295YyPkkOUrlouxjsrKrLNj7Of", + "g2ZL6ZRtHhe3m20Z6P+vBs1n1qBxoN2hB9tpvljtvEXS/B2pOZ2t3ZWXawMXH9DR5axlu8prfXac26fe", + "KbeW2KpliR5TOmWmh4pz5CUQEgop5s7bUteiHcM5ziqNebDfeTs2ChreV8+iG0Uj+Qzs0H3XRqiU1V3R", + "z3hNlW1rbceuqTMxxOf1KeN+4tTV21rLZtmnGNcmgH02wpcri/3n0BT+S8NDE2aoBIJGM4Yz/6WvCehq", + "aCaifThwxVlT9uzteSj72gf+1jyfHzt6m+JtcRa+rLSZuJJsnbpu/fiy92HeQWgidxe+2xItoGfF24IO", + "I6J8F7u2K6lbUjUJG/ZmjHaqP3OzqINAt2YzuLG3MrvOeFYXKIq3s8HJX/bJ3Bn2KBJBPW7qda1pEvZr", + "q55RGgjZB/JgEdFNtD9xjb00iktc7TeZL/Ea6EpTfg8lYt9iRtKmqeJg1LX5k9RUGtdlbnszj0Us+4/F", + "WG3YsoSD85fPv/322z8ejhPxsy/GU/PvplBGIedzzIGqG36mU3O9Hmr0kDYAuYktv1BhZ8wqxc3qwmJC", + "qFPGFKrTKpph4ADty3kA05Ce+kraBJATeEZvQ1IdH3+bPX/9H5PTd68nf3rxv+gLTAe+ljQxEnq0IfiF", + "MaUrWc2j+cOv3r9/R0cdbpw04zdewUpBewMtZDLHEUn7kDNcSgtqV0rymiu6xJbMWDY+XRkc+ZhGlimp", + "dbCfh2iW7900Ld0jTYRzqXMB6REr+dHV46NQHcfw7FJTGZQSRU7KtU/m7KozoSYAIz/YNVO5HnFh6ZIZ", + "blfj62EWTOSaVv8v/wKtCvRcCtrStYSSKVYUWJBQQFb04Fyz+iNbolepzcqpwCf2xRE8evTMKvj24jpq", + "ohEePToJFQz8zuyoR0Qbqcv0ciX4/y0R0Fxs5EPXwAS8MqZ8SynYUl5yd0ABM31JA/8L3aLC2HFYZeSS", + "2Y0VVFyVDP5WbrIEx5Y48hEU3pqqx3AReIuSRWGHmElloQiPn0LOVrrx25NMEsywbuPP37yGI7g4+xPt", + "dhv2egrymGvPTDm3iKWAa6btzD5+oCn9EABX8pGlvtQHZjCFLvlwpDNZWtIRLvhjinaYwMi4yPkVzysC", + "hQW4rAwwioEg5Z+MC66MhEOMd9W04Fkd6EiOJ4cLgUccnkD644v3cOTqL6VD/zGXmaaMePokSxSs5OMV", + "Wxb1I20kqIsXjzy221f7cMUeEQnIkBIL+PD+1eTs9cXpszcvzn5wRYH0JS+1i1TJFphdgs+6XjVBmwc5", + "XmEhS2cgFb4KNoNrpshexbVnp4cEijoGJZhcDFNGO7RlwkeGdgo4mwAknQha6LO3b99fvD8/fTc5Pfvp", + "9c+TFz+dvn6Twr9B9Nd3pxcXf357fpY6vzrmTv52JWyd4H0wkypzTh1P0zXVdIvdHo7hFAqcs2zl1+L5", + "ZkrisxTAYKZQL5pMGa6BL0upfI0PBpqLeYGJSFFcjerzSsPt2L4cmV9gYC5erQGW5wqpDQAhl/82rWOJ", + "U+d61yHlDXRB1ZnckL6O/BQhVOQAqwR8OH8DGuf2GDVkVnYsVkPQMpieAkk0SGzYJQKD9KOd81MKH87f", + "JKJuxuIL17vk6UePZvt1Xnn0aJyI5y610B494cWOPiwEmwuq8kII501I9ocu7oe3j9yKuwVgFlLISrnl", + "+qovKSyQ5ahOEqFRkG9gez0Y0NfcOUddwxEnmVJgYiIEXhdc4ChHsiBY6ctVprFw2Cxwk4ITAfTQE0ci", + "0ro8Suor3ThafHwM3qI9hrdFHliPOwEKEBES3MIT4bbkyn+td65ID2GOzuPhsNxj64gK6YT9BJBTlVZt", + "P5wWhWsbUj9DYl1zvVH3Cr1gJZ5A+jHxFVqTwQkkA8fGffEUx8aTwSd7sB2OGFDJxaHd2M1Y2c5nG0Jd", + "ua6uMtJEDRarRNTlRj4mvpyem308HvvZrIjDDdnFG4nFkqVVlYI1dXD1mFpHOEY8OBl8Oz4efzto+dVr", + "Rmsp96hJmp6jiTqaL7XjW9107rRV1FyDFAgojFpBiaod4goftGVoxC1aAYvfaKiNrSNnVC95dmnZrXQs", + "Rft8vAW7QooHstIdNIG2Vu6CBRNraeSBebvkfd6K5G+ne3ajh4gl4og6K5QuJ7esfJEmYkfaWzpc4iuX", + "4nU+OBlY3e6nkBjeaXT05Pj4zjqwNCn4kS4szxkVsnQALOih4eDp8eO+QetVHnVa19BL3+5+qWmURDJ/", + "yDwiSIBFD78Sl6ifucX5ogZw4K4ySx2HFpPZXDe6PikRXcT0IaCjrA7KjiLoucdAz89cNqh/1/ctgoOz", + "ZxRA+vf//huFitm/7WAxJz+03CR1DcNW56NQLnQIZVHZezJ1QZEpLFnp4nILYuoUTUvS/Tc6hO1uC9i1", + "P4SQXagjdhOxPWSX+GorgK2Lmz+i6ca03yOGdieKYOkLJ3he4dq5fB1kPUeW+3jgzSXtwtLhoKyiSEgR", + "Nro3dnkML31EZQhKDKqF1yoSYSUa5QMUm4jHH4hX9Qc6WvIinPgRjZVfzyRq+Pntewhe7bYDMFxFDRoG", + "nQs0WrnIYCK8QEI0uOEinxmKMNcVFYmZVQW8+/A+hoDvqggC0k6fSefQv3vc8wGtn7qmDasnfPqa6O+W", + "lT800g8HT5882Weadt+wLqlcsE0CCaipb83Q15CJzKFSR6jpTFlGS5iKazEvB98ea7KWycocDsGgapcx", + "9GzbqoKtCJRhO7LDK3JOqTdo1frO/saJCDfKk+MnwJdLzDkzWKy+h1IWBTiNtrMhX2/LSJBTEsqcAhfi", + "IdxtU3ve6aP/ySgmNMmAY3gtRi5Yo6UfTEMk43qQTyDIa3snzBgv3LZeKHVRlaiuuJbKbjsR1BhlavnM", + "KFf8CgV4WSxU8YCDNOM3oNDZupyw6xURb7M4jFG4z/zy8UybF8yTu6OwtRyzeH8+x6DqZx6Myr5zb9xv", + "u7069kvXwVMWKax2Tlw94AO3vFzIkSw3br3mOohGlHwuNTehGl482xBEuhFv98iJuxNFoOh+AS1YqRfy", + "KwnLfpV1fJznHreFf+0gioLdSuQfvJvn3uC94eCKXX4a1dfWTKwA5Qx2u6W76IXU6h+FhizU6XoTJ6cO", + "+/ZS3Kw1l3rfblXFNcwKRv6bNOZx9JZNOx6x9ykmYsP+x81mm6sNFt306Lon8WuzCdhewtfjO0XBqGLs", + "S948oLB1/Mfdb9QdkO9COnstrrhBy+8DZn0WDzn6yPNPTR3yiKuU6YzlVJOgdkV+oxvPrEXU4DkN4Rz0", + "sBuwL5okhrBn9EaNsB2keRrrYeLKlT7kKT/d/Ubdlrh7Xm61wPY6q2GnafhfPsYaeVMIQ38b73V/7y9k", + "A8yiVbRaZ7aUBkGqTgJPJBbIlzB36e2xs2yil+6J+WyGRz2w5tfHfLzC99tFyztgPq7NqIvnapAlp5vt", + "NnzI+ze3CjKumacebNDEWglmVhSuEjJNRL2Ph7WF2hnMLnG1gbmnYuXK22KhkdwOldBoDutXnT25KIjt", + "EZdrWuu7EMiaJMk3O+g00w8xTUURi0r75R7xM9J5N4Ktf8LV1xbQlqsm9MbCn7m2qhr4zJ1lB4sCyvTL", + "a22b8KNHddvkR49cP5nJJa7SThvWgBMtB9L7jp1ML+S1rt19DDJZrmBaGSMF3X8MkoErRtL4gBJnV1jJ", + "yslxGtHFE5HVNhkEB/QYLppIBcrM9a87/HP+PpfHmPZLeb4j8n3Ked2mug8s6XVbbffgcfalYt8Xy2Ra", + "V0EkC835o6gb4YE7BTGLksRgvPfgSl5iMBhfCy9/nQp/QbeeYWKViEtcWensSl76oIcS1ZLZzdV2YSWv", + "rTpqCc+hnQtwWDJ1iXkinKu7acmeBrcGq3JO9VV5QQMrJONCPrQkkohWII4PjKHIEmYMLkvTssi56gKN", + "Oevp8eO45cmuoEb4+xCUdsue574t/D+G7HkeEGF/rIxF6+z0wqUfk/Uu5cnghILFP6U7+p+v81znHiN1", + "G2/KgglGZZ91plyXsMY7CwfJgOlLX7Am2DVJmi0L6SKgIBZ684gcKleMZsktxyUrWTI4pNblrBMrV4dC", + "9Tjc1prF36flpa8vfYQt1o96Q9OgHa45OPnLL200aeedNQdBB+psDdRfoj5aOCgpbqxzPVdmEcEkZ7YY", + "tePA43f3/4+KzygOYr37ezoEF3xNikra7gSfDikgSOlERE0qafABWCoIsqCLgguxuHJGl3MinHZmmhjD", + "VjJ2CKms9xHcdxaLLyl6hXJdD8dQO+KMrLJFI984Xis1UixfLGAvesd3uvrf1y3fmeRW9/zTWHVzD6Lq", + "S9WhO9JVWh6iYMNoBfnvwF+ysfVj7VsRfKJDZyNML9CMnhMCnUArfPUH51/huXOtfF/Hun6fiAu2xAtu", + "8IcLo3hmvod3zCx+OErttd0ItISfvvWRD0Xow3qnjVmMu+5mA7UiYaTKkIZYx2zPZ33nSiYCwTCqwx8N", + "iCEY3Q9udtrGPLCe320GE+Gxb0I0vmsQlnvLfIMCscaoPgXA8ZiDgAZDWMOCw8E2UeXTQxNVz8Xx4sbb", + "pX1gdxOfOpMUMLC23b3vDde1ZouvmGRl3QrYHVE/hTChFWkt6+dCG1Vlxj05dVHrFFfm4i46IeaUcNVL", + "wd/DT+xmdDrHH47THjKwS96HRwYsqEsKfsZZdljdC5F3+FzTlWcHnF1a7M4IK2I+zBgX2uUNwt2qdt0E", + "tdfClXGCHg61ERh1Kaym3XQwSsScGYRZpegLwa743IljU1xwUr3jnKtHSvsJ7zVaD7fxieet2+cuTjuM", + "104WdImEuw+8XSBl67E7YSmSk+VEpmAaG1qdF7UZkZzoQoITkbZLu1CdyFbhmdA4r11bpsaIkLCaCF1K", + "A5WYsSUvOFPO3aV9Z8umVoy/7ayyqtvFdFxk7WY1nb6IztVFU8bl/lzVkRo2MYe1h/QX2Oc6CHPaoVRd", + "n2AbL/fGnIi9IubNqQH61VT1u+CyX6Z+W7YsBVp4L1cN+A80nwtqpxayLiDHK57h9ouxXZWj12j+rqlc", + "cW9YHOunEcHikP/xRVbm79y6t7/02qdhhDiaaChApGhJC9z1V23zcszgGtp+3KfFda3cwAObXOvGJv1H", + "+sUGV+c1v9/gqdOmdZSzNHLdyUVihSujQ7379edKx3eAouceM70BuawxLIKcEX7gs4a2mZBPa8/YGM6U", + "dKlzNXhIm+RGg+/GMASFMz10zQld//xhIuydXcdl6jGcoROt7c2CQlbzhbPMuez9kJXVDhdIRG0OofBE", + "anJAYfPc9IcFtAluz8gASmOcynx1+Ft2xn4x3tSRBeEgySVVFHSWvtEMJfz1eWY7TK8vjq8X/scPyHEe", + "6vq+g1P5kRIuG+qifCXqqRK/a7qyUWze5pEAqVd2wC2RHW16D8VmvL5EtUZALxQXLs07hB2HnrGJOFjv", + "5TSETiunw+89kbfoeIrgwkZkIjQvXBZNnUlfo2h/xMj93qvRMj4PbE/aguUhV6D8Ymz/TYaQ3AFVvaPW", + "BIGm6hyb3Yyt/8L0abZTDCbeL6PErSEJDFLX09wFeFJUPyutYlDnnI2oBRDli1hc8xdvIg5S98PEfZEe", + "BlXZZdMSOfuep8Agx8KwMbxjWruoB0LrNBFGwjUvG7ZERSODmTfwgDFYqgPeKgEfIVjKCX6G92f1bSZo", + "kep9kmZ7wt1quSxRPJxG+VDSsmjdBH6jVMPVCcne2LfefPdhOUKddbHrLSv6qA9N1/01ZvK2RAFsc8Mt", + "DlK3zNyDg7im+ffFQmixRpIrZtPi1ziJ0o9J076/5X3nM2CJCEd6zTRccvvIENIZKzTSE8JKLPY3d87O", + "yvb8zWuy8mnvIuUCcpyhGlEVhqqkohtMUdo7N1STYc4o2cmFqfvqcbIqciiQXWEiVCWgkNmlBiuMUE8I", + "qWppohKGF8TbHo8WslLw/v2bXgb03EH9vrmCm2aredcBPQQLhGro/yDiqlu9wy5H4xts4IDnuCylBejh", + "Z5JI3b3wPijkAkVur1hKm7N3Kimu3leqYYqFdLjmHReWf9f38TgRoRfDd8c+hb0kG3RRUEGJR4+aEh4C", + "59JwOulHj058j5PtlTesQKwwQwtZ0u8/q9ZGIg6o1gIV1ygpt0Zgk5Lerb/hK28cjuHPvgaQVcw7FTZc", + "TFds5b7cRqdOCPeldiK1N9yiX1qwBRpJfQQt5j5xmFxv9mwS4Qtso+4l65e+o+SOmN8dVU4kMA9ZzOt9", + "eCBG4RxgejiGM1c+9SRShqQVAeyA2ViwHSzjMcDrHGg4iK2/N074ngQsf2hfRRfaxBpiS1GwNJXBXa1V", + "Olb6r2ZiP58RMnpaipCK5VsUZjg4GXxMBvRjMjhJXB1fiv6yl+YwGTi2QL+p0WP6yjIy+mLJuBjPJX1J", + "LxJzSwYnj4fJoGkBlAxOnhx/SsTmRNTSxU8UHdX1fLEjPokOECoj7TXCMHGlrSdL+/m7p/E15VLgZy2o", + "Zjr0oNH05ZPjJ78bHT8dPfn9+8e/P3ny3cnx8f9OBuuvOljVMxPXnYQkXgJfPfXE25qTwcm3T39fP+yl", + "ScwnVM7S/nps9+dut/1xsMMGoim43IW/BE+NQzSHeXDgSy1TSyvW4uUOIRNBW9Zw0BSocUqbtBsFLlzI", + "wdYbhLKwv1CeuF/VIXgFhDQws4IJvD0HR0et745q1XPJNfXq+UrKw/0CwysfdWoMdSv88d2HOuN6WunV", + "GKjcnP13COk5GrUandq7Mq1vaVcoOCQe6Go+R21x5ppxAwe+rJbP0G8FH7XG6m5moxb2p7X85Gq65GZd", + "itJwsGQ38N3x5wt+gru24Xcj+UUlBpriXm9KO8PXvSrdCnYbJzK5dLVm/nF5RiUuhbwWvx2O8YXmhud0", + "JGvG9i+yOOwIyyfmwjpmHFLtTurctiXPKaa7bqzs43bKBdOYDiF1t2zOdSavUGF+VF+4R3Th2me6FzSV", + "9MSClfZS9vwphPoEXcuxPSEjS0tEtzqbK3zSFE9eby8TUvyosCnlTaVrkoFfqFvB2lrH8HrW9oEmgoos", + "S1hwTekGjIIGXFFAB20SXHheYFOWL8KM7j8JoCO27Ii/cGcbooat4mc3cPhVgmHeWA24xre1ECRVuba+", + "lHEclEQq6BankC/zm22hL029K+7LUkH1XJzz3fXvo/SEitzjVvpT8oYvmUEQyBRqMxLI54uprBS4hSXC", + "l6NrI+83GrKFkktcjuYSMlkU6IK14dx3SWQKE2GXNJrxwsXATFeQ1u0iLTVTocjUiqq+4+LQd1EcvT0f", + "1V0UE0GM+HAIaWjVmcLBNPTmHLqymvQMF/PDOtTPN9JMXUlGY9nAEpULnzaSmr2T1cY3wCS4MKsr2YVO", + "URvfnJqWS3aZ1urr6t36JBEAo7rc2t//+2/rTS3T4/HTFA4yVvCpIjPqTCrYaHDpxgm9LanRpSvoRLmZ", + "ZGRx2cNsKq8QXv188WfX73LtxVJqTlY1+7brj+ueSkTa6WxIdUS3dHGMMJxuU8l7EoDifVsfWAbqaZ8Z", + "Y3odRKIAFF/YPXR7eTi3zT+bahOpRjWEK8yMVEA+SiunWTWZWHlb30nEQUsx8dWLrX6zU4FZF3JJrLD0", + "QXpQrVZ7uwipTXbCWPZEv+oTUOTAbebQM9vW1eO/2CqauWeOcpxRlSAfZH0fao8jg7PWRPdD+80MX4nu", + "2wvop/mf/G0DbdD/E5J5N8xIjowcNTu296SL3wPfqewzcPeO3TQxrA32/fvAVzv2V72n2gvYA1+9n8os", + "/vnR1UJmRFW2a1Hyi7iswhkqFNn9eRXb+U+FdOigfRuP1FFaamV2pDRykuBb/UeNvKRcpNTjFYl3nP71", + "pd99iXKSj0PrQPjhB9d4gj55YbmUZVU4v7oWvCzRaKBVOM+6x25gMKsKV4QbFI4UstyK1ZTdXhXm+7r6", + "vl7QezNZFPIaqtLZGGs5yQEYqGY+y53jjwbNucLMxBOVA9LXh3JPdX/DBF+Jvlvz95N3Cwr//FRN6fVh", + "v97vSrTxeWTtI9Dv9wq68JPck8IUae/50OpSrA3ntosogP2fHV8dYAKLm65IVIIDZ+c4qm+mw9sib5jg", + "464o+gv/5P2HGYeZYq6C8NM/TJBS8BbIK1RXHK/hwMjSXkiUq+Ka64XcFbL66sP7iLdvocCehXG88WmJ", + "huXMsJPQG27Y7jDRVDunPhrDkAbtS2jGyjZTQivLFhRzf5C2G1qnh0MQ1XKKCuTM3v4baW/OKFc/0wpB", + "DgXKazfFX+VU92RUP0Bh551VbnxlZ2/gvotU2QsH56Ma0nWd5nbo5Rp2TdHzB9fqZ69ySS2ngrxMBu0y", + "Sf6sXSUvli3g4t/fcIOh3/vjRDj3ShP7+d3xtz6kqzty3XzIhbaE9kJ1dyHfSdBOevYMStJJGC/0GHxn", + "uL//1/+AkGuN7kL7qV2VkV45aNwjhrgZtrtFCZbciswEibu2lt1mCU0vqAMcz8cW5pWo6fhwa6mNN/wK", + "BXlzLL7FSmnUiNgdpduS8y+/WOnIYVgssu6i1TLR4rwc5Wgw6/QucR3Z1FWdW69LpJVUqhicDI5IAPOr", + "2ujCSQBwVbM98dpl6yaQzm3j0/BjbzrxDLNVZvne8/MPZ4edNx2f33zZXfzDloVo2MitLtPRccU1NagZ", + "3H/eHPr9QiGOyHHa8M1SSSMzkoJDhntwXm2OcPruNeQyq5YoDKFg81Yus+h2fFbl0FVF8OVShk35FZfr", + "NVxP8fejUm55ZB111pgrw7Bkgs3Rrqr1KhVz3XzXV0Csq21121XWeZ/ka3nz+uji7E92jta4oRrdp18+", + "/d8AAAD//w==", +} + +// decodeSpec returns the embedded OpenAPI spec as raw JSON bytes, +// after base64-decoding and flate-decompressing the embedded blob. +func decodeSpec() ([]byte, error) { + encoded := strings.Join(swaggerSpec, "") + compressed, err := base64.StdEncoding.DecodeString(encoded) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr := flate.NewReader(bytes.NewReader(compressed)) + var buf bytes.Buffer + if _, err := buf.ReadFrom(zr); err != nil { + return nil, fmt.Errorf("read flate: %w", err) + } + if err := zr.Close(); err != nil { + return nil, fmt.Errorf("close flate reader: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cache of the decoded OpenAPI spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSpec returns the OpenAPI specification corresponding to the generated +// code in this file. External references in the spec are resolved through +// PathToRawSpec; externally-referenced files must be embedded in their +// corresponding Go packages (via the import-mapping feature). URL-based +// external refs are not supported. +func GetSpec() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} + +// GetSpecJSON returns the raw JSON bytes of the embedded OpenAPI +// specification: decompressed but not unmarshaled. External references +// are not resolved here; the bytes are the spec exactly as embedded by +// codegen. The result is cached at package init time, so repeated calls +// are cheap. +func GetSpecJSON() ([]byte, error) { + return rawSpec() +} + +// GetSwagger returns the OpenAPI specification corresponding to the +// generated code in this file. +// +// Deprecated: GetSwagger predates kin-openapi renaming openapi3.Swagger +// to openapi3.T. Use [GetSpec] instead. This wrapper is retained for +// backwards compatibility. +func GetSwagger() (*openapi3.T, error) { + return GetSpec() +} diff --git a/server/internal/httpapi/openapi/spec_test.go b/server/internal/httpapi/openapi/spec_test.go new file mode 100644 index 0000000..d4ca6ca --- /dev/null +++ b/server/internal/httpapi/openapi/spec_test.go @@ -0,0 +1,37 @@ +package openapi + +import ( + "testing" +) + +// TestSpecLoadsAndValidates exercises the embedded spec loader. If +// doc/openapi.yaml drifts from a parseable OpenAPI 3.x document, this +// fails before any handler test does. +func TestSpecLoadsAndValidates(t *testing.T) { + swagger, err := GetSwagger() + if err != nil { + t.Fatalf("GetSwagger: %v", err) + } + if swagger == nil || swagger.Info == nil { + t.Fatal("nil swagger or info section") + } + if got := swagger.Info.Title; got != "cix-server API" { + t.Errorf("info.title = %q, want %q", got, "cix-server API") + } + if got := swagger.Info.Version; got != "v1" { + t.Errorf("info.version = %q, want %q", got, "v1") + } + + // Sanity: every operation in the ServerInterface has a matching path + // in the spec. We check by counting operations rather than naming + // each one — keeps the test from drifting whenever an endpoint is + // added. + if swagger.Paths == nil { + t.Fatal("paths section missing") + } + pathCount := swagger.Paths.Len() + const wantMin = 13 // 18 endpoints share a few path keys (CRUD on same path) + if pathCount < wantMin { + t.Errorf("paths.len() = %d, expected at least %d (spec may have lost endpoints)", pathCount, wantMin) + } +} diff --git a/server/internal/httpapi/projects.go b/server/internal/httpapi/projects.go index fce9286..ed9b8b9 100644 --- a/server/internal/httpapi/projects.go +++ b/server/internal/httpapi/projects.go @@ -1,18 +1,10 @@ package httpapi -import ( - "encoding/json" - "errors" - "net/http" - - "github.com/go-chi/chi/v5" - - "github.com/dvcdsys/code-index/server/internal/projects" -) - -// --------------------------------------------------------------------------- -// JSON request / response types (match Python schemas exactly) -// --------------------------------------------------------------------------- +// JSON request / response types kept for tests that unmarshal handler +// responses into them. Wire-compatible with the generated openapi types in +// internal/httpapi/openapi — every JSON tag matches doc/openapi.yaml. The +// Server struct in server.go now drives the actual HTTP responses; these +// types remain only as stable test fixtures. type projectSettingsJSON struct { ExcludePatterns []string `json:"exclude_patterns"` @@ -27,15 +19,15 @@ type projectStatsJSON struct { } type projectResponse struct { - HostPath string `json:"host_path"` - ContainerPath string `json:"container_path"` - Languages []string `json:"languages"` - Settings projectSettingsJSON `json:"settings"` - Stats projectStatsJSON `json:"stats"` - Status string `json:"status"` - CreatedAt string `json:"created_at"` - UpdatedAt string `json:"updated_at"` - LastIndexedAt *string `json:"last_indexed_at"` + HostPath string `json:"host_path"` + ContainerPath string `json:"container_path"` + Languages []string `json:"languages"` + Settings projectSettingsJSON `json:"settings"` + Stats projectStatsJSON `json:"stats"` + Status string `json:"status"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + LastIndexedAt *string `json:"last_indexed_at"` } type projectListResponse struct { @@ -51,163 +43,8 @@ type updateProjectRequest struct { Settings *projectSettingsJSON `json:"settings"` } -// --------------------------------------------------------------------------- -// Converters -// --------------------------------------------------------------------------- - -func projectToResponse(p *projects.Project) projectResponse { - langs := p.Languages - if langs == nil { - langs = []string{} - } - return projectResponse{ - HostPath: p.HostPath, - ContainerPath: p.ContainerPath, - Languages: langs, - Settings: projectSettingsJSON{ - ExcludePatterns: p.Settings.ExcludePatterns, - MaxFileSize: p.Settings.MaxFileSize, - }, - Stats: projectStatsJSON{ - TotalFiles: p.Stats.TotalFiles, - IndexedFiles: p.Stats.IndexedFiles, - TotalChunks: p.Stats.TotalChunks, - TotalSymbols: p.Stats.TotalSymbols, - }, - Status: p.Status, - CreatedAt: p.CreatedAt, - UpdatedAt: p.UpdatedAt, - LastIndexedAt: p.LastIndexedAt, - } -} - -// --------------------------------------------------------------------------- -// Handlers -// --------------------------------------------------------------------------- - -// POST /api/v1/projects -func createProjectHandler(d Deps) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - var body createProjectRequest - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - writeError(w, http.StatusUnprocessableEntity, "invalid request body") - return - } - if body.HostPath == "" { - writeError(w, http.StatusUnprocessableEntity, "host_path is required") - return - } - - p, err := projects.Create(r.Context(), d.DB, projects.CreateRequest{HostPath: body.HostPath}) - if err != nil { - if errors.Is(err, projects.ErrConflict) { - writeError(w, http.StatusConflict, err.Error()) - return - } - writeError(w, http.StatusInternalServerError, err.Error()) - return - } - writeJSON(w, http.StatusCreated, projectToResponse(p)) - } -} - -// GET /api/v1/projects -func listProjectsHandler(d Deps) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - list, err := projects.List(r.Context(), d.DB) - if err != nil { - writeError(w, http.StatusInternalServerError, err.Error()) - return - } - resp := make([]projectResponse, 0, len(list)) - for i := range list { - resp = append(resp, projectToResponse(&list[i])) - } - writeJSON(w, http.StatusOK, projectListResponse{Projects: resp, Total: len(resp)}) - } -} - -// GET /api/v1/projects/{path} -func getProjectHandler(d Deps) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - pathHash := chi.URLParam(r, "path") - p, err := projects.GetByHash(r.Context(), d.DB, pathHash) - if err != nil { - if errors.Is(err, projects.ErrNotFound) { - writeError(w, http.StatusNotFound, err.Error()) - return - } - writeError(w, http.StatusInternalServerError, err.Error()) - return - } - writeJSON(w, http.StatusOK, projectToResponse(p)) - } -} - -// PATCH /api/v1/projects/{path} -func patchProjectHandler(d Deps) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - pathHash := chi.URLParam(r, "path") - p, err := projects.GetByHash(r.Context(), d.DB, pathHash) - if err != nil { - if errors.Is(err, projects.ErrNotFound) { - writeError(w, http.StatusNotFound, err.Error()) - return - } - writeError(w, http.StatusInternalServerError, err.Error()) - return - } - - var body updateProjectRequest - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - writeError(w, http.StatusUnprocessableEntity, "invalid request body") - return - } - - var settingsPtr *projects.Settings - if body.Settings != nil { - s := projects.Settings{ - ExcludePatterns: body.Settings.ExcludePatterns, - MaxFileSize: body.Settings.MaxFileSize, - } - settingsPtr = &s - } - - updated, err := projects.Patch(r.Context(), d.DB, p.HostPath, projects.UpdateRequest{Settings: settingsPtr}) - if err != nil { - writeError(w, http.StatusInternalServerError, err.Error()) - return - } - writeJSON(w, http.StatusOK, projectToResponse(updated)) - } -} - -// DELETE /api/v1/projects/{path} -func deleteProjectHandler(d Deps) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - pathHash := chi.URLParam(r, "path") - p, err := projects.GetByHash(r.Context(), d.DB, pathHash) - if err != nil { - if errors.Is(err, projects.ErrNotFound) { - writeError(w, http.StatusNotFound, err.Error()) - return - } - writeError(w, http.StatusInternalServerError, err.Error()) - return - } - - if err := projects.Delete(r.Context(), d.DB, p.HostPath); err != nil { - writeError(w, http.StatusInternalServerError, err.Error()) - return - } - w.WriteHeader(http.StatusNoContent) - } -} - -// --------------------------------------------------------------------------- -// Error helper -// --------------------------------------------------------------------------- - -func writeError(w http.ResponseWriter, code int, msg string) { - writeJSON(w, code, map[string]any{"detail": msg}) -} +// ensure the request shapes are referenced even when only read by tests. +var ( + _ = createProjectRequest{} + _ = updateProjectRequest{} +) diff --git a/server/internal/httpapi/projects_test.go b/server/internal/httpapi/projects_test.go index 4738cca..6ad0da7 100644 --- a/server/internal/httpapi/projects_test.go +++ b/server/internal/httpapi/projects_test.go @@ -20,7 +20,9 @@ func newTestDeps(t *testing.T) Deps { t.Fatalf("open test db: %v", err) } t.Cleanup(func() { d.Close() }) - return Deps{DB: d} + // AuthDisabled=true matches the historical behaviour of these tests + // (no Authorization header sent). Production wiring sets a real key. + return Deps{DB: d, AuthDisabled: true} } func doRequest(t *testing.T, router http.Handler, method, path string, body any) *httptest.ResponseRecorder { diff --git a/server/internal/httpapi/router.go b/server/internal/httpapi/router.go index a1b7dfd..12f46b7 100644 --- a/server/internal/httpapi/router.go +++ b/server/internal/httpapi/router.go @@ -1,6 +1,8 @@ // Package httpapi wires the chi router and HTTP handlers for the Go server. -// Phase 1: /health and /api/v1/status. -// Phase 2: project CRUD + symbol/definition/reference/file search + summary. +// +// All routes are described in doc/openapi.yaml; the generated chi shim in +// internal/httpapi/openapi mounts them onto the router and dispatches to +// methods on the Server struct (see server.go). package httpapi import ( @@ -9,8 +11,13 @@ import ( "log/slog" "net/http" + "github.com/dvcdsys/code-index/server/internal/apikeys" "github.com/dvcdsys/code-index/server/internal/embeddings" + "github.com/dvcdsys/code-index/server/internal/httpapi/openapi" "github.com/dvcdsys/code-index/server/internal/indexer" + "github.com/dvcdsys/code-index/server/internal/runtimecfg" + "github.com/dvcdsys/code-index/server/internal/sessions" + "github.com/dvcdsys/code-index/server/internal/users" "github.com/dvcdsys/code-index/server/internal/vectorstore" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" @@ -38,10 +45,16 @@ type Deps struct { Backend string EmbeddingModel string Logger *slog.Logger - // APIKey is the shared secret compared against the `Authorization: Bearer` - // header. When empty the server runs in dev mode and skips auth — matches - // the behaviour advertised in cmd/cix-server/main.go's startup warning. - APIKey string + // AuthDisabled, when true, omits the auth middleware entirely — every + // route becomes reachable without credentials. Off by default. Toggle + // via CIX_AUTH_DISABLED=true (config.go) for local dev or tests. + AuthDisabled bool + // Users / Sessions / APIKeys back the dashboard auth model. Required + // in production; tests may pass nil + AuthDisabled=true to skip the + // gate. + Users *users.Service + Sessions *sessions.Service + APIKeys *apikeys.Service // EmbeddingSvc is the in-process embeddings service. May be nil when the // server is started with CIX_EMBEDDINGS_ENABLED=false (e.g. in router // tests). Phase 5 uses it for semantic search. @@ -52,34 +65,21 @@ type Deps struct { // Indexer drives the three-phase index protocol (Phase 5). Nil-safe: the // indexing endpoints return 503 when absent. Indexer *indexer.Service + // RuntimeCfg backs the dashboard's /admin/runtime-config endpoints. Nil + // in router-only tests; admin handlers return 503 when absent. + RuntimeCfg *runtimecfg.Service } -// NewRouter builds the chi router with middleware and all Phase 1+2 routes. +// NewRouter builds the chi router with middleware and the generated +// OpenAPI-derived routes. // // Project paths contain slashes that cannot be embedded in plain URL segments. // We follow the Python approach of SHA1-hashing them (first 16 hex chars) and // using the hash as the URL key. See internal/projects.HashPath for details. // -// Route list: -// -// GET /health -// GET /api/v1/status -// POST /api/v1/projects create project -// GET /api/v1/projects list projects -// GET /api/v1/projects/{path} get project by hash -// PATCH /api/v1/projects/{path} patch project settings -// DELETE /api/v1/projects/{path} delete project -// POST /api/v1/projects/{path}/search/symbols symbol name search -// POST /api/v1/projects/{path}/search/definitions go-to-definition -// POST /api/v1/projects/{path}/search/references find references -// POST /api/v1/projects/{path}/search/files file path search -// POST /api/v1/projects/{path}/search semantic search -// POST /api/v1/projects/{path}/index/begin start indexing session -// POST /api/v1/projects/{path}/index/files stream files -// POST /api/v1/projects/{path}/index/finish commit session -// POST /api/v1/projects/{path}/index/cancel idempotent cancel -// GET /api/v1/projects/{path}/index/status progress / last run -// GET /api/v1/projects/{path}/summary project summary +// Auth: every route except `GET /health` lives behind the `requireAPIKey` +// middleware. The generated chi-server mounts under a sub-router so the gate +// stays in one place. func NewRouter(d Deps) http.Handler { r := chi.NewRouter() @@ -87,46 +87,41 @@ func NewRouter(d Deps) http.Handler { r.Use(middleware.Recoverer) r.Use(serverVersionHeader(d.ServerVersion)) r.Use(structuredLogger(d.Logger)) - - // Public probe — no auth, matches Python api/app/routers/health.py. - r.Get("/health", healthHandler(d)) - - // Everything else lives behind the API-key middleware so the gate matches - // Python's `Depends(verify_api_key)` applied in each router module. - r.Group(func(pr chi.Router) { - pr.Use(requireAPIKey(d.APIKey)) - - // Phase 1 — status probe (authenticated, unlike /health). - pr.Get("/api/v1/status", statusHandler(d)) - - // Phase 2 — project CRUD. - pr.Post("/api/v1/projects", createProjectHandler(d)) - pr.Get("/api/v1/projects", listProjectsHandler(d)) - - // Project-scoped routes: {path} is a 16-char SHA1 hash of the host_path. - pr.Get("/api/v1/projects/{path}", getProjectHandler(d)) - pr.Patch("/api/v1/projects/{path}", patchProjectHandler(d)) - pr.Delete("/api/v1/projects/{path}", deleteProjectHandler(d)) - - // Phase 2 — search endpoints. - pr.Post("/api/v1/projects/{path}/search/symbols", symbolSearchHandler(d)) - pr.Post("/api/v1/projects/{path}/search/definitions", definitionSearchHandler(d)) - pr.Post("/api/v1/projects/{path}/search/references", referenceSearchHandler(d)) - pr.Post("/api/v1/projects/{path}/search/files", fileSearchHandler(d)) - - // Phase 5 — semantic search. - pr.Post("/api/v1/projects/{path}/search", semanticSearchHandler(d)) - - // Phase 5 — three-phase indexing protocol. - pr.Post("/api/v1/projects/{path}/index/begin", indexBeginHandler(d)) - pr.Post("/api/v1/projects/{path}/index/files", indexFilesHandler(d)) - pr.Post("/api/v1/projects/{path}/index/finish", indexFinishHandler(d)) - pr.Post("/api/v1/projects/{path}/index/cancel", indexCancelHandler(d)) - pr.Get("/api/v1/projects/{path}/index/status", indexStatusHandler(d)) - - // Phase 2 — summary. - pr.Get("/api/v1/projects/{path}/summary", projectSummaryHandler(d)) - }) + r.Use(limitBodySize()) + + srv := &Server{Deps: d, loginLimiter: newLoginLimiter()} + + // Auth — the middleware is installed unless AuthDisabled is true. Every + // authenticated route accepts EITHER an active session cookie OR a + // Bearer API key; admin-only routes additionally require role=admin. + // requireAuth skips public paths (see isPublicPath in middleware.go): + // /health, /docs, /docs/*, /openapi.json plus the bootstrap-status and + // login endpoints. + if !d.AuthDisabled { + r.Use(requireAuth(d)) + } else if d.Logger != nil { + // Loud signal — every authenticated request will pass without checks. + // The startup banner in main.go also logs this; we duplicate here so + // router-only test runs surface the same warning. + d.Logger.Warn("auth disabled (CIX_AUTH_DISABLED=true) — every endpoint is reachable without an API key") + } + + // Documentation — Swagger UI shell + the embedded OpenAPI spec served + // from the bytes in openapi.gen.go. Both are public regardless of auth. + r.Get("/docs", docsIndexHandler) + r.Get("/docs/*", docsAssetsHandler) + r.Get("/openapi.json", openapiSpecHandler) + + // Dashboard — embedded React SPA (Vite build under + // internal/httpapi/dashboard/dist). Static assets are public; every API + // call the SPA makes still travels through requireAuth above, so the + // pages render but show the login screen until /auth/me succeeds. + r.Get("/dashboard", dashboardIndexHandler) + r.Get("/dashboard/*", dashboardAssetsHandler) + + // All API operations — chi.HandlerFromMux walks the spec and registers + // one chi route per OpenAPI operation, dispatching to Server methods. + openapi.HandlerFromMux(srv, r) return r } diff --git a/server/internal/httpapi/search.go b/server/internal/httpapi/search.go index d745182..62242f8 100644 --- a/server/internal/httpapi/search.go +++ b/server/internal/httpapi/search.go @@ -3,23 +3,23 @@ package httpapi import ( "context" "database/sql" - "encoding/json" "errors" "net/http" "path/filepath" "sort" "strings" - "time" "github.com/go-chi/chi/v5" - "github.com/dvcdsys/code-index/server/internal/embeddings" - "github.com/dvcdsys/code-index/server/internal/langdetect" "github.com/dvcdsys/code-index/server/internal/projects" "github.com/dvcdsys/code-index/server/internal/symbolindex" "github.com/dvcdsys/code-index/server/internal/vectorstore" ) +// --------------------------------------------------------------------------- +// vectorStoreResult / dedupe — used by SemanticSearch fan-out (server.go). +// --------------------------------------------------------------------------- + // vectorStoreResult wraps a vectorstore.SearchResult so fan-out can dedupe by // (file_path, start_line, end_line) across multiple language-scoped queries. type vectorStoreResult struct { @@ -38,9 +38,9 @@ func wrapResults(rs []vectorstore.SearchResult) []vectorStoreResult { // Preserves the relative order of the first-seen instances. func dedupByLocation(rs []vectorStoreResult) []vectorStoreResult { type key struct { - fp string - start int - end int + fp string + start int + end int } seen := make(map[key]int, len(rs)) out := rs[:0] @@ -59,7 +59,12 @@ func dedupByLocation(rs []vectorStoreResult) []vectorStoreResult { } // --------------------------------------------------------------------------- -// Request / response types (match Python schemas/search.py exactly) +// Wire-format types kept as test fixtures. +// +// Server.* methods in server.go emit the openapi.* equivalents; these inline +// types mirror the same JSON tags so existing *_test.go can still +// `json.Unmarshal` into them. Do not add new fields here — extend the +// OpenAPI spec instead. // --------------------------------------------------------------------------- type symbolSearchRequest struct { @@ -100,10 +105,10 @@ type fileSearchResponse struct { } type definitionRequest struct { - Symbol string `json:"symbol"` - Kind string `json:"kind"` - FilePath string `json:"file_path"` - Limit int `json:"limit"` + Symbol string `json:"symbol"` + Kind string `json:"kind"` + FilePath string `json:"file_path"` + Limit int `json:"limit"` } type definitionItem struct { @@ -166,9 +171,20 @@ type projectSummaryResponse struct { RecentSymbols []symbolEntry `json:"recent_symbols"` } +// Suppress "unused" warnings — the request shapes are populated by +// json.Unmarshal in tests, which static analysis cannot see. +var ( + _ = symbolSearchRequest{} + _ = fileSearchRequest{} + _ = definitionRequest{} + _ = referenceRequest{} +) + // --------------------------------------------------------------------------- // resolveProjectFromHash looks up the project by URL path hash. -// Returns the project or writes a 404 and returns nil. +// Returns the project or writes a 404 and returns nil. Shared by Server +// methods (server.go) — kept here because the chi.URLParam dependency +// belongs with the search/path-handling code. // --------------------------------------------------------------------------- func resolveProjectFromHash(w http.ResponseWriter, r *http.Request, d Deps) *projects.Project { @@ -186,327 +202,11 @@ func resolveProjectFromHash(w http.ResponseWriter, r *http.Request, d Deps) *pro } // --------------------------------------------------------------------------- -// POST /api/v1/projects/{path}/search/symbols -// --------------------------------------------------------------------------- - -func symbolSearchHandler(d Deps) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - p := resolveProjectFromHash(w, r, d) - if p == nil { - return - } - - var body symbolSearchRequest - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - writeError(w, http.StatusUnprocessableEntity, "invalid request body") - return - } - if body.Query == "" { - writeError(w, http.StatusUnprocessableEntity, "query is required") - return - } - if body.Limit <= 0 { - body.Limit = 20 - } - - symbols, err := symbolindex.SearchByName(r.Context(), d.DB, p.HostPath, body.Query, body.Kinds, body.Limit) - if err != nil { - writeError(w, http.StatusInternalServerError, err.Error()) - return - } - - results := make([]symbolResultItem, 0, len(symbols)) - for _, s := range symbols { - results = append(results, symbolResultItem{ - Name: s.Name, - Kind: s.Kind, - FilePath: s.FilePath, - Line: s.Line, - EndLine: s.EndLine, - Language: s.Language, - Signature: s.Signature, - ParentName: s.ParentName, - }) - } - writeJSON(w, http.StatusOK, symbolSearchResponse{Results: results, Total: len(results)}) - } -} - -// --------------------------------------------------------------------------- -// POST /api/v1/projects/{path}/search/definitions -// --------------------------------------------------------------------------- - -func definitionSearchHandler(d Deps) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - p := resolveProjectFromHash(w, r, d) - if p == nil { - return - } - - var body definitionRequest - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - writeError(w, http.StatusUnprocessableEntity, "invalid request body") - return - } - if body.Symbol == "" { - writeError(w, http.StatusUnprocessableEntity, "symbol is required") - return - } - if body.Limit <= 0 { - body.Limit = 10 - } - - syms, err := symbolindex.SearchDefinitions(r.Context(), d.DB, p.HostPath, body.Symbol, body.Kind, body.FilePath, body.Limit) - if err != nil { - writeError(w, http.StatusInternalServerError, err.Error()) - return - } - - results := make([]definitionItem, 0, len(syms)) - for _, s := range syms { - results = append(results, definitionItem{ - Name: s.Name, - Kind: s.Kind, - FilePath: s.FilePath, - Line: s.Line, - EndLine: s.EndLine, - Language: s.Language, - Signature: s.Signature, - ParentName: s.ParentName, - }) - } - writeJSON(w, http.StatusOK, definitionResponse{Results: results, Total: len(results)}) - } -} - -// --------------------------------------------------------------------------- -// POST /api/v1/projects/{path}/search/references -// --------------------------------------------------------------------------- - -func referenceSearchHandler(d Deps) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - p := resolveProjectFromHash(w, r, d) - if p == nil { - return - } - - var body referenceRequest - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - writeError(w, http.StatusUnprocessableEntity, "invalid request body") - return - } - if body.Symbol == "" { - writeError(w, http.StatusUnprocessableEntity, "symbol is required") - return - } - if body.Limit <= 0 { - body.Limit = 50 - } - - refs, err := symbolindex.SearchReferences(r.Context(), d.DB, p.HostPath, body.Symbol, body.FilePath, body.Limit) - if err != nil { - writeError(w, http.StatusInternalServerError, err.Error()) - return - } - - // m3 — the refs table stores only token locations (name, file, line, - // col) so `Content` is intentionally empty and `EndLine == StartLine`. - // Matches the Python `ReferenceIndexService` shape. Clients that need - // source snippets should follow up with a semantic search or a - // file-read; populating Content here would require a full-file - // re-read on every request and was deemed too costly. - results := make([]referenceItem, 0, len(refs)) - for _, ref := range refs { - results = append(results, referenceItem{ - FilePath: ref.FilePath, - StartLine: ref.Line, - EndLine: ref.Line, - Content: "", - ChunkType: "reference", - SymbolName: ref.Name, - Language: ref.Language, - }) - } - writeJSON(w, http.StatusOK, referenceResponse{Results: results, Total: len(results)}) - } -} - -// --------------------------------------------------------------------------- -// POST /api/v1/projects/{path}/search/files -// --------------------------------------------------------------------------- - -func fileSearchHandler(d Deps) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - p := resolveProjectFromHash(w, r, d) - if p == nil { - return - } - - var body fileSearchRequest - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - writeError(w, http.StatusUnprocessableEntity, "invalid request body") - return - } - if body.Query == "" { - writeError(w, http.StatusUnprocessableEntity, "query is required") - return - } - if body.Limit <= 0 { - body.Limit = 20 - } - - var results []fileResultItem - { - rows, err := d.DB.QueryContext(r.Context(), - `SELECT file_path FROM file_hashes WHERE project_path = ? AND file_path LIKE ? ORDER BY file_path LIMIT ?`, - p.HostPath, "%"+body.Query+"%", body.Limit, - ) - if err != nil { - writeError(w, http.StatusInternalServerError, err.Error()) - return - } - for rows.Next() { - var fp string - if err := rows.Scan(&fp); err != nil { - rows.Close() - writeError(w, http.StatusInternalServerError, err.Error()) - return - } - lang := langdetect.Detect(fp) - var langPtr *string - if lang != "" { - langPtr = &lang - } - results = append(results, fileResultItem{FilePath: fp, Language: langPtr}) - } - // m1 — a WAL / IO error during iteration would otherwise return a - // partial list with HTTP 200 and no hint that anything went wrong. - if err := rows.Err(); err != nil { - rows.Close() - writeError(w, http.StatusInternalServerError, err.Error()) - return - } - rows.Close() - } - if results == nil { - results = []fileResultItem{} - } - writeJSON(w, http.StatusOK, fileSearchResponse{Results: results, Total: len(results)}) - } -} - -// --------------------------------------------------------------------------- -// GET /api/v1/projects/{path}/summary -// --------------------------------------------------------------------------- - -func projectSummaryHandler(d Deps) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - p := resolveProjectFromHash(w, r, d) - if p == nil { - return - } - - // Top directories — from file_hashes. - dirCount := map[string]int{} - { - rows, err := d.DB.QueryContext(r.Context(), - `SELECT file_path FROM file_hashes WHERE project_path = ?`, p.HostPath, - ) - if err != nil { - writeError(w, http.StatusInternalServerError, err.Error()) - return - } - for rows.Next() { - var fp string - if err := rows.Scan(&fp); err != nil { - rows.Close() - writeError(w, http.StatusInternalServerError, err.Error()) - return - } - // Mirrors Python path bucketing logic. - parts := splitPath(fp) - var key string - if len(parts) > 3 { - key = joinPath(parts[:4]) - } else if len(parts) > 1 { - key = joinPath(parts[:2]) - } - if key != "" { - dirCount[key]++ - } - } - if err := rows.Err(); err != nil { // m1 - rows.Close() - writeError(w, http.StatusInternalServerError, err.Error()) - return - } - rows.Close() - } - - topDirs := topN(dirCount, 10) - - // Recent symbols. - var recentSyms []symbolEntry - { - symRows, err := d.DB.QueryContext(r.Context(), - `SELECT name, kind, file_path, language FROM symbols WHERE project_path = ? LIMIT 20`, - p.HostPath, - ) - if err != nil { - writeError(w, http.StatusInternalServerError, err.Error()) - return - } - for symRows.Next() { - var s symbolEntry - if err := symRows.Scan(&s.Name, &s.Kind, &s.FilePath, &s.Language); err != nil { - symRows.Close() - writeError(w, http.StatusInternalServerError, err.Error()) - return - } - recentSyms = append(recentSyms, s) - } - if err := symRows.Err(); err != nil { // m1 - symRows.Close() - writeError(w, http.StatusInternalServerError, err.Error()) - return - } - symRows.Close() - } - if recentSyms == nil { - recentSyms = []symbolEntry{} - } - - // Total symbol count. - var totalSymbols int - _ = d.DB.QueryRowContext(r.Context(), - `SELECT COUNT(*) FROM symbols WHERE project_path = ?`, p.HostPath, - ).Scan(&totalSymbols) - - langs := p.Languages - if langs == nil { - langs = []string{} - } - - writeJSON(w, http.StatusOK, projectSummaryResponse{ - HostPath: p.HostPath, - Status: p.Status, - Languages: langs, - TotalFiles: p.Stats.TotalFiles, - TotalChunks: p.Stats.TotalChunks, - TotalSymbols: totalSymbols, - TopDirectories: topDirs, - RecentSymbols: recentSyms, - }) - } -} - -// --------------------------------------------------------------------------- -// Path helpers — mirror Python Path(fp).parts logic +// Path helpers — mirror Python Path(fp).parts logic. Used by +// Server.GetProjectSummary to bucket file paths into top-level directories. // --------------------------------------------------------------------------- func splitPath(fp string) []string { - // filepath.SplitList is for PATH env — use manual split. - // We want to split by "/" for consistency with Python pathlib. var parts []string for { dir, base := filepath.Split(fp) @@ -535,58 +235,19 @@ func joinPath(parts []string) string { return result } -// topN returns the top-n directory entries by count. -func topN(m map[string]int, n int) []dirEntry { - type kv struct { - k string - v int - } - var kvs []kv - for k, v := range m { - kvs = append(kvs, kv{k, v}) - } - // Sort descending. - for i := 1; i < len(kvs); i++ { - j := i - for j > 0 && kvs[j].v > kvs[j-1].v { - kvs[j], kvs[j-1] = kvs[j-1], kvs[j] - j-- - } - } - if n > len(kvs) { - n = len(kvs) - } - out := make([]dirEntry, n) - for i := 0; i < n; i++ { - out[i] = dirEntry{Path: kvs[i].k, FileCount: kvs[i].v} - } - return out -} - -// Ensure symbolindex and sql are used (avoid import cycle in future if moved). -var _ = (*sql.DB)(nil) -var _ = symbolindex.Symbol{} +// Ensure symbolindex and sql are referenced (compile-time guard against +// accidental import removal during refactors). +var ( + _ = (*sql.DB)(nil) + _ = symbolindex.Symbol{} +) // --------------------------------------------------------------------------- -// Semantic search — POST /api/v1/projects/{path}/search +// Internal types used by the semantic-search fan-out / merge / group-by-file +// pipeline. The public wire format lives in openapi.SemanticSearchResponse; +// these types describe the intermediate state inside Server.SemanticSearch. // --------------------------------------------------------------------------- -type searchRequest struct { - Query string `json:"query"` - Limit int `json:"limit"` - Languages []string `json:"languages"` - Paths []string `json:"paths"` - // Excludes drops any result whose file path matches one of the prefixes. - // Mirrors Paths' matching semantics (prefix or substring) but inverted — - // useful for ignoring fixtures, vendored, or legacy directories without - // adding them to .cixignore (which would prevent indexing entirely). - Excludes []string `json:"excludes"` - // MinScore is a pointer so we can distinguish "not provided" from an - // explicit zero. Python uses a Pydantic default (0.1) which also allows - // explicit 0 through — mirror that here. m2 fix. - MinScore *float32 `json:"min_score,omitempty"` -} - // searchResultItem is the per-chunk match used INTERNALLY during retrieval. // It is not exposed in the JSON response — the wire format groups matches // by file (see fileGroupResult). The merge step (mergeOverlappingHits) @@ -604,11 +265,8 @@ type searchResultItem struct { NestedHits []nestedHit `json:"nested_hits,omitempty"` } -// nestedHit is a compact view of a chunk that was merged INTO another -// result by mergeOverlappingHits (e.g. an H2 section absorbed into its -// containing H1). The parent's `content` already includes the inner -// textually; this just records the metadata so renderers can show a -// breadcrumb and let the user jump to the exact line. +// nestedHit records a chunk that was merged INTO another result by +// mergeOverlappingHits (e.g. an H2 section absorbed into its containing H1). type nestedHit struct { StartLine int `json:"start_line"` EndLine int `json:"end_line"` @@ -617,10 +275,7 @@ type nestedHit struct { Score float32 `json:"score"` } -// fileMatch is one search hit inside a file group. Mirrors the per-chunk -// information from searchResultItem but without the file_path/language -// (those live one level up on fileGroupResult — same for every match in -// the group). +// fileMatch is one search hit inside a file group. type fileMatch struct { StartLine int `json:"start_line"` EndLine int `json:"end_line"` @@ -632,11 +287,7 @@ type fileMatch struct { } // fileGroupResult is the top-level unit of search output: one file with -// every match inside it that passed min_score. Files are ranked by -// BestScore (the highest match score in the group) and matches inside are -// ordered by StartLine ascending so the renderer reads top-to-bottom like -// the actual file. There is no per-file cap on matches — the only filter -// inside a file is the similarity threshold. +// every match inside it that passed min_score. type fileGroupResult struct { FilePath string `json:"file_path"` Language string `json:"language,omitempty"` @@ -644,133 +295,29 @@ type fileGroupResult struct { Matches []fileMatch `json:"matches"` } +// searchResponse is the JSON-unmarshal target used by tests. Server.SemanticSearch +// emits openapi.SemanticSearchResponse, which is byte-identical on the wire. type searchResponse struct { Results []fileGroupResult `json:"results"` Total int `json:"total"` QueryTimeMS float64 `json:"query_time_ms"` } -// semanticSearchHandler implements POST /api/v1/projects/{path}/search, -// matching api/app/routers/search.py semantic_search behaviour: -// - embed query with prefix -// - query vectorstore with limit*2 and optional where(language) -// - post-filter by min_score + paths (prefix OR substring) -// - trim to limit, round query_time_ms to 1 decimal -func semanticSearchHandler(d Deps) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - p := resolveProjectFromHash(w, r, d) - if p == nil { - return - } - if d.VectorStore == nil || d.EmbeddingSvc == nil { - writeError(w, http.StatusServiceUnavailable, "semantic search not configured") - return - } - - var body searchRequest - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - writeError(w, http.StatusUnprocessableEntity, "invalid request body") - return - } - if strings.TrimSpace(body.Query) == "" { - writeError(w, http.StatusUnprocessableEntity, "query is required") - return - } - if body.Limit <= 0 { - body.Limit = 10 - } - // m2 — only apply default when the caller did not send the field. - // Explicit 0 means "return everything above the HNSW floor". - // Default 0.4 calibrated against CodeRankEmbed-Q8_0 with the - // path-aware embedding format. Clients that want the historical - // 0.1 behavior must send min_score explicitly. - minScore := float32(0.4) - if body.MinScore != nil { - minScore = *body.MinScore - } - - start := time.Now() - - qEmb, err := d.EmbeddingSvc.EmbedQuery(r.Context(), body.Query) - if err != nil { - if retry, busy := embeddings.IsBusy(err); busy { - w.Header().Set("Retry-After", strconvItoa(retry)) - writeError(w, http.StatusServiceUnavailable, - "GPU is busy processing another embedding request, retry after "+strconvItoa(retry)+"s") - return - } - if errors.Is(err, embeddings.ErrDisabled) { - writeError(w, http.StatusServiceUnavailable, "embeddings disabled") - return - } - writeError(w, http.StatusInternalServerError, err.Error()) - return - } - - // Post-filter (path/language) state is computed once outside the - // window loop — cheap and doesn't depend on factor. - langSet := map[string]struct{}{} - for _, l := range body.Languages { - langSet[l] = struct{}{} - } - applyPostLangFilter := len(body.Languages) > maxFanoutSearch - - // Windowed retrieval. The user's --limit is a count of FILES, not - // chunks. We start with a chunk over-fetch of limit×2, group by - // file, and if we don't have `limit` distinct files yet, re-fetch - // with limit×4, ×8, ×16. Stops early when: - // - we have ≥ limit file groups, OR - // - the vector store returned fewer rows than asked (HNSW is - // exhausted), OR - // - factor cap (×16) reached. - // - // Inside each file there is no count cap — every match that passed - // min_score is shown. The threshold is the only intra-file filter. - var fileGroups []fileGroupResult - factor := 2 - for { - n := body.Limit * factor - rawWrapped, err := fetchVectorResults( - r.Context(), d.VectorStore, p.HostPath, qEmb, n, body.Languages, - ) - if err != nil { - writeError(w, http.StatusInternalServerError, err.Error()) - return - } - filtered := filterToSearchItems(rawWrapped, minScore, body.Paths, body.Excludes, langSet, applyPostLangFilter) - merged := mergeOverlappingHits(filtered) - fileGroups = groupByFile(merged) - if len(fileGroups) >= body.Limit { - break - } - if len(rawWrapped) < n { - break - } - if factor >= maxFactorSearch { - break - } - factor *= 2 - } - - if len(fileGroups) > body.Limit { - fileGroups = fileGroups[:body.Limit] - } +// --------------------------------------------------------------------------- +// Constants + helpers shared with server.go (Server.SemanticSearch). +// --------------------------------------------------------------------------- - elapsedMS := float64(time.Since(start).Microseconds()) / 1000.0 - elapsedMS = float64(int(elapsedMS*10+0.5)) / 10 +// maxFanoutSearch is the language-count threshold above which we drop +// per-language pre-filter and fall back to a single over-fetched query +// with post-filter. +const maxFanoutSearch = 4 - writeJSON(w, http.StatusOK, searchResponse{ - Results: fileGroups, - Total: len(fileGroups), - QueryTimeMS: elapsedMS, - }) - } -} +// maxFactorSearch caps the windowed retrieval expansion. With limit=10 +// and factor=16 we top out at 160 raw results. +const maxFactorSearch = 16 -// groupByFile takes the merged per-chunk results and lifts them into -// file-grouped output. Inside each file matches are sorted by StartLine -// ascending (natural reading order); files are sorted by BestScore -// descending (hottest hit drives the rank). +// groupByFile lifts merged per-chunk results into per-file groups, sorted by +// best score descending (with a stable tie-break on file_path). func groupByFile(items []searchResultItem) []fileGroupResult { if len(items) == 0 { return nil @@ -812,32 +359,19 @@ func groupByFile(items []searchResultItem) []fileGroupResult { if groups[i].BestScore != groups[j].BestScore { return groups[i].BestScore > groups[j].BestScore } - // Tie-break by file path so output is stable in tests. return groups[i].FilePath < groups[j].FilePath }) return groups } -// maxFanoutSearch is the language-count threshold above which we drop -// per-language pre-filter and fall back to a single over-fetched query -// with post-filter. Same value as the previous inline `maxFanout`. -const maxFanoutSearch = 4 - -// maxFactorSearch caps the windowed retrieval expansion. With body.Limit=10 -// and factor=16 we top out at 160 raw results — enough to fill the budget -// even on heavily nested markdown without spending all day re-querying. -const maxFactorSearch = 16 - // fetchVectorResults performs the per-language fan-out vector-store query -// at the given limit and returns deduped, score-sorted results. Extracted -// from semanticSearchHandler so the windowed retry loop can call it with -// growing `n` values without duplicating the four-case switch. +// at the given limit and returns deduped, score-sorted results. // -// The fan-out strategy mirrors the original inline logic: 0 languages → -// single query; 1 language → single query with where-filter; 2..maxFanout -// → N queries with per-language where-filter, deduped and re-sorted by -// score; >maxFanout → single oversized query, post-filter handled by -// caller (filterToSearchItems with applyPostLangFilter=true). +// The fan-out strategy: 0 languages → single query; 1 language → single +// query with where-filter; 2..maxFanout → N queries with per-language +// where-filter, deduped and re-sorted by score; >maxFanout → single +// oversized query, post-filter handled by caller (filterToSearchItems with +// applyPostLangFilter=true). func fetchVectorResults( ctx context.Context, store *vectorstore.Store, @@ -887,8 +421,7 @@ func fetchVectorResults( // filterToSearchItems applies min-score, language post-filter, path // whitelist (paths), and path blacklist (excludes). It does NOT truncate — // the merge step needs the full filtered set to identify all overlaps -// before deciding which to drop. Truncation happens after merge in the -// caller. +// before deciding which to drop. func filterToSearchItems( wrapped []vectorStoreResult, minScore float32, @@ -945,29 +478,3 @@ func filterToSearchItems( } return filtered } - -// strconvItoa avoids pulling strconv just for one call in this file — mirrors -// the pattern used elsewhere in the package. -func strconvItoa(n int) string { - // strconv is already imported elsewhere in the package? No — keep inline. - // Use fmt-free int-to-string. - if n == 0 { - return "0" - } - neg := n < 0 - if neg { - n = -n - } - var buf [20]byte - i := len(buf) - for n > 0 { - i-- - buf[i] = byte('0' + n%10) - n /= 10 - } - if neg { - i-- - buf[i] = '-' - } - return string(buf[i:]) -} diff --git a/server/internal/httpapi/server.go b/server/internal/httpapi/server.go new file mode 100644 index 0000000..5f57941 --- /dev/null +++ b/server/internal/httpapi/server.go @@ -0,0 +1,1070 @@ +package httpapi + +import ( + "context" + "encoding/json" + "errors" + "net/http" + "os" + "path/filepath" + "sort" + "strconv" + "strings" + "time" + + "github.com/dvcdsys/code-index/server/internal/embeddings" + "github.com/dvcdsys/code-index/server/internal/httpapi/openapi" + "github.com/dvcdsys/code-index/server/internal/indexer" + "github.com/dvcdsys/code-index/server/internal/langdetect" + "github.com/dvcdsys/code-index/server/internal/projects" + "github.com/dvcdsys/code-index/server/internal/symbolindex" + "github.com/dvcdsys/code-index/server/internal/vectorstore" +) + +// Server is the chi-server implementation generated from doc/openapi.yaml. +// It owns the Deps bundle and translates between the generated types and +// the internal package types (projects.Project, indexer.Progress, etc.). +// +// Wire format guarantee: every JSON shape this struct emits is byte-identical +// to the pre-OpenAPI handler closures it replaced. The migration changes +// internal organisation only — request/response keys, status codes, and +// header behaviour are unchanged. +type Server struct { + Deps Deps + + // loginLimiter throttles POST /auth/login. Initialised by NewRouter + // so each test fixture (and each running server) gets its own + // in-memory state. + loginLimiter *loginLimiter +} + +// Compile-time assertion that Server implements the generated interface. +var _ openapi.ServerInterface = (*Server)(nil) + +// --------------------------------------------------------------------------- +// Probe endpoints +// --------------------------------------------------------------------------- + +// GetHealth — GET /health (public). +func (s *Server) GetHealth(w http.ResponseWriter, r *http.Request) { + if s.Deps.DB != nil { + pingCtx, cancel := context.WithTimeout(r.Context(), time.Second) + defer cancel() + if err := s.Deps.DB.PingContext(pingCtx); err != nil { + writeJSON(w, http.StatusServiceUnavailable, map[string]any{ + "status": "unhealthy", + "reason": "db unreachable", + }) + return + } + } + writeJSON(w, http.StatusOK, map[string]any{"status": "ok"}) +} + +// GetStatus — GET /api/v1/status. +func (s *Server) GetStatus(w http.ResponseWriter, r *http.Request) { + projectCount := 0 + activeJobs := 0 + if s.Deps.DB != nil { + _ = s.Deps.DB.QueryRowContext(r.Context(), + `SELECT COUNT(*) FROM projects`).Scan(&projectCount) + _ = s.Deps.DB.QueryRowContext(r.Context(), + `SELECT COUNT(*) FROM index_runs WHERE status = 'running'`).Scan(&activeJobs) + } + modelLoaded := false + if s.Deps.EmbeddingSvc != nil { + readyCtx, cancel := context.WithTimeout(r.Context(), 500*time.Millisecond) + modelLoaded = s.Deps.EmbeddingSvc.Ready(readyCtx) == nil + cancel() + } + // PR-E — embedding_model must reflect the LIVE config (after any + // dashboard runtime override + restart), not the boot-time value + // stamped into Deps. Fall back to Deps when the service is a fake or + // disabled, so test fixtures still get a stable string. + model := s.Deps.EmbeddingModel + if es, ok := s.Deps.EmbeddingSvc.(*embeddings.Service); ok && es != nil { + if cfg := es.Config(); cfg != nil && cfg.EmbeddingModel != "" { + model = cfg.EmbeddingModel + } + } + writeJSON(w, http.StatusOK, map[string]any{ + "status": "ok", + "backend": s.Deps.Backend, + "server_version": s.Deps.ServerVersion, + "api_version": s.Deps.APIVersion, + "model_loaded": modelLoaded, + "embedding_model": model, + "projects": projectCount, + "active_indexing_jobs": activeJobs, + }) +} + +// --------------------------------------------------------------------------- +// Project CRUD +// --------------------------------------------------------------------------- + +// CreateProject — POST /api/v1/projects. +func (s *Server) CreateProject(w http.ResponseWriter, r *http.Request) { + var body openapi.CreateProjectRequest + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + writeError(w, http.StatusUnprocessableEntity, "invalid request body") + return + } + if body.HostPath == "" { + writeError(w, http.StatusUnprocessableEntity, "host_path is required") + return + } + p, err := projects.Create(r.Context(), s.Deps.DB, projects.CreateRequest{HostPath: body.HostPath}) + if err != nil { + if errors.Is(err, projects.ErrConflict) || errors.Is(err, projects.ErrOverlap) { + writeError(w, http.StatusConflict, err.Error()) + return + } + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + writeJSON(w, http.StatusCreated, projectToOpenAPI(p)) +} + +// ListProjects — GET /api/v1/projects. +func (s *Server) ListProjects(w http.ResponseWriter, r *http.Request) { + list, err := projects.List(r.Context(), s.Deps.DB) + if err != nil { + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + out := make([]openapi.Project, 0, len(list)) + for i := range list { + out = append(out, projectToOpenAPI(&list[i])) + } + writeJSON(w, http.StatusOK, openapi.ProjectListResponse{ + Projects: out, + Total: len(out), + }) +} + +// GetProject — GET /api/v1/projects/{path}. +func (s *Server) GetProject(w http.ResponseWriter, r *http.Request, path openapi.ProjectHash) { + p := s.lookupProject(w, r, path) + if p == nil { + return + } + out := projectToOpenAPI(p) + s.enrichProjectStorage(&out, p) + writeJSON(w, http.StatusOK, out) +} + +// enrichProjectStorage fills the storage-related Project fields. Skipped +// when embeddings are disabled / unavailable — callers see those as nil and +// the dashboard hides the section. Per-call os.Stat is cheap enough for the +// single-project endpoint; we deliberately do NOT enrich the list endpoint +// (would multiply stat calls × N projects on every page load). +func (s *Server) enrichProjectStorage(out *openapi.Project, p *projects.Project) { + es, ok := s.Deps.EmbeddingSvc.(*embeddings.Service) + if !ok || es == nil { + return + } + cfg := es.Config() + if cfg == nil { + return + } + sqlitePath := cfg.DynamicSQLitePath() + if sqlitePath != "" { + out.SqlitePath = ptrString(sqlitePath) + if info, err := os.Stat(sqlitePath); err == nil { + sz := info.Size() + out.SqliteSizeBytes = &sz + } + } + if cfg.ChromaPersistDir != "" { + col := vectorstore.CollectionName(p.HostPath) + dir := filepath.Join(cfg.DynamicChromaPersistDir(), col) + out.ChromaPath = ptrString(dir) + if sz, ok := dirSizeBytes(dir); ok { + out.ChromaSizeBytes = &sz + } + } +} + +// dirSizeBytes walks dir and sums regular-file sizes. Returns (0,false) on +// any error (missing dir, permission, etc.) so callers can decide to omit +// the field rather than report a misleading 0. +func dirSizeBytes(dir string) (int64, bool) { + var total int64 + walkErr := filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + info, err := d.Info() + if err != nil { + return nil + } + total += info.Size() + return nil + }) + if walkErr != nil { + return 0, false + } + return total, true +} + +// UpdateProject — PATCH /api/v1/projects/{path}. Admin-only: settings +// changes can shrink the indexing surface (exclude_patterns, max_file_size) +// and viewers should not be able to silently de-index a project. +func (s *Server) UpdateProject(w http.ResponseWriter, r *http.Request, path openapi.ProjectHash) { + if _, ok := s.mustBeAdmin(w, r); !ok { + return + } + p := s.lookupProject(w, r, path) + if p == nil { + return + } + var body openapi.UpdateProjectRequest + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + writeError(w, http.StatusUnprocessableEntity, "invalid request body") + return + } + var settingsPtr *projects.Settings + if body.Settings != nil { + s := projects.Settings{ + ExcludePatterns: body.Settings.ExcludePatterns, + MaxFileSize: body.Settings.MaxFileSize, + } + settingsPtr = &s + } + updated, err := projects.Patch(r.Context(), s.Deps.DB, p.HostPath, projects.UpdateRequest{Settings: settingsPtr}) + if err != nil { + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + writeJSON(w, http.StatusOK, projectToOpenAPI(updated)) +} + +// DeleteProject — DELETE /api/v1/projects/{path}. Admin-only: dropping a +// project also wipes its symbols/refs/embeddings and is destructive enough +// that it must not be reachable from a viewer-scoped session or API key. +func (s *Server) DeleteProject(w http.ResponseWriter, r *http.Request, path openapi.ProjectHash) { + if _, ok := s.mustBeAdmin(w, r); !ok { + return + } + p := s.lookupProject(w, r, path) + if p == nil { + return + } + if err := projects.Delete(r.Context(), s.Deps.DB, p.HostPath); err != nil { + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + w.WriteHeader(http.StatusNoContent) +} + +// --------------------------------------------------------------------------- +// Symbol / definition / reference / file search +// --------------------------------------------------------------------------- + +// SearchSymbols — POST /api/v1/projects/{path}/search/symbols. +func (s *Server) SearchSymbols(w http.ResponseWriter, r *http.Request, path openapi.ProjectHash) { + p := s.lookupProject(w, r, path) + if p == nil { + return + } + var body openapi.SymbolSearchRequest + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + writeError(w, http.StatusUnprocessableEntity, "invalid request body") + return + } + if body.Query == "" { + writeError(w, http.StatusUnprocessableEntity, "query is required") + return + } + limit := clampLimit(body.Limit, 20) + kinds := derefStringSlice(body.Kinds) + + symbols, err := symbolindex.SearchByName(r.Context(), s.Deps.DB, p.HostPath, body.Query, kinds, limit) + if err != nil { + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + results := make([]openapi.SymbolResultItem, 0, len(symbols)) + for _, sym := range symbols { + results = append(results, openapi.SymbolResultItem{ + Name: sym.Name, + Kind: sym.Kind, + FilePath: sym.FilePath, + Line: sym.Line, + EndLine: sym.EndLine, + Language: sym.Language, + Signature: sym.Signature, + ParentName: sym.ParentName, + }) + } + writeJSON(w, http.StatusOK, openapi.SymbolSearchResponse{ + Results: results, + Total: len(results), + }) +} + +// SearchDefinitions — POST /api/v1/projects/{path}/search/definitions. +func (s *Server) SearchDefinitions(w http.ResponseWriter, r *http.Request, path openapi.ProjectHash) { + p := s.lookupProject(w, r, path) + if p == nil { + return + } + var body openapi.DefinitionRequest + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + writeError(w, http.StatusUnprocessableEntity, "invalid request body") + return + } + if body.Symbol == "" { + writeError(w, http.StatusUnprocessableEntity, "symbol is required") + return + } + limit := clampLimit(body.Limit, 10) + kind := derefString(body.Kind) + filePath := derefString(body.FilePath) + + syms, err := symbolindex.SearchDefinitions(r.Context(), s.Deps.DB, p.HostPath, body.Symbol, kind, filePath, limit) + if err != nil { + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + results := make([]openapi.DefinitionItem, 0, len(syms)) + for _, sym := range syms { + results = append(results, openapi.DefinitionItem{ + Name: sym.Name, + Kind: sym.Kind, + FilePath: sym.FilePath, + Line: sym.Line, + EndLine: sym.EndLine, + Language: sym.Language, + Signature: sym.Signature, + ParentName: sym.ParentName, + }) + } + writeJSON(w, http.StatusOK, openapi.DefinitionResponse{ + Results: results, + Total: len(results), + }) +} + +// SearchReferences — POST /api/v1/projects/{path}/search/references. +func (s *Server) SearchReferences(w http.ResponseWriter, r *http.Request, path openapi.ProjectHash) { + p := s.lookupProject(w, r, path) + if p == nil { + return + } + var body openapi.ReferenceRequest + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + writeError(w, http.StatusUnprocessableEntity, "invalid request body") + return + } + if body.Symbol == "" { + writeError(w, http.StatusUnprocessableEntity, "symbol is required") + return + } + limit := clampLimit(body.Limit, 50) + filePath := derefString(body.FilePath) + + refs, err := symbolindex.SearchReferences(r.Context(), s.Deps.DB, p.HostPath, body.Symbol, filePath, limit) + if err != nil { + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + results := make([]openapi.ReferenceItem, 0, len(refs)) + for _, ref := range refs { + results = append(results, openapi.ReferenceItem{ + FilePath: ref.FilePath, + StartLine: ref.Line, + EndLine: ref.Line, + Content: "", + ChunkType: openapi.ReferenceItemChunkType("reference"), + SymbolName: ref.Name, + Language: ref.Language, + }) + } + writeJSON(w, http.StatusOK, openapi.ReferenceResponse{ + Results: results, + Total: len(results), + }) +} + +// SearchFiles — POST /api/v1/projects/{path}/search/files. +func (s *Server) SearchFiles(w http.ResponseWriter, r *http.Request, path openapi.ProjectHash) { + p := s.lookupProject(w, r, path) + if p == nil { + return + } + var body openapi.FileSearchRequest + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + writeError(w, http.StatusUnprocessableEntity, "invalid request body") + return + } + if body.Query == "" { + writeError(w, http.StatusUnprocessableEntity, "query is required") + return + } + limit := clampLimit(body.Limit, 20) + + rows, err := s.Deps.DB.QueryContext(r.Context(), + `SELECT file_path FROM file_hashes WHERE project_path = ? AND file_path LIKE ? ORDER BY file_path LIMIT ?`, + p.HostPath, "%"+body.Query+"%", limit, + ) + if err != nil { + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + results := make([]openapi.FileResultItem, 0, limit) + for rows.Next() { + var fp string + if err := rows.Scan(&fp); err != nil { + rows.Close() + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + lang := langdetect.Detect(fp) + var langPtr *string + if lang != "" { + langPtr = &lang + } + results = append(results, openapi.FileResultItem{ + FilePath: fp, + Language: langPtr, + }) + } + if err := rows.Err(); err != nil { + rows.Close() + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + rows.Close() + writeJSON(w, http.StatusOK, openapi.FileSearchResponse{ + Results: results, + Total: len(results), + }) +} + +// --------------------------------------------------------------------------- +// Project summary +// --------------------------------------------------------------------------- + +// GetProjectSummary — GET /api/v1/projects/{path}/summary. +func (s *Server) GetProjectSummary(w http.ResponseWriter, r *http.Request, path openapi.ProjectHash) { + p := s.lookupProject(w, r, path) + if p == nil { + return + } + + dirCount := map[string]int{} + { + rows, err := s.Deps.DB.QueryContext(r.Context(), + `SELECT file_path FROM file_hashes WHERE project_path = ?`, p.HostPath, + ) + if err != nil { + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + for rows.Next() { + var fp string + if err := rows.Scan(&fp); err != nil { + rows.Close() + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + parts := splitPath(fp) + var key string + if len(parts) > 3 { + key = joinPath(parts[:4]) + } else if len(parts) > 1 { + key = joinPath(parts[:2]) + } + if key != "" { + dirCount[key]++ + } + } + if err := rows.Err(); err != nil { + rows.Close() + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + rows.Close() + } + topDirs := topNDirs(dirCount, 10) + + var recentSyms []openapi.SymbolEntry + { + symRows, err := s.Deps.DB.QueryContext(r.Context(), + `SELECT name, kind, file_path, language FROM symbols WHERE project_path = ? LIMIT 20`, + p.HostPath, + ) + if err != nil { + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + for symRows.Next() { + var e openapi.SymbolEntry + if err := symRows.Scan(&e.Name, &e.Kind, &e.FilePath, &e.Language); err != nil { + symRows.Close() + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + recentSyms = append(recentSyms, e) + } + if err := symRows.Err(); err != nil { + symRows.Close() + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + symRows.Close() + } + if recentSyms == nil { + recentSyms = []openapi.SymbolEntry{} + } + + var totalSymbols int + _ = s.Deps.DB.QueryRowContext(r.Context(), + `SELECT COUNT(*) FROM symbols WHERE project_path = ?`, p.HostPath, + ).Scan(&totalSymbols) + + langs := p.Languages + if langs == nil { + langs = []string{} + } + + writeJSON(w, http.StatusOK, openapi.ProjectSummary{ + PathHash: projects.HashPath(p.HostPath), + HostPath: p.HostPath, + Status: p.Status, + Languages: langs, + TotalFiles: p.Stats.TotalFiles, + TotalChunks: p.Stats.TotalChunks, + TotalSymbols: totalSymbols, + TopDirectories: topDirs, + RecentSymbols: recentSyms, + }) +} + +// --------------------------------------------------------------------------- +// Semantic search +// --------------------------------------------------------------------------- + +// SemanticSearch — POST /api/v1/projects/{path}/search. +func (s *Server) SemanticSearch(w http.ResponseWriter, r *http.Request, path openapi.ProjectHash) { + p := s.lookupProject(w, r, path) + if p == nil { + return + } + if s.Deps.VectorStore == nil || s.Deps.EmbeddingSvc == nil { + writeError(w, http.StatusServiceUnavailable, "semantic search not configured") + return + } + var body openapi.SemanticSearchRequest + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + writeError(w, http.StatusUnprocessableEntity, "invalid request body") + return + } + if strings.TrimSpace(body.Query) == "" { + writeError(w, http.StatusUnprocessableEntity, "query is required") + return + } + limit := clampLimit(body.Limit, 10) + languages := derefStringSlice(body.Languages) + paths := derefStringSlice(body.Paths) + excludes := derefStringSlice(body.Excludes) + + minScore := float32(0.4) + if body.MinScore != nil { + minScore = *body.MinScore + } + + start := time.Now() + + qEmb, err := s.Deps.EmbeddingSvc.EmbedQuery(r.Context(), body.Query) + if err != nil { + if retry, busy := embeddings.IsBusy(err); busy { + w.Header().Set("Retry-After", strconv.Itoa(retry)) + writeError(w, http.StatusServiceUnavailable, + "GPU is busy processing another embedding request, retry after "+strconv.Itoa(retry)+"s") + return + } + if errors.Is(err, embeddings.ErrDisabled) { + writeError(w, http.StatusServiceUnavailable, "embeddings disabled") + return + } + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + + langSet := map[string]struct{}{} + for _, l := range languages { + langSet[l] = struct{}{} + } + applyPostLangFilter := len(languages) > maxFanoutSearch + + var fileGroups []fileGroupResult + factor := 2 + for { + n := limit * factor + rawWrapped, err := fetchVectorResults( + r.Context(), s.Deps.VectorStore, p.HostPath, qEmb, n, languages, + ) + if err != nil { + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + filtered := filterToSearchItems(rawWrapped, minScore, paths, excludes, langSet, applyPostLangFilter) + merged := mergeOverlappingHits(filtered) + fileGroups = groupByFile(merged) + if len(fileGroups) >= limit { + break + } + if len(rawWrapped) < n { + break + } + if factor >= maxFactorSearch { + break + } + factor *= 2 + } + if len(fileGroups) > limit { + fileGroups = fileGroups[:limit] + } + + elapsedMS := float64(time.Since(start).Microseconds()) / 1000.0 + elapsedMS = float64(int(elapsedMS*10+0.5)) / 10 + + writeJSON(w, http.StatusOK, openapi.SemanticSearchResponse{ + Results: fileGroupsToOpenAPI(fileGroups), + Total: len(fileGroups), + QueryTimeMs: elapsedMS, + }) +} + +// fileGroupsToOpenAPI converts the internal []fileGroupResult into the +// generated []openapi.FileGroupResult. Wire-compat: openapi.FileGroupResult +// uses *string for Language (with omitempty) instead of plain string — +// nil pointer and empty string both produce an absent JSON key. +func fileGroupsToOpenAPI(in []fileGroupResult) []openapi.FileGroupResult { + out := make([]openapi.FileGroupResult, len(in)) + for i, g := range in { + var langPtr *string + if g.Language != "" { + lang := g.Language + langPtr = &lang + } + matches := make([]openapi.FileMatch, len(g.Matches)) + for j, m := range g.Matches { + var nested *[]openapi.NestedHit + if len(m.NestedHits) > 0 { + ns := make([]openapi.NestedHit, len(m.NestedHits)) + for k, n := range m.NestedHits { + var symPtr *string + if n.SymbolName != "" { + v := n.SymbolName + symPtr = &v + } + ns[k] = openapi.NestedHit{ + StartLine: n.StartLine, + EndLine: n.EndLine, + SymbolName: symPtr, + ChunkType: n.ChunkType, + Score: n.Score, + } + } + nested = &ns + } + var symPtr *string + if m.SymbolName != "" { + v := m.SymbolName + symPtr = &v + } + matches[j] = openapi.FileMatch{ + StartLine: m.StartLine, + EndLine: m.EndLine, + Content: m.Content, + Score: m.Score, + ChunkType: m.ChunkType, + SymbolName: symPtr, + NestedHits: nested, + } + } + out[i] = openapi.FileGroupResult{ + FilePath: g.FilePath, + Language: langPtr, + BestScore: g.BestScore, + Matches: matches, + } + } + return out +} + +// --------------------------------------------------------------------------- +// Indexing — three-phase protocol +// --------------------------------------------------------------------------- + +// IndexBegin — POST /api/v1/projects/{path}/index/begin. +func (s *Server) IndexBegin(w http.ResponseWriter, r *http.Request, path openapi.ProjectHash) { + p := s.lookupProject(w, r, path) + if p == nil { + return + } + if s.Deps.Indexer == nil { + writeError(w, http.StatusServiceUnavailable, "indexer not configured") + return + } + var body openapi.IndexBeginRequest + // Body is optional — accept empty request. + if r.ContentLength > 0 { + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + writeError(w, http.StatusUnprocessableEntity, "invalid request body") + return + } + } + full := body.Full != nil && *body.Full + + runID, stored, err := s.Deps.Indexer.BeginIndexing(r.Context(), p.HostPath, full) + if err != nil { + if errors.Is(err, indexer.ErrSessionConflict) { + writeError(w, http.StatusConflict, err.Error()) + return + } + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + if stored == nil { + stored = map[string]string{} + } + writeJSON(w, http.StatusOK, openapi.IndexBeginResponse{ + RunId: runID, + StoredHashes: stored, + }) +} + +// IndexFiles — POST /api/v1/projects/{path}/index/files. +// +// Honours `Accept: application/x-ndjson` to switch into the streaming +// variant; otherwise returns the legacy single-JSON summary. +func (s *Server) IndexFiles(w http.ResponseWriter, r *http.Request, path openapi.ProjectHash, params openapi.IndexFilesParams) { + p := s.lookupProject(w, r, path) + if p == nil { + return + } + if s.Deps.Indexer == nil { + writeError(w, http.StatusServiceUnavailable, "indexer not configured") + return + } + var body openapi.IndexFilesRequest + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + writeError(w, http.StatusUnprocessableEntity, "invalid request body") + return + } + if body.RunId == "" { + writeError(w, http.StatusUnprocessableEntity, "run_id is required") + return + } + if len(body.Files) > maxFilesPerBatch { + writeError(w, http.StatusUnprocessableEntity, "too many files in batch (max 50)") + return + } + files := make([]indexer.FilePayload, len(body.Files)) + for i, f := range body.Files { + files[i] = indexer.FilePayload{ + Path: f.Path, + Content: f.Content, + ContentHash: f.ContentHash, + Language: derefString(f.Language), + Size: f.Size, + } + } + + // Accept-header negotiation. We re-read directly from the header rather + // than trusting params.Accept alone because chi-server passes the + // header verbatim — old clients that omit Accept get the JSON branch, + // new clients that send `application/x-ndjson` get the stream. + if acceptsNDJSON(r.Header.Get("Accept")) { + indexFilesStreamingHandler(s.Deps, p, body.RunId, files, w, r) + return + } + + accepted, chunks, total, err := s.Deps.Indexer.ProcessFiles(r.Context(), p.HostPath, body.RunId, files) + if err != nil { + if retry, busy := embeddings.IsBusy(err); busy { + w.Header().Set("Retry-After", strconv.Itoa(retry)) + writeError(w, http.StatusServiceUnavailable, + "GPU is busy processing another embedding request, retry after "+strconv.Itoa(retry)+"s") + return + } + if errors.Is(err, indexer.ErrNoSession) || errors.Is(err, indexer.ErrProjectMismatch) { + writeError(w, http.StatusNotFound, err.Error()) + return + } + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + writeJSON(w, http.StatusOK, openapi.IndexFilesResponse{ + FilesAccepted: accepted, + ChunksCreated: chunks, + FilesProcessedTotal: total, + }) +} + +// IndexFinish — POST /api/v1/projects/{path}/index/finish. +func (s *Server) IndexFinish(w http.ResponseWriter, r *http.Request, path openapi.ProjectHash) { + p := s.lookupProject(w, r, path) + if p == nil { + return + } + if s.Deps.Indexer == nil { + writeError(w, http.StatusServiceUnavailable, "indexer not configured") + return + } + var body openapi.IndexFinishRequest + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + writeError(w, http.StatusUnprocessableEntity, "invalid request body") + return + } + if body.RunId == "" { + writeError(w, http.StatusUnprocessableEntity, "run_id is required") + return + } + deletedPaths := derefStringSlice(body.DeletedPaths) + totalDiscovered := derefIntOrDefault(body.TotalFilesDiscovered, 0) + + status, files, chunks, err := s.Deps.Indexer.FinishIndexing( + r.Context(), p.HostPath, body.RunId, deletedPaths, totalDiscovered, + ) + if err != nil { + if errors.Is(err, indexer.ErrNoSession) || errors.Is(err, indexer.ErrProjectMismatch) { + writeError(w, http.StatusNotFound, err.Error()) + return + } + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + writeJSON(w, http.StatusOK, openapi.IndexFinishResponse{ + Status: openapi.IndexFinishResponseStatus(status), + FilesProcessed: files, + ChunksCreated: chunks, + }) +} + +// IndexCancel — POST /api/v1/projects/{path}/index/cancel. Open to any +// authenticated user. The CLI calls this in its defer-cleanup on early +// exit (Ctrl-C, network drop), so gating it on admin would leave run +// locks hanging for viewers until the 1-hour TTL — worse UX than the +// theoretical DoS we'd be preventing. If you need owner-scoped semantics +// later, key off projects.indexing_run.started_by_user_id. +func (s *Server) IndexCancel(w http.ResponseWriter, r *http.Request, path openapi.ProjectHash) { + p := s.lookupProject(w, r, path) + if p == nil { + return + } + if s.Deps.Indexer == nil { + writeJSON(w, http.StatusOK, openapi.IndexCancelResponse{Cancelled: false}) + return + } + cancelled, err := s.Deps.Indexer.CancelIndexing(r.Context(), p.HostPath) + if err != nil { + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + writeJSON(w, http.StatusOK, openapi.IndexCancelResponse{Cancelled: cancelled}) +} + +// IndexStatus — GET /api/v1/projects/{path}/index/status. +func (s *Server) IndexStatus(w http.ResponseWriter, r *http.Request, path openapi.ProjectHash) { + p := s.lookupProject(w, r, path) + if p == nil { + return + } + if s.Deps.Indexer == nil { + writeJSON(w, http.StatusOK, openapi.IndexProgressResponse{Status: "idle"}) + return + } + prog := s.Deps.Indexer.GetProgress(p.HostPath) + if prog != nil { + // Active session — emit the full progress payload. We use a + // raw map (not openapi.IndexProgressInfo) to preserve the + // historical wire shape: every field is present even when + // integer-zero (Python emitted these keys unconditionally). + writeJSON(w, http.StatusOK, openapi.IndexProgressResponse{ + Status: openapi.IndexProgressResponseStatus(prog.Status), + Progress: &openapi.IndexProgressInfo{ + Phase: progressPhasePtr(prog.Phase), + FilesDiscovered: ptrInt(prog.FilesDiscovered), + FilesProcessed: ptrInt(prog.FilesProcessed), + FilesTotal: ptrInt(prog.FilesTotal), + ChunksCreated: ptrInt(prog.ChunksCreated), + ElapsedSeconds: ptrFloat64(roundFloat1(prog.ElapsedSeconds)), + RunId: ptrString(prog.RunID), + }, + }) + return + } + // Fall back to last run row. + row := s.Deps.DB.QueryRowContext(r.Context(), + `SELECT status, files_processed, files_total, chunks_created + FROM index_runs WHERE project_path = ? ORDER BY started_at DESC LIMIT 1`, + p.HostPath, + ) + var status string + var filesProcessed, filesTotal, chunks int + if err := row.Scan(&status, &filesProcessed, &filesTotal, &chunks); err != nil { + writeJSON(w, http.StatusOK, openapi.IndexProgressResponse{Status: "idle"}) + return + } + writeJSON(w, http.StatusOK, openapi.IndexProgressResponse{ + Status: openapi.IndexProgressResponseStatus(status), + Progress: &openapi.IndexProgressInfo{ + FilesProcessed: ptrInt(filesProcessed), + FilesTotal: ptrInt(filesTotal), + ChunksCreated: ptrInt(chunks), + }, + }) +} + +// --------------------------------------------------------------------------- +// Internal helpers — type conversion + legacy lookup wrapper +// --------------------------------------------------------------------------- + +// lookupProject resolves the {path} URL parameter. Wraps resolveProjectFromHash +// so generated method signatures stay clean. +func (s *Server) lookupProject(w http.ResponseWriter, r *http.Request, _ openapi.ProjectHash) *projects.Project { + // Use the helper that already exists in search.go — it pulls the + // {path} chi URL param from r and writes a 404 on miss. + return resolveProjectFromHash(w, r, s.Deps) +} + +// projectToOpenAPI converts the internal projects.Project (string dates, +// flat Settings/Stats) into the generated openapi.Project (time.Time dates, +// embedded openapi.ProjectSettings/Stats). +// +// Wire-compat: projects.Project.CreatedAt/UpdatedAt/LastIndexedAt are +// RFC3339Nano strings produced by indexer.nowUTC (time.RFC3339Nano). Go's +// time.Time MarshalJSON also emits RFC3339Nano; round-tripping through +// time.Parse → time.Time produces byte-identical JSON output. +func projectToOpenAPI(p *projects.Project) openapi.Project { + langs := p.Languages + if langs == nil { + langs = []string{} + } + created, _ := time.Parse(time.RFC3339Nano, p.CreatedAt) + updated, _ := time.Parse(time.RFC3339Nano, p.UpdatedAt) + var lastIndexed *time.Time + if p.LastIndexedAt != nil { + t, err := time.Parse(time.RFC3339Nano, *p.LastIndexedAt) + if err == nil { + lastIndexed = &t + } + } + out := openapi.Project{ + PathHash: projects.HashPath(p.HostPath), + HostPath: p.HostPath, + ContainerPath: p.ContainerPath, + Languages: langs, + Settings: openapi.ProjectSettings{ + ExcludePatterns: p.Settings.ExcludePatterns, + MaxFileSize: p.Settings.MaxFileSize, + }, + Stats: openapi.ProjectStats{ + TotalFiles: p.Stats.TotalFiles, + IndexedFiles: p.Stats.IndexedFiles, + TotalChunks: p.Stats.TotalChunks, + TotalSymbols: p.Stats.TotalSymbols, + }, + Status: openapi.ProjectStatus(p.Status), + CreatedAt: created, + UpdatedAt: updated, + LastIndexedAt: lastIndexed, + } + if p.IndexedWithModel != nil { + v := *p.IndexedWithModel + out.IndexedWithModel = &v + } + return out +} + +// topNDirs sorts a path→count map descending and returns the top n entries +// as openapi.DirEntry. Replaces the closure-private topN that lived in +// search.go's projectSummaryHandler. +func topNDirs(m map[string]int, n int) []openapi.DirEntry { + type kv struct { + k string + v int + } + kvs := make([]kv, 0, len(m)) + for k, v := range m { + kvs = append(kvs, kv{k, v}) + } + sort.SliceStable(kvs, func(i, j int) bool { return kvs[i].v > kvs[j].v }) + if n > len(kvs) { + n = len(kvs) + } + out := make([]openapi.DirEntry, n) + for i := 0; i < n; i++ { + out[i] = openapi.DirEntry{Path: kvs[i].k, FileCount: kvs[i].v} + } + return out +} + +// progressPhasePtr converts a phase string into a typed pointer; empty +// strings collapse to nil so omitempty drops the key from the wire payload. +func progressPhasePtr(s string) *openapi.IndexProgressInfoPhase { + if s == "" { + return nil + } + v := openapi.IndexProgressInfoPhase(s) + return &v +} + +func ptrInt(v int) *int { return &v } +func ptrFloat64(v float64) *float64 { return &v } +func ptrString(v string) *string { return &v } + +// derefString returns the string pointed to by p, or "" if p is nil. +func derefString(p *string) string { + if p == nil { + return "" + } + return *p +} + +// derefStringSlice returns the slice pointed to by p, or nil if p is nil. +// Use this for optional array fields where downstream code accepts nil. +func derefStringSlice(p *[]string) []string { + if p == nil { + return nil + } + return *p +} + +// derefIntOrDefault returns the int pointed to by p when present and > 0; +// otherwise returns def. This matches the original handlers' behaviour +// `if body.Limit <= 0 { body.Limit = default }`. +func derefIntOrDefault(p *int, def int) int { + if p == nil || *p <= 0 { + return def + } + return *p +} + +// maxResultLimit caps user-supplied limit values to a sane upper bound. +// Anchored at 1000 — far above any legitimate dashboard / CLI page size, +// well below any value that would balloon a slice allocation or stall a +// LIMIT-clause query. CodeQL flags raw user-int → make() or SQL LIMIT +// flows; clampLimit is the chokepoint. +const maxResultLimit = 1000 + +// clampLimit is derefIntOrDefault that additionally caps the result at +// maxResultLimit. Use this for any value that goes into a slice +// allocation or a SQL LIMIT clause; use derefIntOrDefault when the int +// is just metadata. +func clampLimit(p *int, def int) int { + n := derefIntOrDefault(p, def) + if n > maxResultLimit { + return maxResultLimit + } + return n +} diff --git a/server/internal/indexer/indexer.go b/server/internal/indexer/indexer.go index 5639b06..ee766ea 100644 --- a/server/internal/indexer/indexer.go +++ b/server/internal/indexer/indexer.go @@ -102,6 +102,12 @@ type Service struct { // ": " formatting for projects that have not been // reindexed under the new format. embedIncludePath bool + + // embeddingModel is the active embedding model identifier persisted on + // projects.indexed_with_model at FinishIndexing. Set via + // SetEmbeddingModel from main; empty string keeps the column NULL so + // unit tests that skip the setter don't need to know about drift. + embeddingModel string } // New constructs a Service. All deps are required except logger (falls back to @@ -134,6 +140,14 @@ func (s *Service) SetEmbedIncludePath(v bool) { s.embedIncludePath = v } +// SetEmbeddingModel records the model identifier the indexer will write to +// projects.indexed_with_model at FinishIndexing. Called from main once the +// runtime config is resolved; empty string disables the write (the column +// stays NULL — desired for tests that don't care about drift tracking). +func (s *Service) SetEmbeddingModel(model string) { + s.embeddingModel = model +} + // --------------------------------------------------------------------------- // Phase 1 — begin // --------------------------------------------------------------------------- @@ -717,12 +731,17 @@ func (s *Service) FinishIndexing( ) langsJSON := marshalJSONStringArray(langs) + // PR-E — capture the active embedding model so the dashboard can flag + // projects whose vectors were produced under a different model than the + // one currently loaded in the sidecar. NULLIF keeps the column NULL when + // SetEmbeddingModel was never called (tests / pre-PR-E codepaths). if _, err := s.db.ExecContext(ctx, `UPDATE projects SET stats = ?, languages = ?, status = 'indexed', - last_indexed_at = ?, updated_at = ? + last_indexed_at = ?, updated_at = ?, + indexed_with_model = NULLIF(?, '') WHERE host_path = ?`, - statsJSON, langsJSON, now, now, projectPath, + statsJSON, langsJSON, now, now, s.embeddingModel, projectPath, ); err != nil { return "", 0, 0, fmt.Errorf("update project stats: %w", err) } diff --git a/server/internal/indexer/indexer_test.go b/server/internal/indexer/indexer_test.go index 405ad3f..02941cb 100644 --- a/server/internal/indexer/indexer_test.go +++ b/server/internal/indexer/indexer_test.go @@ -439,6 +439,70 @@ func TestFinishIndexing_UpdatesProject(t *testing.T) { } } +// TestFinishIndexing_CapturesEmbeddingModel ensures FinishIndexing writes the +// model identifier set via SetEmbeddingModel into projects.indexed_with_model. +// Empty model (default for tests that don't call the setter) keeps the column +// NULL so the dashboard treats those projects as "indexed before drift +// tracking existed" rather than as stale. +func TestFinishIndexing_CapturesEmbeddingModel(t *testing.T) { + d := openTestDB(t) + seedProject(t, d, "/proj") + + ctx := context.Background() + vs := newStore(t) + svc := New(d, vs, &fakeEmbedder{dim: 8}, nil) + svc.SetEmbeddingModel("test/model-v1") + + runID, _, err := svc.BeginIndexing(ctx, "/proj", false) + if err != nil { + t.Fatalf("BeginIndexing: %v", err) + } + goFile := "package main\nfunc X() {}\n" + if _, _, _, err := svc.ProcessFiles(ctx, "/proj", runID, []FilePayload{ + {Path: "/proj/a.go", Content: goFile, ContentHash: sha256hex(goFile), Language: "go"}, + }); err != nil { + t.Fatalf("ProcessFiles: %v", err) + } + if _, _, _, err := svc.FinishIndexing(ctx, "/proj", runID, nil, 1); err != nil { + t.Fatalf("FinishIndexing: %v", err) + } + + var model sql.NullString + if err := d.QueryRowContext(ctx, + `SELECT indexed_with_model FROM projects WHERE host_path = ?`, "/proj", + ).Scan(&model); err != nil { + t.Fatalf("select indexed_with_model: %v", err) + } + if !model.Valid || model.String != "test/model-v1" { + t.Errorf("indexed_with_model = %+v, want test/model-v1", model) + } + + // Round 2: another project, no setter call → column stays NULL. + seedProject(t, d, "/proj2") + svc2 := New(d, vs, &fakeEmbedder{dim: 8}, nil) // no SetEmbeddingModel + runID2, _, err := svc2.BeginIndexing(ctx, "/proj2", false) + if err != nil { + t.Fatalf("BeginIndexing /proj2: %v", err) + } + if _, _, _, err := svc2.ProcessFiles(ctx, "/proj2", runID2, []FilePayload{ + {Path: "/proj2/b.go", Content: goFile, ContentHash: sha256hex(goFile), Language: "go"}, + }); err != nil { + t.Fatalf("ProcessFiles /proj2: %v", err) + } + if _, _, _, err := svc2.FinishIndexing(ctx, "/proj2", runID2, nil, 1); err != nil { + t.Fatalf("FinishIndexing /proj2: %v", err) + } + var model2 sql.NullString + if err := d.QueryRowContext(ctx, + `SELECT indexed_with_model FROM projects WHERE host_path = ?`, "/proj2", + ).Scan(&model2); err != nil { + t.Fatalf("select indexed_with_model /proj2: %v", err) + } + if model2.Valid { + t.Errorf("indexed_with_model /proj2 = %q, want NULL", model2.String) + } +} + func TestFinishIndexing_DeletesPaths(t *testing.T) { d := openTestDB(t) seedProject(t, d, "/proj") diff --git a/server/internal/projects/projects.go b/server/internal/projects/projects.go index d1a5bcc..e8212c4 100644 --- a/server/internal/projects/projects.go +++ b/server/internal/projects/projects.go @@ -20,6 +20,12 @@ var ErrNotFound = errors.New("project not found") // ErrConflict is returned when a project with the same path already exists. var ErrConflict = errors.New("project already exists") +// ErrOverlap is returned when the new project path is nested inside an +// existing project (or vice versa). Overlapping projects double-index the +// same files, blow up storage, and make search results ambiguous — +// always indicates a registration mistake the operator should resolve. +var ErrOverlap = errors.New("project path overlaps an existing project") + // Settings mirrors Python ProjectSettings. type Settings struct { ExcludePatterns []string `json:"exclude_patterns"` @@ -56,6 +62,11 @@ type Project struct { CreatedAt string UpdatedAt string LastIndexedAt *string + // IndexedWithModel is the embedding model identifier captured at the + // last successful FinishIndexing. nil for projects that have never been + // indexed under PR-E (or never indexed at all). The dashboard renders + // nil as a neutral "Unknown" badge, NOT as drift. + IndexedWithModel *string } // CreateRequest mirrors Python ProjectCreate. @@ -81,7 +92,7 @@ func hashPath(path string) string { b := h.Sum(nil) const hexchars = "0123456789abcdef" out := make([]byte, 16) - for i := 0; i < 8; i++ { + for i := range 8 { out[i*2] = hexchars[b[i]>>4] out[i*2+1] = hexchars[b[i]&0xf] } @@ -92,7 +103,9 @@ func hashPath(path string) string { // CRUD // --------------------------------------------------------------------------- -// Create inserts a new project. Returns ErrConflict if the path already exists. +// Create inserts a new project. Returns ErrConflict if the path already +// exists, or ErrOverlap if the path is a parent or descendant of any existing +// project. // // We pass host_path through unchanged to match Python // (api/app/routers/projects.py). Normalising here (e.g. stripping trailing @@ -102,6 +115,12 @@ func Create(ctx context.Context, db *sql.DB, req CreateRequest) (*Project, error hostPath := req.HostPath now := time.Now().UTC().Format(time.RFC3339Nano) + if conflict, err := findOverlap(ctx, db, hostPath); err != nil { + return nil, fmt.Errorf("check overlap: %w", err) + } else if conflict != "" { + return nil, fmt.Errorf("%w: %s already registered", ErrOverlap, conflict) + } + defaultSettings := DefaultSettings() settingsJSON, err := json.Marshal(defaultSettings) if err != nil { @@ -127,10 +146,50 @@ func Create(ctx context.Context, db *sql.DB, req CreateRequest) (*Project, error return Get(ctx, db, hostPath) } +// findOverlap returns the host_path of the first existing project that is a +// parent or descendant of `candidate`, or "" if none. Same path is treated as +// "no overlap" — the unique-index on host_path raises ErrConflict for that +// case with a more specific message. +// +// Path comparison strips a single trailing slash from both sides and then +// requires either: +// - existing is a prefix of candidate followed by '/' (existing is parent), or +// - candidate is a prefix of existing followed by '/' (existing is descendant) +// +// Symlinks are intentionally NOT resolved: storing canonical paths would +// silently change identifiers across machines and break stored hashes. +func findOverlap(ctx context.Context, db *sql.DB, candidate string) (string, error) { + cand := strings.TrimSuffix(candidate, "/") + if cand == "" { + return "", nil + } + + rows, err := db.QueryContext(ctx, `SELECT host_path FROM projects`) + if err != nil { + return "", err + } + defer rows.Close() + + for rows.Next() { + var existing string + if err := rows.Scan(&existing); err != nil { + return "", err + } + ex := strings.TrimSuffix(existing, "/") + if ex == "" || ex == cand { + continue + } + if strings.HasPrefix(cand, ex+"/") || strings.HasPrefix(ex, cand+"/") { + return existing, nil + } + } + return "", rows.Err() +} + // Get retrieves a project by its host_path. Returns ErrNotFound if absent. func Get(ctx context.Context, db *sql.DB, hostPath string) (*Project, error) { row := db.QueryRowContext(ctx, - `SELECT host_path, container_path, languages, settings, stats, status, created_at, updated_at, last_indexed_at + `SELECT host_path, container_path, languages, settings, stats, status, created_at, updated_at, last_indexed_at, indexed_with_model FROM projects WHERE host_path = ?`, hostPath, ) return scanProject(hostPath, row) @@ -158,7 +217,7 @@ func GetByHash(ctx context.Context, db *sql.DB, pathHash string) (*Project, erro // List returns all projects ordered by created_at descending. func List(ctx context.Context, db *sql.DB) ([]Project, error) { rows, err := db.QueryContext(ctx, - `SELECT host_path, container_path, languages, settings, stats, status, created_at, updated_at, last_indexed_at + `SELECT host_path, container_path, languages, settings, stats, status, created_at, updated_at, last_indexed_at, indexed_with_model FROM projects ORDER BY created_at DESC`, ) if err != nil { @@ -218,16 +277,17 @@ func Delete(ctx context.Context, db *sql.DB, hostPath string) error { func scanProject(hostPath string, row *sql.Row) (*Project, error) { var ( - hp, containerPath string - langsJSON, settingsJSON string - statsJSON, status string - createdAt, updatedAt string - lastIndexedAt *string + hp, containerPath string + langsJSON, settingsJSON string + statsJSON, status string + createdAt, updatedAt string + lastIndexedAt *string + indexedWithModel *string ) err := row.Scan( &hp, &containerPath, &langsJSON, &settingsJSON, &statsJSON, - &status, &createdAt, &updatedAt, &lastIndexedAt, + &status, &createdAt, &updatedAt, &lastIndexedAt, &indexedWithModel, ) if errors.Is(err, sql.ErrNoRows) { return nil, fmt.Errorf("%w: %s", ErrNotFound, hostPath) @@ -235,7 +295,7 @@ func scanProject(hostPath string, row *sql.Row) (*Project, error) { if err != nil { return nil, fmt.Errorf("scan project row: %w", err) } - return buildProject(hp, containerPath, langsJSON, settingsJSON, statsJSON, status, createdAt, updatedAt, lastIndexedAt) + return buildProject(hp, containerPath, langsJSON, settingsJSON, statsJSON, status, createdAt, updatedAt, lastIndexedAt, indexedWithModel) } func scanProjectRow(rows *sql.Rows) (*Project, error) { @@ -243,20 +303,21 @@ func scanProjectRow(rows *sql.Rows) (*Project, error) { hostPath, containerPath string langsJSON, settingsJSON string statsJSON, status string - createdAt, updatedAt string + createdAt, updatedAt string lastIndexedAt *string + indexedWithModel *string ) if err := rows.Scan( &hostPath, &containerPath, &langsJSON, &settingsJSON, &statsJSON, - &status, &createdAt, &updatedAt, &lastIndexedAt, + &status, &createdAt, &updatedAt, &lastIndexedAt, &indexedWithModel, ); err != nil { return nil, fmt.Errorf("scan project: %w", err) } - return buildProject(hostPath, containerPath, langsJSON, settingsJSON, statsJSON, status, createdAt, updatedAt, lastIndexedAt) + return buildProject(hostPath, containerPath, langsJSON, settingsJSON, statsJSON, status, createdAt, updatedAt, lastIndexedAt, indexedWithModel) } -func buildProject(hostPath, containerPath, langsJSON, settingsJSON, statsJSON, status, createdAt, updatedAt string, lastIndexedAt *string) (*Project, error) { +func buildProject(hostPath, containerPath, langsJSON, settingsJSON, statsJSON, status, createdAt, updatedAt string, lastIndexedAt, indexedWithModel *string) (*Project, error) { var langs []string if err := json.Unmarshal([]byte(langsJSON), &langs); err != nil { langs = nil @@ -273,14 +334,15 @@ func buildProject(hostPath, containerPath, langsJSON, settingsJSON, statsJSON, s } return &Project{ - HostPath: hostPath, - ContainerPath: containerPath, - Languages: langs, - Settings: settings, - Stats: stats, - Status: status, - CreatedAt: createdAt, - UpdatedAt: updatedAt, - LastIndexedAt: lastIndexedAt, + HostPath: hostPath, + ContainerPath: containerPath, + Languages: langs, + Settings: settings, + Stats: stats, + Status: status, + CreatedAt: createdAt, + UpdatedAt: updatedAt, + LastIndexedAt: lastIndexedAt, + IndexedWithModel: indexedWithModel, }, nil } diff --git a/server/internal/projects/projects_test.go b/server/internal/projects/projects_test.go index aa78e85..9691136 100644 --- a/server/internal/projects/projects_test.go +++ b/server/internal/projects/projects_test.go @@ -85,6 +85,46 @@ func TestCreate_Conflict(t *testing.T) { } } +// TestCreate_RejectsOverlap covers both directions and a few cosmetic +// variants (trailing slash) of the parent/descendant containment check. +// Sibling and string-prefix-but-not-path-prefix cases must succeed — +// otherwise we'd block legitimate adjacent projects. +func TestCreate_RejectsOverlap(t *testing.T) { + cases := []struct { + name string + seed, attempt string + wantOverlapErr bool + }{ + {"new path is descendant", "/repo", "/repo/server", true}, + {"new path is ancestor", "/repo/server", "/repo", true}, + {"deep nesting still caught", "/repo", "/repo/a/b/c/d", true}, + {"trailing slash on seed", "/repo/", "/repo/server", true}, + {"trailing slash on candidate", "/repo", "/repo/server/", true}, + {"sibling is fine", "/repo/server", "/repo/cli", false}, + // "/repo-other" shares "/repo" as a string prefix but NOT as a path + // prefix — must not be rejected. + {"prefix-but-not-path-prefix is fine", "/repo", "/repo-other", false}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + d := openTestDB(t) + ctx := context.Background() + if _, err := Create(ctx, d, CreateRequest{HostPath: tc.seed}); err != nil { + t.Fatalf("seed Create(%q) failed: %v", tc.seed, err) + } + _, err := Create(ctx, d, CreateRequest{HostPath: tc.attempt}) + if tc.wantOverlapErr { + if !errors.Is(err, ErrOverlap) { + t.Fatalf("Create(%q) error = %v, want ErrOverlap", tc.attempt, err) + } + } else if err != nil { + t.Fatalf("Create(%q) failed unexpectedly: %v", tc.attempt, err) + } + }) + } +} + func TestGet_NotFound(t *testing.T) { d := openTestDB(t) ctx := context.Background() diff --git a/server/internal/runtimecfg/runtimecfg.go b/server/internal/runtimecfg/runtimecfg.go new file mode 100644 index 0000000..c9d875f --- /dev/null +++ b/server/internal/runtimecfg/runtimecfg.go @@ -0,0 +1,357 @@ +// Package runtimecfg owns the dashboard-overridable subset of cix-server +// configuration. It sits one layer above package config (which is env-only, +// immutable, loaded once at boot). Resolution order for every field: +// +// db value (runtime_settings row, set via dashboard) → +// env value (CIX_* loaded into config.Config at boot) → +// hardcoded recommended default +// +// A Snapshot also carries a Source map so the dashboard can render a "DB" / +// "Env" / "Recommended" pill next to each field, telling the admin where the +// current value came from. Set replaces the row wholesale (UPSERT id=1) so +// "clear this override" is just sending NULL. +package runtimecfg + +import ( + "context" + "database/sql" + "errors" + "fmt" + "runtime" + "time" + + "github.com/dvcdsys/code-index/server/internal/config" +) + +// Source labels the origin of a resolved value; emitted in Snapshot.Source so +// the UI can render a "DB" / "Env" / "Recommended" pill next to each field. +const ( + SourceDB = "db" + SourceEnv = "env" + SourceRecommended = "recommended" +) + +// FieldEmbeddingModel and friends name the keys in Snapshot.Source. Exported +// so handler/UI code can iterate without copy-pasting string literals. +const ( + FieldEmbeddingModel = "embedding_model" + FieldLlamaCtxSize = "llama_ctx_size" + FieldLlamaNGpuLayers = "llama_n_gpu_layers" + FieldLlamaNThreads = "llama_n_threads" + FieldMaxEmbeddingConcurrency = "max_embedding_concurrency" + FieldLlamaBatchSize = "llama_batch_size" +) + +// Snapshot is a fully-resolved runtime config — every field is populated, no +// pointers / nullables — and the Source map records where each value came +// from. Returned by Get; consumed by main when wiring the embeddings service. +type Snapshot struct { + EmbeddingModel string + LlamaCtxSize int + LlamaNGpuLayers int + LlamaNThreads int + MaxEmbeddingConcurrency int + LlamaBatchSize int + + // Source maps Field* constants to one of SourceDB/SourceEnv/SourceRecommended. + Source map[string]string + + // UpdatedAt is the runtime_settings.updated_at value when at least one + // field came from DB; zero otherwise. + UpdatedAt time.Time + // UpdatedBy is the runtime_settings.updated_by value when at least one + // field came from DB; empty otherwise. + UpdatedBy string +} + +// Patch carries the new values an admin wants to write. nil pointers mean +// "don't change this field" — including "clear the DB override and fall back +// to env / recommended". To clear, send a non-nil pointer to the zero value +// for numeric fields, or a non-nil pointer to "" for the model. The handler +// does the nil/non-nil discrimination from the JSON request body. +type Patch struct { + EmbeddingModel *string + LlamaCtxSize *int + LlamaNGpuLayers *int + LlamaNThreads *int + MaxEmbeddingConcurrency *int + LlamaBatchSize *int +} + +// Service resolves runtime config from the DB, falling through to env-loaded +// config, then to hardcoded recommended values. Safe for concurrent use; the +// underlying *sql.DB pool is the only shared mutable state. +type Service struct { + db *sql.DB + env *config.Config +} + +// New constructs a Service. env is the bootstrap config.Config loaded at +// startup; runtimecfg never mutates it (the env layer is immutable by +// design). +func New(db *sql.DB, env *config.Config) *Service { + return &Service{db: db, env: env} +} + +// Recommended returns the hardcoded fallback Snapshot. These values are the +// project-wide "sensible defaults" — what we'd pick on a clean install with +// no env overrides and no DB row. Used by the UI to show "(Recommended)" +// alongside the current value. +func (s *Service) Recommended() Snapshot { + defaultGpu := 0 + if runtime.GOOS == "darwin" { + defaultGpu = -1 + } + return Snapshot{ + EmbeddingModel: "awhiteside/CodeRankEmbed-Q8_0-GGUF", + LlamaCtxSize: 2048, + LlamaNGpuLayers: defaultGpu, + LlamaNThreads: runtime.NumCPU() / 2, + MaxEmbeddingConcurrency: 5, + LlamaBatchSize: 2048, + Source: map[string]string{}, + } +} + +type dbRow struct { + embeddingModel sql.NullString + llamaCtxSize sql.NullInt64 + llamaNGpuLayers sql.NullInt64 + llamaNThreads sql.NullInt64 + maxEmbeddingConcurrency sql.NullInt64 + llamaBatchSize sql.NullInt64 + updatedAt sql.NullString + updatedBy sql.NullString +} + +func (s *Service) loadRow(ctx context.Context) (dbRow, bool, error) { + var r dbRow + err := s.db.QueryRowContext(ctx, ` + SELECT embedding_model, llama_ctx_size, llama_n_gpu_layers, + llama_n_threads, max_embedding_concurrency, llama_batch_size, + updated_at, updated_by + FROM runtime_settings WHERE id = 1 + `).Scan( + &r.embeddingModel, &r.llamaCtxSize, &r.llamaNGpuLayers, + &r.llamaNThreads, &r.maxEmbeddingConcurrency, &r.llamaBatchSize, + &r.updatedAt, &r.updatedBy, + ) + if errors.Is(err, sql.ErrNoRows) { + return dbRow{}, false, nil + } + if err != nil { + return dbRow{}, false, fmt.Errorf("select runtime_settings: %w", err) + } + return r, true, nil +} + +// Get resolves the current effective Snapshot. Always returns a populated +// Snapshot; a nil DB row simply means every field falls through to env / +// recommended. +func (s *Service) Get(ctx context.Context) (Snapshot, error) { + row, hasRow, err := s.loadRow(ctx) + if err != nil { + return Snapshot{}, err + } + rec := s.Recommended() + out := Snapshot{Source: map[string]string{}} + + // String — embedding model. + switch { + case hasRow && row.embeddingModel.Valid && row.embeddingModel.String != "": + out.EmbeddingModel = row.embeddingModel.String + out.Source[FieldEmbeddingModel] = SourceDB + case s.env != nil && s.env.EmbeddingModel != "": + out.EmbeddingModel = s.env.EmbeddingModel + out.Source[FieldEmbeddingModel] = SourceEnv + default: + out.EmbeddingModel = rec.EmbeddingModel + out.Source[FieldEmbeddingModel] = SourceRecommended + } + + // Numeric fields — pull env value with fallback so the helper handles all + // three layers uniformly. >0 from DB always wins; 0 == "unset". + out.LlamaCtxSize = resolveInt(row.llamaCtxSize, hasRow, envIntOrZero(s.env, "ctx"), rec.LlamaCtxSize, &out.Source, FieldLlamaCtxSize) + out.LlamaNGpuLayers = resolveIntSigned(row.llamaNGpuLayers, hasRow, envIntOrSentinel(s.env, "gpu"), rec.LlamaNGpuLayers, &out.Source, FieldLlamaNGpuLayers) + out.LlamaNThreads = resolveInt(row.llamaNThreads, hasRow, envIntOrZero(s.env, "threads"), rec.LlamaNThreads, &out.Source, FieldLlamaNThreads) + out.MaxEmbeddingConcurrency = resolveInt(row.maxEmbeddingConcurrency, hasRow, envIntOrZero(s.env, "conc"), rec.MaxEmbeddingConcurrency, &out.Source, FieldMaxEmbeddingConcurrency) + out.LlamaBatchSize = resolveInt(row.llamaBatchSize, hasRow, envIntOrZero(s.env, "batch"), rec.LlamaBatchSize, &out.Source, FieldLlamaBatchSize) + + if hasRow { + if row.updatedAt.Valid { + if t, err := time.Parse(time.RFC3339Nano, row.updatedAt.String); err == nil { + out.UpdatedAt = t + } + } + if row.updatedBy.Valid { + out.UpdatedBy = row.updatedBy.String + } + } + return out, nil +} + +// resolveInt picks DB > env > recommended for >0 fields. Used for ctx, +// threads, concurrency, batch — all where 0 is a sentinel meaning "unset". +func resolveInt(dbVal sql.NullInt64, hasRow bool, envVal int, recVal int, src *map[string]string, field string) int { + if hasRow && dbVal.Valid && dbVal.Int64 > 0 { + (*src)[field] = SourceDB + return int(dbVal.Int64) + } + if envVal > 0 { + (*src)[field] = SourceEnv + return envVal + } + (*src)[field] = SourceRecommended + return recVal +} + +// resolveIntSigned is the n_gpu_layers special case: -1 (Metal: all layers) +// and 0 (CPU-only) are both legitimate values, so we treat any *non-NULL* DB +// row as authoritative. Env is authoritative whenever it differs from the +// platform default; recommended is the final fallback. +func resolveIntSigned(dbVal sql.NullInt64, hasRow bool, envVal int, recVal int, src *map[string]string, field string) int { + if hasRow && dbVal.Valid { + (*src)[field] = SourceDB + return int(dbVal.Int64) + } + if envVal != recVal { + (*src)[field] = SourceEnv + return envVal + } + (*src)[field] = SourceRecommended + return recVal +} + +// envIntOrZero pulls a numeric value from the loaded env config or returns 0 +// (the "unset" sentinel resolveInt understands). Centralised here so all +// callers route through one place — easier to keep in sync if config.Config +// gains more fields. +func envIntOrZero(env *config.Config, which string) int { + if env == nil { + return 0 + } + switch which { + case "ctx": + return env.LlamaCtxSize + case "threads": + return env.LlamaNThreads + case "conc": + return env.MaxEmbeddingConcurrency + case "batch": + return env.LlamaBatchSize + } + return 0 +} + +// envIntOrSentinel mirrors envIntOrZero for fields where 0 is a legitimate +// value — currently only n_gpu_layers. Returns the env value verbatim. +func envIntOrSentinel(env *config.Config, which string) int { + if env == nil { + return 0 + } + if which == "gpu" { + return env.LlamaNGpuLayers + } + return 0 +} + +// Set applies a Patch to the runtime_settings row. nil pointer fields are +// preserved. Pointers to zero values (or empty strings) clear the override +// → next Get falls through to env / recommended for that field. +func (s *Service) Set(ctx context.Context, patch Patch, updatedBy string) error { + row, hasRow, err := s.loadRow(ctx) + if err != nil { + return err + } + + // Merge: start with current row (or empty), overlay patch. + merged := row + if patch.EmbeddingModel != nil { + if *patch.EmbeddingModel == "" { + merged.embeddingModel = sql.NullString{} + } else { + merged.embeddingModel = sql.NullString{String: *patch.EmbeddingModel, Valid: true} + } + } + mergeInt(&merged.llamaCtxSize, patch.LlamaCtxSize) + mergeInt(&merged.llamaNGpuLayers, patch.LlamaNGpuLayers) + mergeInt(&merged.llamaNThreads, patch.LlamaNThreads) + mergeInt(&merged.maxEmbeddingConcurrency, patch.MaxEmbeddingConcurrency) + mergeInt(&merged.llamaBatchSize, patch.LlamaBatchSize) + + now := time.Now().UTC().Format(time.RFC3339Nano) + if hasRow { + _, err = s.db.ExecContext(ctx, ` + UPDATE runtime_settings + SET embedding_model = ?, llama_ctx_size = ?, llama_n_gpu_layers = ?, + llama_n_threads = ?, max_embedding_concurrency = ?, llama_batch_size = ?, + updated_at = ?, updated_by = ? + WHERE id = 1 + `, + nullStr(merged.embeddingModel), nullInt(merged.llamaCtxSize), + nullInt(merged.llamaNGpuLayers), nullInt(merged.llamaNThreads), + nullInt(merged.maxEmbeddingConcurrency), nullInt(merged.llamaBatchSize), + now, updatedBy, + ) + } else { + _, err = s.db.ExecContext(ctx, ` + INSERT INTO runtime_settings ( + id, embedding_model, llama_ctx_size, llama_n_gpu_layers, + llama_n_threads, max_embedding_concurrency, llama_batch_size, + updated_at, updated_by + ) VALUES (1, ?, ?, ?, ?, ?, ?, ?, ?) + `, + nullStr(merged.embeddingModel), nullInt(merged.llamaCtxSize), + nullInt(merged.llamaNGpuLayers), nullInt(merged.llamaNThreads), + nullInt(merged.maxEmbeddingConcurrency), nullInt(merged.llamaBatchSize), + now, updatedBy, + ) + } + if err != nil { + return fmt.Errorf("upsert runtime_settings: %w", err) + } + return nil +} + +// mergeInt overlays a patch field onto a NullInt64. nil patch keeps current; +// non-nil patch with zero clears (= NULL); non-nil non-zero sets. +func mergeInt(cur *sql.NullInt64, patch *int) { + if patch == nil { + return + } + if *patch == 0 { + *cur = sql.NullInt64{} + return + } + *cur = sql.NullInt64{Int64: int64(*patch), Valid: true} +} + +func nullStr(v sql.NullString) any { + if !v.Valid { + return nil + } + return v.String +} + +func nullInt(v sql.NullInt64) any { + if !v.Valid { + return nil + } + return v.Int64 +} + +// ApplyTo merges the resolved Snapshot's settings onto a *config.Config so the +// rest of the server (embeddings supervisor, indexer, etc.) reads the +// effective values from one struct. Mutates env in place — callers usually +// pass a freshly-loaded copy at boot. +func (snap Snapshot) ApplyTo(env *config.Config) { + if env == nil { + return + } + env.EmbeddingModel = snap.EmbeddingModel + env.LlamaCtxSize = snap.LlamaCtxSize + env.LlamaNGpuLayers = snap.LlamaNGpuLayers + env.LlamaNThreads = snap.LlamaNThreads + env.MaxEmbeddingConcurrency = snap.MaxEmbeddingConcurrency + env.LlamaBatchSize = snap.LlamaBatchSize +} diff --git a/server/internal/runtimecfg/runtimecfg_test.go b/server/internal/runtimecfg/runtimecfg_test.go new file mode 100644 index 0000000..4baacb5 --- /dev/null +++ b/server/internal/runtimecfg/runtimecfg_test.go @@ -0,0 +1,154 @@ +package runtimecfg + +import ( + "context" + "testing" + + "github.com/dvcdsys/code-index/server/internal/config" + "github.com/dvcdsys/code-index/server/internal/db" +) + +func openTestDB(t *testing.T) *Service { + t.Helper() + d, err := db.Open(":memory:") + if err != nil { + t.Fatalf("db.Open: %v", err) + } + t.Cleanup(func() { d.Close() }) + env := &config.Config{ + EmbeddingModel: "env/model-a", + LlamaCtxSize: 4096, + LlamaNGpuLayers: 8, + LlamaNThreads: 4, + MaxEmbeddingConcurrency: 2, + LlamaBatchSize: 1024, + } + return New(d, env) +} + +// TestGet_NoRow_FallsThroughToEnv covers the "fresh install with env vars +// set, no dashboard overrides yet" path. Every field should be sourced from +// env and the Source map should reflect that uniformly. +func TestGet_NoRow_FallsThroughToEnv(t *testing.T) { + svc := openTestDB(t) + got, err := svc.Get(context.Background()) + if err != nil { + t.Fatalf("Get: %v", err) + } + + if got.EmbeddingModel != "env/model-a" { + t.Errorf("EmbeddingModel = %q, want env/model-a", got.EmbeddingModel) + } + if got.LlamaCtxSize != 4096 || got.LlamaNGpuLayers != 8 || got.LlamaNThreads != 4 { + t.Errorf("numeric env fields not propagated: %+v", got) + } + for _, f := range []string{ + FieldEmbeddingModel, FieldLlamaCtxSize, FieldLlamaNGpuLayers, + FieldLlamaNThreads, FieldMaxEmbeddingConcurrency, FieldLlamaBatchSize, + } { + if got.Source[f] != SourceEnv { + t.Errorf("Source[%s] = %q, want env", f, got.Source[f]) + } + } +} + +// TestGet_FallsThroughToRecommended covers a clean DB + an empty env config. +// Every field should come from Recommended() and the Source map should say so. +func TestGet_FallsThroughToRecommended(t *testing.T) { + d, err := db.Open(":memory:") + if err != nil { + t.Fatalf("db.Open: %v", err) + } + defer d.Close() + svc := New(d, &config.Config{}) // env zero-valued + + got, err := svc.Get(context.Background()) + if err != nil { + t.Fatalf("Get: %v", err) + } + rec := svc.Recommended() + if got.EmbeddingModel != rec.EmbeddingModel { + t.Errorf("EmbeddingModel = %q, want %q", got.EmbeddingModel, rec.EmbeddingModel) + } + if got.LlamaCtxSize != rec.LlamaCtxSize { + t.Errorf("LlamaCtxSize = %d, want %d", got.LlamaCtxSize, rec.LlamaCtxSize) + } + if got.Source[FieldEmbeddingModel] != SourceRecommended { + t.Errorf("Source[model] = %q, want recommended", got.Source[FieldEmbeddingModel]) + } +} + +// TestSet_Get_RoundTrip overlays a partial patch and verifies that: +// - the patched field is now sourced from DB +// - unpatched fields still come from env +// - clearing (zero / "") returns the field to env source on next Get +func TestSet_Get_RoundTrip(t *testing.T) { + svc := openTestDB(t) + ctx := context.Background() + + model := "db/model-override" + ctxSize := 8192 + if err := svc.Set(ctx, Patch{ + EmbeddingModel: &model, + LlamaCtxSize: &ctxSize, + }, "tester"); err != nil { + t.Fatalf("Set: %v", err) + } + + got, err := svc.Get(ctx) + if err != nil { + t.Fatalf("Get: %v", err) + } + if got.EmbeddingModel != model || got.Source[FieldEmbeddingModel] != SourceDB { + t.Errorf("model not from DB after Set: val=%q src=%q", got.EmbeddingModel, got.Source[FieldEmbeddingModel]) + } + if got.LlamaCtxSize != ctxSize || got.Source[FieldLlamaCtxSize] != SourceDB { + t.Errorf("ctx not from DB after Set: val=%d src=%q", got.LlamaCtxSize, got.Source[FieldLlamaCtxSize]) + } + // Untouched field still env. + if got.LlamaNThreads != 4 || got.Source[FieldLlamaNThreads] != SourceEnv { + t.Errorf("untouched threads field shifted source: val=%d src=%q", got.LlamaNThreads, got.Source[FieldLlamaNThreads]) + } + if got.UpdatedBy != "tester" { + t.Errorf("UpdatedBy = %q, want tester", got.UpdatedBy) + } + if got.UpdatedAt.IsZero() { + t.Error("UpdatedAt should be populated after Set") + } + + // Clear the model override (empty string) — should return to env source. + empty := "" + if err := svc.Set(ctx, Patch{EmbeddingModel: &empty}, "tester"); err != nil { + t.Fatalf("Set clear: %v", err) + } + got2, err := svc.Get(ctx) + if err != nil { + t.Fatalf("Get after clear: %v", err) + } + if got2.EmbeddingModel != "env/model-a" || got2.Source[FieldEmbeddingModel] != SourceEnv { + t.Errorf("model didn't fall back to env after clear: val=%q src=%q", got2.EmbeddingModel, got2.Source[FieldEmbeddingModel]) + } + // Other DB-set field (ctx) preserved. + if got2.LlamaCtxSize != ctxSize || got2.Source[FieldLlamaCtxSize] != SourceDB { + t.Errorf("ctx override lost during model clear: val=%d src=%q", got2.LlamaCtxSize, got2.Source[FieldLlamaCtxSize]) + } +} + +// TestApplyTo verifies a Snapshot mutates *config.Config in-place so the +// rest of the server reads the effective values from one struct. +func TestApplyTo(t *testing.T) { + snap := Snapshot{ + EmbeddingModel: "x", + LlamaCtxSize: 1, + LlamaNGpuLayers: 2, + LlamaNThreads: 3, + MaxEmbeddingConcurrency: 4, + LlamaBatchSize: 5, + } + cfg := &config.Config{EmbeddingModel: "old", LlamaCtxSize: 99} + snap.ApplyTo(cfg) + if cfg.EmbeddingModel != "x" || cfg.LlamaCtxSize != 1 || cfg.LlamaNGpuLayers != 2 || + cfg.LlamaNThreads != 3 || cfg.MaxEmbeddingConcurrency != 4 || cfg.LlamaBatchSize != 5 { + t.Errorf("ApplyTo did not overwrite all fields: %+v", cfg) + } +} diff --git a/server/internal/sessions/sessions.go b/server/internal/sessions/sessions.go new file mode 100644 index 0000000..15d4507 --- /dev/null +++ b/server/internal/sessions/sessions.go @@ -0,0 +1,287 @@ +// Package sessions implements the dashboard's cookie-backed login sessions. +// +// A session is identified by a 256-bit random token. The token itself is +// only ever known to the user's browser (it lives in the cix_session +// HttpOnly cookie); the database stores sha256(token) so a leaked snapshot +// cannot be used to impersonate active sessions. Every other API in this +// package speaks the public hash id — generated at Create, returned by +// ListForUser, and accepted by Touch/Delete — so callers do not need to +// hold the raw token after issuing the cookie. +// +// Rolling expiry: every Touch pushes expires_at out by SessionTTL. This +// matches typical browser-tab usage (you stay logged in as long as you +// keep using it) without long-lived bearer tokens. +package sessions + +import ( + "context" + "crypto/rand" + "crypto/sha256" + "database/sql" + "encoding/base64" + "encoding/hex" + "errors" + "fmt" + "time" + + "github.com/dvcdsys/code-index/server/internal/users" +) + +// SessionTTL is the rolling lifetime of a session: how long after the +// last request before the cookie expires. 14 days matches typical +// "stay signed in" behaviour for an internal admin tool. +const SessionTTL = 14 * 24 * time.Hour + +// CookieName is the name of the HTTP cookie carrying the raw session token. +const CookieName = "cix_session" + +var ( + ErrNotFound = errors.New("session not found") + ErrExpired = errors.New("session expired") + ErrDisabled = errors.New("user account is disabled") +) + +// Session is a single login session with its associated user attached. +// ID is the public hash id (sha256-hex of the raw token). The raw token +// is only ever returned by Create — every other call works with the hash. +type Session struct { + ID string + UserID string + CreatedAt time.Time + ExpiresAt time.Time + LastSeenAt time.Time + LastSeenIP string + LastSeenUA string +} + +// Created is the bundle returned by Create: the persisted Session (with +// its public hash id) plus the raw token the caller must put in the +// cookie. The raw token is never persisted — losing it means the user +// can never reuse this session, which is the whole point. +type Created struct { + Session Session + RawToken string +} + +// Service wraps the sessions table. +type Service struct { + DB *sql.DB +} + +// New returns a Service. +func New(db *sql.DB) *Service { return &Service{DB: db} } + +// Create issues a new session for userID. The returned RawToken is what +// must be set in the browser cookie; only sha256(RawToken) hits the DB. +func (s *Service) Create(ctx context.Context, userID, ip, ua string) (Created, error) { + raw, err := newRawToken() + if err != nil { + return Created{}, fmt.Errorf("generate session token: %w", err) + } + hash := HashToken(raw) + now := time.Now().UTC() + exp := now.Add(SessionTTL) + _, err = s.DB.ExecContext(ctx, + `INSERT INTO sessions (id, user_id, created_at, expires_at, last_seen_at, last_seen_ip, last_seen_ua) + VALUES (?, ?, ?, ?, ?, ?, ?)`, + hash, userID, + now.Format(time.RFC3339Nano), + exp.Format(time.RFC3339Nano), + now.Format(time.RFC3339Nano), + nullableString(ip), nullableString(ua), + ) + if err != nil { + return Created{}, fmt.Errorf("insert session: %w", err) + } + return Created{ + Session: Session{ + ID: hash, + UserID: userID, + CreatedAt: now, + ExpiresAt: exp, + LastSeenAt: now, + LastSeenIP: ip, + LastSeenUA: ua, + }, + RawToken: raw, + }, nil +} + +// Get looks up a session by the raw cookie token (the value the browser +// sends back on every request). Internally hashes the token and queries +// by hash. Returns ErrNotFound for unknown tokens, ErrExpired when the +// session has timed out (Get also deletes expired rows opportunistically), +// and ErrDisabled when the user has been disabled since the session was +// created. +func (s *Service) Get(ctx context.Context, rawToken string) (Session, users.User, error) { + if rawToken == "" { + return Session{}, users.User{}, ErrNotFound + } + hash := HashToken(rawToken) + row := s.DB.QueryRowContext(ctx, + `SELECT s.id, s.user_id, s.created_at, s.expires_at, s.last_seen_at, s.last_seen_ip, s.last_seen_ua, + u.email, u.role, u.must_change_password, u.created_at, u.updated_at, u.disabled_at + FROM sessions s + JOIN users u ON u.id = s.user_id + WHERE s.id = ?`, hash) + + var ( + sess Session + ip, ua sql.NullString + createdAt, expiresAt, lastSeenAt string + uEmail, uRole string + uMcp int + uCreatedAt, uUpdatedAt string + uDisabledAt sql.NullString + ) + err := row.Scan( + &sess.ID, &sess.UserID, &createdAt, &expiresAt, &lastSeenAt, &ip, &ua, + &uEmail, &uRole, &uMcp, &uCreatedAt, &uUpdatedAt, &uDisabledAt, + ) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return Session{}, users.User{}, ErrNotFound + } + return Session{}, users.User{}, fmt.Errorf("scan session: %w", err) + } + sess.CreatedAt, _ = time.Parse(time.RFC3339Nano, createdAt) + sess.ExpiresAt, _ = time.Parse(time.RFC3339Nano, expiresAt) + sess.LastSeenAt, _ = time.Parse(time.RFC3339Nano, lastSeenAt) + sess.LastSeenIP = ip.String + sess.LastSeenUA = ua.String + + if time.Now().UTC().After(sess.ExpiresAt) { + // Garbage-collect this row so it can't be re-tried. Best-effort: + // failure to delete is fine, the next GC pass will catch it. + _, _ = s.DB.ExecContext(ctx, `DELETE FROM sessions WHERE id = ?`, sess.ID) + return Session{}, users.User{}, ErrExpired + } + + u := users.User{ + ID: sess.UserID, + Email: uEmail, + Role: uRole, + MustChangePassword: uMcp == 1, + } + u.CreatedAt, _ = time.Parse(time.RFC3339Nano, uCreatedAt) + u.UpdatedAt, _ = time.Parse(time.RFC3339Nano, uUpdatedAt) + if uDisabledAt.Valid { + t, _ := time.Parse(time.RFC3339Nano, uDisabledAt.String) + u.DisabledAt = &t + return Session{}, users.User{}, ErrDisabled + } + return sess, u, nil +} + +// Touch slides expires_at forward by SessionTTL and refreshes the last-seen +// metadata. Called by middleware on every authenticated request; the +// id argument is the public hash id (Session.ID), not the raw token. +func (s *Service) Touch(ctx context.Context, id, ip, ua string) error { + now := time.Now().UTC() + exp := now.Add(SessionTTL) + _, err := s.DB.ExecContext(ctx, + `UPDATE sessions + SET expires_at = ?, last_seen_at = ?, last_seen_ip = ?, last_seen_ua = ? + WHERE id = ?`, + exp.Format(time.RFC3339Nano), + now.Format(time.RFC3339Nano), + nullableString(ip), nullableString(ua), + id, + ) + if err != nil { + return fmt.Errorf("touch session: %w", err) + } + return nil +} + +// Delete removes a single session (logout-from-this-device). The id is +// the public hash id. +func (s *Service) Delete(ctx context.Context, id string) error { + _, err := s.DB.ExecContext(ctx, `DELETE FROM sessions WHERE id = ?`, id) + return err +} + +// DeleteAllForUser wipes every session of a user. +func (s *Service) DeleteAllForUser(ctx context.Context, userID string) error { + _, err := s.DB.ExecContext(ctx, `DELETE FROM sessions WHERE user_id = ?`, userID) + return err +} + +// DeleteAllForUserExcept is like DeleteAllForUser but keeps one session +// alive (typically the one carrying the password-change request itself). +// keepID is the public hash id. +func (s *Service) DeleteAllForUserExcept(ctx context.Context, userID, keepID string) error { + _, err := s.DB.ExecContext(ctx, + `DELETE FROM sessions WHERE user_id = ? AND id != ?`, userID, keepID) + return err +} + +// ListForUser returns active (non-expired) sessions for a user, newest +// first. Used by the Settings page to show "where am I logged in?". +// Session.ID is the public hash id; the raw tokens are never returned. +func (s *Service) ListForUser(ctx context.Context, userID string) ([]Session, error) { + now := time.Now().UTC().Format(time.RFC3339Nano) + rows, err := s.DB.QueryContext(ctx, + `SELECT id, user_id, created_at, expires_at, last_seen_at, last_seen_ip, last_seen_ua + FROM sessions + WHERE user_id = ? AND expires_at > ? + ORDER BY last_seen_at DESC`, userID, now) + if err != nil { + return nil, fmt.Errorf("list sessions: %w", err) + } + defer rows.Close() + var out []Session + for rows.Next() { + var ( + s Session + ip, ua sql.NullString + createdAt, expiresAt, lastSeenAt string + ) + if err := rows.Scan(&s.ID, &s.UserID, &createdAt, &expiresAt, &lastSeenAt, &ip, &ua); err != nil { + return nil, err + } + s.CreatedAt, _ = time.Parse(time.RFC3339Nano, createdAt) + s.ExpiresAt, _ = time.Parse(time.RFC3339Nano, expiresAt) + s.LastSeenAt, _ = time.Parse(time.RFC3339Nano, lastSeenAt) + s.LastSeenIP = ip.String + s.LastSeenUA = ua.String + out = append(out, s) + } + return out, rows.Err() +} + +// GC deletes all expired sessions. Safe to run periodically. Returns the +// number of rows removed. +func (s *Service) GC(ctx context.Context) (int64, error) { + now := time.Now().UTC().Format(time.RFC3339Nano) + res, err := s.DB.ExecContext(ctx, `DELETE FROM sessions WHERE expires_at <= ?`, now) + if err != nil { + return 0, fmt.Errorf("gc sessions: %w", err) + } + n, _ := res.RowsAffected() + return n, nil +} + +// HashToken returns the public id (sha256-hex) for a raw session token. +// Exposed so tests can verify that the cookie value never appears in the +// DB column. +func HashToken(raw string) string { + h := sha256.Sum256([]byte(raw)) + return hex.EncodeToString(h[:]) +} + +// newRawToken returns a fresh 256-bit base64url-encoded random token. +func newRawToken() (string, error) { + var b [32]byte + if _, err := rand.Read(b[:]); err != nil { + return "", err + } + return base64.RawURLEncoding.EncodeToString(b[:]), nil +} + +func nullableString(s string) any { + if s == "" { + return nil + } + return s +} diff --git a/server/internal/sessions/sessions_test.go b/server/internal/sessions/sessions_test.go new file mode 100644 index 0000000..ee79c37 --- /dev/null +++ b/server/internal/sessions/sessions_test.go @@ -0,0 +1,196 @@ +package sessions + +import ( + "context" + "errors" + "strings" + "testing" + "time" + + "github.com/dvcdsys/code-index/server/internal/db" + "github.com/dvcdsys/code-index/server/internal/users" +) + +type fixture struct { + S *Service + UserID string +} + +func newFixture(t *testing.T) fixture { + t.Helper() + d, err := db.Open(":memory:") + if err != nil { + t.Fatalf("open db: %v", err) + } + t.Cleanup(func() { _ = d.Close() }) + usrSvc := users.New(d) + u, err := usrSvc.Create(context.Background(), "a@b.com", "password1234", users.RoleViewer, false) + if err != nil { + t.Fatalf("create user: %v", err) + } + return fixture{S: New(d), UserID: u.ID} +} + +func TestCreateAndGet(t *testing.T) { + f := newFixture(t) + c, err := f.S.Create(context.Background(), f.UserID, "10.0.0.1", "tester/1") + if err != nil { + t.Fatalf("Create: %v", err) + } + got, u, err := f.S.Get(context.Background(), c.RawToken) + if err != nil { + t.Fatalf("Get: %v", err) + } + if got.ID != c.Session.ID { + t.Errorf("Get returned different id: %v vs %v", got.ID, c.Session.ID) + } + if u.ID != f.UserID { + t.Errorf("Get returned different user: %v vs %v", u.ID, f.UserID) + } +} + +// TestCreate_StoresHashNotRawToken proves the headline security property: +// the value the browser sends in the cookie never appears in the sessions +// table. Only sha256(token) is persisted, so a leaked DB snapshot cannot +// be replayed to impersonate active sessions. +func TestCreate_StoresHashNotRawToken(t *testing.T) { + f := newFixture(t) + c, err := f.S.Create(context.Background(), f.UserID, "", "") + if err != nil { + t.Fatalf("Create: %v", err) + } + if c.RawToken == "" || c.Session.ID == "" { + t.Fatalf("Create returned empty token / id") + } + if c.RawToken == c.Session.ID { + t.Fatalf("RawToken and Session.ID must differ (token=%q id=%q)", c.RawToken, c.Session.ID) + } + + var dbID string + if err := f.S.DB.QueryRow(`SELECT id FROM sessions LIMIT 1`).Scan(&dbID); err != nil { + t.Fatalf("query: %v", err) + } + if dbID != c.Session.ID { + t.Errorf("DB id=%q, want hash %q", dbID, c.Session.ID) + } + if dbID == c.RawToken { + t.Errorf("DB stored raw token %q — must store the hash instead", c.RawToken) + } + if strings.Contains(dbID, c.RawToken) { + t.Errorf("DB id %q leaks the raw token %q", dbID, c.RawToken) + } + // Hash must be deterministic and match the public helper. + if HashToken(c.RawToken) != c.Session.ID { + t.Errorf("HashToken(raw) = %q, want Session.ID %q", HashToken(c.RawToken), c.Session.ID) + } +} + +// TestGet_RejectsRawHashLookup ensures an attacker who somehow knows the +// stored hash (e.g. from a leaked DB) cannot use it as the cookie value +// directly — Get hashes its argument before querying, so passing the +// hash hashes it again and finds nothing. +func TestGet_RejectsRawHashLookup(t *testing.T) { + f := newFixture(t) + c, _ := f.S.Create(context.Background(), f.UserID, "", "") + // Lookup with the raw token works. + if _, _, err := f.S.Get(context.Background(), c.RawToken); err != nil { + t.Fatalf("lookup with raw token: %v", err) + } + // Lookup with the stored hash must fail. + if _, _, err := f.S.Get(context.Background(), c.Session.ID); !errors.Is(err, ErrNotFound) { + t.Errorf("Get(hash) err = %v, want ErrNotFound", err) + } +} + +func TestTouch_SlidesExpires(t *testing.T) { + f := newFixture(t) + c, _ := f.S.Create(context.Background(), f.UserID, "", "") + originalExp := c.Session.ExpiresAt + + // Forward time by sleeping a hair so timestamps differ. + time.Sleep(10 * time.Millisecond) + if err := f.S.Touch(context.Background(), c.Session.ID, "127.0.0.1", "after"); err != nil { + t.Fatalf("Touch: %v", err) + } + got, _, _ := f.S.Get(context.Background(), c.RawToken) + if !got.ExpiresAt.After(originalExp) { + t.Errorf("ExpiresAt should slide forward; got %v vs %v", got.ExpiresAt, originalExp) + } + if got.LastSeenIP != "127.0.0.1" || got.LastSeenUA != "after" { + t.Errorf("Touch did not update IP/UA: %v / %v", got.LastSeenIP, got.LastSeenUA) + } +} + +func TestGet_Expired(t *testing.T) { + f := newFixture(t) + c, _ := f.S.Create(context.Background(), f.UserID, "", "") + // Force-expire by writing a past timestamp directly. + if _, err := f.S.DB.Exec(`UPDATE sessions SET expires_at = ? WHERE id = ?`, + time.Now().Add(-time.Hour).UTC().Format(time.RFC3339Nano), c.Session.ID); err != nil { + t.Fatalf("update expires_at: %v", err) + } + if _, _, err := f.S.Get(context.Background(), c.RawToken); !errors.Is(err, ErrExpired) { + t.Errorf("Get on expired session err = %v, want ErrExpired", err) + } +} + +func TestDelete(t *testing.T) { + f := newFixture(t) + c, _ := f.S.Create(context.Background(), f.UserID, "", "") + if err := f.S.Delete(context.Background(), c.Session.ID); err != nil { + t.Fatalf("Delete: %v", err) + } + if _, _, err := f.S.Get(context.Background(), c.RawToken); !errors.Is(err, ErrNotFound) { + t.Errorf("Get after Delete err = %v, want ErrNotFound", err) + } +} + +func TestDeleteAllForUserExcept(t *testing.T) { + f := newFixture(t) + keep, _ := f.S.Create(context.Background(), f.UserID, "", "") + gone1, _ := f.S.Create(context.Background(), f.UserID, "", "") + gone2, _ := f.S.Create(context.Background(), f.UserID, "", "") + + if err := f.S.DeleteAllForUserExcept(context.Background(), f.UserID, keep.Session.ID); err != nil { + t.Fatalf("DeleteAllForUserExcept: %v", err) + } + if _, _, err := f.S.Get(context.Background(), keep.RawToken); err != nil { + t.Errorf("kept session lost: %v", err) + } + for _, g := range []Created{gone1, gone2} { + if _, _, err := f.S.Get(context.Background(), g.RawToken); !errors.Is(err, ErrNotFound) { + t.Errorf("session %v should have been deleted, got err=%v", g.Session.ID, err) + } + } +} + +func TestGC(t *testing.T) { + f := newFixture(t) + live, _ := f.S.Create(context.Background(), f.UserID, "", "") + dead, _ := f.S.Create(context.Background(), f.UserID, "", "") + _, _ = f.S.DB.Exec(`UPDATE sessions SET expires_at = ? WHERE id = ?`, + time.Now().Add(-time.Hour).UTC().Format(time.RFC3339Nano), dead.Session.ID) + + n, err := f.S.GC(context.Background()) + if err != nil { + t.Fatalf("GC: %v", err) + } + if n != 1 { + t.Errorf("GC removed = %d, want 1", n) + } + if _, _, err := f.S.Get(context.Background(), live.RawToken); err != nil { + t.Errorf("live session lost after GC: %v", err) + } +} + +func TestGet_DisabledUser(t *testing.T) { + f := newFixture(t) + c, _ := f.S.Create(context.Background(), f.UserID, "", "") + // Disable the user directly (bypass the LastAdminBlock guard since + // the seeded user is a viewer, not an admin). + now := time.Now().UTC().Format(time.RFC3339Nano) + _, _ = f.S.DB.Exec(`UPDATE users SET disabled_at = ? WHERE id = ?`, now, f.UserID) + if _, _, err := f.S.Get(context.Background(), c.RawToken); !errors.Is(err, ErrDisabled) { + t.Errorf("Get on disabled-user session err = %v, want ErrDisabled", err) + } +} diff --git a/server/internal/users/users.go b/server/internal/users/users.go new file mode 100644 index 0000000..1cf55d6 --- /dev/null +++ b/server/internal/users/users.go @@ -0,0 +1,415 @@ +// Package users implements the dashboard's user-account model: email + +// bcrypt password + role. Replaces the old single-CIX_API_KEY auth, which +// could not distinguish actors. CLI access still flows through Bearer +// tokens, but those tokens now belong to a user via internal/apikeys. +package users + +import ( + "context" + "database/sql" + "errors" + "fmt" + "strings" + "time" + + "github.com/google/uuid" + "golang.org/x/crypto/bcrypt" +) + +// Roles. Kept open-coded (string constants) rather than a typed enum so +// SQL queries and HTTP handlers can compare without import gymnastics. +const ( + RoleAdmin = "admin" + RoleViewer = "viewer" +) + +// BcryptCost is the work factor for password hashing. 12 is the current +// industry default — tunable here without touching call sites if the +// hardware moves. +const BcryptCost = 12 + +var ( + ErrNotFound = errors.New("user not found") + ErrEmailTaken = errors.New("email already in use") + ErrInvalidLogin = errors.New("invalid email or password") + ErrUserDisabled = errors.New("user account is disabled") + ErrLastAdminBlock = errors.New("cannot remove the last active admin") + ErrInvalidRole = errors.New("invalid role") +) + +// User is the row shape returned by Service. password_hash never leaves +// the package — callers only see metadata + the role bit they need. +type User struct { + ID string + Email string + Role string + MustChangePassword bool + CreatedAt time.Time + UpdatedAt time.Time + DisabledAt *time.Time +} + +// Service wraps the users table. Stateless — safe to share across handlers. +type Service struct { + DB *sql.DB +} + +// New returns a Service bound to db. +func New(db *sql.DB) *Service { return &Service{DB: db} } + +// Count returns the total number of users (including disabled). Used by the +// bootstrap path in main.go to decide whether to seed an admin from env. +func (s *Service) Count(ctx context.Context) (int, error) { + var n int + if err := s.DB.QueryRowContext(ctx, `SELECT COUNT(1) FROM users`).Scan(&n); err != nil { + return 0, fmt.Errorf("count users: %w", err) + } + return n, nil +} + +// Create inserts a new user with the given plaintext password (hashed +// here). mustChangePassword=true is the right call for any account whose +// initial password came from somewhere other than the user themselves +// (env bootstrap, admin invite). +func (s *Service) Create(ctx context.Context, email, password, role string, mustChangePassword bool) (User, error) { + email = normalizeEmail(email) + if email == "" { + return User{}, fmt.Errorf("email required") + } + if !validRole(role) { + return User{}, ErrInvalidRole + } + if password == "" { + return User{}, fmt.Errorf("password required") + } + + hash, err := bcrypt.GenerateFromPassword([]byte(password), BcryptCost) + if err != nil { + return User{}, fmt.Errorf("hash password: %w", err) + } + + id := uuid.NewString() + now := time.Now().UTC().Format(time.RFC3339Nano) + mcp := 0 + if mustChangePassword { + mcp = 1 + } + + _, err = s.DB.ExecContext(ctx, + `INSERT INTO users (id, email, password_hash, role, must_change_password, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?)`, + id, email, string(hash), role, mcp, now, now, + ) + if err != nil { + if isUniqueViolation(err) { + return User{}, ErrEmailTaken + } + return User{}, fmt.Errorf("insert user: %w", err) + } + + return s.GetByID(ctx, id) +} + +// GetByID returns a user by id. ErrNotFound when absent. +func (s *Service) GetByID(ctx context.Context, id string) (User, error) { + return s.scanOne(ctx, `WHERE id = ?`, id) +} + +// GetByEmail returns a user by email (case-insensitive). ErrNotFound when absent. +func (s *Service) GetByEmail(ctx context.Context, email string) (User, error) { + return s.scanOne(ctx, `WHERE email = ? COLLATE NOCASE`, normalizeEmail(email)) +} + +// List returns every user, ordered oldest-first (admin UI usually wants +// stable ordering, and created_at is monotonic). +func (s *Service) List(ctx context.Context) ([]User, error) { + rows, err := s.DB.QueryContext(ctx, listSelect+` ORDER BY created_at ASC`) + if err != nil { + return nil, fmt.Errorf("list users: %w", err) + } + defer rows.Close() + var out []User + for rows.Next() { + u, err := scanUserRow(rows) + if err != nil { + return nil, err + } + out = append(out, u) + } + return out, rows.Err() +} + +// UserWithStats decorates User with admin-table aggregates: latest session +// timestamp + counts of non-expired sessions and non-revoked api keys. +type UserWithStats struct { + User + LastLoginAt *time.Time + ActiveSessionsCount int + APIKeysCount int +} + +// ListWithStats returns users joined with three aggregates used by the +// dashboard's admin /users table: last_login_at (= newest session +// created_at), active_sessions_count (= non-expired sessions), +// api_keys_count (= non-revoked api_keys). Performed as one query with +// correlated subqueries; per-user counts mean the n+1 stays inside SQLite. +func (s *Service) ListWithStats(ctx context.Context) ([]UserWithStats, error) { + now := time.Now().UTC().Format(time.RFC3339Nano) + const q = ` + SELECT id, email, role, must_change_password, created_at, updated_at, disabled_at, + (SELECT MAX(created_at) FROM sessions WHERE user_id = users.id), + (SELECT COUNT(1) FROM sessions WHERE user_id = users.id AND expires_at > ?), + (SELECT COUNT(1) FROM api_keys WHERE owner_user_id = users.id AND revoked_at IS NULL) + FROM users ORDER BY created_at ASC` + rows, err := s.DB.QueryContext(ctx, q, now) + if err != nil { + return nil, fmt.Errorf("list users with stats: %w", err) + } + defer rows.Close() + + var out []UserWithStats + for rows.Next() { + var ( + u User + mcp int + createdAt, updatedAt string + disabledAt sql.NullString + lastLogin sql.NullString + activeSessions, apiKeys int + ) + if err := rows.Scan( + &u.ID, &u.Email, &u.Role, &mcp, &createdAt, &updatedAt, &disabledAt, + &lastLogin, &activeSessions, &apiKeys, + ); err != nil { + return nil, err + } + u.MustChangePassword = mcp == 1 + u.CreatedAt, _ = time.Parse(time.RFC3339Nano, createdAt) + u.UpdatedAt, _ = time.Parse(time.RFC3339Nano, updatedAt) + if disabledAt.Valid { + t, _ := time.Parse(time.RFC3339Nano, disabledAt.String) + u.DisabledAt = &t + } + ws := UserWithStats{ + User: u, + ActiveSessionsCount: activeSessions, + APIKeysCount: apiKeys, + } + if lastLogin.Valid { + t, _ := time.Parse(time.RFC3339Nano, lastLogin.String) + ws.LastLoginAt = &t + } + out = append(out, ws) + } + return out, rows.Err() +} + +// Authenticate verifies email+password. Returns ErrInvalidLogin for any +// auth failure (bad password OR missing user) — never leak which one. +// Disabled accounts return ErrUserDisabled. +func (s *Service) Authenticate(ctx context.Context, email, password string) (User, error) { + email = normalizeEmail(email) + row := s.DB.QueryRowContext(ctx, + `SELECT id, password_hash, role, must_change_password, created_at, updated_at, disabled_at, email + FROM users WHERE email = ? COLLATE NOCASE`, email) + + var ( + u User + hash string + mcp int + disabledAt sql.NullString + createdAt string + updatedAt string + emailOut string + ) + if err := row.Scan(&u.ID, &hash, &u.Role, &mcp, &createdAt, &updatedAt, &disabledAt, &emailOut); err != nil { + if errors.Is(err, sql.ErrNoRows) { + // Match the timing of a hash-compare to mitigate user-enumeration + // via response time. CompareHashAndPassword on a known-bad hash + // burns the same cost as a real login. + _ = bcrypt.CompareHashAndPassword([]byte("$2a$12$invalidinvalidinvalidinvalidinvalidinvalidinvalidinvali"), []byte(password)) + return User{}, ErrInvalidLogin + } + return User{}, fmt.Errorf("scan user: %w", err) + } + if disabledAt.Valid { + return User{}, ErrUserDisabled + } + if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)); err != nil { + return User{}, ErrInvalidLogin + } + u.Email = emailOut + u.MustChangePassword = mcp == 1 + u.CreatedAt, _ = time.Parse(time.RFC3339Nano, createdAt) + u.UpdatedAt, _ = time.Parse(time.RFC3339Nano, updatedAt) + if disabledAt.Valid { + t, _ := time.Parse(time.RFC3339Nano, disabledAt.String) + u.DisabledAt = &t + } + return u, nil +} + +// UpdatePassword sets a new password and clears must_change_password. The +// caller is responsible for invalidating any old sessions if desired — +// see internal/sessions DeleteAllForUser. +func (s *Service) UpdatePassword(ctx context.Context, id, newPassword string) error { + if newPassword == "" { + return fmt.Errorf("new password required") + } + hash, err := bcrypt.GenerateFromPassword([]byte(newPassword), BcryptCost) + if err != nil { + return fmt.Errorf("hash password: %w", err) + } + now := time.Now().UTC().Format(time.RFC3339Nano) + res, err := s.DB.ExecContext(ctx, + `UPDATE users SET password_hash = ?, must_change_password = 0, updated_at = ? WHERE id = ?`, + string(hash), now, id) + if err != nil { + return fmt.Errorf("update password: %w", err) + } + if n, _ := res.RowsAffected(); n == 0 { + return ErrNotFound + } + return nil +} + +// SetRole changes a user's role. Refuses to demote the last active admin +// to keep the system reachable. +func (s *Service) SetRole(ctx context.Context, id, role string) error { + if !validRole(role) { + return ErrInvalidRole + } + if role != RoleAdmin { + if err := s.guardLastAdmin(ctx, id); err != nil { + return err + } + } + now := time.Now().UTC().Format(time.RFC3339Nano) + res, err := s.DB.ExecContext(ctx, + `UPDATE users SET role = ?, updated_at = ? WHERE id = ?`, role, now, id) + if err != nil { + return fmt.Errorf("update role: %w", err) + } + if n, _ := res.RowsAffected(); n == 0 { + return ErrNotFound + } + return nil +} + +// SetDisabled flips the disabled flag. Disabled users cannot authenticate. +// Refuses to disable the last active admin (mirrors SetRole's guard). +func (s *Service) SetDisabled(ctx context.Context, id string, disabled bool) error { + if disabled { + if err := s.guardLastAdmin(ctx, id); err != nil { + return err + } + } + now := time.Now().UTC().Format(time.RFC3339Nano) + var res sql.Result + var err error + if disabled { + res, err = s.DB.ExecContext(ctx, + `UPDATE users SET disabled_at = ?, updated_at = ? WHERE id = ?`, now, now, id) + } else { + res, err = s.DB.ExecContext(ctx, + `UPDATE users SET disabled_at = NULL, updated_at = ? WHERE id = ?`, now, id) + } + if err != nil { + return fmt.Errorf("update disabled_at: %w", err) + } + if n, _ := res.RowsAffected(); n == 0 { + return ErrNotFound + } + return nil +} + +// Delete removes a user (cascades to sessions + api_keys via FK). +// Refuses to delete the last active admin. +func (s *Service) Delete(ctx context.Context, id string) error { + if err := s.guardLastAdmin(ctx, id); err != nil { + return err + } + res, err := s.DB.ExecContext(ctx, `DELETE FROM users WHERE id = ?`, id) + if err != nil { + return fmt.Errorf("delete user: %w", err) + } + if n, _ := res.RowsAffected(); n == 0 { + return ErrNotFound + } + return nil +} + +// guardLastAdmin returns ErrLastAdminBlock if id is the only enabled +// admin in the system. Used by demotion / disable / delete to keep at +// least one admin reachable. +func (s *Service) guardLastAdmin(ctx context.Context, id string) error { + u, err := s.GetByID(ctx, id) + if err != nil { + return err + } + if u.Role != RoleAdmin || u.DisabledAt != nil { + return nil + } + var n int + if err := s.DB.QueryRowContext(ctx, + `SELECT COUNT(1) FROM users WHERE role = 'admin' AND disabled_at IS NULL`).Scan(&n); err != nil { + return fmt.Errorf("count admins: %w", err) + } + if n <= 1 { + return ErrLastAdminBlock + } + return nil +} + +// --- helpers --- + +const listSelect = `SELECT id, email, role, must_change_password, created_at, updated_at, disabled_at FROM users` + +func (s *Service) scanOne(ctx context.Context, where string, args ...any) (User, error) { + row := s.DB.QueryRowContext(ctx, listSelect+" "+where, args...) + u, err := scanUserRow(row) + if errors.Is(err, sql.ErrNoRows) { + return User{}, ErrNotFound + } + return u, err +} + +type rowScanner interface { + Scan(dest ...any) error +} + +func scanUserRow(r rowScanner) (User, error) { + var ( + u User + mcp int + createdAt, updatedAt string + disabledAt sql.NullString + ) + if err := r.Scan(&u.ID, &u.Email, &u.Role, &mcp, &createdAt, &updatedAt, &disabledAt); err != nil { + return User{}, err + } + u.MustChangePassword = mcp == 1 + u.CreatedAt, _ = time.Parse(time.RFC3339Nano, createdAt) + u.UpdatedAt, _ = time.Parse(time.RFC3339Nano, updatedAt) + if disabledAt.Valid { + t, _ := time.Parse(time.RFC3339Nano, disabledAt.String) + u.DisabledAt = &t + } + return u, nil +} + +func normalizeEmail(s string) string { return strings.TrimSpace(strings.ToLower(s)) } + +func validRole(r string) bool { return r == RoleAdmin || r == RoleViewer } + +// isUniqueViolation matches modernc.org/sqlite's UNIQUE-constraint error +// without taking a hard import dependency on its error type. The driver +// formats these as "constraint failed: UNIQUE constraint failed: ...". +func isUniqueViolation(err error) bool { + if err == nil { + return false + } + msg := err.Error() + return strings.Contains(msg, "UNIQUE constraint failed") || + strings.Contains(msg, "constraint failed: UNIQUE") +} diff --git a/server/internal/users/users_test.go b/server/internal/users/users_test.go new file mode 100644 index 0000000..d7cc97c --- /dev/null +++ b/server/internal/users/users_test.go @@ -0,0 +1,251 @@ +package users + +import ( + "context" + "database/sql" + "errors" + "strings" + "testing" + "time" + + "github.com/dvcdsys/code-index/server/internal/db" +) + +func newTestService(t *testing.T) *Service { + t.Helper() + d, err := db.Open(":memory:") + if err != nil { + t.Fatalf("open db: %v", err) + } + t.Cleanup(func() { _ = d.Close() }) + return New(d) +} + +func TestCreateAndAuthenticate(t *testing.T) { + s := newTestService(t) + u, err := s.Create(context.Background(), "Alice@Example.com", "supersecret", RoleAdmin, true) + if err != nil { + t.Fatalf("Create: %v", err) + } + if u.Email != "alice@example.com" { + t.Errorf("email not normalised: %q", u.Email) + } + if !u.MustChangePassword { + t.Errorf("MustChangePassword should be true after seeded creation") + } + + got, err := s.Authenticate(context.Background(), "ALICE@example.com", "supersecret") + if err != nil { + t.Fatalf("Authenticate: %v", err) + } + if got.ID != u.ID { + t.Errorf("Authenticate returned different user: %v vs %v", got.ID, u.ID) + } +} + +func TestAuthenticate_Wrong(t *testing.T) { + s := newTestService(t) + _, _ = s.Create(context.Background(), "a@b.com", "rightpassword", RoleViewer, false) + _, err := s.Authenticate(context.Background(), "a@b.com", "wrong") + if !errors.Is(err, ErrInvalidLogin) { + t.Errorf("err = %v, want ErrInvalidLogin", err) + } + _, err = s.Authenticate(context.Background(), "ghost@b.com", "anything") + if !errors.Is(err, ErrInvalidLogin) { + t.Errorf("missing user err = %v, want ErrInvalidLogin (no enumeration)", err) + } +} + +func TestEmailUniqueness(t *testing.T) { + s := newTestService(t) + _, err := s.Create(context.Background(), "a@b.com", "password1", RoleViewer, false) + if err != nil { + t.Fatalf("first Create: %v", err) + } + _, err = s.Create(context.Background(), "A@B.com", "password2", RoleViewer, false) + if !errors.Is(err, ErrEmailTaken) { + t.Errorf("err = %v, want ErrEmailTaken (case-insensitive uniqueness)", err) + } +} + +func TestUpdatePassword_ClearsMustChange(t *testing.T) { + s := newTestService(t) + u, _ := s.Create(context.Background(), "a@b.com", "initial-password", RoleViewer, true) + if err := s.UpdatePassword(context.Background(), u.ID, "newpassword123"); err != nil { + t.Fatalf("UpdatePassword: %v", err) + } + got, err := s.GetByID(context.Background(), u.ID) + if err != nil { + t.Fatalf("GetByID: %v", err) + } + if got.MustChangePassword { + t.Errorf("MustChangePassword should be cleared after UpdatePassword") + } + if _, err := s.Authenticate(context.Background(), "a@b.com", "newpassword123"); err != nil { + t.Errorf("Authenticate with new password: %v", err) + } + if _, err := s.Authenticate(context.Background(), "a@b.com", "initial-password"); !errors.Is(err, ErrInvalidLogin) { + t.Errorf("old password should no longer authenticate, got %v", err) + } +} + +func TestSetRole_LastAdminBlock(t *testing.T) { + s := newTestService(t) + a, _ := s.Create(context.Background(), "a@b.com", "password1", RoleAdmin, false) + if err := s.SetRole(context.Background(), a.ID, RoleViewer); !errors.Is(err, ErrLastAdminBlock) { + t.Errorf("demoting last admin err = %v, want ErrLastAdminBlock", err) + } + // Add a second admin — now demotion of the first must succeed. + _, _ = s.Create(context.Background(), "b@b.com", "password2", RoleAdmin, false) + if err := s.SetRole(context.Background(), a.ID, RoleViewer); err != nil { + t.Errorf("demoting with another admin around: %v", err) + } +} + +func TestSetDisabled_LastAdminBlock(t *testing.T) { + s := newTestService(t) + a, _ := s.Create(context.Background(), "a@b.com", "password1", RoleAdmin, false) + if err := s.SetDisabled(context.Background(), a.ID, true); !errors.Is(err, ErrLastAdminBlock) { + t.Errorf("disabling last admin err = %v, want ErrLastAdminBlock", err) + } +} + +func TestDelete_LastAdminBlock(t *testing.T) { + s := newTestService(t) + a, _ := s.Create(context.Background(), "a@b.com", "password1", RoleAdmin, false) + if err := s.Delete(context.Background(), a.ID); !errors.Is(err, ErrLastAdminBlock) { + t.Errorf("deleting last admin err = %v, want ErrLastAdminBlock", err) + } +} + +func TestInvalidRole(t *testing.T) { + s := newTestService(t) + _, err := s.Create(context.Background(), "a@b.com", "password1", "superadmin", false) + if !errors.Is(err, ErrInvalidRole) { + t.Errorf("Create with bad role err = %v, want ErrInvalidRole", err) + } +} + +func TestList(t *testing.T) { + s := newTestService(t) + for _, em := range []string{"a@b.com", "b@b.com", "c@b.com"} { + _, _ = s.Create(context.Background(), em, "password1234", RoleViewer, false) + } + list, err := s.List(context.Background()) + if err != nil { + t.Fatalf("List: %v", err) + } + if len(list) != 3 { + t.Errorf("list length = %d, want 3", len(list)) + } +} + +// Sanity: the dummy bcrypt-compare in Authenticate's no-row branch must +// not crash. Without this, a missing-user lookup would panic on a bad +// hash. The check is inside the function — we just need to exercise it. +func TestAuthenticate_NoUserDoesNotPanic(t *testing.T) { + s := newTestService(t) + _, err := s.Authenticate(context.Background(), "nobody@example.com", "anything") + if err == nil || !strings.Contains(err.Error(), "invalid email or password") { + t.Errorf("err = %v, want ErrInvalidLogin", err) + } +} + +// TestListWithStats verifies the joined aggregates: last_login_at (newest +// session), active_sessions_count (non-expired only), api_keys_count +// (non-revoked only). All three feed the dashboard's admin /users table. +func TestListWithStats(t *testing.T) { + ctx := context.Background() + s := newTestService(t) + now := time.Now().UTC() + + // Three users: alice (active session + 2 keys, 1 revoked), bob (expired + // session + 1 active key), carol (no sessions, no keys). + alice, err := s.Create(ctx, "alice@b.com", "password1234", RoleAdmin, false) + if err != nil { + t.Fatalf("create alice: %v", err) + } + bob, err := s.Create(ctx, "bob@b.com", "password1234", RoleViewer, false) + if err != nil { + t.Fatalf("create bob: %v", err) + } + carol, err := s.Create(ctx, "carol@b.com", "password1234", RoleViewer, false) + if err != nil { + t.Fatalf("create carol: %v", err) + } + + insertSession := func(userID string, created, expires time.Time) { + _, err := s.DB.ExecContext(ctx, + `INSERT INTO sessions (id, user_id, created_at, expires_at, last_seen_at) + VALUES (?, ?, ?, ?, ?)`, + "sess-"+userID+"-"+created.Format("150405.999999999"), + userID, + created.Format(time.RFC3339Nano), + expires.Format(time.RFC3339Nano), + created.Format(time.RFC3339Nano), + ) + if err != nil { + t.Fatalf("insert session for %s: %v", userID, err) + } + } + insertKey := func(userID, name string, revoked bool) { + var revokedAt sql.NullString + if revoked { + revokedAt = sql.NullString{Valid: true, String: now.Format(time.RFC3339Nano)} + } + _, err := s.DB.ExecContext(ctx, + `INSERT INTO api_keys (id, owner_user_id, name, prefix, hash, created_at, revoked_at) + VALUES (?, ?, ?, ?, ?, ?, ?)`, + "ak-"+userID+"-"+name, userID, name, + "cix_"+name[:4], "hash-"+userID+"-"+name, + now.Format(time.RFC3339Nano), revokedAt, + ) + if err != nil { + t.Fatalf("insert key for %s: %v", userID, err) + } + } + + // Alice: 2 sessions (newer = "now", older = 1 hour ago), both active. + insertSession(alice.ID, now.Add(-1*time.Hour), now.Add(13*24*time.Hour)) + aliceLatest := now.Add(-5 * time.Minute) + insertSession(alice.ID, aliceLatest, now.Add(14*24*time.Hour)) + insertKey(alice.ID, "live1", false) + insertKey(alice.ID, "live2", false) + insertKey(alice.ID, "deadx", true) + + // Bob: 1 expired session, 1 active key. last_login_at still set + // (expired session still counts as a past login event). + bobOnly := now.Add(-30 * 24 * time.Hour) + insertSession(bob.ID, bobOnly, now.Add(-16*24*time.Hour)) + insertKey(bob.ID, "bobok", false) + + // Carol: nothing. + _ = carol + + got, err := s.ListWithStats(ctx) + if err != nil { + t.Fatalf("ListWithStats: %v", err) + } + if len(got) != 3 { + t.Fatalf("len = %d, want 3", len(got)) + } + + by := map[string]UserWithStats{} + for _, u := range got { + by[u.Email] = u + } + + if a := by["alice@b.com"]; a.ActiveSessionsCount != 2 || a.APIKeysCount != 2 || + a.LastLoginAt == nil || !a.LastLoginAt.Equal(aliceLatest.Truncate(time.Nanosecond)) { + t.Errorf("alice stats wrong: sess=%d keys=%d last=%v (want sess=2 keys=2 last=%v)", + a.ActiveSessionsCount, a.APIKeysCount, a.LastLoginAt, aliceLatest) + } + if b := by["bob@b.com"]; b.ActiveSessionsCount != 0 || b.APIKeysCount != 1 || b.LastLoginAt == nil { + t.Errorf("bob stats wrong: sess=%d keys=%d last=%v (want sess=0 keys=1 last set)", + b.ActiveSessionsCount, b.APIKeysCount, b.LastLoginAt) + } + if c := by["carol@b.com"]; c.ActiveSessionsCount != 0 || c.APIKeysCount != 0 || c.LastLoginAt != nil { + t.Errorf("carol stats wrong: sess=%d keys=%d last=%v (want all zero/nil)", + c.ActiveSessionsCount, c.APIKeysCount, c.LastLoginAt) + } +} diff --git a/server/internal/vectorstore/store.go b/server/internal/vectorstore/store.go index 69c124a..e00bcf9 100644 --- a/server/internal/vectorstore/store.go +++ b/server/internal/vectorstore/store.go @@ -63,6 +63,11 @@ func collectionName(projectPath string) string { return fmt.Sprintf("project_%x", h) } +// CollectionName is the exported alias for the per-project chromem-go +// collection identifier. The dashboard's project-detail card uses it to +// resolve the on-disk directory under cfg.DynamicChromaPersistDir(). +func CollectionName(projectPath string) string { return collectionName(projectPath) } + // docID format: "{md5hex(filePath)[:12]}:{startLine}-{endLine}:{idx}" // // The positional `idx` is required because overlapping-window or repeated
    +)) +TableHead.displayName = "TableHead" + +const TableCell = React.forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableCell.displayName = "TableCell" + +const TableCaption = React.forwardRef< + HTMLTableCaptionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
    +)) +TableCaption.displayName = "TableCaption" + +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +} diff --git a/server/dashboard/src/ui/tabs.tsx b/server/dashboard/src/ui/tabs.tsx new file mode 100644 index 0000000..e147b0e --- /dev/null +++ b/server/dashboard/src/ui/tabs.tsx @@ -0,0 +1,53 @@ +import * as React from "react" +import * as TabsPrimitive from "@radix-ui/react-tabs" + +import { cn } from "@/lib/cn" + +const Tabs = TabsPrimitive.Root + +const TabsList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsList.displayName = TabsPrimitive.List.displayName + +const TabsTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName + +const TabsContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsContent.displayName = TabsPrimitive.Content.displayName + +export { Tabs, TabsList, TabsTrigger, TabsContent } diff --git a/server/dashboard/src/ui/tooltip.tsx b/server/dashboard/src/ui/tooltip.tsx new file mode 100644 index 0000000..d64e00c --- /dev/null +++ b/server/dashboard/src/ui/tooltip.tsx @@ -0,0 +1,30 @@ +"use client" + +import * as React from "react" +import * as TooltipPrimitive from "@radix-ui/react-tooltip" + +import { cn } from "@/lib/cn" + +const TooltipProvider = TooltipPrimitive.Provider + +const Tooltip = TooltipPrimitive.Root + +const TooltipTrigger = TooltipPrimitive.Trigger + +const TooltipContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + +)) +TooltipContent.displayName = TooltipPrimitive.Content.displayName + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } diff --git a/server/dashboard/src/vite-env.d.ts b/server/dashboard/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/server/dashboard/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/server/dashboard/tailwind.config.ts b/server/dashboard/tailwind.config.ts new file mode 100644 index 0000000..1bcfab6 --- /dev/null +++ b/server/dashboard/tailwind.config.ts @@ -0,0 +1,91 @@ +import type { Config } from 'tailwindcss'; +import animate from 'tailwindcss-animate'; + +// Notion-like palette — muted neutrals, a single accent. Light theme is the +// default; dark variants are wired but the toggle lands in PR-D. +export default { + darkMode: 'class', + content: ['./index.html', './src/**/*.{ts,tsx}'], + theme: { + container: { + center: true, + padding: '1.5rem', + screens: { '2xl': '1280px' }, + }, + extend: { + colors: { + border: 'hsl(var(--border))', + input: 'hsl(var(--input))', + ring: 'hsl(var(--ring))', + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))', + }, + secondary: { + DEFAULT: 'hsl(var(--secondary))', + foreground: 'hsl(var(--secondary-foreground))', + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))', + }, + accent: { + DEFAULT: 'hsl(var(--accent))', + foreground: 'hsl(var(--accent-foreground))', + }, + destructive: { + DEFAULT: 'hsl(var(--destructive))', + foreground: 'hsl(var(--destructive-foreground))', + }, + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))', + }, + popover: { + DEFAULT: 'hsl(var(--popover))', + foreground: 'hsl(var(--popover-foreground))', + }, + }, + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)', + }, + fontFamily: { + sans: [ + 'ui-sans-serif', + '-apple-system', + 'BlinkMacSystemFont', + 'Segoe UI', + 'Inter', + 'system-ui', + 'sans-serif', + ], + mono: [ + 'ui-monospace', + 'SFMono-Regular', + 'JetBrains Mono', + 'Menlo', + 'monospace', + ], + }, + keyframes: { + 'accordion-down': { + from: { height: '0' }, + to: { height: 'var(--radix-accordion-content-height)' }, + }, + 'accordion-up': { + from: { height: 'var(--radix-accordion-content-height)' }, + to: { height: '0' }, + }, + }, + animation: { + 'accordion-down': 'accordion-down 0.2s ease-out', + 'accordion-up': 'accordion-up 0.2s ease-out', + }, + }, + }, + plugins: [animate], +} satisfies Config; diff --git a/server/dashboard/tsconfig.json b/server/dashboard/tsconfig.json new file mode 100644 index 0000000..2847b19 --- /dev/null +++ b/server/dashboard/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true, + "verbatimModuleSyntax": true, + "esModuleInterop": true, + + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/server/dashboard/tsconfig.node.json b/server/dashboard/tsconfig.node.json new file mode 100644 index 0000000..c75a336 --- /dev/null +++ b/server/dashboard/tsconfig.node.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.ts"] +} diff --git a/server/dashboard/tsconfig.node.tsbuildinfo b/server/dashboard/tsconfig.node.tsbuildinfo new file mode 100644 index 0000000..62c7bf9 --- /dev/null +++ b/server/dashboard/tsconfig.node.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./vite.config.ts"],"version":"5.9.3"} \ No newline at end of file diff --git a/server/dashboard/tsconfig.tsbuildinfo b/server/dashboard/tsconfig.tsbuildinfo new file mode 100644 index 0000000..2599c3e --- /dev/null +++ b/server/dashboard/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/api/client.ts","./src/api/generated.ts","./src/api/types.ts","./src/app/app.tsx","./src/app/footer.tsx","./src/app/shell.tsx","./src/app/sidebar.tsx","./src/app/themeprovider.tsx","./src/app/providers.tsx","./src/auth/authprovider.tsx","./src/auth/bootstrapneededpage.tsx","./src/auth/changepasswordpage.tsx","./src/auth/loginpage.tsx","./src/auth/useauth.ts","./src/lib/cn.ts","./src/lib/editorpreference.ts","./src/lib/formatdate.ts","./src/lib/theme.ts","./src/lib/useserverstatus.ts","./src/modules/registry.ts","./src/modules/types.ts","./src/modules/api-keys/apikeyspage.tsx","./src/modules/api-keys/hooks.ts","./src/modules/api-keys/index.ts","./src/modules/api-keys/components/apikeytable.tsx","./src/modules/api-keys/components/createapikeydialog.tsx","./src/modules/api-keys/components/revokeapikeydialog.tsx","./src/modules/home/homepage.tsx","./src/modules/home/index.ts","./src/modules/projects/projectdetailpage.tsx","./src/modules/projects/projectslistpage.tsx","./src/modules/projects/projectspage.tsx","./src/modules/projects/hooks.ts","./src/modules/projects/index.ts","./src/modules/projects/components/deleteprojectdialog.tsx","./src/modules/projects/components/projectcard.tsx","./src/modules/projects/components/projectinfocard.tsx","./src/modules/search/searchpage.tsx","./src/modules/search/hooks.ts","./src/modules/search/index.ts","./src/modules/search/components/filters.tsx","./src/modules/search/components/resultfilecard.tsx","./src/modules/search/components/resultsnippet.tsx","./src/modules/search/components/searchinput.tsx","./src/modules/server/serverpage.tsx","./src/modules/server/hooks.ts","./src/modules/server/index.ts","./src/modules/server/components/saveandrestartdialog.tsx","./src/modules/server/components/sidecarstatebadge.tsx","./src/modules/server/components/sourcepill.tsx","./src/modules/server/sections/advancedsection.tsx","./src/modules/server/sections/embeddingmodelsection.tsx","./src/modules/server/sections/runtimeparamssection.tsx","./src/modules/server/sections/sidecarsection.tsx","./src/modules/settings/settingspage.tsx","./src/modules/settings/hooks.ts","./src/modules/settings/index.ts","./src/modules/settings/components/changepasswordform.tsx","./src/modules/settings/components/sessionrow.tsx","./src/modules/settings/sections/editorsection.tsx","./src/modules/settings/sections/profilesection.tsx","./src/modules/settings/sections/sessionssection.tsx","./src/modules/settings/sections/themesection.tsx","./src/modules/users/userspage.tsx","./src/modules/users/hooks.ts","./src/modules/users/index.ts","./src/modules/users/components/deleteuserdialog.tsx","./src/modules/users/components/disableuserbutton.tsx","./src/modules/users/components/inviteuserdialog.tsx","./src/modules/users/components/userroleselect.tsx","./src/modules/users/components/userstable.tsx","./src/ui/alert.tsx","./src/ui/badge.tsx","./src/ui/button.tsx","./src/ui/card.tsx","./src/ui/dialog.tsx","./src/ui/input.tsx","./src/ui/label.tsx","./src/ui/radio-group.tsx","./src/ui/scroll-area.tsx","./src/ui/select.tsx","./src/ui/skeleton.tsx","./src/ui/slider.tsx","./src/ui/sonner.tsx","./src/ui/switch.tsx","./src/ui/table.tsx","./src/ui/tabs.tsx","./src/ui/tooltip.tsx"],"version":"5.9.3"} \ No newline at end of file diff --git a/server/dashboard/vite.config.d.ts b/server/dashboard/vite.config.d.ts new file mode 100644 index 0000000..340562a --- /dev/null +++ b/server/dashboard/vite.config.d.ts @@ -0,0 +1,2 @@ +declare const _default: import("vite").UserConfig; +export default _default; diff --git a/server/dashboard/vite.config.js b/server/dashboard/vite.config.js new file mode 100644 index 0000000..8c0170b --- /dev/null +++ b/server/dashboard/vite.config.js @@ -0,0 +1,44 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import path from 'node:path'; +// Vite is told two non-default things: +// 1. base: '/dashboard/' — the Go server mounts the SPA under that prefix +// so all asset URLs need to be rewritten accordingly. +// 2. build.outDir: ../internal/httpapi/dashboard/dist — output lands inside +// the Go embed.FS root so `go build` picks it up automatically. +// +// In dev (`npm run dev`), /api requests are proxied to the running Go server +// on the default cix-server port (21847) so cookie auth works through the +// dev server origin. +export default defineConfig({ + plugins: [react()], + base: '/dashboard/', + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, + server: { + port: 5173, + proxy: { + '/api': { + target: 'http://localhost:21847', + changeOrigin: false, + secure: false, + }, + }, + }, + build: { + outDir: '../internal/httpapi/dashboard/dist', + emptyOutDir: true, + sourcemap: false, + rollupOptions: { + output: { + // Stable hashed names; Go fileserver caches via Cache-Control. + entryFileNames: 'assets/[name]-[hash].js', + chunkFileNames: 'assets/[name]-[hash].js', + assetFileNames: 'assets/[name]-[hash][extname]', + }, + }, + }, +}); diff --git a/server/dashboard/vite.config.ts b/server/dashboard/vite.config.ts new file mode 100644 index 0000000..b1f84a3 --- /dev/null +++ b/server/dashboard/vite.config.ts @@ -0,0 +1,45 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import path from 'node:path'; + +// Vite is told two non-default things: +// 1. base: '/dashboard/' — the Go server mounts the SPA under that prefix +// so all asset URLs need to be rewritten accordingly. +// 2. build.outDir: ../internal/httpapi/dashboard/dist — output lands inside +// the Go embed.FS root so `go build` picks it up automatically. +// +// In dev (`npm run dev`), /api requests are proxied to the running Go server +// on the default cix-server port (21847) so cookie auth works through the +// dev server origin. +export default defineConfig({ + plugins: [react()], + base: '/dashboard/', + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, + server: { + port: 5173, + proxy: { + '/api': { + target: 'http://localhost:21847', + changeOrigin: false, + secure: false, + }, + }, + }, + build: { + outDir: '../internal/httpapi/dashboard/dist', + emptyOutDir: true, + sourcemap: false, + rollupOptions: { + output: { + // Stable hashed names; Go fileserver caches via Cache-Control. + entryFileNames: 'assets/[name]-[hash].js', + chunkFileNames: 'assets/[name]-[hash].js', + assetFileNames: 'assets/[name]-[hash][extname]', + }, + }, + }, +}); diff --git a/server/go.mod b/server/go.mod index e3fe407..c5e8332 100644 --- a/server/go.mod +++ b/server/go.mod @@ -3,20 +3,44 @@ module github.com/dvcdsys/code-index/server go 1.25.9 require ( + github.com/getkin/kin-openapi v0.135.0 github.com/go-chi/chi/v5 v5.2.4 github.com/google/uuid v1.6.0 + github.com/oapi-codegen/runtime v1.4.0 github.com/odvcencio/gotreesitter v0.0.0-20260423084729-38e2b42712f2 github.com/philippgille/chromem-go v0.7.0 + golang.org/x/crypto v0.50.0 modernc.org/sqlite v1.34.1 ) require ( + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect + github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/oapi-codegen/oapi-codegen/v2 v2.7.0 // indirect + github.com/oasdiff/yaml v0.0.9 // indirect + github.com/oasdiff/yaml3 v0.0.9 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - golang.org/x/sys v0.22.0 // indirect + github.com/speakeasy-api/jsonpath v0.6.3 // indirect + github.com/speakeasy-api/openapi v1.19.2 // indirect + github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/mod v0.34.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.43.0 // indirect + golang.org/x/text v0.36.0 // indirect + golang.org/x/tools v0.43.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect modernc.org/libc v1.55.3 // indirect modernc.org/mathutil v1.6.0 // indirect @@ -24,3 +48,5 @@ require ( modernc.org/strutil v1.2.0 // indirect modernc.org/token v1.1.0 // indirect ) + +tool github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen diff --git a/server/go.sum b/server/go.sum index 4cd893c..1debe9e 100644 --- a/server/go.sum +++ b/server/go.sum @@ -1,32 +1,216 @@ +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= +github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58= +github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w= +github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/getkin/kin-openapi v0.135.0 h1:751SjYfbiwqukYuVjwYEIKNfrSwS5YpA7DZnKSwQgtg= +github.com/getkin/kin-openapi v0.135.0/go.mod h1:6dd5FJl6RdX4usBtFBaQhk9q62Yb2J0Mk5IhUO/QqFI= github.com/go-chi/chi/v5 v5.2.4 h1:WtFKPHwlywe8Srng8j2BhOD9312j9cGUxG1SP4V2cR4= github.com/go-chi/chi/v5 v5.2.4/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oapi-codegen/oapi-codegen/v2 v2.7.0 h1:/8daqIYZfwnsHEAZdHUu9m0D5LA+5DoJCP7zLlT5Cs0= +github.com/oapi-codegen/oapi-codegen/v2 v2.7.0/go.mod h1:qzFy6iuobJw/hD1aRILee4G87/ShmhR0xYCwcUtZMCw= +github.com/oapi-codegen/runtime v1.4.0 h1:KLOSFOp7UzkbS7Cs1ms6NBEKYr0WmH2wZG0KKbd2er4= +github.com/oapi-codegen/runtime v1.4.0/go.mod h1:5sw5fxCDmnOzKNYmkVNF8d34kyUeejJEY8HNT2WaPec= +github.com/oasdiff/yaml v0.0.9 h1:zQOvd2UKoozsSsAknnWoDJlSK4lC0mpmjfDsfqNwX48= +github.com/oasdiff/yaml v0.0.9/go.mod h1:8lvhgJG4xiKPj3HN5lDow4jZHPlx1i7dIwzkdAo6oAM= +github.com/oasdiff/yaml3 v0.0.9 h1:rWPrKccrdUm8J0F3sGuU+fuh9+1K/RdJlWF7O/9yw2g= +github.com/oasdiff/yaml3 v0.0.9/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= github.com/odvcencio/gotreesitter v0.0.0-20260423084729-38e2b42712f2 h1:UghQ3CfMxD2blnk/TVD88UOOR+hd4Mv5m5PfjShRmwI= github.com/odvcencio/gotreesitter v0.0.0-20260423084729-38e2b42712f2/go.mod h1:Sx+iYJBfw5xSWkSttLSuFvguJctlH+ma1BTxZ0MPCqo= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/philippgille/chromem-go v0.7.0 h1:4jfvfyKymjKNfGxBUhHUcj1kp7B17NL/I1P+vGh1RvY= github.com/philippgille/chromem-go v0.7.0/go.mod h1:hTd+wGEm/fFPQl7ilfCwQXkgEUxceYh86iIdoKMolPo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/speakeasy-api/jsonpath v0.6.3 h1:c+QPwzAOdrWvzycuc9HFsIZcxKIaWcNpC+xhOW9rJxU= +github.com/speakeasy-api/jsonpath v0.6.3/go.mod h1:2cXloNuQ+RSXi5HTRaeBh7JEmjRXTiaKpFTdZiL7URI= +github.com/speakeasy-api/openapi v1.19.2 h1:md90tE71/M8jS3cuRlsuWP5Aed4xoG5PSRvXeZgCv/M= +github.com/speakeasy-api/openapi v1.19.2/go.mod h1:UfKa7FqE4jgexJZuj51MmdHAFGmDv0Zaw3+yOd81YKU= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk= +github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= +golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= -golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= +golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y= diff --git a/server/internal/apikeys/apikeys.go b/server/internal/apikeys/apikeys.go new file mode 100644 index 0000000..c8a580c --- /dev/null +++ b/server/internal/apikeys/apikeys.go @@ -0,0 +1,361 @@ +// Package apikeys implements named, owner-scoped API keys for CLI/SDK +// access. Replaces the single-CIX_API_KEY model with one row per issued +// key, each tied to a user. Plaintext keys are returned exactly once at +// Generate time; only sha256(key) is persisted, so a stolen DB never +// leaks live credentials. +package apikeys + +import ( + "context" + "crypto/rand" + "crypto/sha256" + "database/sql" + "encoding/base64" + "encoding/hex" + "errors" + "fmt" + "strings" + "time" + + "github.com/google/uuid" + + "github.com/dvcdsys/code-index/server/internal/users" +) + +// KeyPrefix is the recognisable prefix every issued key starts with — +// makes accidental leaks easy to grep for in logs and source control +// (a la GitHub's "ghp_..." pattern). +const KeyPrefix = "cix_" + +// PrefixDisplayLen is how many characters of the full key are stored +// verbatim in the prefix column for UI display ("cix_a1b2c3d4..."). Must +// be small enough that the displayed prefix is not itself sufficient to +// recover the key, but large enough that it's distinguishable in lists. +const PrefixDisplayLen = 12 // KeyPrefix("cix_") + 8 random hex chars + +var ( + ErrNotFound = errors.New("api key not found") + ErrInvalidKey = errors.New("invalid api key") + ErrAlreadyRevoked = errors.New("api key already revoked") + ErrUserDisabled = errors.New("api key owner is disabled") +) + +// ApiKey is the metadata view of a key. The plaintext value is only ever +// returned by Generate (in a separate string) — once issued, the server +// never sees the plaintext again. +type ApiKey struct { + ID string + OwnerUserID string + Name string + Prefix string + CreatedAt time.Time + LastUsedAt *time.Time + LastUsedIP string + LastUsedUA string + RevokedAt *time.Time +} + +// Service wraps the api_keys table. +type Service struct { + DB *sql.DB +} + +// New returns a Service. +func New(db *sql.DB) *Service { return &Service{DB: db} } + +// Generate issues a new key for ownerUserID. Returns (fullKey, ApiKey). +// Save the fullKey somewhere safe NOW — it will not be retrievable +// later. The ApiKey returned has prefix populated for UI display. +func (s *Service) Generate(ctx context.Context, ownerUserID, name string) (string, ApiKey, error) { + name = strings.TrimSpace(name) + if name == "" { + return "", ApiKey{}, fmt.Errorf("api key name required") + } + full, err := newKey() + if err != nil { + return "", ApiKey{}, fmt.Errorf("generate key: %w", err) + } + + id := uuid.NewString() + now := time.Now().UTC().Format(time.RFC3339Nano) + prefix := full[:PrefixDisplayLen] + hash := hashKey(full) + + _, err = s.DB.ExecContext(ctx, + `INSERT INTO api_keys (id, owner_user_id, name, prefix, hash, created_at) + VALUES (?, ?, ?, ?, ?, ?)`, + id, ownerUserID, name, prefix, hash, now, + ) + if err != nil { + return "", ApiKey{}, fmt.Errorf("insert api key: %w", err) + } + + ak, err := s.GetByID(ctx, id) + if err != nil { + return "", ApiKey{}, err + } + return full, ak, nil +} + +// ImportLegacy seeds the table with an externally-provided key value +// (used once at bootstrap to migrate the single CIX_API_KEY env var into +// a real api_keys row). Same hashing as Generate; the only difference is +// that fullKey is supplied by the caller rather than freshly generated. +func (s *Service) ImportLegacy(ctx context.Context, ownerUserID, name, fullKey string) (ApiKey, error) { + name = strings.TrimSpace(name) + if name == "" { + return ApiKey{}, fmt.Errorf("api key name required") + } + if fullKey == "" { + return ApiKey{}, fmt.Errorf("fullKey required") + } + id := uuid.NewString() + now := time.Now().UTC().Format(time.RFC3339Nano) + prefix := fullKey + if len(prefix) > PrefixDisplayLen { + prefix = prefix[:PrefixDisplayLen] + } + hash := hashKey(fullKey) + _, err := s.DB.ExecContext(ctx, + `INSERT INTO api_keys (id, owner_user_id, name, prefix, hash, created_at) + VALUES (?, ?, ?, ?, ?, ?)`, + id, ownerUserID, name, prefix, hash, now) + if err != nil { + return ApiKey{}, fmt.Errorf("insert legacy api key: %w", err) + } + return s.GetByID(ctx, id) +} + +// Authenticate looks up a key by its plaintext value, returning the +// owning user if the key is valid and active. Constant-time within the +// hash compare; we rely on sha256 not bcrypt because keys are 256 bits +// of entropy and brute-forcing the hash is irrelevant. +func (s *Service) Authenticate(ctx context.Context, fullKey string) (users.User, ApiKey, error) { + if !strings.HasPrefix(fullKey, KeyPrefix) { + return users.User{}, ApiKey{}, ErrInvalidKey + } + hash := hashKey(fullKey) + row := s.DB.QueryRowContext(ctx, + `SELECT k.id, k.owner_user_id, k.name, k.prefix, k.created_at, + k.last_used_at, k.last_used_ip, k.last_used_ua, k.revoked_at, + u.email, u.role, u.must_change_password, + u.created_at, u.updated_at, u.disabled_at + FROM api_keys k + JOIN users u ON u.id = k.owner_user_id + WHERE k.hash = ?`, hash) + + var ( + ak ApiKey + createdAt string + lastUsedAt, revokedAt sql.NullString + lastUsedIP, lastUsedUA sql.NullString + uEmail, uRole string + uMcp int + uCreatedAt, uUpdatedAt string + uDisabledAt sql.NullString + ) + err := row.Scan( + &ak.ID, &ak.OwnerUserID, &ak.Name, &ak.Prefix, &createdAt, + &lastUsedAt, &lastUsedIP, &lastUsedUA, &revokedAt, + &uEmail, &uRole, &uMcp, &uCreatedAt, &uUpdatedAt, &uDisabledAt, + ) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return users.User{}, ApiKey{}, ErrInvalidKey + } + return users.User{}, ApiKey{}, fmt.Errorf("scan api key: %w", err) + } + ak.CreatedAt, _ = time.Parse(time.RFC3339Nano, createdAt) + if lastUsedAt.Valid { + t, _ := time.Parse(time.RFC3339Nano, lastUsedAt.String) + ak.LastUsedAt = &t + } + ak.LastUsedIP = lastUsedIP.String + ak.LastUsedUA = lastUsedUA.String + if revokedAt.Valid { + t, _ := time.Parse(time.RFC3339Nano, revokedAt.String) + ak.RevokedAt = &t + return users.User{}, ApiKey{}, ErrInvalidKey + } + + u := users.User{ + ID: ak.OwnerUserID, + Email: uEmail, + Role: uRole, + MustChangePassword: uMcp == 1, + } + u.CreatedAt, _ = time.Parse(time.RFC3339Nano, uCreatedAt) + u.UpdatedAt, _ = time.Parse(time.RFC3339Nano, uUpdatedAt) + if uDisabledAt.Valid { + t, _ := time.Parse(time.RFC3339Nano, uDisabledAt.String) + u.DisabledAt = &t + return users.User{}, ApiKey{}, ErrUserDisabled + } + return u, ak, nil +} + +// GetByID returns one key. ErrNotFound when absent. +func (s *Service) GetByID(ctx context.Context, id string) (ApiKey, error) { + row := s.DB.QueryRowContext(ctx, + `SELECT id, owner_user_id, name, prefix, created_at, + last_used_at, last_used_ip, last_used_ua, revoked_at + FROM api_keys WHERE id = ?`, id) + return scanKeyRow(row) +} + +// ListForOwner returns every key owned by a user, including revoked ones +// (UI fades them out — but the operator should see history). +func (s *Service) ListForOwner(ctx context.Context, ownerUserID string) ([]ApiKey, error) { + rows, err := s.DB.QueryContext(ctx, + `SELECT id, owner_user_id, name, prefix, created_at, + last_used_at, last_used_ip, last_used_ua, revoked_at + FROM api_keys WHERE owner_user_id = ? ORDER BY created_at DESC`, ownerUserID) + if err != nil { + return nil, fmt.Errorf("list api keys: %w", err) + } + defer rows.Close() + return scanKeyRows(rows) +} + +// ListAll is the admin view: every key in the system, newest first. +func (s *Service) ListAll(ctx context.Context) ([]ApiKey, error) { + rows, err := s.DB.QueryContext(ctx, + `SELECT id, owner_user_id, name, prefix, created_at, + last_used_at, last_used_ip, last_used_ua, revoked_at + FROM api_keys ORDER BY created_at DESC`) + if err != nil { + return nil, fmt.Errorf("list all api keys: %w", err) + } + defer rows.Close() + return scanKeyRows(rows) +} + +// CountActiveForOwner returns how many non-revoked keys a user has. +// Used by bootstrap to decide whether to seed an env-imported key. +func (s *Service) CountActiveForOwner(ctx context.Context, ownerUserID string) (int, error) { + var n int + err := s.DB.QueryRowContext(ctx, + `SELECT COUNT(1) FROM api_keys WHERE owner_user_id = ? AND revoked_at IS NULL`, + ownerUserID).Scan(&n) + if err != nil { + return 0, fmt.Errorf("count keys: %w", err) + } + return n, nil +} + +// Revoke marks a key as revoked. Subsequent Authenticate calls fail with +// ErrInvalidKey. Idempotent — re-revoking returns ErrAlreadyRevoked but +// does not modify the row. +func (s *Service) Revoke(ctx context.Context, id string) error { + ak, err := s.GetByID(ctx, id) + if err != nil { + return err + } + if ak.RevokedAt != nil { + return ErrAlreadyRevoked + } + now := time.Now().UTC().Format(time.RFC3339Nano) + _, err = s.DB.ExecContext(ctx, + `UPDATE api_keys SET revoked_at = ? WHERE id = ?`, now, id) + if err != nil { + return fmt.Errorf("revoke api key: %w", err) + } + return nil +} + +// Touch updates the last-used metadata for a key. Called by middleware +// on every successful Bearer auth. +func (s *Service) Touch(ctx context.Context, id, ip, ua string) error { + now := time.Now().UTC().Format(time.RFC3339Nano) + _, err := s.DB.ExecContext(ctx, + `UPDATE api_keys SET last_used_at = ?, last_used_ip = ?, last_used_ua = ? WHERE id = ?`, + now, nullableString(ip), nullableString(ua), id) + if err != nil { + return fmt.Errorf("touch api key: %w", err) + } + return nil +} + +// --- helpers --- + +// newKey returns a fresh `cix_<43 random base64url chars>` token. +// 32 random bytes → 43 base64url chars = 256 bits of entropy. The +// length matches GitHub-class personal-access-token verbosity and +// puts brute-force comfortably out of reach for any attacker, on +// any timescale, even against a non-stretched hash. Older keys +// issued at the previous 192-bit length keep working — the hash +// column is the lookup key, not the length. +func newKey() (string, error) { + var b [32]byte + if _, err := rand.Read(b[:]); err != nil { + return "", err + } + return KeyPrefix + base64.RawURLEncoding.EncodeToString(b[:]), nil +} + +// hashKey returns hex(sha256(fullKey)). SHA-256 is the right +// primitive here — NOT bcrypt/argon2/PBKDF2 — because the pre-image +// is 256 bits of CSPRNG output (server-issued; never user-chosen). +// At that entropy floor, brute-forcing the hash is computationally +// indistinguishable from brute-forcing the underlying random bytes, +// and adding a slow KDF would only tax every authenticated request +// (~25–250 ms each at typical bcrypt costs) without raising the +// security floor a single bit. This is the same pattern GitHub / +// Stripe / AWS use for their API tokens. CodeQL's +// `go/insufficient-password-hash` rule is heuristic and treats any +// SHA-256 over a string-typed value as a password hash — that +// heuristic does not apply to high-entropy machine-issued tokens. +func hashKey(fullKey string) string { + h := sha256.Sum256([]byte(fullKey)) + return hex.EncodeToString(h[:]) +} + +func scanKeyRow(r interface { + Scan(dest ...any) error +}) (ApiKey, error) { + var ( + ak ApiKey + createdAt string + lastUsedAt, revokedAt sql.NullString + lastUsedIP, lastUsedUA sql.NullString + ) + err := r.Scan(&ak.ID, &ak.OwnerUserID, &ak.Name, &ak.Prefix, &createdAt, + &lastUsedAt, &lastUsedIP, &lastUsedUA, &revokedAt) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return ApiKey{}, ErrNotFound + } + return ApiKey{}, err + } + ak.CreatedAt, _ = time.Parse(time.RFC3339Nano, createdAt) + if lastUsedAt.Valid { + t, _ := time.Parse(time.RFC3339Nano, lastUsedAt.String) + ak.LastUsedAt = &t + } + ak.LastUsedIP = lastUsedIP.String + ak.LastUsedUA = lastUsedUA.String + if revokedAt.Valid { + t, _ := time.Parse(time.RFC3339Nano, revokedAt.String) + ak.RevokedAt = &t + } + return ak, nil +} + +func scanKeyRows(rows *sql.Rows) ([]ApiKey, error) { + var out []ApiKey + for rows.Next() { + ak, err := scanKeyRow(rows) + if err != nil { + return nil, err + } + out = append(out, ak) + } + return out, rows.Err() +} + +func nullableString(s string) any { + if s == "" { + return nil + } + return s +} diff --git a/server/internal/apikeys/apikeys_test.go b/server/internal/apikeys/apikeys_test.go new file mode 100644 index 0000000..db10e99 --- /dev/null +++ b/server/internal/apikeys/apikeys_test.go @@ -0,0 +1,173 @@ +package apikeys + +import ( + "context" + "errors" + "strings" + "testing" + "time" + + "github.com/dvcdsys/code-index/server/internal/db" + "github.com/dvcdsys/code-index/server/internal/users" +) + +type fixture struct { + S *Service + UserID string +} + +func newFixture(t *testing.T) fixture { + t.Helper() + d, err := db.Open(":memory:") + if err != nil { + t.Fatalf("open db: %v", err) + } + t.Cleanup(func() { _ = d.Close() }) + usrSvc := users.New(d) + u, err := usrSvc.Create(context.Background(), "a@b.com", "password1234", users.RoleAdmin, false) + if err != nil { + t.Fatalf("create user: %v", err) + } + return fixture{S: New(d), UserID: u.ID} +} + +func TestGenerate_FormatAndAuthenticate(t *testing.T) { + f := newFixture(t) + full, ak, err := f.S.Generate(context.Background(), f.UserID, "my-cli") + if err != nil { + t.Fatalf("Generate: %v", err) + } + if !strings.HasPrefix(full, KeyPrefix) { + t.Errorf("full key %q missing %s prefix", full, KeyPrefix) + } + // Body of the key past the prefix is base64url(32 bytes) = 43 chars + // (256 bits of entropy — GitHub-class). + if len(full)-len(KeyPrefix) != 43 { + t.Errorf("body length = %d, want 43", len(full)-len(KeyPrefix)) + } + if ak.Prefix != full[:PrefixDisplayLen] { + t.Errorf("stored prefix %q does not match key head %q", ak.Prefix, full[:PrefixDisplayLen]) + } + // Re-authenticate with the plaintext value: must round-trip back to + // the same user + key. + u, gotAk, err := f.S.Authenticate(context.Background(), full) + if err != nil { + t.Fatalf("Authenticate: %v", err) + } + if u.ID != f.UserID { + t.Errorf("authenticated as wrong user: %v", u.ID) + } + if gotAk.ID != ak.ID { + t.Errorf("authenticated to wrong key: %v vs %v", gotAk.ID, ak.ID) + } +} + +func TestAuthenticate_BadKey(t *testing.T) { + f := newFixture(t) + cases := []string{ + "", + "not-a-cix-key", + KeyPrefix + "but-too-short", + KeyPrefix + strings.Repeat("x", 32), // right shape, wrong content + } + for _, c := range cases { + if _, _, err := f.S.Authenticate(context.Background(), c); !errors.Is(err, ErrInvalidKey) { + t.Errorf("Authenticate(%q) err = %v, want ErrInvalidKey", c, err) + } + } +} + +func TestRevoke_Blocks(t *testing.T) { + f := newFixture(t) + full, ak, _ := f.S.Generate(context.Background(), f.UserID, "soon-revoked") + if err := f.S.Revoke(context.Background(), ak.ID); err != nil { + t.Fatalf("Revoke: %v", err) + } + if _, _, err := f.S.Authenticate(context.Background(), full); !errors.Is(err, ErrInvalidKey) { + t.Errorf("Authenticate after Revoke err = %v, want ErrInvalidKey", err) + } + // Re-revoke is idempotent. + if err := f.S.Revoke(context.Background(), ak.ID); !errors.Is(err, ErrAlreadyRevoked) { + t.Errorf("re-Revoke err = %v, want ErrAlreadyRevoked", err) + } +} + +func TestTouch(t *testing.T) { + f := newFixture(t) + _, ak, _ := f.S.Generate(context.Background(), f.UserID, "touched") + if ak.LastUsedAt != nil { + t.Fatalf("freshly-issued key should not have LastUsedAt set, got %v", ak.LastUsedAt) + } + time.Sleep(5 * time.Millisecond) + if err := f.S.Touch(context.Background(), ak.ID, "10.0.0.1", "UA/1"); err != nil { + t.Fatalf("Touch: %v", err) + } + got, err := f.S.GetByID(context.Background(), ak.ID) + if err != nil { + t.Fatalf("GetByID: %v", err) + } + if got.LastUsedAt == nil || got.LastUsedIP != "10.0.0.1" || got.LastUsedUA != "UA/1" { + t.Errorf("Touch did not persist metadata: %+v", got) + } +} + +func TestImportLegacy(t *testing.T) { + f := newFixture(t) + const legacy = "my-old-cix-api-key-from-env" + ak, err := f.S.ImportLegacy(context.Background(), f.UserID, "env-bootstrap", legacy) + if err != nil { + t.Fatalf("ImportLegacy: %v", err) + } + // The legacy value doesn't have the cix_ prefix; Authenticate's + // prefix gate should reject it. + if _, _, err := f.S.Authenticate(context.Background(), legacy); !errors.Is(err, ErrInvalidKey) { + t.Errorf("legacy key without cix_ prefix should be rejected by Authenticate, got %v", err) + } + if ak.Name != "env-bootstrap" { + t.Errorf("Name = %q", ak.Name) + } +} + +func TestImportLegacy_RoundTripWhenPrefixed(t *testing.T) { + f := newFixture(t) + // A legacy key that already has the cix_ prefix DOES authenticate — + // covers the upgrade path where a user happened to set CIX_API_KEY=cix_... + const legacy = KeyPrefix + "exact-32-char-body-1234567890ab" + if _, err := f.S.ImportLegacy(context.Background(), f.UserID, "env-bootstrap", legacy); err != nil { + t.Fatalf("ImportLegacy: %v", err) + } + if _, _, err := f.S.Authenticate(context.Background(), legacy); err != nil { + t.Errorf("Authenticate of prefixed legacy key: %v", err) + } +} + +func TestListForOwner(t *testing.T) { + f := newFixture(t) + for i := 0; i < 3; i++ { + _, _, _ = f.S.Generate(context.Background(), f.UserID, "key-"+string(rune('a'+i))) + } + list, err := f.S.ListForOwner(context.Background(), f.UserID) + if err != nil { + t.Fatalf("ListForOwner: %v", err) + } + if len(list) != 3 { + t.Errorf("list length = %d, want 3", len(list)) + } +} + +func TestAuthenticate_DisabledUser(t *testing.T) { + f := newFixture(t) + full, _, _ := f.S.Generate(context.Background(), f.UserID, "soon-disabled") + + // Need a second admin so disabling the first one doesn't trip + // users.guardLastAdmin (apikeys tests don't use the users service + // for the disable, but the fixture user is the only admin, so we + // raw-update disabled_at to bypass that). + now := time.Now().UTC().Format(time.RFC3339Nano) + if _, err := f.S.DB.Exec(`UPDATE users SET disabled_at = ? WHERE id = ?`, now, f.UserID); err != nil { + t.Fatalf("disable user: %v", err) + } + if _, _, err := f.S.Authenticate(context.Background(), full); !errors.Is(err, ErrUserDisabled) { + t.Errorf("Authenticate of disabled-user key err = %v, want ErrUserDisabled", err) + } +} diff --git a/server/internal/config/config.go b/server/internal/config/config.go index f60832f..951d3fb 100644 --- a/server/internal/config/config.go +++ b/server/internal/config/config.go @@ -17,6 +17,12 @@ import ( // the Python server (21847) during parallel PoC rollout. type Config struct { APIKey string + // AuthDisabled, when true, makes the server skip the API-key check on + // every endpoint. Off by default — must be turned on EXPLICITLY via + // CIX_AUTH_DISABLED=true (and also requires CIX_API_KEY to be empty). + // Replaces the older "empty API key = no auth" implicit bypass; the new + // behaviour fails loud when CIX_API_KEY is missing without the flag. + AuthDisabled bool Port int EmbeddingModel string ChromaPersistDir string @@ -35,9 +41,22 @@ type Config struct { LlamaTransport string // CIX_LLAMA_TRANSPORT; "unix" or "tcp". LlamaCtxSize int // CIX_LLAMA_CTX; defaults to MaxChunkTokens + 128 when unset. LlamaNGpuLayers int // CIX_N_GPU_LAYERS; -1 on darwin (Metal all layers), 0 elsewhere. + LlamaNThreads int // CIX_LLAMA_THREADS; CPU thread count for llama-server (--threads). 0 = auto. + LlamaBatchSize int // CIX_LLAMA_BATCH; llama-server logical batch size (-b). 0 = match LlamaCtxSize. LlamaStartupSec int // CIX_LLAMA_STARTUP_TIMEOUT; readiness probe ceiling in seconds. EmbeddingsEnabled bool // CIX_EMBEDDINGS_ENABLED; test hook to bypass sidecar entirely. + // BootstrapGGUFPath, when set, points at a .gguf file outside the cix + // cache that should be imported on first boot — atomically copied into + // `//` so subsequent restarts use + // the cache without re-downloading. Idempotent: if the target file + // already exists, the import is skipped. Source: CIX_BOOTSTRAP_GGUF_PATH. + // + // Typical use: a Docker bind-mounting an existing HF cache file as + // read-only (`/bootstrap/model.gguf:ro`) so the named cix-models volume + // gets seeded once, then the bind can be removed. + BootstrapGGUFPath string + // EmbedIncludePath toggles a path+language+symbol preamble in front of // each chunk before sending it to the embedder. Improves retrieval for // queries whose terms appear in file paths (e.g. "server search handler"), @@ -50,6 +69,15 @@ type Config struct { // not present in the registry are warned-and-ignored at startup. // Source: CIX_LANGUAGES (comma-separated, case-insensitive). Languages []string + + // Dashboard auth bootstrap. When the users table is empty AND both of + // these are set, main.go creates the first admin from these values. + // The user is flagged must_change_password=1 so the env-supplied + // password is never the long-term credential. Both ignored if the + // users table already has rows. Sources: CIX_BOOTSTRAP_ADMIN_EMAIL + + // CIX_BOOTSTRAP_ADMIN_PASSWORD. + BootstrapAdminEmail string + BootstrapAdminPassword string } // ModelSafeName returns the embedding model name normalised for use inside @@ -75,14 +103,25 @@ func (c *Config) DynamicChromaPersistDir() string { // Load reads CIX_* environment variables and returns a populated Config. // Returns an error if a numeric variable is present but unparseable. +// +// Defaults for SQLitePath / ChromaPersistDir resolve to ~/.cix/data/... so a +// fresh `make run` works without any env-file editing. Containers (CUDA + CPU +// Dockerfiles, portainer-stack.yml) override these explicitly with /data/... +// where /data is the persistent volume — no behaviour change in production. func Load() (*Config, error) { c := &Config{ APIKey: getenv("CIX_API_KEY", ""), EmbeddingModel: getenv("CIX_EMBEDDING_MODEL", "awhiteside/CodeRankEmbed-Q8_0-GGUF"), - ChromaPersistDir: getenv("CIX_CHROMA_PERSIST_DIR", "/data/chroma"), - SQLitePath: getenv("CIX_SQLITE_PATH", "/data/sqlite/projects.db"), + ChromaPersistDir: getenv("CIX_CHROMA_PERSIST_DIR", defaultChromaPersistDir()), + SQLitePath: getenv("CIX_SQLITE_PATH", defaultSQLitePath()), } + authOff, err := getenvBool("CIX_AUTH_DISABLED", false) + if err != nil { + return nil, err + } + c.AuthDisabled = authOff + port, err := getenvInt("CIX_PORT", 8001) if err != nil { return nil, err @@ -95,7 +134,7 @@ func Load() (*Config, error) { } c.MaxFileSize = maxFileSize - maxConc, err := getenvInt("CIX_MAX_EMBEDDING_CONCURRENCY", 1) + maxConc, err := getenvInt("CIX_MAX_EMBEDDING_CONCURRENCY", 5) if err != nil { return nil, err } @@ -147,6 +186,25 @@ func Load() (*Config, error) { } c.LlamaNGpuLayers = gpuLayers + // CIX_LLAMA_THREADS: when 0 (default), llama-server picks the count via + // std::thread::hardware_concurrency. The dashboard runtime config can + // override at runtime; an explicit env value still acts as the bootstrap + // default the runtime layer falls back to. + threads, err := getenvInt("CIX_LLAMA_THREADS", 0) + if err != nil { + return nil, err + } + c.LlamaNThreads = threads + + // CIX_LLAMA_BATCH: when 0 (default), the supervisor uses LlamaCtxSize so + // a single chunk fits in one batch (matches the prior --ubatch-size=ctx + // behaviour). Lower values trade throughput for memory. + batch, err := getenvInt("CIX_LLAMA_BATCH", 0) + if err != nil { + return nil, err + } + c.LlamaBatchSize = batch + startup, err := getenvInt("CIX_LLAMA_STARTUP_TIMEOUT", 60) if err != nil { return nil, err @@ -159,6 +217,8 @@ func Load() (*Config, error) { } c.EmbeddingsEnabled = enabled + c.BootstrapGGUFPath = getenv("CIX_BOOTSTRAP_GGUF_PATH", "") + includePath, err := getenvBool("CIX_EMBED_INCLUDE_PATH", true) if err != nil { return nil, err @@ -173,20 +233,33 @@ func Load() (*Config, error) { } } + c.BootstrapAdminEmail = getenv("CIX_BOOTSTRAP_ADMIN_EMAIL", "") + c.BootstrapAdminPassword = getenv("CIX_BOOTSTRAP_ADMIN_PASSWORD", "") + return c, nil } -// Validate sanity-checks the Phase 3 fields and applies the dev-fallback rule -// for CIX_GGUF_PATH. It must be called after Load (main.go invokes it before -// constructing the embeddings service). Returns an error only for values that -// cannot be made safe with a default. +// Validate sanity-checks the Phase 3 fields. It must be called after Load +// (main.go invokes it before constructing the embeddings service). Returns +// an error only for values that cannot be made safe with a default. // -// Dev fallback: when EmbeddingsEnabled is true and GGUFPath is empty, we look -// for `bench/results/reference_gguf_path.txt` relative to the CWD. If present, -// we use its contents as the GGUF path so the parity gate works without the -// developer having to set an env var. This is a deliberate PoC ergonomic — -// it is silent when the file is missing and the HF downloader picks up. +// PR-E removed the implicit `bench/results/reference_gguf_path.txt` dev +// fallback that used to silently stamp `cfg.GGUFPath` here. The dashboard's +// runtime-config UI now requires the operator to pick exactly one of: +// (a) HF repo ID → cix downloads to its own cache, or (b) absolute path → +// used as-is. There is no longer a "magic file the user didn't paste" +// resolution branch — it confused the UI ("No cached models" while the +// sidecar happily ran an out-of-cache GGUF) and made provenance opaque. +// Parity-gate developers must set CIX_GGUF_PATH explicitly (the test-gate +// Makefile target now reads bench/results/reference_gguf_path.txt and +// passes it through as an env var instead of relying on this code path). func (c *Config) Validate() error { + // NOTE: the old "CIX_API_KEY required unless CIX_AUTH_DISABLED" check is + // gone. Auth gating moved into the bootstrap path in main.go: the server + // now refuses to start when the users table is empty AND no + // CIX_BOOTSTRAP_ADMIN_{EMAIL,PASSWORD} were supplied. CIX_API_KEY is now + // optional (it's imported as a legacy api_keys row at first boot if set). + // CIX_AUTH_DISABLED still works and bypasses auth wholesale. if c.LlamaTransport != "unix" && c.LlamaTransport != "tcp" { return fmt.Errorf("CIX_LLAMA_TRANSPORT=%q, must be 'unix' or 'tcp'", c.LlamaTransport) } @@ -196,14 +269,43 @@ func (c *Config) Validate() error { if c.LlamaStartupSec <= 0 { return fmt.Errorf("CIX_LLAMA_STARTUP_TIMEOUT=%d, must be positive", c.LlamaStartupSec) } - if c.EmbeddingsEnabled && c.GGUFPath == "" { - if path := readDevFallbackGGUF(); path != "" { - c.GGUFPath = path - } - } return nil } +// defaultDataDir returns the platform-specific root for runtime data +// (SQLite + chromem-go). Used to build defaults for CIX_SQLITE_PATH and +// CIX_CHROMA_PERSIST_DIR when neither env var is set. +// +// Order of resolution: +// 1. $CIX_DATA_DIR if set — explicit override +// 2. ~/.cix/data when $HOME is resolvable — typical dev case +// 3. /tmp/cix-data when $HOME is missing — fallback for headless / CI +// +// Container deployments (Dockerfile, Dockerfile.cuda, portainer-stack.yml) +// set CIX_SQLITE_PATH and CIX_CHROMA_PERSIST_DIR explicitly to /data/... so +// this function is never reached in production. +func defaultDataDir() string { + if v := os.Getenv("CIX_DATA_DIR"); v != "" { + return v + } + if home, err := os.UserHomeDir(); err == nil { + return filepath.Join(home, ".cix", "data") + } + return filepath.Join(os.TempDir(), "cix-data") +} + +// defaultSQLitePath resolves the local SQLite database path under the +// platform data dir. The `_` suffix from DynamicSQLitePath is appended at +// query time, not here. +func defaultSQLitePath() string { + return filepath.Join(defaultDataDir(), "sqlite", "projects.db") +} + +// defaultChromaPersistDir resolves the local chromem-go persist directory. +func defaultChromaPersistDir() string { + return filepath.Join(defaultDataDir(), "chroma") +} + // defaultGGUFCacheDir chooses a platform-appropriate location for downloaded // GGUF files. We prefer XDG_CACHE_HOME when set (matches Linux conventions), // then fall back to ~/Library/Caches on darwin and ~/.cache elsewhere. @@ -250,28 +352,6 @@ func defaultLlamaSocketPath() string { return filepath.Join(os.TempDir(), fmt.Sprintf("cix-llama-%d.sock", os.Getpid())) } -// readDevFallbackGGUF reads bench/results/reference_gguf_path.txt relative to -// the CWD if it exists. Empty return means "no fallback available"; callers -// then rely on HF download. -func readDevFallbackGGUF() string { - const refFile = "bench/results/reference_gguf_path.txt" - data, err := os.ReadFile(refFile) - if err != nil { - return "" - } - path := strings.TrimSpace(string(data)) - if path == "" { - return "" - } - // Only use the fallback when the file actually exists on disk. Otherwise - // we'd stamp a non-existent path and the supervisor would fail later with - // a less friendly error. - if _, err := os.Stat(path); err != nil { - return "" - } - return path -} - func getenv(key, def string) string { if v, ok := os.LookupEnv(key); ok { return v diff --git a/server/internal/config/config_test.go b/server/internal/config/config_test.go index 7d5e602..bf4ff9d 100644 --- a/server/internal/config/config_test.go +++ b/server/internal/config/config_test.go @@ -1,6 +1,7 @@ package config import ( + "strings" "testing" ) @@ -97,25 +98,88 @@ func TestLoadPhase3Defaults(t *testing.T) { func TestValidateBadTransport(t *testing.T) { unsetAll(t) + // Auth-off so the auth-gate check (which runs first) lets us reach the + // transport check we actually want to exercise. + t.Setenv("CIX_AUTH_DISABLED", "true") t.Setenv("CIX_LLAMA_TRANSPORT", "udp") c, err := Load() if err != nil { t.Fatalf("Load: %v", err) } - if err := c.Validate(); err == nil { - t.Fatal("Validate: expected error for bogus transport") + err = c.Validate() + if err == nil || !strings.Contains(err.Error(), "CIX_LLAMA_TRANSPORT") { + t.Fatalf("Validate: expected transport error, got %v", err) } } func TestValidateBadCtx(t *testing.T) { unsetAll(t) + t.Setenv("CIX_AUTH_DISABLED", "true") t.Setenv("CIX_LLAMA_CTX", "0") c, err := Load() if err != nil { t.Fatalf("Load: %v", err) } - if err := c.Validate(); err == nil { - t.Fatal("Validate: expected error for non-positive ctx") + err = c.Validate() + if err == nil || !strings.Contains(err.Error(), "CIX_LLAMA_CTX") { + t.Fatalf("Validate: expected ctx error, got %v", err) + } +} + +// TestValidate_NoLongerGuardsAuth — the explicit-or-die check on +// CIX_API_KEY moved out of config.Validate when the dashboard branch +// introduced per-user accounts. Auth gating is now main.go's job (it +// refuses to start with an empty users table and no +// CIX_BOOTSTRAP_ADMIN_* env). This test pins down the new permissive +// behaviour so a future revert wouldn't sneak past CI. +func TestValidate_NoLongerGuardsAuth(t *testing.T) { + cases := []struct { + name string + apiKey string + authOff string + }{ + {name: "no key, no flag", apiKey: "", authOff: ""}, + {name: "no key, flag=false", apiKey: "", authOff: "false"}, + {name: "no key, flag=true", apiKey: "", authOff: "true"}, + {name: "key set, no flag", apiKey: "secret"}, + {name: "key set, flag=true", apiKey: "secret", authOff: "true"}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + unsetAll(t) + if tc.apiKey != "" { + t.Setenv("CIX_API_KEY", tc.apiKey) + } + if tc.authOff != "" { + t.Setenv("CIX_AUTH_DISABLED", tc.authOff) + } + c, err := Load() + if err != nil { + t.Fatalf("Load: %v", err) + } + if err := c.Validate(); err != nil { + t.Errorf("Validate must not block on auth fields, got %v", err) + } + }) + } +} + +// TestLoadBootstrapFields ensures the new CIX_BOOTSTRAP_ADMIN_* env vars +// land on the Config. The actual seed-or-skip decision lives in main.go +// where it has access to the users service. +func TestLoadBootstrapFields(t *testing.T) { + unsetAll(t) + t.Setenv("CIX_BOOTSTRAP_ADMIN_EMAIL", "admin@example.com") + t.Setenv("CIX_BOOTSTRAP_ADMIN_PASSWORD", "changeme") + c, err := Load() + if err != nil { + t.Fatalf("Load: %v", err) + } + if c.BootstrapAdminEmail != "admin@example.com" { + t.Errorf("BootstrapAdminEmail = %q", c.BootstrapAdminEmail) + } + if c.BootstrapAdminPassword != "changeme" { + t.Errorf("BootstrapAdminPassword not loaded") } } @@ -154,6 +218,13 @@ func unsetAll(t *testing.T) { "CIX_GGUF_PATH", "CIX_GGUF_CACHE_DIR", "CIX_LLAMA_BIN_DIR", "CIX_LLAMA_SOCKET", "CIX_LLAMA_TRANSPORT", "CIX_LLAMA_CTX", "CIX_N_GPU_LAYERS", "CIX_LLAMA_STARTUP_TIMEOUT", "CIX_EMBEDDINGS_ENABLED", + // Auth gating — without this, a developer's shell with + // CIX_AUTH_DISABLED=true would silently make Validate succeed + // on tests that expect a missing-key failure. + "CIX_AUTH_DISABLED", + // Bootstrap — wipe so the Load tests don't accidentally inherit + // a developer's local bootstrap-admin shell vars. + "CIX_BOOTSTRAP_ADMIN_EMAIL", "CIX_BOOTSTRAP_ADMIN_PASSWORD", } { t.Setenv(k, "sentinel") osUnsetenv(k) diff --git a/server/internal/db/db.go b/server/internal/db/db.go index d9766a5..942ec6e 100644 --- a/server/internal/db/db.go +++ b/server/internal/db/db.go @@ -67,6 +67,15 @@ func Open(path string) (*sql.DB, error) { return nil, fmt.Errorf("migrate path_hash: %w", err) } + // PR-E — add indexed_with_model to projects on pre-PR-E databases. Same + // PRAGMA-table_info pattern as migratePathHash; no backfill (NULL means + // "indexed before drift tracking landed" — UI renders this as Unknown, + // not as a stale-model warning). + if err := migrateIndexedWithModel(db); err != nil { + _ = db.Close() + return nil, fmt.Errorf("migrate indexed_with_model: %w", err) + } + return db, nil } @@ -134,6 +143,42 @@ func migratePathHash(db *sql.DB) error { return nil } +// migrateIndexedWithModel adds projects.indexed_with_model to pre-PR-E +// databases. Idempotent: PRAGMA table_info first; ALTER only if absent. Rows +// stay NULL — the dashboard treats NULL as "indexed before drift tracking +// existed" and renders a neutral Unknown badge rather than the destructive +// drift highlight. +func migrateIndexedWithModel(db *sql.DB) error { + rows, err := db.Query(`PRAGMA table_info(projects)`) + if err != nil { + return fmt.Errorf("table_info: %w", err) + } + have := false + for rows.Next() { + var ( + cid int + name, typ string + notnull, pk int + dflt sql.NullString + ) + if err := rows.Scan(&cid, &name, &typ, ¬null, &dflt, &pk); err != nil { + rows.Close() + return err + } + if name == "indexed_with_model" { + have = true + } + } + rows.Close() + if have { + return nil + } + if _, err := db.Exec(`ALTER TABLE projects ADD COLUMN indexed_with_model TEXT`); err != nil { + return fmt.Errorf("add indexed_with_model column: %w", err) + } + return nil +} + // HashHostPath returns the 16-char SHA1 prefix used as the URL segment for // projects. Exported so projects.Create and the migration share one // implementation (keep it byte-identical to projects.HashPath). diff --git a/server/internal/db/db_test.go b/server/internal/db/db_test.go index 9d99114..293ec35 100644 --- a/server/internal/db/db_test.go +++ b/server/internal/db/db_test.go @@ -134,6 +134,73 @@ func TestOpenMigratesPreM7DB(t *testing.T) { } } +// TestOpenMigratesPreEDB simulates a pre-PR-E database (projects table without +// indexed_with_model column, no runtime_settings table) and verifies Open +// migrates it cleanly + the new column is queryable on existing rows. +func TestOpenMigratesPreEDB(t *testing.T) { + tmp := filepath.Join(t.TempDir(), "pre-e.db") + + // Stage a pre-PR-E projects table that already has path_hash (post-m7) + // but lacks indexed_with_model. No runtime_settings table at all. + seed, err := sql.Open(DriverName, "file:"+tmp) + if err != nil { + t.Fatalf("seed Open: %v", err) + } + if _, err := seed.Exec(`CREATE TABLE projects ( + host_path TEXT PRIMARY KEY, + container_path TEXT NOT NULL, + languages TEXT DEFAULT '[]', + settings TEXT DEFAULT '{}', + stats TEXT DEFAULT '{}', + status TEXT DEFAULT 'created', + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, + last_indexed_at TEXT, + path_hash TEXT + )`); err != nil { + t.Fatalf("seed CREATE TABLE: %v", err) + } + if _, err := seed.Exec( + `INSERT INTO projects (host_path, container_path, created_at, updated_at, path_hash) + VALUES ('/legacy/proj', '/legacy/proj', '2024-01-01', '2024-01-01', 'abc')`, + ); err != nil { + t.Fatalf("seed INSERT: %v", err) + } + seed.Close() + + database, err := Open(tmp) + if err != nil { + t.Fatalf("Open migrates pre-PR-E DB: %v", err) + } + defer database.Close() + defer os.Remove(tmp) + + // indexed_with_model column exists and is queryable. Pre-existing rows + // must stay NULL — UI relies on this to render the neutral "Unknown" + // badge instead of the destructive drift highlight. + var model sql.NullString + if err := database.QueryRow( + `SELECT indexed_with_model FROM projects WHERE host_path = ?`, "/legacy/proj", + ).Scan(&model); err != nil { + t.Fatalf("select indexed_with_model: %v", err) + } + if model.Valid { + t.Errorf("legacy row indexed_with_model = %q, want NULL", model.String) + } + + // runtime_settings table exists with the single-row CHECK in place. + if _, err := database.Exec( + `INSERT INTO runtime_settings (id, embedding_model, updated_at) VALUES (1, 'foo', '2026-01-01')`, + ); err != nil { + t.Fatalf("runtime_settings insert: %v", err) + } + if _, err := database.Exec( + `INSERT INTO runtime_settings (id, embedding_model, updated_at) VALUES (2, 'bar', '2026-01-01')`, + ); err == nil { + t.Error("expected CHECK(id=1) violation on second row, got nil") + } +} + func TestSymbolsIndexExists(t *testing.T) { database, err := Open(":memory:") if err != nil { diff --git a/server/internal/db/schema.go b/server/internal/db/schema.go index 78f2f30..34b9910 100644 --- a/server/internal/db/schema.go +++ b/server/internal/db/schema.go @@ -18,7 +18,12 @@ CREATE TABLE IF NOT EXISTS projects ( -- O(n) GetByHash scan with an O(log n) index lookup. Computed in Go on -- insert; the column is nullable here so migrating databases can backfill -- lazily via Open's ALTER+UPDATE hook. - path_hash TEXT + path_hash TEXT, + -- indexed_with_model is the embedding model identifier active when the + -- project was last indexed. NULL on legacy rows (pre-PR-E) until next + -- reindex. Compared against the live runtime model to surface a "stale + -- model" badge on the dashboard project list. + indexed_with_model TEXT ); -- NOTE: CREATE INDEX on path_hash is intentionally NOT here. Pre-m7 databases @@ -82,6 +87,68 @@ CREATE TABLE IF NOT EXISTS index_runs ( error_message TEXT, FOREIGN KEY (project_path) REFERENCES projects(host_path) ON DELETE CASCADE ); + +-- Dashboard auth: users/sessions/api_keys. +-- Added in the dashboard branch when the single-CIX_API_KEY model was +-- replaced with per-user accounts and named API keys. Old deployments are +-- still expected to come up cleanly: the bootstrap flow in main.go creates +-- the first admin from CIX_BOOTSTRAP_ADMIN_{EMAIL,PASSWORD} on a fresh DB. +CREATE TABLE IF NOT EXISTS users ( + id TEXT PRIMARY KEY, + email TEXT NOT NULL COLLATE NOCASE, + password_hash TEXT NOT NULL, + role TEXT NOT NULL DEFAULT 'viewer', + must_change_password INTEGER NOT NULL DEFAULT 0, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, + disabled_at TEXT +); +CREATE UNIQUE INDEX IF NOT EXISTS idx_users_email ON users(email COLLATE NOCASE); + +CREATE TABLE IF NOT EXISTS sessions ( + id TEXT PRIMARY KEY, + user_id TEXT NOT NULL, + created_at TEXT NOT NULL, + expires_at TEXT NOT NULL, + last_seen_at TEXT NOT NULL, + last_seen_ip TEXT, + last_seen_ua TEXT, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE +); +CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id); +CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at); + +CREATE TABLE IF NOT EXISTS api_keys ( + id TEXT PRIMARY KEY, + owner_user_id TEXT NOT NULL, + name TEXT NOT NULL, + prefix TEXT NOT NULL, + hash TEXT NOT NULL UNIQUE, + created_at TEXT NOT NULL, + last_used_at TEXT, + last_used_ip TEXT, + last_used_ua TEXT, + revoked_at TEXT, + FOREIGN KEY (owner_user_id) REFERENCES users(id) ON DELETE CASCADE +); +CREATE INDEX IF NOT EXISTS idx_apikeys_owner ON api_keys(owner_user_id); +CREATE INDEX IF NOT EXISTS idx_apikeys_hash ON api_keys(hash); + +-- runtime_settings holds the single dashboard-overridable runtime config row +-- (PR-E). NULL columns mean "fall through to env / recommended". The +-- CHECK(id=1) constraint enforces a single-row table; UPSERT uses +-- INSERT OR REPLACE on id=1. +CREATE TABLE IF NOT EXISTS runtime_settings ( + id INTEGER PRIMARY KEY CHECK(id=1), + embedding_model TEXT, + llama_ctx_size INTEGER, + llama_n_gpu_layers INTEGER, + llama_n_threads INTEGER, + max_embedding_concurrency INTEGER, + llama_batch_size INTEGER, + updated_at TEXT NOT NULL, + updated_by TEXT +); ` // ExpectedTables lists the tables the schema creates. Used by db_test and by @@ -92,4 +159,8 @@ var ExpectedTables = []string{ "symbols", "refs", "index_runs", + "users", + "sessions", + "api_keys", + "runtime_settings", } diff --git a/server/internal/embeddings/bootstrap_test.go b/server/internal/embeddings/bootstrap_test.go new file mode 100644 index 0000000..455df72 --- /dev/null +++ b/server/internal/embeddings/bootstrap_test.go @@ -0,0 +1,115 @@ +package embeddings + +import ( + "io/fs" + "log/slog" + "os" + "path/filepath" + "testing" +) + +// TestImportBootstrapGGUF_HappyPath covers the typical Docker scenario: a +// fresh cix-models volume + a bind-mounted source GGUF outside the cache. +// First import copies the file into the cache layout; the second call is +// a no-op (idempotent) and returns the existing path. +func TestImportBootstrapGGUF_HappyPath(t *testing.T) { + tmp := t.TempDir() + cacheDir := filepath.Join(tmp, "cache") + srcPath := filepath.Join(tmp, "src", "model.gguf") + + if err := os.MkdirAll(filepath.Dir(srcPath), 0o755); err != nil { + t.Fatalf("mkdir src dir: %v", err) + } + payload := []byte("not really a gguf, but bytes are bytes") + if err := os.WriteFile(srcPath, payload, 0o644); err != nil { + t.Fatalf("write src: %v", err) + } + + repo := "owner/repo-Q8" + logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) + + got, err := importBootstrapGGUF(cacheDir, repo, srcPath, logger) + if err != nil { + t.Fatalf("import: %v", err) + } + want := filepath.Join(cacheDir, "owner__repo-Q8", "model.gguf") + if got != want { + t.Fatalf("path = %q, want %q", got, want) + } + if data, err := os.ReadFile(got); err != nil { + t.Fatalf("read imported: %v", err) + } else if string(data) != string(payload) { + t.Errorf("imported file content mismatch") + } + + // Second import = no-op: target already exists, must return same path. + got2, err := importBootstrapGGUF(cacheDir, repo, srcPath, logger) + if err != nil { + t.Fatalf("second import: %v", err) + } + if got2 != want { + t.Errorf("second call path = %q, want %q", got2, want) + } + + // And no `.partial` file should be left around. + matches, _ := filepath.Glob(filepath.Join(cacheDir, "owner__repo-Q8", "*.partial")) + if len(matches) > 0 { + t.Errorf("leftover partials: %v", matches) + } +} + +// TestImportBootstrapGGUF_MissingSource — a missing source isn't an error; +// it returns ("", nil) so resolveGGUFPath can fall through to HF download. +// This matches the "operator set the env optimistically" use case. +func TestImportBootstrapGGUF_MissingSource(t *testing.T) { + cacheDir := t.TempDir() + got, err := importBootstrapGGUF(cacheDir, "owner/repo", filepath.Join(cacheDir, "missing.gguf"), slog.Default()) + if err != nil { + t.Fatalf("missing source should not error: %v", err) + } + if got != "" { + t.Errorf("got %q, want empty (so caller falls through to download)", got) + } +} + +// TestImportBootstrapGGUF_DirectoryRejected — passing a directory is a +// configuration mistake; we surface it loud so the operator notices. +func TestImportBootstrapGGUF_DirectoryRejected(t *testing.T) { + cacheDir := t.TempDir() + srcDir := filepath.Join(cacheDir, "not-a-file") + _ = os.MkdirAll(srcDir, 0o755) + _, err := importBootstrapGGUF(cacheDir, "owner/repo", srcDir, slog.Default()) + if err == nil { + t.Fatal("expected error for directory source, got nil") + } +} + +// TestImportBootstrapGGUF_PreservesContents ensures we don't truncate or +// corrupt the file mid-copy. Uses a 1 MiB payload to exercise the io.Copy +// loop multiple times. +func TestImportBootstrapGGUF_PreservesContents(t *testing.T) { + tmp := t.TempDir() + srcPath := filepath.Join(tmp, "big.gguf") + const size = 1 << 20 // 1 MiB + buf := make([]byte, size) + for i := range buf { + buf[i] = byte(i % 251) + } + if err := os.WriteFile(srcPath, buf, 0o644); err != nil { + t.Fatalf("write src: %v", err) + } + got, err := importBootstrapGGUF(filepath.Join(tmp, "cache"), "x/y", srcPath, slog.Default()) + if err != nil { + t.Fatalf("import: %v", err) + } + info, err := os.Stat(got) + if err != nil { + t.Fatalf("stat target: %v", err) + } + if info.Size() != size { + t.Errorf("size = %d, want %d", info.Size(), size) + } + if info.Mode()&fs.ModeType != 0 { + t.Errorf("target is not a regular file: mode=%v", info.Mode()) + } +} diff --git a/server/internal/embeddings/queue.go b/server/internal/embeddings/queue.go index 5e00be6..479233c 100644 --- a/server/internal/embeddings/queue.go +++ b/server/internal/embeddings/queue.go @@ -3,6 +3,7 @@ package embeddings import ( "context" "sync" + "sync/atomic" "time" ) @@ -28,9 +29,17 @@ type Queue struct { slots chan struct{} timeout time.Duration - mu sync.Mutex - avgBatchSec float64 - estFinishAtMs int64 // unix millis; 0 when no batch is in flight + mu sync.Mutex + avgBatchSec float64 + estFinishAtMs int64 // unix millis; 0 when no batch is in flight + + // blocked is set by BlockNew to make Acquire fail fast with ErrBusy. Used + // during a sidecar Restart so a queued caller doesn't get its request + // dispatched against a child process that's about to be killed. + blocked atomic.Bool + // inFlight is incremented by Acquire and decremented by Release. WaitDrain + // polls this to know when the queue has fully quiesced. + inFlight atomic.Int64 } // NewQueue constructs a queue with the given max concurrency and acquire @@ -50,7 +59,14 @@ func NewQueue(concurrency int, timeout time.Duration) *Queue { // Acquire blocks until a slot is free, the context is cancelled, or the // per-queue timeout fires. On timeout it returns *ErrBusy with a RetryAfter // hint derived from the EMA — callers surface this as HTTP 503. +// +// When the queue is in BlockNew state (Service.Restart drain), Acquire fails +// fast with ErrBusy so the dashboard's Save & Restart doesn't deadlock +// against a queue full of waiting callers. func (q *Queue) Acquire(ctx context.Context) error { + if q.blocked.Load() { + return &ErrBusy{RetryAfter: minRetryAfterSec} + } var ( cancel context.CancelFunc qctx = ctx @@ -64,6 +80,7 @@ func (q *Queue) Acquire(ctx context.Context) error { // so the busy response can tell clients roughly how long to wait. select { case q.slots <- struct{}{}: + q.inFlight.Add(1) q.markBatchStart() return nil case <-qctx.Done(): @@ -83,9 +100,45 @@ func (q *Queue) Acquire(ctx context.Context) error { // is caught in tests rather than silently leaking slots. func (q *Queue) Release(start time.Time) { <-q.slots + q.inFlight.Add(-1) q.updateEMA(time.Since(start)) } +// BlockNew puts the queue in drain mode: subsequent Acquire calls fail fast +// with ErrBusy. Idempotent. Used by Service.Restart so a sidecar swap can +// proceed without contending with new HTTP-driven embed requests. +func (q *Queue) BlockNew() { q.blocked.Store(true) } + +// Resume lifts the BlockNew gate so Acquire works again. Idempotent. +func (q *Queue) Resume() { q.blocked.Store(false) } + +// WaitDrain blocks until in-flight Acquire holders all release their slot, or +// ctx fires. Returns ctx.Err() on timeout — caller can decide whether to +// proceed with the restart anyway (forcing in-flight calls to fail mid-call) +// or abort. Polls every 50ms; cheap because in-flight is an atomic counter. +func (q *Queue) WaitDrain(ctx context.Context) error { + if q.inFlight.Load() == 0 { + return nil + } + t := time.NewTicker(50 * time.Millisecond) + defer t.Stop() + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-t.C: + if q.inFlight.Load() == 0 { + return nil + } + } + } +} + +// InFlight reports the current number of acquired-but-not-released slots. +// Exposed for the sidecar status payload so the dashboard can render +// "draining (N in flight)" during a restart. +func (q *Queue) InFlight() int { return int(q.inFlight.Load()) } + // EstimatedWaitSec returns the EMA-based wait estimate. Exposed for tests and // for debug endpoints that want to surface queue health. func (q *Queue) EstimatedWaitSec() float64 { diff --git a/server/internal/embeddings/queue_test.go b/server/internal/embeddings/queue_test.go index 85f7b5c..f3111e6 100644 --- a/server/internal/embeddings/queue_test.go +++ b/server/internal/embeddings/queue_test.go @@ -150,3 +150,85 @@ func TestNewQueueClampsConcurrency(t *testing.T) { t.Errorf("slots cap = %d, want 1", cap(q.slots)) } } + +// TestQueueBlockNew_RejectsNewAcquires covers the PR-E sidecar restart drain +// path: BlockNew makes Acquire fail fast with ErrBusy so an admin's Save & +// Restart doesn't deadlock against a backlog of waiting search calls. +func TestQueueBlockNew_RejectsNewAcquires(t *testing.T) { + q := NewQueue(2, time.Second) + q.BlockNew() + err := q.Acquire(context.Background()) + var busy *ErrBusy + if !errors.As(err, &busy) { + t.Fatalf("Acquire after BlockNew = %v, want ErrBusy", err) + } + q.Resume() + if err := q.Acquire(context.Background()); err != nil { + t.Fatalf("Acquire after Resume = %v, want nil", err) + } + q.Release(time.Now()) +} + +// TestQueueWaitDrain_BlocksUntilInFlightZero verifies the drain primitive +// the PR-E Service.Restart relies on: WaitDrain returns once every Acquire +// has been released, regardless of how many slots the queue has. +func TestQueueWaitDrain_BlocksUntilInFlightZero(t *testing.T) { + q := NewQueue(3, time.Second) + startA, startB := time.Now(), time.Now() + if err := q.Acquire(context.Background()); err != nil { + t.Fatalf("Acquire A: %v", err) + } + if err := q.Acquire(context.Background()); err != nil { + t.Fatalf("Acquire B: %v", err) + } + if got := q.InFlight(); got != 2 { + t.Fatalf("InFlight = %d, want 2", got) + } + + // WaitDrain must block while slots are held — release them on a + // goroutine and ensure WaitDrain returns shortly after. + done := make(chan error, 1) + go func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + done <- q.WaitDrain(ctx) + }() + + time.Sleep(80 * time.Millisecond) + q.Release(startA) + time.Sleep(40 * time.Millisecond) + q.Release(startB) + + select { + case err := <-done: + if err != nil { + t.Errorf("WaitDrain after both releases: %v", err) + } + case <-time.After(500 * time.Millisecond): + t.Fatal("WaitDrain did not return after slots fully released") + } + if got := q.InFlight(); got != 0 { + t.Errorf("InFlight after drain = %d, want 0", got) + } +} + +// TestQueueWaitDrain_RespectsContext ensures the drain timeout we use during +// Restart actually fires — without it a stuck embed call could wedge the +// admin's intentional restart forever. +func TestQueueWaitDrain_RespectsContext(t *testing.T) { + q := NewQueue(1, time.Second) + hold := time.Now() + if err := q.Acquire(context.Background()); err != nil { + t.Fatalf("Acquire: %v", err) + } + defer q.Release(hold) + + ctx, cancel := context.WithTimeout(context.Background(), 80*time.Millisecond) + defer cancel() + if err := q.WaitDrain(ctx); !errors.Is(err, context.DeadlineExceeded) { + t.Errorf("WaitDrain err = %v, want DeadlineExceeded", err) + } +} + +// Avoid unused-var lint while keeping the suppress simple. +var _ = sync.Mutex{} diff --git a/server/internal/embeddings/service.go b/server/internal/embeddings/service.go index 22ec9f9..ee3a7d2 100644 --- a/server/internal/embeddings/service.go +++ b/server/internal/embeddings/service.go @@ -4,8 +4,10 @@ import ( "context" "errors" "fmt" + "io" "log/slog" "os" + "path/filepath" "strings" "time" @@ -60,7 +62,10 @@ func New(ctx context.Context, cfg *config.Config, logger *slog.Logger) (*Service Transport: cfg.LlamaTransport, CtxSize: cfg.LlamaCtxSize, NGpuLayers: cfg.LlamaNGpuLayers, + NThreads: cfg.LlamaNThreads, + BatchSize: cfg.LlamaBatchSize, StartupSec: cfg.LlamaStartupSec, + Model: cfg.EmbeddingModel, } sup, err := newSupervisor(ctx, supCfg, logger) @@ -77,6 +82,28 @@ func New(ctx context.Context, cfg *config.Config, logger *slog.Logger) (*Service }, nil } +// Config returns the *config.Config the service was constructed with. The +// pointer is shared; callers that mutate it in place must understand they +// are racing the supervisor — only the dashboard restart path is supposed +// to do this, and it does so behind queue.BlockNew + sup.Restart. +func (s *Service) Config() *config.Config { + if s == nil { + return nil + } + return s.cfg +} + +// CacheDirFromService returns the GGUF cache directory the dashboard's +// /admin/models handler should walk. Returns "" when the EmbeddingsQuerier +// isn't a *Service (test fakes) or when the service is disabled. +func CacheDirFromService(q any) string { + s, ok := q.(*Service) + if !ok || s == nil || s.cfg == nil { + return "" + } + return s.cfg.GGUFCacheDir +} + // Stop tears the supervisor down within the ctx deadline. Safe to call on a // disabled or partially-initialised Service. func (s *Service) Stop(ctx context.Context) error { @@ -86,6 +113,93 @@ func (s *Service) Stop(ctx context.Context) error { return s.sup.Stop(ctx) } +// Status returns a snapshot of the sidecar process state for the dashboard. +// Returns SupervisorStatus{State: "disabled"} when the service was started +// with embeddings turned off — the dashboard renders a banner in that case +// and disables the runtime-config save buttons. +func (s *Service) Status() SupervisorStatus { + if s == nil || s.disabled { + return SupervisorStatus{State: "disabled"} + } + if s.sup == nil { + return SupervisorStatus{State: "failed", LastError: "supervisor not initialised"} + } + st := s.sup.Status() + if s.queue != nil { + // Annotate with in-flight count so the UI can show "draining (N)" + // during a restart cycle. + st.InFlight = s.queue.InFlight() + } + return st +} + +// Restart drains the embedding queue, stops the current sidecar child, and +// spawns a new one with the new config. cfg is the freshly-resolved +// runtimecfg-on-top-of-env Config snapshot — Restart does not consult any +// stored boot config. +// +// On success, the new sidecar is ready to serve embeddings before this +// returns. On failure, the supervisor enters the "failed" state and the +// queue is reopened (so callers get the existing ErrSupervisor / ErrBusy +// rather than a permanent block). +func (s *Service) Restart(ctx context.Context, cfg *config.Config) error { + if s == nil || s.disabled { + return ErrDisabled + } + if s.sup == nil { + return ErrSupervisor + } + + // Drain: refuse new acquires, then wait for in-flight to settle. 30s + // matches the documented restart UX in the dashboard plan; longer values + // would let a stuck embedding call block the operator's intentional + // restart indefinitely. + s.queue.BlockNew() + defer s.queue.Resume() + drainCtx, drainCancel := context.WithTimeout(ctx, 30*time.Second) + if err := s.queue.WaitDrain(drainCtx); err != nil { + drainCancel() + s.logger.Warn("embeddings: drain timed out, proceeding with restart anyway", + "in_flight", s.queue.InFlight(), "err", err, + ) + } else { + drainCancel() + } + + // Resolve the (possibly new) GGUF path before tearing down the current + // child — if resolution fails, we stay on the running sidecar instead of + // crashing it for a config we can't honour. + ggufPath, err := resolveGGUFPath(ctx, cfg, s.logger) + if err != nil { + return fmt.Errorf("resolve gguf for restart: %w", err) + } + + // Update queue concurrency / prefix to match the new model. The buffered + // slot channel can't be resized in place; we swap the queue, but only + // AFTER drain so no caller is mid-Acquire/Release on the old channel. + if cfg.MaxEmbeddingConcurrency != cap(s.queue.slots) { + s.queue = NewQueue(cfg.MaxEmbeddingConcurrency, time.Duration(cfg.EmbeddingQueueTimeout)*time.Second) + // New queue starts unblocked; that's fine because we hold the + // *previous* queue's blocked state via deferred Resume. The previous + // queue is now garbage and won't see any callers. + } + s.prefix = ResolveQueryPrefix(cfg.EmbeddingModel) + + supCfg := supervisorConfig{ + BinDir: cfg.LlamaBinDir, + GGUFPath: ggufPath, + SocketPath: cfg.LlamaSocketPath, + Transport: cfg.LlamaTransport, + CtxSize: cfg.LlamaCtxSize, + NGpuLayers: cfg.LlamaNGpuLayers, + NThreads: cfg.LlamaNThreads, + BatchSize: cfg.LlamaBatchSize, + StartupSec: cfg.LlamaStartupSec, + Model: cfg.EmbeddingModel, + } + return s.sup.Restart(ctx, supCfg) +} + // Ready reports whether the embeddings pipeline is currently able to serve a // request. Returns nil when the model is loaded and the supervisor is healthy, // ErrDisabled when embeddings are turned off, or ErrSupervisor/ErrNotReady @@ -299,12 +413,20 @@ func (s *Service) embedRaw(ctx context.Context, texts []string) ([][]float32, er } // resolveGGUFPath walks the precedence chain: -// 1. CIX_GGUF_PATH (already applied to cfg.GGUFPath before Validate). -// 2. bench/results/reference_gguf_path.txt dev fallback (Validate handles it). -// 3. Cached file under cfg.GGUFCacheDir//*.gguf. -// 4. HuggingFace download (this is the path that actually writes to disk). +// 1. CIX_GGUF_PATH (absolute path env override, validated by Stat). +// 2. cfg.EmbeddingModel as absolute path — when the dashboard's "Local +// path" mode wrote it through to the runtime_settings row. +// 3. Cached file under cfg.GGUFCacheDir//*.gguf when +// cfg.EmbeddingModel is an HF repo ID. +// 4. CIX_BOOTSTRAP_GGUF_PATH one-shot import — copies the file into +// the cache layout, then behaves like step 3 forever after. +// 5. HuggingFace download into the same cix cache (this is the path +// that actually writes to disk). // -// Only step 4 can be expensive; all others are stat-only. +// PR-E removed the implicit `bench/results/reference_gguf_path.txt` dev +// fallback that used to short-circuit step 2 — operators must now make +// the choice explicitly via env or the dashboard. Only step 5 is +// expensive; all others are stat-only or one-time copies. func resolveGGUFPath(ctx context.Context, cfg *config.Config, logger *slog.Logger) (string, error) { if cfg.GGUFPath != "" { if _, err := os.Stat(cfg.GGUFPath); err != nil { @@ -312,11 +434,19 @@ func resolveGGUFPath(ctx context.Context, cfg *config.Config, logger *slog.Logge } return cfg.GGUFPath, nil } - // The embedding model is an HF repo id like "awhiteside/CodeRankEmbed-Q8_0-GGUF". - // Only repo ids contain a slash; a raw filesystem path would have been - // captured by the CIX_GGUF_PATH branch above. + // PR-E — the dashboard's "Local path" mode writes an absolute path into + // embedding_model. Treat it as such instead of trying to interpret it + // as an HF repo id (which would fail the slash check or, worse, send + // the path to api.huggingface.co). + if filepath.IsAbs(cfg.EmbeddingModel) { + if _, err := os.Stat(cfg.EmbeddingModel); err != nil { + return "", fmt.Errorf("embedding model path %s: %w", cfg.EmbeddingModel, err) + } + return cfg.EmbeddingModel, nil + } + // HF repo ids look like "/" — exactly one slash, no leading "/". if !strings.Contains(cfg.EmbeddingModel, "/") { - return "", fmt.Errorf("embedding model %q is neither a path nor an HF repo id", cfg.EmbeddingModel) + return "", fmt.Errorf("embedding model %q is neither an absolute path nor an HF repo id (owner/repo)", cfg.EmbeddingModel) } // Cache-hit short-circuit: if we already downloaded a .gguf from this repo @@ -327,9 +457,102 @@ func resolveGGUFPath(ctx context.Context, cfg *config.Config, logger *slog.Logge return cached, nil } + // CIX_BOOTSTRAP_GGUF_PATH — one-time import path. Used so a fresh + // container with a freshly-mounted cache volume doesn't have to + // re-download a 280 MB GGUF the operator already has on disk. Once + // the file lands in the cache layout, the next boot satisfies the + // findCachedGGUF branch above and the bootstrap path is never read + // again (idempotent — repeated boots with the same env are no-ops). + if cfg.BootstrapGGUFPath != "" { + imported, err := importBootstrapGGUF(cfg.GGUFCacheDir, cfg.EmbeddingModel, cfg.BootstrapGGUFPath, logger) + if err != nil { + logger.Warn("bootstrap gguf import failed; falling through to HF download", + "src", cfg.BootstrapGGUFPath, "err", err) + } else if imported != "" { + return imported, nil + } + } + return DownloadGGUF(ctx, cfg.EmbeddingModel, cfg.GGUFCacheDir, logger) } +// importBootstrapGGUF copies srcPath into // +// atomically (write to .partial, fsync, rename). Returns the final path +// on success, "" if the source is missing (caller falls through to HF +// download), or an error for IO problems we should surface to the operator. +// +// safe_repo derived from the HF repo id (`owner/repo` → `owner__repo`) +// to match DownloadGGUF's layout exactly — so subsequent boots' cache +// scan finds the imported file under the same name HF would have used. +func importBootstrapGGUF(cacheDir, repo, srcPath string, logger *slog.Logger) (string, error) { + if cacheDir == "" || repo == "" { + return "", nil + } + srcInfo, err := os.Stat(srcPath) + if err != nil { + // Missing file is not a hard error — the operator may have set + // the env optimistically with a path that lives on a host they + // haven't mounted yet. Let the caller fall through to download. + if os.IsNotExist(err) { + return "", nil + } + return "", fmt.Errorf("stat bootstrap gguf %s: %w", srcPath, err) + } + if srcInfo.IsDir() { + return "", fmt.Errorf("bootstrap gguf %s is a directory, expected file", srcPath) + } + + safeRepo := strings.ReplaceAll(repo, "/", "__") + targetDir := filepath.Join(cacheDir, safeRepo) + if err := os.MkdirAll(targetDir, 0o755); err != nil { + return "", fmt.Errorf("mkdir cache dir: %w", err) + } + finalPath := filepath.Join(targetDir, filepath.Base(srcPath)) + + // Idempotency: if a previous boot already imported the same file, + // trust it — re-importing would be wasted IO and could race with a + // concurrent boot of a sibling container against a shared volume. + if _, err := os.Stat(finalPath); err == nil { + return finalPath, nil + } + + logger.Info("importing bootstrap gguf into cache", + "src", srcPath, "dst", finalPath, "size", srcInfo.Size()) + + src, err := os.Open(srcPath) + if err != nil { + return "", fmt.Errorf("open bootstrap gguf: %w", err) + } + defer src.Close() + + partial := finalPath + ".partial" + dst, err := os.OpenFile(partial, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644) + if err != nil { + return "", fmt.Errorf("create cache target: %w", err) + } + + if _, err := io.Copy(dst, src); err != nil { + _ = dst.Close() + _ = os.Remove(partial) + return "", fmt.Errorf("copy bootstrap gguf: %w", err) + } + if err := dst.Sync(); err != nil { + _ = dst.Close() + _ = os.Remove(partial) + return "", fmt.Errorf("fsync bootstrap gguf: %w", err) + } + if err := dst.Close(); err != nil { + _ = os.Remove(partial) + return "", fmt.Errorf("close bootstrap gguf: %w", err) + } + if err := os.Rename(partial, finalPath); err != nil { + _ = os.Remove(partial) + return "", fmt.Errorf("atomic rename bootstrap gguf: %w", err) + } + logger.Info("bootstrap gguf imported", "path", finalPath) + return finalPath, nil +} + // findCachedGGUF looks for a previously-downloaded .gguf under the standard // cache layout produced by DownloadGGUF. Returns "" on any miss (including // IO errors) so the caller proceeds to the download path. diff --git a/server/internal/embeddings/supervisor.go b/server/internal/embeddings/supervisor.go index 2f11c95..70b27e0 100644 --- a/server/internal/embeddings/supervisor.go +++ b/server/internal/embeddings/supervisor.go @@ -34,14 +34,20 @@ const restartWindow = 5 * time.Minute // talk to llama-server. It is populated by Service.New from *config.Config // so the supervisor does not import the config package directly. type supervisorConfig struct { - BinDir string // where llama-server + dylibs live - GGUFPath string // absolute path to the model file - SocketPath string // unix socket path (only used when Transport == "unix") - Transport string // "unix" or "tcp" - CtxSize int - NGpuLayers int - StartupSec int - TCPPort int // 0 = auto-pick, only relevant for tcp transport + BinDir string // where llama-server + dylibs live + GGUFPath string // absolute path to the model file + SocketPath string // unix socket path (only used when Transport == "unix") + Transport string // "unix" or "tcp" + CtxSize int + NGpuLayers int + NThreads int // 0 = let llama-server auto-detect via hardware_concurrency + BatchSize int // 0 = match CtxSize (preserves prior --ubatch-size behaviour) + StartupSec int + TCPPort int // 0 = auto-pick, only relevant for tcp transport + // Model is the human-readable identifier (HF repo id or absolute path) + // that the dashboard surfaces in the sidecar status card. The supervisor + // does not act on this field — it's recorded for observability only. + Model string } // supervisor owns the llama-server child process. It is responsible for: @@ -70,6 +76,11 @@ type supervisor struct { readySignal chan struct{} waiterDone chan struct{} // closed after the exit-watcher goroutine returns + + // lastSpawnErr is the most recent spawn() / readiness error, surfaced by + // Status() so the dashboard can render "Sidecar failed to start: ..." + // without grepping logs. Cleared on the next successful spawn. + lastSpawnErr atomic.Value // string } // newSupervisor validates the config, clamps the transport if needed, and @@ -178,6 +189,18 @@ func (s *supervisor) spawn(ctx context.Context) error { "--ubatch-size", strconv.Itoa(s.cfg.CtxSize), "--n-gpu-layers", strconv.Itoa(s.cfg.NGpuLayers), } + // PR-E — only pass --threads when the operator explicitly set one. With + // 0 we let llama-server pick via std::thread::hardware_concurrency, which + // is the saner default for unknown deployment shapes. + if s.cfg.NThreads > 0 { + argv = append(argv, "--threads", strconv.Itoa(s.cfg.NThreads)) + } + // PR-E — logical batch size override. Default keeps the prior --ubatch = + // ctx-size invariant intact. Passing a value <= ctx-size is safe; > + // ctx-size is rejected by llama-server itself. + if s.cfg.BatchSize > 0 { + argv = append(argv, "-b", strconv.Itoa(s.cfg.BatchSize)) + } switch s.cfg.Transport { case "unix": // Clear any stale socket file from a previous crashed run. @@ -244,11 +267,13 @@ func (s *supervisor) spawn(ctx context.Context) error { defer cancel() if err := s.waitReady(readyCtx); err != nil { s.logger.Error("llama-server readiness probe failed, killing child", "err", err) + s.lastSpawnErr.Store(err.Error()) s.killGroup() <-s.waiterDone return fmt.Errorf("%w: %v", ErrNotReady, err) } close(s.readySignal) + s.lastSpawnErr.Store("") // clear any stale error from a prior failed start s.logger.Info("llama-server ready", "elapsed", time.Since(s.startedAt).String()) return nil } @@ -441,6 +466,110 @@ func (s *supervisor) killGroup() { } } +// Restart tears the current child down and respawns with newCfg. The caller +// (Service.Restart) is responsible for draining the embedding queue first so +// in-flight HTTP calls don't fail mid-request. +// +// Restart fully resets state that the auto-restart-on-crash machinery may +// have left in place: the stopping flag is cleared so the new exit-watcher +// goroutine treats subsequent crashes as crashes (not as deliberate stops), +// the dead flag is cleared so Embed callers stop short-circuiting with +// ErrSupervisor, and restartAt is wiped so the operator's intentional cycle +// doesn't count against the 3-strikes-in-5-minutes budget. +// +// On spawn failure, dead is set: the operator's new config is broken and the +// supervisor needs another deliberate Restart (with corrected config) to +// recover. The caller surfaces this as a destructive toast in the UI. +func (s *supervisor) Restart(ctx context.Context, newCfg supervisorConfig) error { + // Stop the current child. Use a deadline carved from ctx so a stuck + // SIGTERM can't hold up the whole restart. + stopCtx, stopCancel := context.WithTimeout(ctx, 30*time.Second) + if err := s.Stop(stopCtx); err != nil { + stopCancel() + s.logger.Warn("supervisor: stop during restart returned error (continuing)", "err", err) + } else { + stopCancel() + } + + // Reset state. Stop has already drained the prior waiter via waiterDone. + s.mu.Lock() + s.cfg = newCfg + s.restartAt = nil + s.mu.Unlock() + s.dead.Store(false) + s.stopping.Store(false) + + // Re-validate: a different model path / transport may now be invalid. + if err := validateSupervisorConfig(&newCfg, s.logger); err != nil { + s.dead.Store(true) + s.lastSpawnErr.Store(err.Error()) + return fmt.Errorf("validate restart config: %w", err) + } + s.mu.Lock() + s.cfg = newCfg + // Same transport? Keep the existing client. A transport switch is not + // supported via Restart — config validates that field as immutable above + // (Transport is stamped from env at boot and can't be overridden via the + // dashboard runtime-config surface). + s.mu.Unlock() + + if err := s.spawn(ctx); err != nil { + s.dead.Store(true) + // spawn already stored a more granular error via lastSpawnErr; only + // set the umbrella one if it didn't. + if v, _ := s.lastSpawnErr.Load().(string); v == "" { + s.lastSpawnErr.Store(err.Error()) + } + return err + } + return nil +} + +// SupervisorStatus is the dashboard-facing view of the sidecar process. +// Returned by Service.Status and rendered in the SidecarSection card. Fields +// are deliberately scalar so the JSON body is stable across versions. +type SupervisorStatus struct { + State string // "running" | "failed" | "starting" | "disabled" + PID int // 0 when not started or already reaped + Uptime time.Duration + Model string + Ready bool + LastError string // last spawn / readiness error; "" when healthy + InFlight int // queue.InFlight() at the moment Status was sampled +} + +// Status returns a snapshot of the supervisor's current state. Safe to call +// concurrently with Restart and Embed*. +func (s *supervisor) Status() SupervisorStatus { + st := SupervisorStatus{Model: s.cfg.Model} + if v, _ := s.lastSpawnErr.Load().(string); v != "" { + st.LastError = v + } + if s.dead.Load() { + st.State = "failed" + return st + } + s.mu.RLock() + cmd := s.cmd + startedAt := s.startedAt + readyCh := s.readySignal + s.mu.RUnlock() + if cmd != nil && cmd.Process != nil { + st.PID = cmd.Process.Pid + } + if !startedAt.IsZero() { + st.Uptime = time.Since(startedAt) + } + select { + case <-readyCh: + st.Ready = true + st.State = "running" + default: + st.State = "starting" + } + return st +} + // Ready blocks until the current child is ready or ctx expires. func (s *supervisor) Ready(ctx context.Context) error { if s.dead.Load() { diff --git a/server/internal/httpapi/admin_server.go b/server/internal/httpapi/admin_server.go new file mode 100644 index 0000000..b579bc3 --- /dev/null +++ b/server/internal/httpapi/admin_server.go @@ -0,0 +1,355 @@ +// admin_server.go holds the PR-E "Server" admin handlers: runtime config, +// sidecar restart/status, GGUF cache enumeration. Auth: every handler routes +// through mustBeAdmin → 403 for non-admin sessions / API keys. +// +// Wire format: hand-written payload structs (not the openapi.gen ones) so +// we can stamp time.Time as RFC3339Nano and emit map[string]string for the +// per-field source label without fighting the generator's nullable handling. +package httpapi + +import ( + "context" + "encoding/json" + "errors" + "net/http" + "os" + "path/filepath" + "strings" + "sync/atomic" + "time" + + "github.com/dvcdsys/code-index/server/internal/embeddings" + "github.com/dvcdsys/code-index/server/internal/runtimecfg" + + "github.com/google/uuid" +) + +// runtimeConfigPayload is the JSON shape of GET/PUT /admin/runtime-config. +// Matches openapi.RuntimeConfig but is hand-written so updated_at uses the +// project-wide RFC3339Nano stamp and source values stay raw strings (the +// generated enum type would force a layer of conversion at no benefit). +type runtimeConfigPayload struct { + EmbeddingModel string `json:"embedding_model"` + LlamaCtxSize int `json:"llama_ctx_size"` + LlamaNGpuLayers int `json:"llama_n_gpu_layers"` + LlamaNThreads int `json:"llama_n_threads"` + MaxEmbeddingConcurrency int `json:"max_embedding_concurrency"` + LlamaBatchSize int `json:"llama_batch_size"` + Source map[string]string `json:"source"` + Recommended *recommendedSnapshotPayload `json:"recommended,omitempty"` + UpdatedAt *string `json:"updated_at,omitempty"` + UpdatedBy *string `json:"updated_by,omitempty"` +} + +type recommendedSnapshotPayload struct { + EmbeddingModel string `json:"embedding_model"` + LlamaCtxSize int `json:"llama_ctx_size"` + LlamaNGpuLayers int `json:"llama_n_gpu_layers"` + LlamaNThreads int `json:"llama_n_threads"` + MaxEmbeddingConcurrency int `json:"max_embedding_concurrency"` + LlamaBatchSize int `json:"llama_batch_size"` +} + +func snapshotToPayload(snap runtimecfg.Snapshot, rec runtimecfg.Snapshot) runtimeConfigPayload { + out := runtimeConfigPayload{ + EmbeddingModel: snap.EmbeddingModel, + LlamaCtxSize: snap.LlamaCtxSize, + LlamaNGpuLayers: snap.LlamaNGpuLayers, + LlamaNThreads: snap.LlamaNThreads, + MaxEmbeddingConcurrency: snap.MaxEmbeddingConcurrency, + LlamaBatchSize: snap.LlamaBatchSize, + Source: snap.Source, + Recommended: &recommendedSnapshotPayload{ + EmbeddingModel: rec.EmbeddingModel, + LlamaCtxSize: rec.LlamaCtxSize, + LlamaNGpuLayers: rec.LlamaNGpuLayers, + LlamaNThreads: rec.LlamaNThreads, + MaxEmbeddingConcurrency: rec.MaxEmbeddingConcurrency, + LlamaBatchSize: rec.LlamaBatchSize, + }, + } + if !snap.UpdatedAt.IsZero() { + stamp := snap.UpdatedAt.UTC().Format(time.RFC3339Nano) + out.UpdatedAt = &stamp + } + if snap.UpdatedBy != "" { + v := snap.UpdatedBy + out.UpdatedBy = &v + } + return out +} + +// GetRuntimeConfig — GET /api/v1/admin/runtime-config (admin only). +func (s *Server) GetRuntimeConfig(w http.ResponseWriter, r *http.Request) { + if _, ok := s.mustBeAdmin(w, r); !ok { + return + } + if s.Deps.RuntimeCfg == nil { + writeError(w, http.StatusServiceUnavailable, "runtime config not available") + return + } + snap, err := s.Deps.RuntimeCfg.Get(r.Context()) + if err != nil { + writeError(w, http.StatusInternalServerError, "could not load runtime config") + return + } + writeJSON(w, http.StatusOK, snapshotToPayload(snap, s.Deps.RuntimeCfg.Recommended())) +} + +// PutRuntimeConfig — PUT /api/v1/admin/runtime-config (admin only). +// +// The request body is a partial patch. Pointers tell us "this field was +// supplied"; the value tells us what to do with it (zero = clear override, +// non-zero = set override). nil pointers leave the existing override alone. +func (s *Server) PutRuntimeConfig(w http.ResponseWriter, r *http.Request) { + ac, ok := s.mustBeAdmin(w, r) + if !ok { + return + } + if s.Deps.RuntimeCfg == nil { + writeError(w, http.StatusServiceUnavailable, "runtime config not available") + return + } + + var body struct { + EmbeddingModel *string `json:"embedding_model"` + LlamaCtxSize *int `json:"llama_ctx_size"` + LlamaNGpuLayers *int `json:"llama_n_gpu_layers"` + LlamaNThreads *int `json:"llama_n_threads"` + MaxEmbeddingConcurrency *int `json:"max_embedding_concurrency"` + LlamaBatchSize *int `json:"llama_batch_size"` + } + dec := json.NewDecoder(r.Body) + dec.DisallowUnknownFields() + if err := dec.Decode(&body); err != nil { + writeError(w, http.StatusUnprocessableEntity, "invalid JSON body") + return + } + + // Light validation: refuse obviously broken numeric values. Negative + // n_gpu_layers other than -1 is also broken (only -1 is the "all layers" + // sentinel), but we let it through and trust llama-server to error at + // spawn time — the supervisor surfaces that via SidecarStatus.last_error. + if body.LlamaCtxSize != nil && *body.LlamaCtxSize < 0 { + writeError(w, http.StatusUnprocessableEntity, "llama_ctx_size must be >= 0") + return + } + if body.LlamaNThreads != nil && *body.LlamaNThreads < 0 { + writeError(w, http.StatusUnprocessableEntity, "llama_n_threads must be >= 0") + return + } + if body.MaxEmbeddingConcurrency != nil && *body.MaxEmbeddingConcurrency < 0 { + writeError(w, http.StatusUnprocessableEntity, "max_embedding_concurrency must be >= 0") + return + } + if body.LlamaBatchSize != nil && *body.LlamaBatchSize < 0 { + writeError(w, http.StatusUnprocessableEntity, "llama_batch_size must be >= 0") + return + } + + patch := runtimecfg.Patch{ + EmbeddingModel: body.EmbeddingModel, + LlamaCtxSize: body.LlamaCtxSize, + LlamaNGpuLayers: body.LlamaNGpuLayers, + LlamaNThreads: body.LlamaNThreads, + MaxEmbeddingConcurrency: body.MaxEmbeddingConcurrency, + LlamaBatchSize: body.LlamaBatchSize, + } + updatedBy := "" + if ac != nil { + updatedBy = ac.User.Email + } + if err := s.Deps.RuntimeCfg.Set(r.Context(), patch, updatedBy); err != nil { + writeError(w, http.StatusInternalServerError, "could not save runtime config") + return + } + snap, err := s.Deps.RuntimeCfg.Get(r.Context()) + if err != nil { + writeError(w, http.StatusInternalServerError, "saved but could not reload runtime config") + return + } + writeJSON(w, http.StatusOK, snapshotToPayload(snap, s.Deps.RuntimeCfg.Recommended())) +} + +// --------------------------------------------------------------------------- +// Sidecar restart + status +// --------------------------------------------------------------------------- + +// restartTracker holds in-flight restart state for the sidecar. PR-E V1: a +// single global flag is enough — only one restart can run at a time anyway +// because Service.Restart drains then mutates singleton supervisor state. +// Future versions may key by restart_id when we surface progress. +var restartInFlight atomic.Bool + +// RestartSidecar — POST /api/v1/admin/sidecar/restart (admin only). +// +// Returns 202 immediately and runs the actual stop/spawn cycle on a goroutine +// so the HTTP request doesn't block for tens of seconds while the sidecar +// drains, terminates, and respawns. The dashboard polls GET /sidecar/status +// to observe the running → restarting → running transition. +func (s *Server) RestartSidecar(w http.ResponseWriter, r *http.Request) { + if _, ok := s.mustBeAdmin(w, r); !ok { + return + } + if s.Deps.EmbeddingSvc == nil { + writeError(w, http.StatusServiceUnavailable, "embeddings service not available") + return + } + embedSvc, ok := s.Deps.EmbeddingSvc.(*embeddings.Service) + if !ok { + writeError(w, http.StatusServiceUnavailable, "embeddings service does not support restart") + return + } + if s.Deps.RuntimeCfg == nil { + writeError(w, http.StatusServiceUnavailable, "runtime config not available") + return + } + + if !restartInFlight.CompareAndSwap(false, true) { + writeError(w, http.StatusConflict, "another restart is already in progress") + return + } + + id := uuid.NewString() + go func() { + defer restartInFlight.Store(false) + // Resolve the latest config snapshot and apply onto a fresh shallow + // copy of the env config so the supervisor sees the runtime overrides. + bg, cancel := context.WithTimeout(context.Background(), 90*time.Second) + defer cancel() + snap, err := s.Deps.RuntimeCfg.Get(bg) + if err != nil { + s.Deps.Logger.Error("sidecar restart: load runtime config", "err", err, "restart_id", id) + return + } + // We don't have a clean handle on the original *config.Config in + // admin_server.go; the embeddings.Service holds a pointer to the + // process-wide one and Restart mutates it in place. snapshot.ApplyTo + // rewrites the relevant fields on whatever cfg the embedSvc carries. + snap.ApplyTo(embedSvc.Config()) + if err := embedSvc.Restart(bg, embedSvc.Config()); err != nil { + s.Deps.Logger.Error("sidecar restart failed", "err", err, "restart_id", id) + return + } + s.Deps.Logger.Info("sidecar restart complete", "restart_id", id) + }() + + writeJSON(w, http.StatusAccepted, map[string]any{"restart_id": id}) +} + +// GetSidecarStatus — GET /api/v1/admin/sidecar/status (admin only). +func (s *Server) GetSidecarStatus(w http.ResponseWriter, r *http.Request) { + if _, ok := s.mustBeAdmin(w, r); !ok { + return + } + if s.Deps.EmbeddingSvc == nil { + writeJSON(w, http.StatusOK, map[string]any{ + "state": "disabled", + "ready": false, + "in_flight": 0, + }) + return + } + embedSvc, ok := s.Deps.EmbeddingSvc.(*embeddings.Service) + if !ok { + writeJSON(w, http.StatusOK, map[string]any{ + "state": "running", + "ready": true, + "in_flight": 0, + }) + return + } + st := embedSvc.Status() + + body := map[string]any{ + "state": st.State, + "ready": st.Ready, + "in_flight": st.InFlight, + "restart_in_flight": restartInFlight.Load(), + } + if st.PID > 0 { + body["pid"] = st.PID + } + if st.Uptime > 0 { + body["uptime_seconds"] = int(st.Uptime.Seconds()) + } + if st.Model != "" { + body["model"] = st.Model + } + if st.LastError != "" { + body["last_error"] = st.LastError + } + // Active restart wins over the snapshot's transient state — Status() may + // see a momentary "running" while the goroutine is still tearing down. + if restartInFlight.Load() { + body["state"] = "restarting" + } + writeJSON(w, http.StatusOK, body) +} + +// --------------------------------------------------------------------------- +// Cached GGUF model enumeration +// --------------------------------------------------------------------------- + +// ListModels — GET /api/v1/admin/models (admin only). +// +// Walks CIX_GGUF_CACHE_DIR//*.gguf (the layout DownloadGGUF uses) +// and returns one entry per .gguf file. Repo IDs are reconstructed from the +// directory name (we encode HF "owner/model" as "owner__model" to stay +// filesystem-safe). +func (s *Server) ListModels(w http.ResponseWriter, r *http.Request) { + if _, ok := s.mustBeAdmin(w, r); !ok { + return + } + cacheDir := embeddings.CacheDirFromService(s.Deps.EmbeddingSvc) + if cacheDir == "" { + // Service might be disabled / fake in tests — fall through to an + // empty list with no cache_dir (UI shows free-text fallback). + writeJSON(w, http.StatusOK, map[string]any{ + "models": []any{}, + "cache_dir": "", + }) + return + } + type entry struct { + ID string `json:"id"` + Path string `json:"path"` + SizeBytes int64 `json:"size_bytes"` + } + out := []entry{} + + repos, err := os.ReadDir(cacheDir) + if err != nil && !errors.Is(err, os.ErrNotExist) { + s.Deps.Logger.Warn("list models: read cache dir", "dir", cacheDir, "err", err) + } + for _, repo := range repos { + if !repo.IsDir() { + continue + } + repoDir := filepath.Join(cacheDir, repo.Name()) + files, err := os.ReadDir(repoDir) + if err != nil { + continue + } + for _, f := range files { + if f.IsDir() || !strings.EqualFold(filepath.Ext(f.Name()), ".gguf") { + continue + } + info, err := f.Info() + if err != nil { + continue + } + id := strings.Replace(repo.Name(), "__", "/", 1) + out = append(out, entry{ + ID: id, + Path: filepath.Join(repoDir, f.Name()), + SizeBytes: info.Size(), + }) + } + } + + writeJSON(w, http.StatusOK, map[string]any{ + "models": out, + "cache_dir": cacheDir, + }) +} diff --git a/server/internal/httpapi/admin_server_test.go b/server/internal/httpapi/admin_server_test.go new file mode 100644 index 0000000..b99c093 --- /dev/null +++ b/server/internal/httpapi/admin_server_test.go @@ -0,0 +1,274 @@ +package httpapi + +import ( + "bytes" + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/dvcdsys/code-index/server/internal/apikeys" + "github.com/dvcdsys/code-index/server/internal/config" + apidb "github.com/dvcdsys/code-index/server/internal/db" + "github.com/dvcdsys/code-index/server/internal/runtimecfg" + "github.com/dvcdsys/code-index/server/internal/sessions" + "github.com/dvcdsys/code-index/server/internal/users" +) + +// adminFixture extends authTestFixture with a wired runtimecfg.Service so +// the admin handlers under test see a real DB-backed config layer. +type adminFixture struct { + *authTestFixture +} + +func newAdminFixture(t *testing.T) *adminFixture { + t.Helper() + database, err := apidb.Open(":memory:") + if err != nil { + t.Fatalf("open db: %v", err) + } + t.Cleanup(func() { _ = database.Close() }) + + usrSvc := users.New(database) + sessSvc := sessions.New(database) + akSvc := apikeys.New(database) + + admin, err := usrSvc.Create(context.Background(), "admin@example.com", "secret-password", users.RoleAdmin, false) + if err != nil { + t.Fatalf("seed admin: %v", err) + } + viewer, err := usrSvc.Create(context.Background(), "viewer@example.com", "secret-password", users.RoleViewer, false) + if err != nil { + t.Fatalf("seed viewer: %v", err) + } + _ = viewer + + envCfg := &config.Config{ + EmbeddingModel: "env/model", + LlamaCtxSize: 4096, + LlamaNGpuLayers: 8, + LlamaNThreads: 4, + MaxEmbeddingConcurrency: 2, + LlamaBatchSize: 1024, + GGUFCacheDir: t.TempDir(), + } + + deps := Deps{ + DB: database, + ServerVersion: "0.0.0-test", + APIVersion: "v1", + EmbeddingModel: envCfg.EmbeddingModel, + Users: usrSvc, + Sessions: sessSvc, + APIKeys: akSvc, + RuntimeCfg: runtimecfg.New(database, envCfg), + } + return &adminFixture{ + authTestFixture: &authTestFixture{ + Router: NewRouter(deps), + Deps: deps, + UserID: admin.ID, + FullKey: "", + }, + } +} + +func adminCookie(t *testing.T, f *adminFixture) string { + t.Helper() + rr := loginRR(t, f.Router, "admin@example.com", "secret-password") + if rr.Code != http.StatusOK { + t.Fatalf("admin login failed: %d (%s)", rr.Code, rr.Body.String()) + } + c := sessionCookie(rr) + if c == "" { + t.Fatal("admin session cookie missing") + } + return c +} + +func viewerCookie(t *testing.T, f *adminFixture) string { + t.Helper() + rr := loginRR(t, f.Router, "viewer@example.com", "secret-password") + if rr.Code != http.StatusOK { + t.Fatalf("viewer login failed: %d (%s)", rr.Code, rr.Body.String()) + } + c := sessionCookie(rr) + if c == "" { + t.Fatal("viewer session cookie missing") + } + return c +} + +// TestGetRuntimeConfig_AdminSeesEnvSources covers the "fresh install / no DB +// override" path — every field should be marked as sourced from env, and the +// recommended snapshot should round-trip. Pre-PUT. +func TestGetRuntimeConfig_AdminSeesEnvSources(t *testing.T) { + f := newAdminFixture(t) + cookie := adminCookie(t, f) + + req := withCookie(httptest.NewRequest(http.MethodGet, "/api/v1/admin/runtime-config", nil), cookie) + rr := httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + + if rr.Code != http.StatusOK { + t.Fatalf("status = %d (%s)", rr.Code, rr.Body.String()) + } + var body struct { + EmbeddingModel string `json:"embedding_model"` + LlamaCtxSize int `json:"llama_ctx_size"` + Source map[string]string `json:"source"` + Recommended map[string]any `json:"recommended"` + UpdatedAt *string `json:"updated_at"` + } + if err := json.Unmarshal(rr.Body.Bytes(), &body); err != nil { + t.Fatalf("unmarshal: %v", err) + } + if body.EmbeddingModel != "env/model" { + t.Errorf("embedding_model = %q, want env/model", body.EmbeddingModel) + } + if body.LlamaCtxSize != 4096 { + t.Errorf("llama_ctx_size = %d, want 4096", body.LlamaCtxSize) + } + for _, f := range []string{"embedding_model", "llama_ctx_size", "llama_n_gpu_layers", "llama_n_threads"} { + if body.Source[f] != "env" { + t.Errorf("source[%s] = %q, want env", f, body.Source[f]) + } + } + if body.Recommended == nil { + t.Error("recommended block missing — UI relies on it for the 'Recommended' pill") + } + if body.UpdatedAt != nil { + t.Errorf("updated_at = %v, want nil before any PUT", body.UpdatedAt) + } +} + +// TestGetRuntimeConfig_ViewerForbidden — the runtime config surface is +// admin-only; a viewer session must get 403, not the data. +func TestGetRuntimeConfig_ViewerForbidden(t *testing.T) { + f := newAdminFixture(t) + cookie := viewerCookie(t, f) + + req := withCookie(httptest.NewRequest(http.MethodGet, "/api/v1/admin/runtime-config", nil), cookie) + rr := httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + + if rr.Code != http.StatusForbidden { + t.Fatalf("status = %d, want 403 (body=%s)", rr.Code, rr.Body.String()) + } +} + +// TestPutRuntimeConfig_RoundTrip exercises the dashboard's primary flow: +// admin saves a couple of overrides, GET reflects them with source="db", +// untouched fields keep source="env". Then a clear (empty + zero) returns +// the fields to env-sourced. +func TestPutRuntimeConfig_RoundTrip(t *testing.T) { + f := newAdminFixture(t) + cookie := adminCookie(t, f) + + patch := map[string]any{ + "embedding_model": "db/model-v2", + "llama_ctx_size": 8192, + } + body, _ := json.Marshal(patch) + req := withCookie(httptest.NewRequest(http.MethodPut, "/api/v1/admin/runtime-config", bytes.NewReader(body)), cookie) + req.Header.Set("Content-Type", "application/json") + rr := httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + + if rr.Code != http.StatusOK { + t.Fatalf("PUT status = %d (%s)", rr.Code, rr.Body.String()) + } + var got struct { + EmbeddingModel string `json:"embedding_model"` + LlamaCtxSize int `json:"llama_ctx_size"` + LlamaNThreads int `json:"llama_n_threads"` + Source map[string]string `json:"source"` + UpdatedBy *string `json:"updated_by"` + } + if err := json.Unmarshal(rr.Body.Bytes(), &got); err != nil { + t.Fatalf("unmarshal PUT response: %v", err) + } + if got.EmbeddingModel != "db/model-v2" || got.Source["embedding_model"] != "db" { + t.Errorf("model not from DB after PUT: %+v src=%q", got.EmbeddingModel, got.Source["embedding_model"]) + } + if got.LlamaCtxSize != 8192 || got.Source["llama_ctx_size"] != "db" { + t.Errorf("ctx not from DB after PUT: %d src=%q", got.LlamaCtxSize, got.Source["llama_ctx_size"]) + } + if got.LlamaNThreads != 4 || got.Source["llama_n_threads"] != "env" { + t.Errorf("untouched threads field shifted source: val=%d src=%q", got.LlamaNThreads, got.Source["llama_n_threads"]) + } + if got.UpdatedBy == nil || *got.UpdatedBy != "admin@example.com" { + t.Errorf("updated_by = %v, want admin@example.com", got.UpdatedBy) + } + + // Clear the model override; ctx override should remain. + clearBody, _ := json.Marshal(map[string]any{"embedding_model": ""}) + req = withCookie(httptest.NewRequest(http.MethodPut, "/api/v1/admin/runtime-config", bytes.NewReader(clearBody)), cookie) + req.Header.Set("Content-Type", "application/json") + rr = httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + if rr.Code != http.StatusOK { + t.Fatalf("PUT clear status = %d (%s)", rr.Code, rr.Body.String()) + } + if err := json.Unmarshal(rr.Body.Bytes(), &got); err != nil { + t.Fatalf("unmarshal clear: %v", err) + } + if got.EmbeddingModel != "env/model" || got.Source["embedding_model"] != "env" { + t.Errorf("model didn't fall back to env after clear: %q src=%q", got.EmbeddingModel, got.Source["embedding_model"]) + } + if got.LlamaCtxSize != 8192 || got.Source["llama_ctx_size"] != "db" { + t.Errorf("ctx override lost during model clear: %d src=%q", got.LlamaCtxSize, got.Source["llama_ctx_size"]) + } +} + +// TestSidecarStatus_DisabledWhenNoEmbedSvc — when the server boots with +// embeddings disabled, the dashboard still gets a meaningful status payload +// (state="disabled") so it can render the bootstrap-only banner. +func TestSidecarStatus_DisabledWhenNoEmbedSvc(t *testing.T) { + f := newAdminFixture(t) + cookie := adminCookie(t, f) + + req := withCookie(httptest.NewRequest(http.MethodGet, "/api/v1/admin/sidecar/status", nil), cookie) + rr := httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + + if rr.Code != http.StatusOK { + t.Fatalf("status = %d (%s)", rr.Code, rr.Body.String()) + } + var body map[string]any + _ = json.Unmarshal(rr.Body.Bytes(), &body) + if body["state"] != "disabled" { + t.Errorf("state = %v, want 'disabled' when EmbeddingSvc is nil", body["state"]) + } +} + +// TestListModels_EmptyCache — fresh cache directory returns an empty list + +// the cache_dir so the UI can render the "no cached models, use a path" +// fallback without guessing. +func TestListModels_EmptyCache(t *testing.T) { + f := newAdminFixture(t) + cookie := adminCookie(t, f) + + req := withCookie(httptest.NewRequest(http.MethodGet, "/api/v1/admin/models", nil), cookie) + rr := httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + + if rr.Code != http.StatusOK { + t.Fatalf("status = %d (%s)", rr.Code, rr.Body.String()) + } + var body struct { + Models []any `json:"models"` + CacheDir string `json:"cache_dir"` + } + _ = json.Unmarshal(rr.Body.Bytes(), &body) + if len(body.Models) != 0 { + t.Errorf("models = %d, want 0 in fresh fixture", len(body.Models)) + } + // EmbeddingSvc is nil in the fixture, so CacheDirFromService returns "" + // regardless of envCfg.GGUFCacheDir. That's fine — UI treats it as + // "no scan possible" and falls back to free-text input. + if body.CacheDir != "" { + t.Errorf("cache_dir = %q, want empty when EmbeddingSvc is nil", body.CacheDir) + } +} diff --git a/server/internal/httpapi/auth.go b/server/internal/httpapi/auth.go new file mode 100644 index 0000000..8ee8314 --- /dev/null +++ b/server/internal/httpapi/auth.go @@ -0,0 +1,604 @@ +package httpapi + +import ( + "encoding/json" + "errors" + "net/http" + "strings" + "time" + + "github.com/dvcdsys/code-index/server/internal/apikeys" + "github.com/dvcdsys/code-index/server/internal/httpapi/openapi" + "github.com/dvcdsys/code-index/server/internal/sessions" + "github.com/dvcdsys/code-index/server/internal/users" +) + +// userPayload mirrors the OpenAPI `User` schema. Built by hand instead of +// using the generated openapi.User to keep date formatting under our +// control (RFC3339Nano UTC) — keeps wire output stable across Go versions. +type userPayload struct { + ID string `json:"id"` + Email string `json:"email"` + Role string `json:"role"` + MustChangePassword bool `json:"must_change_password"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + Disabled bool `json:"disabled"` + DisabledAt *string `json:"disabled_at"` +} + +func userToPayload(u users.User) userPayload { + p := userPayload{ + ID: u.ID, + Email: u.Email, + Role: u.Role, + MustChangePassword: u.MustChangePassword, + CreatedAt: u.CreatedAt.UTC().Format(time.RFC3339Nano), + UpdatedAt: u.UpdatedAt.UTC().Format(time.RFC3339Nano), + Disabled: u.DisabledAt != nil, + } + if u.DisabledAt != nil { + s := u.DisabledAt.UTC().Format(time.RFC3339Nano) + p.DisabledAt = &s + } + return p +} + +// userWithStatsPayload mirrors the OpenAPI `UserWithStats` schema. Returned +// only by the admin /users list endpoint — keeps the per-request /auth/me +// shape free of N+1 aggregate columns. +type userWithStatsPayload struct { + userPayload + LastLoginAt *string `json:"last_login_at"` + ActiveSessionsCount int `json:"active_sessions_count"` + APIKeysCount int `json:"api_keys_count"` +} + +func userWithStatsToPayload(u users.UserWithStats) userWithStatsPayload { + p := userWithStatsPayload{ + userPayload: userToPayload(u.User), + ActiveSessionsCount: u.ActiveSessionsCount, + APIKeysCount: u.APIKeysCount, + } + if u.LastLoginAt != nil { + s := u.LastLoginAt.UTC().Format(time.RFC3339Nano) + p.LastLoginAt = &s + } + return p +} + +type sessionPayload struct { + ID string `json:"id"` + CreatedAt string `json:"created_at"` + ExpiresAt string `json:"expires_at"` + LastSeenAt string `json:"last_seen_at"` + LastSeenIP *string `json:"last_seen_ip"` + LastSeenUA *string `json:"last_seen_ua"` + IsCurrent bool `json:"is_current"` +} + +func sessionToPayload(s sessions.Session, currentID string) sessionPayload { + p := sessionPayload{ + ID: s.ID, + CreatedAt: s.CreatedAt.UTC().Format(time.RFC3339Nano), + ExpiresAt: s.ExpiresAt.UTC().Format(time.RFC3339Nano), + LastSeenAt: s.LastSeenAt.UTC().Format(time.RFC3339Nano), + IsCurrent: s.ID == currentID, + } + if s.LastSeenIP != "" { + p.LastSeenIP = &s.LastSeenIP + } + if s.LastSeenUA != "" { + p.LastSeenUA = &s.LastSeenUA + } + return p +} + +type apiKeyPayload struct { + ID string `json:"id"` + OwnerUserID string `json:"owner_user_id"` + Name string `json:"name"` + Prefix string `json:"prefix"` + CreatedAt string `json:"created_at"` + LastUsedAt *string `json:"last_used_at"` + LastUsedIP *string `json:"last_used_ip"` + LastUsedUA *string `json:"last_used_ua"` + Revoked bool `json:"revoked"` + RevokedAt *string `json:"revoked_at"` +} + +func apiKeyToPayload(k apikeys.ApiKey) apiKeyPayload { + p := apiKeyPayload{ + ID: k.ID, + OwnerUserID: k.OwnerUserID, + Name: k.Name, + Prefix: k.Prefix, + CreatedAt: k.CreatedAt.UTC().Format(time.RFC3339Nano), + Revoked: k.RevokedAt != nil, + } + if k.LastUsedAt != nil { + s := k.LastUsedAt.UTC().Format(time.RFC3339Nano) + p.LastUsedAt = &s + } + if k.LastUsedIP != "" { + p.LastUsedIP = &k.LastUsedIP + } + if k.LastUsedUA != "" { + p.LastUsedUA = &k.LastUsedUA + } + if k.RevokedAt != nil { + s := k.RevokedAt.UTC().Format(time.RFC3339Nano) + p.RevokedAt = &s + } + return p +} + +// --------------------------------------------------------------------------- +// Auth endpoints +// --------------------------------------------------------------------------- + +// GetBootstrapStatus — GET /api/v1/auth/bootstrap-status (public). +func (s *Server) GetBootstrapStatus(w http.ResponseWriter, r *http.Request) { + if s.Deps.Users == nil { + writeJSON(w, http.StatusOK, map[string]any{"needs_bootstrap": false}) + return + } + n, err := s.Deps.Users.Count(r.Context()) + if err != nil { + writeError(w, http.StatusInternalServerError, "could not check bootstrap status") + return + } + writeJSON(w, http.StatusOK, map[string]any{"needs_bootstrap": n == 0}) +} + +// Login — POST /api/v1/auth/login (public). +func (s *Server) Login(w http.ResponseWriter, r *http.Request) { + if s.Deps.Users == nil || s.Deps.Sessions == nil { + writeError(w, http.StatusServiceUnavailable, "auth not configured") + return + } + var body struct { + Email string `json:"email"` + Password string `json:"password"` + } + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + writeError(w, http.StatusUnprocessableEntity, "invalid JSON body") + return + } + body.Email = strings.TrimSpace(body.Email) + if body.Email == "" || body.Password == "" { + writeError(w, http.StatusUnprocessableEntity, "email and password are required") + return + } + ip := clientIP(r) + if s.loginLimiter != nil { + if ok, retry := s.loginLimiter.allow(ip, body.Email); !ok { + writeRateLimited(w, retry) + return + } + } + u, err := s.Deps.Users.Authenticate(r.Context(), body.Email, body.Password) + if err != nil { + switch { + case errors.Is(err, users.ErrInvalidLogin): + writeError(w, http.StatusUnauthorized, "Invalid email or password") + case errors.Is(err, users.ErrUserDisabled): + writeError(w, http.StatusUnauthorized, "Account is disabled") + default: + writeError(w, http.StatusInternalServerError, "login failed") + } + return + } + created, err := s.Deps.Sessions.Create(r.Context(), u.ID, ip, r.UserAgent()) + if err != nil { + writeError(w, http.StatusInternalServerError, "could not create session") + return + } + if s.loginLimiter != nil { + s.loginLimiter.reset(ip, body.Email) + } + // The cookie carries the raw token; only sha256(token) is in the DB. + setSessionCookie(w, r, created.RawToken, created.Session.ExpiresAt) + writeJSON(w, http.StatusOK, map[string]any{"user": userToPayload(u)}) +} + +// Logout — POST /api/v1/auth/logout. +func (s *Server) Logout(w http.ResponseWriter, r *http.Request) { + ac, ok := authFromCtx(r.Context()) + if !ok { + writeError(w, http.StatusUnauthorized, "Authentication required") + return + } + if ac.Method == "session" && ac.Session != nil && s.Deps.Sessions != nil { + _ = s.Deps.Sessions.Delete(r.Context(), ac.Session.ID) + clearSessionCookie(w, r) + } + w.WriteHeader(http.StatusNoContent) +} + +// GetMe — GET /api/v1/auth/me. +func (s *Server) GetMe(w http.ResponseWriter, r *http.Request) { + ac, ok := authFromCtx(r.Context()) + if !ok { + writeError(w, http.StatusUnauthorized, "Authentication required") + return + } + writeJSON(w, http.StatusOK, map[string]any{ + "user": userToPayload(ac.User), + "auth_method": ac.Method, + }) +} + +// ChangePassword — POST /api/v1/auth/change-password. +// +// Verifies the current password, updates to the new one, and revokes +// every other session of the user (the cookie carrying THIS request is +// preserved so the user stays logged in on the current device). +func (s *Server) ChangePassword(w http.ResponseWriter, r *http.Request) { + ac, ok := authFromCtx(r.Context()) + if !ok { + writeError(w, http.StatusUnauthorized, "Authentication required") + return + } + var body struct { + CurrentPassword string `json:"current_password"` + NewPassword string `json:"new_password"` + } + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + writeError(w, http.StatusUnprocessableEntity, "invalid JSON body") + return + } + if body.NewPassword == "" || len(body.NewPassword) < 8 { + writeError(w, http.StatusUnprocessableEntity, "new password must be at least 8 characters") + return + } + // Re-authenticate with the current password to prove possession. + if _, err := s.Deps.Users.Authenticate(r.Context(), ac.User.Email, body.CurrentPassword); err != nil { + writeError(w, http.StatusUnauthorized, "current password is incorrect") + return + } + if err := s.Deps.Users.UpdatePassword(r.Context(), ac.User.ID, body.NewPassword); err != nil { + writeError(w, http.StatusInternalServerError, "could not update password") + return + } + // Revoke every OTHER session — the current one stays. Best-effort: + // failure here is non-fatal (the new password is already set). + if ac.Session != nil { + _ = s.Deps.Sessions.DeleteAllForUserExcept(r.Context(), ac.User.ID, ac.Session.ID) + } + w.WriteHeader(http.StatusNoContent) +} + +// ListMySessions — GET /api/v1/auth/sessions. +func (s *Server) ListMySessions(w http.ResponseWriter, r *http.Request) { + ac, ok := authFromCtx(r.Context()) + if !ok { + writeError(w, http.StatusUnauthorized, "Authentication required") + return + } + list, err := s.Deps.Sessions.ListForUser(r.Context(), ac.User.ID) + if err != nil { + writeError(w, http.StatusInternalServerError, "could not list sessions") + return + } + currentID := "" + if ac.Session != nil { + currentID = ac.Session.ID + } + out := make([]sessionPayload, 0, len(list)) + for _, s := range list { + out = append(out, sessionToPayload(s, currentID)) + } + writeJSON(w, http.StatusOK, map[string]any{ + "sessions": out, + "total": len(out), + }) +} + +// DeleteMySession — DELETE /api/v1/auth/sessions/{id}. +func (s *Server) DeleteMySession(w http.ResponseWriter, r *http.Request, id string) { + ac, ok := authFromCtx(r.Context()) + if !ok { + writeError(w, http.StatusUnauthorized, "Authentication required") + return + } + // Confirm the session belongs to the caller — otherwise pretend it + // doesn't exist (404) so a user cannot enumerate other people's + // session ids. + list, err := s.Deps.Sessions.ListForUser(r.Context(), ac.User.ID) + if err != nil { + writeError(w, http.StatusInternalServerError, "could not list sessions") + return + } + owns := false + for _, s := range list { + if s.ID == id { + owns = true + break + } + } + if !owns { + writeError(w, http.StatusNotFound, "session not found") + return + } + _ = s.Deps.Sessions.Delete(r.Context(), id) + if ac.Session != nil && ac.Session.ID == id { + clearSessionCookie(w, r) + } + w.WriteHeader(http.StatusNoContent) +} + +// --------------------------------------------------------------------------- +// Admin user endpoints +// --------------------------------------------------------------------------- + +// mustBeAdmin enforces the admin role on a handler. Returns the auth +// context on success; on failure writes the error response and returns +// (nil, false) so the caller can `if _, ok := s.s.mustBeAdmin(w, r); !ok { return }`. +// +// CIX_AUTH_DISABLED dev mode short-circuits both checks: when the operator +// turned auth off entirely, "admin-only" loses meaning and every endpoint +// behaves as if the caller is admin. Production deployments leave the +// flag off; the requireAuth middleware then guarantees a context exists. +func (s *Server) mustBeAdmin(w http.ResponseWriter, r *http.Request) (*authContext, bool) { + if s.Deps.AuthDisabled { + return nil, true + } + ac, ok := authFromCtx(r.Context()) + if !ok { + writeError(w, http.StatusUnauthorized, "Authentication required") + return nil, false + } + if ac.User.Role != users.RoleAdmin { + writeError(w, http.StatusForbidden, "This action requires role: admin") + return nil, false + } + return ac, true +} + +// ListUsers — GET /api/v1/admin/users (admin only). +func (s *Server) ListUsers(w http.ResponseWriter, r *http.Request) { + if _, ok := s.mustBeAdmin(w, r); !ok { + return + } + list, err := s.Deps.Users.ListWithStats(r.Context()) + if err != nil { + writeError(w, http.StatusInternalServerError, "could not list users") + return + } + out := make([]userWithStatsPayload, 0, len(list)) + for _, u := range list { + out = append(out, userWithStatsToPayload(u)) + } + writeJSON(w, http.StatusOK, map[string]any{ + "users": out, + "total": len(out), + }) +} + +// CreateUser — POST /api/v1/admin/users (admin only). +func (s *Server) CreateUser(w http.ResponseWriter, r *http.Request) { + if _, ok := s.mustBeAdmin(w, r); !ok { + return + } + var body struct { + Email string `json:"email"` + InitialPassword string `json:"initial_password"` + Role string `json:"role"` + } + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + writeError(w, http.StatusUnprocessableEntity, "invalid JSON body") + return + } + body.Email = strings.TrimSpace(body.Email) + if body.Email == "" || len(body.InitialPassword) < 8 { + writeError(w, http.StatusUnprocessableEntity, "email and initial_password (>= 8 chars) are required") + return + } + u, err := s.Deps.Users.Create(r.Context(), body.Email, body.InitialPassword, body.Role, true) + if err != nil { + switch { + case errors.Is(err, users.ErrEmailTaken): + writeError(w, http.StatusConflict, "email already in use") + case errors.Is(err, users.ErrInvalidRole): + writeError(w, http.StatusUnprocessableEntity, "role must be 'admin' or 'viewer'") + default: + writeError(w, http.StatusInternalServerError, "could not create user") + } + return + } + writeJSON(w, http.StatusCreated, userToPayload(u)) +} + +// UpdateUser — PATCH /api/v1/admin/users/{id} (admin only). +func (s *Server) UpdateUser(w http.ResponseWriter, r *http.Request, id string) { + if _, ok := s.mustBeAdmin(w, r); !ok { + return + } + var body struct { + Role *string `json:"role"` + Disabled *bool `json:"disabled"` + } + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + writeError(w, http.StatusUnprocessableEntity, "invalid JSON body") + return + } + if body.Role != nil { + if err := s.Deps.Users.SetRole(r.Context(), id, *body.Role); err != nil { + respondUserMutationError(w, err) + return + } + } + if body.Disabled != nil { + if err := s.Deps.Users.SetDisabled(r.Context(), id, *body.Disabled); err != nil { + respondUserMutationError(w, err) + return + } + } + u, err := s.Deps.Users.GetByID(r.Context(), id) + if err != nil { + respondUserMutationError(w, err) + return + } + writeJSON(w, http.StatusOK, userToPayload(u)) +} + +// DeleteUser — DELETE /api/v1/admin/users/{id} (admin only). +func (s *Server) DeleteUser(w http.ResponseWriter, r *http.Request, id string) { + if _, ok := s.mustBeAdmin(w, r); !ok { + return + } + if err := s.Deps.Users.Delete(r.Context(), id); err != nil { + respondUserMutationError(w, err) + return + } + w.WriteHeader(http.StatusNoContent) +} + +func respondUserMutationError(w http.ResponseWriter, err error) { + switch { + case errors.Is(err, users.ErrNotFound): + writeError(w, http.StatusNotFound, "user not found") + case errors.Is(err, users.ErrInvalidRole): + writeError(w, http.StatusUnprocessableEntity, "role must be 'admin' or 'viewer'") + case errors.Is(err, users.ErrLastAdminBlock): + writeError(w, http.StatusForbidden, "cannot remove the last enabled admin") + default: + writeError(w, http.StatusInternalServerError, "user update failed") + } +} + +// --------------------------------------------------------------------------- +// API key endpoints +// --------------------------------------------------------------------------- + +// ListApiKeys — GET /api/v1/api-keys. +func (s *Server) ListApiKeys(w http.ResponseWriter, r *http.Request, params openapi.ListApiKeysParams) { + ac, ok := authFromCtx(r.Context()) + if !ok { + writeError(w, http.StatusUnauthorized, "Authentication required") + return + } + wantAll := params.Owner != nil && *params.Owner == "all" + if wantAll { + if ac.User.Role != users.RoleAdmin { + writeError(w, http.StatusForbidden, "owner=all is admin-only") + return + } + } + var ( + list []apikeys.ApiKey + err error + ) + if wantAll { + list, err = s.Deps.APIKeys.ListAll(r.Context()) + } else { + list, err = s.Deps.APIKeys.ListForOwner(r.Context(), ac.User.ID) + } + if err != nil { + writeError(w, http.StatusInternalServerError, "could not list api keys") + return + } + out := make([]apiKeyPayload, 0, len(list)) + for _, k := range list { + out = append(out, apiKeyToPayload(k)) + } + writeJSON(w, http.StatusOK, map[string]any{ + "api_keys": out, + "total": len(out), + }) +} + +// CreateApiKey — POST /api/v1/api-keys. +func (s *Server) CreateApiKey(w http.ResponseWriter, r *http.Request) { + ac, ok := authFromCtx(r.Context()) + if !ok { + writeError(w, http.StatusUnauthorized, "Authentication required") + return + } + var body struct { + Name string `json:"name"` + } + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + writeError(w, http.StatusUnprocessableEntity, "invalid JSON body") + return + } + body.Name = strings.TrimSpace(body.Name) + if body.Name == "" { + writeError(w, http.StatusUnprocessableEntity, "name is required") + return + } + full, ak, err := s.Deps.APIKeys.Generate(r.Context(), ac.User.ID, body.Name) + if err != nil { + writeError(w, http.StatusInternalServerError, "could not create api key") + return + } + writeJSON(w, http.StatusCreated, map[string]any{ + "api_key": apiKeyToPayload(ak), + "full_key": full, + }) +} + +// RevokeApiKey — DELETE /api/v1/api-keys/{id}. +func (s *Server) RevokeApiKey(w http.ResponseWriter, r *http.Request, id string) { + ac, ok := authFromCtx(r.Context()) + if !ok { + writeError(w, http.StatusUnauthorized, "Authentication required") + return + } + ak, err := s.Deps.APIKeys.GetByID(r.Context(), id) + if err != nil { + if errors.Is(err, apikeys.ErrNotFound) { + writeError(w, http.StatusNotFound, "api key not found") + return + } + writeError(w, http.StatusInternalServerError, "could not look up api key") + return + } + if ak.OwnerUserID != ac.User.ID && ac.User.Role != users.RoleAdmin { + // Hide existence from non-owners — same response as "not found". + writeError(w, http.StatusNotFound, "api key not found") + return + } + if err := s.Deps.APIKeys.Revoke(r.Context(), id); err != nil { + if errors.Is(err, apikeys.ErrAlreadyRevoked) { + w.WriteHeader(http.StatusNoContent) + return + } + writeError(w, http.StatusInternalServerError, "could not revoke api key") + return + } + w.WriteHeader(http.StatusNoContent) +} + +// --------------------------------------------------------------------------- +// Cookie helpers +// --------------------------------------------------------------------------- + +// setSessionCookie writes the cix_session cookie. Secure flag is set +// when the request arrived via TLS — in dev (plain HTTP localhost) the +// flag is omitted so the browser actually stores it. +func setSessionCookie(w http.ResponseWriter, r *http.Request, id string, expiresAt time.Time) { + http.SetCookie(w, &http.Cookie{ + Name: sessions.CookieName, + Value: id, + Path: "/", + Expires: expiresAt, + HttpOnly: true, + SameSite: http.SameSiteStrictMode, + Secure: r.TLS != nil, + }) +} + +func clearSessionCookie(w http.ResponseWriter, r *http.Request) { + http.SetCookie(w, &http.Cookie{ + Name: sessions.CookieName, + Value: "", + Path: "/", + MaxAge: -1, + HttpOnly: true, + SameSite: http.SameSiteStrictMode, + Secure: r.TLS != nil, + }) +} + diff --git a/server/internal/httpapi/auth_test.go b/server/internal/httpapi/auth_test.go new file mode 100644 index 0000000..5162e95 --- /dev/null +++ b/server/internal/httpapi/auth_test.go @@ -0,0 +1,602 @@ +package httpapi + +import ( + "bytes" + "context" + "database/sql" + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/dvcdsys/code-index/server/internal/apikeys" + apidb "github.com/dvcdsys/code-index/server/internal/db" + "github.com/dvcdsys/code-index/server/internal/sessions" + "github.com/dvcdsys/code-index/server/internal/users" +) + +// dbOpenMemory + seedless* are tiny shims for tests that need wired +// services against an empty database (no admin seeded). +func dbOpenMemory(t *testing.T) (*sql.DB, error) { + d, err := apidb.Open(":memory:") + if err == nil { + t.Cleanup(func() { _ = d.Close() }) + } + return d, err +} +func seedlessUsers(d *sql.DB) *users.Service { return users.New(d) } +func seedlessSessions(d *sql.DB) *sessions.Service { return sessions.New(d) } +func seedlessAPIKeys(d *sql.DB) *apikeys.Service { return apikeys.New(d) } + +// loginRR runs POST /api/v1/auth/login against router and returns the +// response recorder. Centralised because every auth-flow test starts the +// same way. +func loginRR(t *testing.T, router http.Handler, email, password string) *httptest.ResponseRecorder { + t.Helper() + body, _ := json.Marshal(map[string]string{"email": email, "password": password}) + req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + rr := httptest.NewRecorder() + router.ServeHTTP(rr, req) + return rr +} + +func sessionCookie(rr *httptest.ResponseRecorder) string { + for _, c := range rr.Result().Cookies() { + if c.Name == sessions.CookieName { + return c.Value + } + } + return "" +} + +// withCookie adds a session cookie to req for tests that simulate a +// logged-in browser. +func withCookie(req *http.Request, cookieValue string) *http.Request { + req.AddCookie(&http.Cookie{Name: sessions.CookieName, Value: cookieValue}) + return req +} + +func TestLogin_HappyPath(t *testing.T) { + f := newAuthFixture(t) + rr := loginRR(t, f.Router, "admin@example.com", "secret-password") + if rr.Code != http.StatusOK { + t.Fatalf("status = %d (body=%s)", rr.Code, rr.Body.String()) + } + if sessionCookie(rr) == "" { + t.Errorf("Set-Cookie missing %s", sessions.CookieName) + } + var body struct { + User struct { + ID string + Email string + } + } + _ = json.Unmarshal(rr.Body.Bytes(), &body) + if body.User.Email != "admin@example.com" { + t.Errorf("user.email = %q", body.User.Email) + } +} + +func TestLogin_WrongPassword(t *testing.T) { + f := newAuthFixture(t) + rr := loginRR(t, f.Router, "admin@example.com", "WRONG") + if rr.Code != http.StatusUnauthorized { + t.Fatalf("status = %d, want 401", rr.Code) + } +} + +// TestLogin_RateLimit_BlocksAfterRepeatedFailures fires N+1 wrong-password +// attempts at the live router and expects the (N+1)th to be 429 with a +// Retry-After header. The fixture's loginLimiter starts with the default +// policy (5/15min per email); the test follows that contract. +func TestLogin_RateLimit_BlocksAfterRepeatedFailures(t *testing.T) { + f := newAuthFixture(t) + for i := range 5 { + rr := loginRR(t, f.Router, "admin@example.com", "WRONG") + if rr.Code != http.StatusUnauthorized { + t.Fatalf("attempt %d: status = %d, want 401", i+1, rr.Code) + } + } + rr := loginRR(t, f.Router, "admin@example.com", "WRONG") + if rr.Code != http.StatusTooManyRequests { + t.Fatalf("6th attempt status = %d, want 429 (body=%s)", rr.Code, rr.Body.String()) + } + if ra := rr.Result().Header.Get("Retry-After"); ra == "" { + t.Errorf("Retry-After header missing on 429") + } + // Even a CORRECT password is now blocked — the limiter checks before + // authenticating, which is what we want for credential-stuffing + // resistance. + rr = loginRR(t, f.Router, "admin@example.com", "secret-password") + if rr.Code != http.StatusTooManyRequests { + t.Errorf("correct password while rate-limited status = %d, want 429", rr.Code) + } +} + +// TestLogin_RateLimit_ResetOnSuccess verifies that a user who fat-fingers +// their password a few times then logs in correctly does not stay locked +// out — the per-(IP, email) counter clears on a successful authentication. +func TestLogin_RateLimit_ResetOnSuccess(t *testing.T) { + f := newAuthFixture(t) + for i := range 4 { + rr := loginRR(t, f.Router, "admin@example.com", "WRONG") + if rr.Code != http.StatusUnauthorized { + t.Fatalf("warmup attempt %d: status = %d", i+1, rr.Code) + } + } + rr := loginRR(t, f.Router, "admin@example.com", "secret-password") + if rr.Code != http.StatusOK { + t.Fatalf("correct password status = %d, want 200", rr.Code) + } + // Counter is now reset; we should be able to fail again 4 more times + // before the next 429 (without the reset, we would hit 5 immediately). + for i := range 4 { + rr := loginRR(t, f.Router, "admin@example.com", "WRONG") + if rr.Code != http.StatusUnauthorized { + t.Fatalf("post-reset attempt %d: status = %d, want 401", i+1, rr.Code) + } + } +} + +func TestLogin_MissingFields(t *testing.T) { + f := newAuthFixture(t) + rr := loginRR(t, f.Router, "", "") + if rr.Code != http.StatusUnprocessableEntity { + t.Fatalf("status = %d, want 422", rr.Code) + } +} + +func TestMe_WithSession(t *testing.T) { + f := newAuthFixture(t) + login := loginRR(t, f.Router, "admin@example.com", "secret-password") + cookie := sessionCookie(login) + + req := withCookie(httptest.NewRequest(http.MethodGet, "/api/v1/auth/me", nil), cookie) + rr := httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + if rr.Code != http.StatusOK { + t.Fatalf("status = %d", rr.Code) + } + var body struct { + User map[string]any `json:"user"` + AuthMethod string `json:"auth_method"` + } + _ = json.Unmarshal(rr.Body.Bytes(), &body) + if body.AuthMethod != "session" { + t.Errorf("auth_method = %q, want 'session'", body.AuthMethod) + } +} + +func TestMe_WithBearer(t *testing.T) { + f := newAuthFixture(t) + req := httptest.NewRequest(http.MethodGet, "/api/v1/auth/me", nil) + req.Header.Set("Authorization", "Bearer "+f.FullKey) + rr := httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + if rr.Code != http.StatusOK { + t.Fatalf("status = %d (body=%s)", rr.Code, rr.Body.String()) + } + var body struct { + AuthMethod string `json:"auth_method"` + } + _ = json.Unmarshal(rr.Body.Bytes(), &body) + if body.AuthMethod != "api_key" { + t.Errorf("auth_method = %q, want 'api_key'", body.AuthMethod) + } +} + +func TestLogout_DropsSession(t *testing.T) { + f := newAuthFixture(t) + login := loginRR(t, f.Router, "admin@example.com", "secret-password") + cookie := sessionCookie(login) + + req := withCookie(httptest.NewRequest(http.MethodPost, "/api/v1/auth/logout", nil), cookie) + rr := httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + if rr.Code != http.StatusNoContent { + t.Fatalf("status = %d", rr.Code) + } + // Subsequent /me with the same cookie must 401. + req = withCookie(httptest.NewRequest(http.MethodGet, "/api/v1/auth/me", nil), cookie) + rr = httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + if rr.Code != http.StatusUnauthorized { + t.Errorf("/me after logout status = %d, want 401", rr.Code) + } +} + +func TestChangePassword_RotatesOtherSessions(t *testing.T) { + f := newAuthFixture(t) + // Two parallel logins (two different "browsers"). + cookieA := sessionCookie(loginRR(t, f.Router, "admin@example.com", "secret-password")) + cookieB := sessionCookie(loginRR(t, f.Router, "admin@example.com", "secret-password")) + + body, _ := json.Marshal(map[string]string{ + "current_password": "secret-password", + "new_password": "an-even-better-password", + }) + req := withCookie(httptest.NewRequest(http.MethodPost, "/api/v1/auth/change-password", bytes.NewReader(body)), cookieA) + req.Header.Set("Content-Type", "application/json") + rr := httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + if rr.Code != http.StatusNoContent { + t.Fatalf("change-password status = %d (body=%s)", rr.Code, rr.Body.String()) + } + + // Cookie A still works. + req = withCookie(httptest.NewRequest(http.MethodGet, "/api/v1/auth/me", nil), cookieA) + rr = httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + if rr.Code != http.StatusOK { + t.Errorf("cookie A status = %d, want 200 (current session preserved)", rr.Code) + } + + // Cookie B must now 401. + req = withCookie(httptest.NewRequest(http.MethodGet, "/api/v1/auth/me", nil), cookieB) + rr = httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + if rr.Code != http.StatusUnauthorized { + t.Errorf("cookie B status = %d, want 401 (other sessions revoked)", rr.Code) + } + + // New password authenticates. + rr = loginRR(t, f.Router, "admin@example.com", "an-even-better-password") + if rr.Code != http.StatusOK { + t.Errorf("login with new password status = %d", rr.Code) + } +} + +func TestBootstrapStatus_True(t *testing.T) { + // Wire services against an empty users table (no Create call) — this + // is the same shape as a brand-new deployment. + database, err := dbOpenMemory(t) + if err != nil { + t.Fatalf("open db: %v", err) + } + router := NewRouter(Deps{ + DB: database, + Users: seedlessUsers(database), + Sessions: seedlessSessions(database), + APIKeys: seedlessAPIKeys(database), + ServerVersion: "0.0.0-test", + AuthDisabled: true, // skip the auth gate so we can hit the public endpoint without setup + }) + req := httptest.NewRequest(http.MethodGet, "/api/v1/auth/bootstrap-status", nil) + rr := httptest.NewRecorder() + router.ServeHTTP(rr, req) + if rr.Code != http.StatusOK { + t.Fatalf("status = %d", rr.Code) + } + if !strings.Contains(rr.Body.String(), `"needs_bootstrap":true`) { + t.Errorf("body = %s, want needs_bootstrap:true", rr.Body.String()) + } +} + +func TestBootstrapStatus_False(t *testing.T) { + f := newAuthFixture(t) // seeds an admin + req := httptest.NewRequest(http.MethodGet, "/api/v1/auth/bootstrap-status", nil) + rr := httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + if rr.Code != http.StatusOK { + t.Fatalf("status = %d", rr.Code) + } + if !strings.Contains(rr.Body.String(), `"needs_bootstrap":false`) { + t.Errorf("body = %s, want needs_bootstrap:false", rr.Body.String()) + } +} + +// --- Admin user CRUD via HTTP --- + +func TestCreateUser_AdminOnly(t *testing.T) { + f := newAuthFixture(t) + cookie := sessionCookie(loginRR(t, f.Router, "admin@example.com", "secret-password")) + + body, _ := json.Marshal(map[string]string{ + "email": "viewer@example.com", "initial_password": "viewerpass1", "role": "viewer", + }) + req := withCookie(httptest.NewRequest(http.MethodPost, "/api/v1/admin/users", bytes.NewReader(body)), cookie) + req.Header.Set("Content-Type", "application/json") + rr := httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + if rr.Code != http.StatusCreated { + t.Fatalf("admin POST status = %d (body=%s)", rr.Code, rr.Body.String()) + } + + // Now try the same request as the viewer — expect 403. + viewerCookie := sessionCookie(loginRR(t, f.Router, "viewer@example.com", "viewerpass1")) + body, _ = json.Marshal(map[string]string{ + "email": "another@example.com", "initial_password": "anotherpass1", "role": "viewer", + }) + req = withCookie(httptest.NewRequest(http.MethodPost, "/api/v1/admin/users", bytes.NewReader(body)), viewerCookie) + req.Header.Set("Content-Type", "application/json") + rr = httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + if rr.Code != http.StatusForbidden { + t.Errorf("viewer POST status = %d, want 403", rr.Code) + } +} + +// --- API key CRUD via HTTP --- + +func TestApiKey_CreateListRevokeFlow(t *testing.T) { + f := newAuthFixture(t) + cookie := sessionCookie(loginRR(t, f.Router, "admin@example.com", "secret-password")) + + body, _ := json.Marshal(map[string]string{"name": "ci-bot"}) + req := withCookie(httptest.NewRequest(http.MethodPost, "/api/v1/api-keys", bytes.NewReader(body)), cookie) + req.Header.Set("Content-Type", "application/json") + rr := httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + if rr.Code != http.StatusCreated { + t.Fatalf("create key status = %d (body=%s)", rr.Code, rr.Body.String()) + } + var created struct { + FullKey string `json:"full_key"` + ApiKey struct{ ID string } `json:"api_key"` + } + _ = json.Unmarshal(rr.Body.Bytes(), &created) + if created.FullKey == "" { + t.Fatalf("create key did not return full_key") + } + + // Use the key — must auth. + req = httptest.NewRequest(http.MethodGet, "/api/v1/auth/me", nil) + req.Header.Set("Authorization", "Bearer "+created.FullKey) + rr = httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + if rr.Code != http.StatusOK { + t.Fatalf("Bearer with new key status = %d", rr.Code) + } + + // Revoke. + req = withCookie(httptest.NewRequest(http.MethodDelete, "/api/v1/api-keys/"+created.ApiKey.ID, nil), cookie) + rr = httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + if rr.Code != http.StatusNoContent { + t.Fatalf("revoke status = %d", rr.Code) + } + + // Same key now 401. + req = httptest.NewRequest(http.MethodGet, "/api/v1/auth/me", nil) + req.Header.Set("Authorization", "Bearer "+created.FullKey) + rr = httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + if rr.Code != http.StatusUnauthorized { + t.Errorf("revoked key status = %d, want 401", rr.Code) + } +} + +func TestApiKey_ListForOwnerHidesOthers(t *testing.T) { + f := newAuthFixture(t) + // Seed a viewer + their own key directly via the underlying services. + v, err := f.Deps.Users.Create(context.Background(), "v@b.com", "viewerpass1", users.RoleViewer, false) + if err != nil { + t.Fatalf("seed viewer: %v", err) + } + if _, _, err := f.Deps.APIKeys.Generate(context.Background(), v.ID, "viewer-only-key"); err != nil { + t.Fatalf("seed viewer key: %v", err) + } + + // Login as viewer — list must contain only their key, not the + // admin's seed key. + cookie := sessionCookie(loginRR(t, f.Router, "v@b.com", "viewerpass1")) + req := withCookie(httptest.NewRequest(http.MethodGet, "/api/v1/api-keys", nil), cookie) + rr := httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + if rr.Code != http.StatusOK { + t.Fatalf("status = %d", rr.Code) + } + var body struct { + Total int `json:"total"` + } + _ = json.Unmarshal(rr.Body.Bytes(), &body) + if body.Total != 1 { + t.Errorf("viewer sees total = %d, want 1 (own key only)", body.Total) + } +} + +// TestProjectMutations_AdminOnly verifies that PATCH and DELETE on a +// project are gated behind the admin role. POST /index/cancel is +// intentionally NOT gated — see comment on IndexCancel for why — and is +// covered separately by TestIndexCancel_AnyAuthenticatedUser. +func TestProjectMutations_AdminOnly(t *testing.T) { + f := newAuthFixture(t) + adminCookie := sessionCookie(loginRR(t, f.Router, "admin@example.com", "secret-password")) + + // Admin creates a project to act on. CreateProject is intentionally + // not admin-only — viewers can register their own projects. + createBody, _ := json.Marshal(map[string]string{"host_path": "/tmp/test-proj"}) + req := withCookie(httptest.NewRequest(http.MethodPost, "/api/v1/projects", bytes.NewReader(createBody)), adminCookie) + req.Header.Set("Content-Type", "application/json") + rr := httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + if rr.Code != http.StatusCreated { + t.Fatalf("admin create project status = %d (body=%s)", rr.Code, rr.Body.String()) + } + var created struct{ PathHash string `json:"path_hash"` } + _ = json.Unmarshal(rr.Body.Bytes(), &created) + if created.PathHash == "" { + t.Fatalf("created project payload missing path_hash: %s", rr.Body.String()) + } + + // Seed a viewer + log in as them. + viewerBody, _ := json.Marshal(map[string]string{ + "email": "viewer@example.com", "initial_password": "viewerpass1", "role": "viewer", + }) + req = withCookie(httptest.NewRequest(http.MethodPost, "/api/v1/admin/users", bytes.NewReader(viewerBody)), adminCookie) + req.Header.Set("Content-Type", "application/json") + rr = httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + if rr.Code != http.StatusCreated { + t.Fatalf("seed viewer status = %d (body=%s)", rr.Code, rr.Body.String()) + } + viewerCookie := sessionCookie(loginRR(t, f.Router, "viewer@example.com", "viewerpass1")) + + // Each gated endpoint must 403 for the viewer, then succeed for the + // admin. PATCH first (mutates settings), DELETE last (destructive). + cases := []struct { + name string + method string + path string + body []byte + adminStatus int + }{ + { + name: "patch settings", + method: http.MethodPatch, + path: "/api/v1/projects/" + created.PathHash, + body: mustJSON(t, map[string]any{ + "settings": map[string]any{"exclude_patterns": []string{"vendor"}}, + }), + adminStatus: http.StatusOK, + }, + { + name: "delete project", + method: http.MethodDelete, + path: "/api/v1/projects/" + created.PathHash, + adminStatus: http.StatusNoContent, + }, + } + for _, c := range cases { + t.Run(c.name+"/viewer-forbidden", func(t *testing.T) { + req := withCookie(httptest.NewRequest(c.method, c.path, bytes.NewReader(c.body)), viewerCookie) + req.Header.Set("Content-Type", "application/json") + rr := httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + if rr.Code != http.StatusForbidden { + t.Errorf("viewer %s %s status = %d, want 403", c.method, c.path, rr.Code) + } + }) + t.Run(c.name+"/admin-allowed", func(t *testing.T) { + req := withCookie(httptest.NewRequest(c.method, c.path, bytes.NewReader(c.body)), adminCookie) + req.Header.Set("Content-Type", "application/json") + rr := httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + if rr.Code != c.adminStatus { + t.Errorf("admin %s %s status = %d, want %d (body=%s)", c.method, c.path, rr.Code, c.adminStatus, rr.Body.String()) + } + }) + } +} + +func mustJSON(t *testing.T, v any) []byte { + t.Helper() + b, err := json.Marshal(v) + if err != nil { + t.Fatalf("marshal: %v", err) + } + return b +} + +// TestIndexCancel_AnyAuthenticatedUser pins the policy that /index/cancel +// is open to any authenticated user. The CLI calls cancel in defer-cleanup +// on early exit (Ctrl-C, network drop), and gating it behind admin would +// strand viewer-owned run locks until the 1-hour TTL. +func TestIndexCancel_AnyAuthenticatedUser(t *testing.T) { + f := newAuthFixture(t) + adminCookie := sessionCookie(loginRR(t, f.Router, "admin@example.com", "secret-password")) + + // Seed a viewer + a project they can cancel against. + viewerBody, _ := json.Marshal(map[string]string{ + "email": "viewer@example.com", "initial_password": "viewerpass1", "role": "viewer", + }) + req := withCookie(httptest.NewRequest(http.MethodPost, "/api/v1/admin/users", bytes.NewReader(viewerBody)), adminCookie) + req.Header.Set("Content-Type", "application/json") + rr := httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + if rr.Code != http.StatusCreated { + t.Fatalf("seed viewer status = %d (body=%s)", rr.Code, rr.Body.String()) + } + viewerCookie := sessionCookie(loginRR(t, f.Router, "viewer@example.com", "viewerpass1")) + + createBody, _ := json.Marshal(map[string]string{"host_path": "/tmp/cancel-test"}) + req = withCookie(httptest.NewRequest(http.MethodPost, "/api/v1/projects", bytes.NewReader(createBody)), viewerCookie) + req.Header.Set("Content-Type", "application/json") + rr = httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + if rr.Code != http.StatusCreated { + t.Fatalf("viewer create project status = %d (body=%s)", rr.Code, rr.Body.String()) + } + var created struct{ PathHash string `json:"path_hash"` } + _ = json.Unmarshal(rr.Body.Bytes(), &created) + + // Viewer cancels — must NOT 403 (idempotent 200 even when no run is active). + req = withCookie(httptest.NewRequest(http.MethodPost, "/api/v1/projects/"+created.PathHash+"/index/cancel", nil), viewerCookie) + rr = httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + if rr.Code != http.StatusOK { + t.Errorf("viewer cancel status = %d, want 200 (body=%s)", rr.Code, rr.Body.String()) + } +} + +// TestListUsers_IncludesStats — admin-list payload must carry the three +// aggregate columns the dashboard's Users table renders. Round-trip the +// JSON to ensure field names match the OpenAPI contract verbatim. +func TestListUsers_IncludesStats(t *testing.T) { + f := newAuthFixture(t) + cookie := sessionCookie(loginRR(t, f.Router, "admin@example.com", "secret-password")) + + // Seed a viewer + give them an api-key so the row is non-trivial. + v, err := f.Deps.Users.Create(context.Background(), "v@b.com", "viewerpass1", users.RoleViewer, false) + if err != nil { + t.Fatalf("seed viewer: %v", err) + } + if _, _, err := f.Deps.APIKeys.Generate(context.Background(), v.ID, "k"); err != nil { + t.Fatalf("seed key: %v", err) + } + + req := withCookie(httptest.NewRequest(http.MethodGet, "/api/v1/admin/users", nil), cookie) + rr := httptest.NewRecorder() + f.Router.ServeHTTP(rr, req) + if rr.Code != http.StatusOK { + t.Fatalf("status = %d (body=%s)", rr.Code, rr.Body.String()) + } + + var body struct { + Total int `json:"total"` + Users []struct { + Email string `json:"email"` + LastLoginAt *string `json:"last_login_at"` + ActiveSessionsCount int `json:"active_sessions_count"` + ApiKeysCount int `json:"api_keys_count"` + } `json:"users"` + } + if err := json.Unmarshal(rr.Body.Bytes(), &body); err != nil { + t.Fatalf("unmarshal: %v (body=%s)", err, rr.Body.String()) + } + if body.Total != 2 { + t.Fatalf("total = %d, want 2", body.Total) + } + by := map[string]int{} + var viewerRow *struct { + Email string `json:"email"` + LastLoginAt *string `json:"last_login_at"` + ActiveSessionsCount int `json:"active_sessions_count"` + ApiKeysCount int `json:"api_keys_count"` + } + for i := range body.Users { + by[body.Users[i].Email] = i + if body.Users[i].Email == "v@b.com" { + viewerRow = &body.Users[i] + } + } + if viewerRow == nil { + t.Fatalf("viewer row missing in payload: %s", rr.Body.String()) + } + if viewerRow.ApiKeysCount != 1 { + t.Errorf("viewer api_keys_count = %d, want 1", viewerRow.ApiKeysCount) + } + if viewerRow.LastLoginAt != nil { + t.Errorf("viewer last_login_at = %v, want null (never logged in)", *viewerRow.LastLoginAt) + } + // Admin: just-logged-in via loginRR → 1 active session. + admin := body.Users[by["admin@example.com"]] + if admin.ActiveSessionsCount < 1 { + t.Errorf("admin active_sessions_count = %d, want >=1", admin.ActiveSessionsCount) + } + if admin.LastLoginAt == nil { + t.Errorf("admin last_login_at should be set after login") + } +} diff --git a/server/internal/httpapi/dashboard.go b/server/internal/httpapi/dashboard.go new file mode 100644 index 0000000..8a0185d --- /dev/null +++ b/server/internal/httpapi/dashboard.go @@ -0,0 +1,154 @@ +package httpapi + +import ( + "io/fs" + "net/http" + "strings" + + "github.com/dvcdsys/code-index/server/internal/httpapi/dashboard" +) + +// dashboardFS is the embedded SPA bundle, rooted at "dist/" so callers +// reference paths like "index.html" or "assets/index-abcd.js". +var dashboardFS = func() fs.FS { + sub, err := fs.Sub(dashboard.Assets, "dist") + if err != nil { + // embed.FS must contain "dist/" — codegen would have caught this + // at build time. A runtime panic here means the embed directive + // was edited without keeping the path in sync. + panic("dashboard: dist/ missing from embed: " + err.Error()) + } + return sub +}() + +// dashboardContentTypeMap mirrors docsContentTypeMap — pin the Content-Type +// for the handful of asset extensions Vite emits. Without this, Safari +// occasionally refuses to execute .js served as "application/javascript" +// (no charset). +var dashboardContentTypeMap = map[string]string{ + ".html": "text/html; charset=utf-8", + ".css": "text/css; charset=utf-8", + ".js": "application/javascript; charset=utf-8", + ".mjs": "application/javascript; charset=utf-8", + ".json": "application/json; charset=utf-8", + ".svg": "image/svg+xml", + ".png": "image/png", + ".ico": "image/x-icon", + ".woff": "font/woff", + ".woff2": "font/woff2", + ".map": "application/json; charset=utf-8", +} + +// dashboardIndexHandler serves the SPA shell on GET /dashboard and /dashboard/. +// The browser then loads /dashboard/assets/* and the runtime React Router +// takes over for client-side navigation. +func dashboardIndexHandler(w http.ResponseWriter, r *http.Request) { + serveDashboardIndex(w, r) +} + +// dashboardAssetsHandler serves anything under /dashboard/. Three cases: +// +// 1. /dashboard/assets/ — return the embedded asset +// 2. /dashboard/ e.g. /dashboard/favicon.svg — return +// the embedded file if it exists, 404 otherwise +// 3. /dashboard/ e.g. /dashboard/projects/abc — fall +// back to index.html so the SPA's HTML5 history routing keeps working +// across browser refreshes +func dashboardAssetsHandler(w http.ResponseWriter, r *http.Request) { + const prefix = "/dashboard/" + name := strings.TrimPrefix(r.URL.Path, prefix) + if name == "" { + serveDashboardIndex(w, r) + return + } + + // History fallback — anything that doesn't look like a file extension + // is treated as an in-app route. + if !strings.Contains(name, ".") { + serveDashboardIndex(w, r) + return + } + + data, err := fs.ReadFile(dashboardFS, name) + if err != nil { + http.NotFound(w, r) + return + } + if ct := dashboardContentTypeFor(name); ct != "" { + w.Header().Set("Content-Type", ct) + } + // Vite emits hashed filenames under assets/, so they're safe to cache + // hard. Everything else (PLACEHOLDER.html, future favicon) gets a short + // max-age so the operator picks up changes within a few minutes. + if strings.HasPrefix(name, "assets/") { + w.Header().Set("Cache-Control", "public, max-age=31536000, immutable") + } else { + w.Header().Set("Cache-Control", "public, max-age=300") + } + _, _ = w.Write(data) +} + +// dashboardPlaceholderHTML is shown when dist/index.html is missing — i.e. +// `go build` ran without `make dashboard-build` first. We could embed a +// separate file but inlining keeps the embed.FS minimal and removes the +// fragility of vite's emptyOutDir wiping any committed placeholder file. +const dashboardPlaceholderHTML = ` + + + + +cix dashboard — not built + + + +

    cix dashboard placeholder

    +

    The React dashboard hasn’t been built into this server binary yet.

    +

    From the repo root run:

    +
    cd server && make dashboard-build && make build
    +

    Then restart the server. If you’re seeing this in production, your CI build forgot to run the dashboard stage.

    + +` + +// serveDashboardIndex returns dist/index.html when present, otherwise the +// inline placeholder above. This lets `go build` succeed on a fresh clone +// without `make dashboard-build` first — the operator gets a clear "you +// need to build the dashboard" message instead of a blank page. +// +// The HTML is never cached: a fresh deploy must invalidate it immediately +// so the new asset hashes are picked up on the next request. +func serveDashboardIndex(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.Header().Set("Cache-Control", "no-store") + if data, err := fs.ReadFile(dashboardFS, "index.html"); err == nil { + _, _ = w.Write(data) + return + } + _, _ = w.Write([]byte(dashboardPlaceholderHTML)) +} + +// dashboardContentTypeFor returns the explicit Content-Type for the given +// filename, or "" if Go's stdlib detection is good enough. +func dashboardContentTypeFor(name string) string { + for ext, ct := range dashboardContentTypeMap { + if strings.HasSuffix(name, ext) { + return ct + } + } + return "" +} diff --git a/server/internal/httpapi/dashboard/dist/.gitkeep b/server/internal/httpapi/dashboard/dist/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/server/internal/httpapi/dashboard/embed.go b/server/internal/httpapi/dashboard/embed.go new file mode 100644 index 0000000..88b2d10 --- /dev/null +++ b/server/internal/httpapi/dashboard/embed.go @@ -0,0 +1,21 @@ +// Package dashboard holds the React SPA bundle that is served at /dashboard. +// +// The bundle is produced by `cd server && make dashboard-build` (which in +// turn runs `npm run build` inside server/dashboard/). Vite output lands in +// this directory's dist/ tree: +// +// dist/index.html +// dist/assets/index-.js +// dist/assets/index-.css +// +// On a fresh clone, dist/ contains only the committed `.gitkeep` marker — +// the real build artefacts are gitignored. The `all:` prefix in the embed +// directive includes dotfiles so the embed.FS is non-empty even before the +// frontend has been built. dashboard.go serves a hardcoded "please build" +// placeholder when index.html is missing. +package dashboard + +import "embed" + +//go:embed all:dist +var Assets embed.FS diff --git a/server/internal/httpapi/dashboard_test.go b/server/internal/httpapi/dashboard_test.go new file mode 100644 index 0000000..697c6ff --- /dev/null +++ b/server/internal/httpapi/dashboard_test.go @@ -0,0 +1,108 @@ +package httpapi + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +// dashboardServer wires the router with auth disabled so the dashboard +// routes can be hit directly. Auth would only catch the API calls anyway — +// /dashboard/* is a public path by design. +func dashboardServer(t *testing.T) *httptest.Server { + t.Helper() + d := Deps{AuthDisabled: true} + srv := httptest.NewServer(NewRouter(d)) + t.Cleanup(srv.Close) + return srv +} + +func TestDashboard_IndexServesHTML(t *testing.T) { + srv := dashboardServer(t) + resp, err := http.Get(srv.URL + "/dashboard") + if err != nil { + t.Fatalf("GET /dashboard: %v", err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + t.Fatalf("status = %d, want 200", resp.StatusCode) + } + if ct := resp.Header.Get("Content-Type"); !strings.HasPrefix(ct, "text/html") { + t.Fatalf("Content-Type = %q, want text/html…", ct) + } +} + +func TestDashboard_HistoryFallback(t *testing.T) { + // Deep links into the SPA must return the same HTML shell so the + // browser can boot React Router and route client-side. Anything + // without a file extension counts as an in-app route. + srv := dashboardServer(t) + for _, path := range []string{ + "/dashboard/projects", + "/dashboard/projects/abc123", + "/dashboard/some/deep/route", + } { + t.Run(path, func(t *testing.T) { + resp, err := http.Get(srv.URL + path) + if err != nil { + t.Fatalf("GET %s: %v", path, err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + t.Fatalf("status = %d, want 200 (history fallback)", resp.StatusCode) + } + if ct := resp.Header.Get("Content-Type"); !strings.HasPrefix(ct, "text/html") { + t.Fatalf("Content-Type = %q, want text/html… (history fallback)", ct) + } + }) + } +} + +func TestDashboard_MissingAsset404(t *testing.T) { + // A path that LOOKS like a file (has an extension) and isn't in the + // embed should 404 rather than fall through to index.html — otherwise + // the SPA would silently absorb /dashboard/foo.js requests. + srv := dashboardServer(t) + resp, err := http.Get(srv.URL + "/dashboard/no-such-file.js") + if err != nil { + t.Fatalf("GET missing asset: %v", err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusNotFound { + t.Fatalf("status = %d, want 404", resp.StatusCode) + } +} + +func TestDashboard_PlaceholderConstantPresent(t *testing.T) { + // The inline placeholder (dashboardPlaceholderHTML in dashboard.go) is + // the fallback when dist/index.html is missing — typically because the + // operator forgot to run `make dashboard-build` before `go build`. We + // can't easily exercise the missing-index path in unit tests (the + // embed.FS is sealed at compile time), but we CAN sanity-check the + // constant itself so a refactor never silently empties it. + if !strings.Contains(dashboardPlaceholderHTML, "make dashboard-build") { + t.Fatal("placeholder HTML must mention `make dashboard-build` so the operator knows what to do") + } + if !strings.Contains(dashboardPlaceholderHTML, " not in public paths") + } + // Sanity — API paths stay gated. + if isPublicPath("/api/v1/projects") { + t.Fatal("/api/v1/projects must NOT be public") + } +} diff --git a/server/internal/httpapi/docs.go b/server/internal/httpapi/docs.go new file mode 100644 index 0000000..1aafc32 --- /dev/null +++ b/server/internal/httpapi/docs.go @@ -0,0 +1,102 @@ +package httpapi + +import ( + "io/fs" + "net/http" + + "github.com/dvcdsys/code-index/server/internal/httpapi/docs" + "github.com/dvcdsys/code-index/server/internal/httpapi/openapi" +) + +// docsContentTypeMap overrides the default Go mime detection for the +// swagger-ui bundle. Without this, .js sometimes resolves to +// "application/javascript" on older systems and Safari refuses to execute. +var docsContentTypeMap = map[string]string{ + ".html": "text/html; charset=utf-8", + ".css": "text/css; charset=utf-8", + ".js": "application/javascript; charset=utf-8", + ".png": "image/png", +} + +// docsFS is the embedded Swagger UI bundle, rooted at swagger-ui/ (i.e. +// "index.html" → swagger-ui/index.html). Computed once at package init. +var docsFS = func() fs.FS { + sub, err := fs.Sub(docs.Assets, "swagger-ui") + if err != nil { + // embed.FS must contain "swagger-ui/" — codegen will catch a typo + // at build time, so a runtime panic here means someone deleted + // the bundle without bumping the embed directive. + panic("docs: swagger-ui bundle missing from embed: " + err.Error()) + } + return sub +}() + +// docsIndexHandler serves the Swagger UI shell on GET /docs (and /docs/). +// The browser then fetches static assets via /docs/ (handled by +// docsAssetsHandler) and the spec via /openapi.json (handled by +// openapiSpecHandler). +func docsIndexHandler(w http.ResponseWriter, r *http.Request) { + serveDocsFile(w, r, "index.html") +} + +// docsAssetsHandler serves anything under /docs/. Strips the /docs/ +// prefix and looks the file up in the embedded bundle. +func docsAssetsHandler(w http.ResponseWriter, r *http.Request) { + const prefix = "/docs/" + name := r.URL.Path[len(prefix):] + if name == "" || name == "/" { + serveDocsFile(w, r, "index.html") + return + } + serveDocsFile(w, r, name) +} + +// serveDocsFile is the common path that reads from the embedded bundle and +// sets the right Content-Type for the file extension. +func serveDocsFile(w http.ResponseWriter, _ *http.Request, name string) { + data, err := fs.ReadFile(docsFS, name) + if err != nil { + http.NotFound(w, nil) + return + } + if ct := contentTypeFor(name); ct != "" { + w.Header().Set("Content-Type", ct) + } + // Static bundle is keyed by version in the URL implicitly (we don't + // version it), so a short cache is the safest default — long enough + // to avoid request storms, short enough to pick up a fresh deploy. + w.Header().Set("Cache-Control", "public, max-age=300") + _, _ = w.Write(data) +} + +// contentTypeFor returns the explicit content type for the given filename, +// or "" if Go's stdlib detection is good enough. +func contentTypeFor(name string) string { + for ext, ct := range docsContentTypeMap { + if hasSuffix(name, ext) { + return ct + } + } + return "" +} + +// hasSuffix is a tiny inline replacement for strings.HasSuffix to avoid +// pulling the strings import into this single-purpose file. +func hasSuffix(s, suffix string) bool { + return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix +} + +// openapiSpecHandler serves the OpenAPI spec at /openapi.json. The spec +// is decoded from the gzip+base64 blob embedded in openapi.gen.go (via +// the embedded-spec oapi-codegen flag) — there is no separate file on +// disk to drift out of sync. +func openapiSpecHandler(w http.ResponseWriter, _ *http.Request) { + data, err := openapi.GetSpecJSON() + if err != nil { + http.Error(w, "spec unavailable: "+err.Error(), http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.Header().Set("Cache-Control", "public, max-age=300") + _, _ = w.Write(data) +} diff --git a/server/internal/httpapi/docs/embed.go b/server/internal/httpapi/docs/embed.go new file mode 100644 index 0000000..921d036 --- /dev/null +++ b/server/internal/httpapi/docs/embed.go @@ -0,0 +1,15 @@ +// Package docs holds the Swagger UI bundle that is served at /docs. +// +// The OpenAPI spec itself is NOT embedded here — it ships compressed inside +// the generated openapi.gen.go (via the embedded-spec oapi-codegen flag) and +// is served via openapi.GetSpecJSON() at /openapi.json. Keeping these +// separate avoids the spec being duplicated in two places that can drift. +// +// The bundle was fetched from jsdelivr (swagger-ui-dist@5.18.2). To bump, +// re-run `make swagger-ui-fetch` in server/. +package docs + +import "embed" + +//go:embed swagger-ui +var Assets embed.FS diff --git a/server/internal/httpapi/docs/swagger-ui/favicon-16x16.png b/server/internal/httpapi/docs/swagger-ui/favicon-16x16.png new file mode 100644 index 0000000..8b194e6 Binary files /dev/null and b/server/internal/httpapi/docs/swagger-ui/favicon-16x16.png differ diff --git a/server/internal/httpapi/docs/swagger-ui/favicon-32x32.png b/server/internal/httpapi/docs/swagger-ui/favicon-32x32.png new file mode 100644 index 0000000..249737f Binary files /dev/null and b/server/internal/httpapi/docs/swagger-ui/favicon-32x32.png differ diff --git a/server/internal/httpapi/docs/swagger-ui/index.html b/server/internal/httpapi/docs/swagger-ui/index.html new file mode 100644 index 0000000..9b46c56 --- /dev/null +++ b/server/internal/httpapi/docs/swagger-ui/index.html @@ -0,0 +1,38 @@ + + + + + cix-server API · Swagger UI + + + + + + +
    + + + + + diff --git a/server/internal/httpapi/docs/swagger-ui/swagger-ui-bundle.js b/server/internal/httpapi/docs/swagger-ui/swagger-ui-bundle.js new file mode 100644 index 0000000..4526219 --- /dev/null +++ b/server/internal/httpapi/docs/swagger-ui/swagger-ui-bundle.js @@ -0,0 +1,2 @@ +/*! For license information please see swagger-ui-bundle.js.LICENSE.txt */ +!function webpackUniversalModuleDefinition(s,o){"object"==typeof exports&&"object"==typeof module?module.exports=o():"function"==typeof define&&define.amd?define([],o):"object"==typeof exports?exports.SwaggerUIBundle=o():s.SwaggerUIBundle=o()}(this,(()=>(()=>{var s,o,i={69119:(s,o)=>{"use strict";Object.defineProperty(o,"__esModule",{value:!0}),o.BLANK_URL=o.relativeFirstCharacters=o.whitespaceEscapeCharsRegex=o.urlSchemeRegex=o.ctrlCharactersRegex=o.htmlCtrlEntityRegex=o.htmlEntitiesRegex=o.invalidProtocolRegex=void 0,o.invalidProtocolRegex=/^([^\w]*)(javascript|data|vbscript)/im,o.htmlEntitiesRegex=/&#(\w+)(^\w|;)?/g,o.htmlCtrlEntityRegex=/&(newline|tab);/gi,o.ctrlCharactersRegex=/[\u0000-\u001F\u007F-\u009F\u2000-\u200D\uFEFF]/gim,o.urlSchemeRegex=/^.+(:|:)/gim,o.whitespaceEscapeCharsRegex=/(\\|%5[cC])((%(6[eE]|72|74))|[nrt])/g,o.relativeFirstCharacters=[".","/"],o.BLANK_URL="about:blank"},16750:(s,o,i)=>{"use strict";o.J=void 0;var u=i(69119);function decodeURI(s){try{return decodeURIComponent(s)}catch(o){return s}}o.J=function sanitizeUrl(s){if(!s)return u.BLANK_URL;var o,i,_=decodeURI(s);do{o=(_=decodeURI(_=(i=_,i.replace(u.ctrlCharactersRegex,"").replace(u.htmlEntitiesRegex,(function(s,o){return String.fromCharCode(o)}))).replace(u.htmlCtrlEntityRegex,"").replace(u.ctrlCharactersRegex,"").replace(u.whitespaceEscapeCharsRegex,"").trim())).match(u.ctrlCharactersRegex)||_.match(u.htmlEntitiesRegex)||_.match(u.htmlCtrlEntityRegex)||_.match(u.whitespaceEscapeCharsRegex)}while(o&&o.length>0);var w=_;if(!w)return u.BLANK_URL;if(function isRelativeUrlWithoutProtocol(s){return u.relativeFirstCharacters.indexOf(s[0])>-1}(w))return w;var x=w.match(u.urlSchemeRegex);if(!x)return w;var C=x[0];return u.invalidProtocolRegex.test(C)?u.BLANK_URL:w}},67526:(s,o)=>{"use strict";o.byteLength=function byteLength(s){var o=getLens(s),i=o[0],u=o[1];return 3*(i+u)/4-u},o.toByteArray=function toByteArray(s){var o,i,w=getLens(s),x=w[0],C=w[1],j=new _(function _byteLength(s,o,i){return 3*(o+i)/4-i}(0,x,C)),L=0,B=C>0?x-4:x;for(i=0;i>16&255,j[L++]=o>>8&255,j[L++]=255&o;2===C&&(o=u[s.charCodeAt(i)]<<2|u[s.charCodeAt(i+1)]>>4,j[L++]=255&o);1===C&&(o=u[s.charCodeAt(i)]<<10|u[s.charCodeAt(i+1)]<<4|u[s.charCodeAt(i+2)]>>2,j[L++]=o>>8&255,j[L++]=255&o);return j},o.fromByteArray=function fromByteArray(s){for(var o,u=s.length,_=u%3,w=[],x=16383,C=0,j=u-_;Cj?j:C+x));1===_?(o=s[u-1],w.push(i[o>>2]+i[o<<4&63]+"==")):2===_&&(o=(s[u-2]<<8)+s[u-1],w.push(i[o>>10]+i[o>>4&63]+i[o<<2&63]+"="));return w.join("")};for(var i=[],u=[],_="undefined"!=typeof Uint8Array?Uint8Array:Array,w="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",x=0;x<64;++x)i[x]=w[x],u[w.charCodeAt(x)]=x;function getLens(s){var o=s.length;if(o%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var i=s.indexOf("=");return-1===i&&(i=o),[i,i===o?0:4-i%4]}function encodeChunk(s,o,u){for(var _,w,x=[],C=o;C>18&63]+i[w>>12&63]+i[w>>6&63]+i[63&w]);return x.join("")}u["-".charCodeAt(0)]=62,u["_".charCodeAt(0)]=63},48287:(s,o,i)=>{"use strict";const u=i(67526),_=i(251),w="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):null;o.Buffer=Buffer,o.SlowBuffer=function SlowBuffer(s){+s!=s&&(s=0);return Buffer.alloc(+s)},o.INSPECT_MAX_BYTES=50;const x=2147483647;function createBuffer(s){if(s>x)throw new RangeError('The value "'+s+'" is invalid for option "size"');const o=new Uint8Array(s);return Object.setPrototypeOf(o,Buffer.prototype),o}function Buffer(s,o,i){if("number"==typeof s){if("string"==typeof o)throw new TypeError('The "string" argument must be of type string. Received type number');return allocUnsafe(s)}return from(s,o,i)}function from(s,o,i){if("string"==typeof s)return function fromString(s,o){"string"==typeof o&&""!==o||(o="utf8");if(!Buffer.isEncoding(o))throw new TypeError("Unknown encoding: "+o);const i=0|byteLength(s,o);let u=createBuffer(i);const _=u.write(s,o);_!==i&&(u=u.slice(0,_));return u}(s,o);if(ArrayBuffer.isView(s))return function fromArrayView(s){if(isInstance(s,Uint8Array)){const o=new Uint8Array(s);return fromArrayBuffer(o.buffer,o.byteOffset,o.byteLength)}return fromArrayLike(s)}(s);if(null==s)throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof s);if(isInstance(s,ArrayBuffer)||s&&isInstance(s.buffer,ArrayBuffer))return fromArrayBuffer(s,o,i);if("undefined"!=typeof SharedArrayBuffer&&(isInstance(s,SharedArrayBuffer)||s&&isInstance(s.buffer,SharedArrayBuffer)))return fromArrayBuffer(s,o,i);if("number"==typeof s)throw new TypeError('The "value" argument must not be of type number. Received type number');const u=s.valueOf&&s.valueOf();if(null!=u&&u!==s)return Buffer.from(u,o,i);const _=function fromObject(s){if(Buffer.isBuffer(s)){const o=0|checked(s.length),i=createBuffer(o);return 0===i.length||s.copy(i,0,0,o),i}if(void 0!==s.length)return"number"!=typeof s.length||numberIsNaN(s.length)?createBuffer(0):fromArrayLike(s);if("Buffer"===s.type&&Array.isArray(s.data))return fromArrayLike(s.data)}(s);if(_)return _;if("undefined"!=typeof Symbol&&null!=Symbol.toPrimitive&&"function"==typeof s[Symbol.toPrimitive])return Buffer.from(s[Symbol.toPrimitive]("string"),o,i);throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof s)}function assertSize(s){if("number"!=typeof s)throw new TypeError('"size" argument must be of type number');if(s<0)throw new RangeError('The value "'+s+'" is invalid for option "size"')}function allocUnsafe(s){return assertSize(s),createBuffer(s<0?0:0|checked(s))}function fromArrayLike(s){const o=s.length<0?0:0|checked(s.length),i=createBuffer(o);for(let u=0;u=x)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+x.toString(16)+" bytes");return 0|s}function byteLength(s,o){if(Buffer.isBuffer(s))return s.length;if(ArrayBuffer.isView(s)||isInstance(s,ArrayBuffer))return s.byteLength;if("string"!=typeof s)throw new TypeError('The "string" argument must be one of type string, Buffer, or ArrayBuffer. Received type '+typeof s);const i=s.length,u=arguments.length>2&&!0===arguments[2];if(!u&&0===i)return 0;let _=!1;for(;;)switch(o){case"ascii":case"latin1":case"binary":return i;case"utf8":case"utf-8":return utf8ToBytes(s).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*i;case"hex":return i>>>1;case"base64":return base64ToBytes(s).length;default:if(_)return u?-1:utf8ToBytes(s).length;o=(""+o).toLowerCase(),_=!0}}function slowToString(s,o,i){let u=!1;if((void 0===o||o<0)&&(o=0),o>this.length)return"";if((void 0===i||i>this.length)&&(i=this.length),i<=0)return"";if((i>>>=0)<=(o>>>=0))return"";for(s||(s="utf8");;)switch(s){case"hex":return hexSlice(this,o,i);case"utf8":case"utf-8":return utf8Slice(this,o,i);case"ascii":return asciiSlice(this,o,i);case"latin1":case"binary":return latin1Slice(this,o,i);case"base64":return base64Slice(this,o,i);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return utf16leSlice(this,o,i);default:if(u)throw new TypeError("Unknown encoding: "+s);s=(s+"").toLowerCase(),u=!0}}function swap(s,o,i){const u=s[o];s[o]=s[i],s[i]=u}function bidirectionalIndexOf(s,o,i,u,_){if(0===s.length)return-1;if("string"==typeof i?(u=i,i=0):i>2147483647?i=2147483647:i<-2147483648&&(i=-2147483648),numberIsNaN(i=+i)&&(i=_?0:s.length-1),i<0&&(i=s.length+i),i>=s.length){if(_)return-1;i=s.length-1}else if(i<0){if(!_)return-1;i=0}if("string"==typeof o&&(o=Buffer.from(o,u)),Buffer.isBuffer(o))return 0===o.length?-1:arrayIndexOf(s,o,i,u,_);if("number"==typeof o)return o&=255,"function"==typeof Uint8Array.prototype.indexOf?_?Uint8Array.prototype.indexOf.call(s,o,i):Uint8Array.prototype.lastIndexOf.call(s,o,i):arrayIndexOf(s,[o],i,u,_);throw new TypeError("val must be string, number or Buffer")}function arrayIndexOf(s,o,i,u,_){let w,x=1,C=s.length,j=o.length;if(void 0!==u&&("ucs2"===(u=String(u).toLowerCase())||"ucs-2"===u||"utf16le"===u||"utf-16le"===u)){if(s.length<2||o.length<2)return-1;x=2,C/=2,j/=2,i/=2}function read(s,o){return 1===x?s[o]:s.readUInt16BE(o*x)}if(_){let u=-1;for(w=i;wC&&(i=C-j),w=i;w>=0;w--){let i=!0;for(let u=0;u_&&(u=_):u=_;const w=o.length;let x;for(u>w/2&&(u=w/2),x=0;x>8,_=i%256,w.push(_),w.push(u);return w}(o,s.length-i),s,i,u)}function base64Slice(s,o,i){return 0===o&&i===s.length?u.fromByteArray(s):u.fromByteArray(s.slice(o,i))}function utf8Slice(s,o,i){i=Math.min(s.length,i);const u=[];let _=o;for(;_239?4:o>223?3:o>191?2:1;if(_+x<=i){let i,u,C,j;switch(x){case 1:o<128&&(w=o);break;case 2:i=s[_+1],128==(192&i)&&(j=(31&o)<<6|63&i,j>127&&(w=j));break;case 3:i=s[_+1],u=s[_+2],128==(192&i)&&128==(192&u)&&(j=(15&o)<<12|(63&i)<<6|63&u,j>2047&&(j<55296||j>57343)&&(w=j));break;case 4:i=s[_+1],u=s[_+2],C=s[_+3],128==(192&i)&&128==(192&u)&&128==(192&C)&&(j=(15&o)<<18|(63&i)<<12|(63&u)<<6|63&C,j>65535&&j<1114112&&(w=j))}}null===w?(w=65533,x=1):w>65535&&(w-=65536,u.push(w>>>10&1023|55296),w=56320|1023&w),u.push(w),_+=x}return function decodeCodePointsArray(s){const o=s.length;if(o<=C)return String.fromCharCode.apply(String,s);let i="",u=0;for(;uu.length?(Buffer.isBuffer(o)||(o=Buffer.from(o)),o.copy(u,_)):Uint8Array.prototype.set.call(u,o,_);else{if(!Buffer.isBuffer(o))throw new TypeError('"list" argument must be an Array of Buffers');o.copy(u,_)}_+=o.length}return u},Buffer.byteLength=byteLength,Buffer.prototype._isBuffer=!0,Buffer.prototype.swap16=function swap16(){const s=this.length;if(s%2!=0)throw new RangeError("Buffer size must be a multiple of 16-bits");for(let o=0;oi&&(s+=" ... "),""},w&&(Buffer.prototype[w]=Buffer.prototype.inspect),Buffer.prototype.compare=function compare(s,o,i,u,_){if(isInstance(s,Uint8Array)&&(s=Buffer.from(s,s.offset,s.byteLength)),!Buffer.isBuffer(s))throw new TypeError('The "target" argument must be one of type Buffer or Uint8Array. Received type '+typeof s);if(void 0===o&&(o=0),void 0===i&&(i=s?s.length:0),void 0===u&&(u=0),void 0===_&&(_=this.length),o<0||i>s.length||u<0||_>this.length)throw new RangeError("out of range index");if(u>=_&&o>=i)return 0;if(u>=_)return-1;if(o>=i)return 1;if(this===s)return 0;let w=(_>>>=0)-(u>>>=0),x=(i>>>=0)-(o>>>=0);const C=Math.min(w,x),j=this.slice(u,_),L=s.slice(o,i);for(let s=0;s>>=0,isFinite(i)?(i>>>=0,void 0===u&&(u="utf8")):(u=i,i=void 0)}const _=this.length-o;if((void 0===i||i>_)&&(i=_),s.length>0&&(i<0||o<0)||o>this.length)throw new RangeError("Attempt to write outside buffer bounds");u||(u="utf8");let w=!1;for(;;)switch(u){case"hex":return hexWrite(this,s,o,i);case"utf8":case"utf-8":return utf8Write(this,s,o,i);case"ascii":case"latin1":case"binary":return asciiWrite(this,s,o,i);case"base64":return base64Write(this,s,o,i);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return ucs2Write(this,s,o,i);default:if(w)throw new TypeError("Unknown encoding: "+u);u=(""+u).toLowerCase(),w=!0}},Buffer.prototype.toJSON=function toJSON(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};const C=4096;function asciiSlice(s,o,i){let u="";i=Math.min(s.length,i);for(let _=o;_u)&&(i=u);let _="";for(let u=o;ui)throw new RangeError("Trying to access beyond buffer length")}function checkInt(s,o,i,u,_,w){if(!Buffer.isBuffer(s))throw new TypeError('"buffer" argument must be a Buffer instance');if(o>_||os.length)throw new RangeError("Index out of range")}function wrtBigUInt64LE(s,o,i,u,_){checkIntBI(o,u,_,s,i,7);let w=Number(o&BigInt(4294967295));s[i++]=w,w>>=8,s[i++]=w,w>>=8,s[i++]=w,w>>=8,s[i++]=w;let x=Number(o>>BigInt(32)&BigInt(4294967295));return s[i++]=x,x>>=8,s[i++]=x,x>>=8,s[i++]=x,x>>=8,s[i++]=x,i}function wrtBigUInt64BE(s,o,i,u,_){checkIntBI(o,u,_,s,i,7);let w=Number(o&BigInt(4294967295));s[i+7]=w,w>>=8,s[i+6]=w,w>>=8,s[i+5]=w,w>>=8,s[i+4]=w;let x=Number(o>>BigInt(32)&BigInt(4294967295));return s[i+3]=x,x>>=8,s[i+2]=x,x>>=8,s[i+1]=x,x>>=8,s[i]=x,i+8}function checkIEEE754(s,o,i,u,_,w){if(i+u>s.length)throw new RangeError("Index out of range");if(i<0)throw new RangeError("Index out of range")}function writeFloat(s,o,i,u,w){return o=+o,i>>>=0,w||checkIEEE754(s,0,i,4),_.write(s,o,i,u,23,4),i+4}function writeDouble(s,o,i,u,w){return o=+o,i>>>=0,w||checkIEEE754(s,0,i,8),_.write(s,o,i,u,52,8),i+8}Buffer.prototype.slice=function slice(s,o){const i=this.length;(s=~~s)<0?(s+=i)<0&&(s=0):s>i&&(s=i),(o=void 0===o?i:~~o)<0?(o+=i)<0&&(o=0):o>i&&(o=i),o>>=0,o>>>=0,i||checkOffset(s,o,this.length);let u=this[s],_=1,w=0;for(;++w>>=0,o>>>=0,i||checkOffset(s,o,this.length);let u=this[s+--o],_=1;for(;o>0&&(_*=256);)u+=this[s+--o]*_;return u},Buffer.prototype.readUint8=Buffer.prototype.readUInt8=function readUInt8(s,o){return s>>>=0,o||checkOffset(s,1,this.length),this[s]},Buffer.prototype.readUint16LE=Buffer.prototype.readUInt16LE=function readUInt16LE(s,o){return s>>>=0,o||checkOffset(s,2,this.length),this[s]|this[s+1]<<8},Buffer.prototype.readUint16BE=Buffer.prototype.readUInt16BE=function readUInt16BE(s,o){return s>>>=0,o||checkOffset(s,2,this.length),this[s]<<8|this[s+1]},Buffer.prototype.readUint32LE=Buffer.prototype.readUInt32LE=function readUInt32LE(s,o){return s>>>=0,o||checkOffset(s,4,this.length),(this[s]|this[s+1]<<8|this[s+2]<<16)+16777216*this[s+3]},Buffer.prototype.readUint32BE=Buffer.prototype.readUInt32BE=function readUInt32BE(s,o){return s>>>=0,o||checkOffset(s,4,this.length),16777216*this[s]+(this[s+1]<<16|this[s+2]<<8|this[s+3])},Buffer.prototype.readBigUInt64LE=defineBigIntMethod((function readBigUInt64LE(s){validateNumber(s>>>=0,"offset");const o=this[s],i=this[s+7];void 0!==o&&void 0!==i||boundsError(s,this.length-8);const u=o+256*this[++s]+65536*this[++s]+this[++s]*2**24,_=this[++s]+256*this[++s]+65536*this[++s]+i*2**24;return BigInt(u)+(BigInt(_)<>>=0,"offset");const o=this[s],i=this[s+7];void 0!==o&&void 0!==i||boundsError(s,this.length-8);const u=o*2**24+65536*this[++s]+256*this[++s]+this[++s],_=this[++s]*2**24+65536*this[++s]+256*this[++s]+i;return(BigInt(u)<>>=0,o>>>=0,i||checkOffset(s,o,this.length);let u=this[s],_=1,w=0;for(;++w=_&&(u-=Math.pow(2,8*o)),u},Buffer.prototype.readIntBE=function readIntBE(s,o,i){s>>>=0,o>>>=0,i||checkOffset(s,o,this.length);let u=o,_=1,w=this[s+--u];for(;u>0&&(_*=256);)w+=this[s+--u]*_;return _*=128,w>=_&&(w-=Math.pow(2,8*o)),w},Buffer.prototype.readInt8=function readInt8(s,o){return s>>>=0,o||checkOffset(s,1,this.length),128&this[s]?-1*(255-this[s]+1):this[s]},Buffer.prototype.readInt16LE=function readInt16LE(s,o){s>>>=0,o||checkOffset(s,2,this.length);const i=this[s]|this[s+1]<<8;return 32768&i?4294901760|i:i},Buffer.prototype.readInt16BE=function readInt16BE(s,o){s>>>=0,o||checkOffset(s,2,this.length);const i=this[s+1]|this[s]<<8;return 32768&i?4294901760|i:i},Buffer.prototype.readInt32LE=function readInt32LE(s,o){return s>>>=0,o||checkOffset(s,4,this.length),this[s]|this[s+1]<<8|this[s+2]<<16|this[s+3]<<24},Buffer.prototype.readInt32BE=function readInt32BE(s,o){return s>>>=0,o||checkOffset(s,4,this.length),this[s]<<24|this[s+1]<<16|this[s+2]<<8|this[s+3]},Buffer.prototype.readBigInt64LE=defineBigIntMethod((function readBigInt64LE(s){validateNumber(s>>>=0,"offset");const o=this[s],i=this[s+7];void 0!==o&&void 0!==i||boundsError(s,this.length-8);const u=this[s+4]+256*this[s+5]+65536*this[s+6]+(i<<24);return(BigInt(u)<>>=0,"offset");const o=this[s],i=this[s+7];void 0!==o&&void 0!==i||boundsError(s,this.length-8);const u=(o<<24)+65536*this[++s]+256*this[++s]+this[++s];return(BigInt(u)<>>=0,o||checkOffset(s,4,this.length),_.read(this,s,!0,23,4)},Buffer.prototype.readFloatBE=function readFloatBE(s,o){return s>>>=0,o||checkOffset(s,4,this.length),_.read(this,s,!1,23,4)},Buffer.prototype.readDoubleLE=function readDoubleLE(s,o){return s>>>=0,o||checkOffset(s,8,this.length),_.read(this,s,!0,52,8)},Buffer.prototype.readDoubleBE=function readDoubleBE(s,o){return s>>>=0,o||checkOffset(s,8,this.length),_.read(this,s,!1,52,8)},Buffer.prototype.writeUintLE=Buffer.prototype.writeUIntLE=function writeUIntLE(s,o,i,u){if(s=+s,o>>>=0,i>>>=0,!u){checkInt(this,s,o,i,Math.pow(2,8*i)-1,0)}let _=1,w=0;for(this[o]=255&s;++w>>=0,i>>>=0,!u){checkInt(this,s,o,i,Math.pow(2,8*i)-1,0)}let _=i-1,w=1;for(this[o+_]=255&s;--_>=0&&(w*=256);)this[o+_]=s/w&255;return o+i},Buffer.prototype.writeUint8=Buffer.prototype.writeUInt8=function writeUInt8(s,o,i){return s=+s,o>>>=0,i||checkInt(this,s,o,1,255,0),this[o]=255&s,o+1},Buffer.prototype.writeUint16LE=Buffer.prototype.writeUInt16LE=function writeUInt16LE(s,o,i){return s=+s,o>>>=0,i||checkInt(this,s,o,2,65535,0),this[o]=255&s,this[o+1]=s>>>8,o+2},Buffer.prototype.writeUint16BE=Buffer.prototype.writeUInt16BE=function writeUInt16BE(s,o,i){return s=+s,o>>>=0,i||checkInt(this,s,o,2,65535,0),this[o]=s>>>8,this[o+1]=255&s,o+2},Buffer.prototype.writeUint32LE=Buffer.prototype.writeUInt32LE=function writeUInt32LE(s,o,i){return s=+s,o>>>=0,i||checkInt(this,s,o,4,4294967295,0),this[o+3]=s>>>24,this[o+2]=s>>>16,this[o+1]=s>>>8,this[o]=255&s,o+4},Buffer.prototype.writeUint32BE=Buffer.prototype.writeUInt32BE=function writeUInt32BE(s,o,i){return s=+s,o>>>=0,i||checkInt(this,s,o,4,4294967295,0),this[o]=s>>>24,this[o+1]=s>>>16,this[o+2]=s>>>8,this[o+3]=255&s,o+4},Buffer.prototype.writeBigUInt64LE=defineBigIntMethod((function writeBigUInt64LE(s,o=0){return wrtBigUInt64LE(this,s,o,BigInt(0),BigInt("0xffffffffffffffff"))})),Buffer.prototype.writeBigUInt64BE=defineBigIntMethod((function writeBigUInt64BE(s,o=0){return wrtBigUInt64BE(this,s,o,BigInt(0),BigInt("0xffffffffffffffff"))})),Buffer.prototype.writeIntLE=function writeIntLE(s,o,i,u){if(s=+s,o>>>=0,!u){const u=Math.pow(2,8*i-1);checkInt(this,s,o,i,u-1,-u)}let _=0,w=1,x=0;for(this[o]=255&s;++_>>=0,!u){const u=Math.pow(2,8*i-1);checkInt(this,s,o,i,u-1,-u)}let _=i-1,w=1,x=0;for(this[o+_]=255&s;--_>=0&&(w*=256);)s<0&&0===x&&0!==this[o+_+1]&&(x=1),this[o+_]=(s/w|0)-x&255;return o+i},Buffer.prototype.writeInt8=function writeInt8(s,o,i){return s=+s,o>>>=0,i||checkInt(this,s,o,1,127,-128),s<0&&(s=255+s+1),this[o]=255&s,o+1},Buffer.prototype.writeInt16LE=function writeInt16LE(s,o,i){return s=+s,o>>>=0,i||checkInt(this,s,o,2,32767,-32768),this[o]=255&s,this[o+1]=s>>>8,o+2},Buffer.prototype.writeInt16BE=function writeInt16BE(s,o,i){return s=+s,o>>>=0,i||checkInt(this,s,o,2,32767,-32768),this[o]=s>>>8,this[o+1]=255&s,o+2},Buffer.prototype.writeInt32LE=function writeInt32LE(s,o,i){return s=+s,o>>>=0,i||checkInt(this,s,o,4,2147483647,-2147483648),this[o]=255&s,this[o+1]=s>>>8,this[o+2]=s>>>16,this[o+3]=s>>>24,o+4},Buffer.prototype.writeInt32BE=function writeInt32BE(s,o,i){return s=+s,o>>>=0,i||checkInt(this,s,o,4,2147483647,-2147483648),s<0&&(s=4294967295+s+1),this[o]=s>>>24,this[o+1]=s>>>16,this[o+2]=s>>>8,this[o+3]=255&s,o+4},Buffer.prototype.writeBigInt64LE=defineBigIntMethod((function writeBigInt64LE(s,o=0){return wrtBigUInt64LE(this,s,o,-BigInt("0x8000000000000000"),BigInt("0x7fffffffffffffff"))})),Buffer.prototype.writeBigInt64BE=defineBigIntMethod((function writeBigInt64BE(s,o=0){return wrtBigUInt64BE(this,s,o,-BigInt("0x8000000000000000"),BigInt("0x7fffffffffffffff"))})),Buffer.prototype.writeFloatLE=function writeFloatLE(s,o,i){return writeFloat(this,s,o,!0,i)},Buffer.prototype.writeFloatBE=function writeFloatBE(s,o,i){return writeFloat(this,s,o,!1,i)},Buffer.prototype.writeDoubleLE=function writeDoubleLE(s,o,i){return writeDouble(this,s,o,!0,i)},Buffer.prototype.writeDoubleBE=function writeDoubleBE(s,o,i){return writeDouble(this,s,o,!1,i)},Buffer.prototype.copy=function copy(s,o,i,u){if(!Buffer.isBuffer(s))throw new TypeError("argument should be a Buffer");if(i||(i=0),u||0===u||(u=this.length),o>=s.length&&(o=s.length),o||(o=0),u>0&&u=this.length)throw new RangeError("Index out of range");if(u<0)throw new RangeError("sourceEnd out of bounds");u>this.length&&(u=this.length),s.length-o>>=0,i=void 0===i?this.length:i>>>0,s||(s=0),"number"==typeof s)for(_=o;_=u+4;i-=3)o=`_${s.slice(i-3,i)}${o}`;return`${s.slice(0,i)}${o}`}function checkIntBI(s,o,i,u,_,w){if(s>i||s3?0===o||o===BigInt(0)?`>= 0${u} and < 2${u} ** ${8*(w+1)}${u}`:`>= -(2${u} ** ${8*(w+1)-1}${u}) and < 2 ** ${8*(w+1)-1}${u}`:`>= ${o}${u} and <= ${i}${u}`,new j.ERR_OUT_OF_RANGE("value",_,s)}!function checkBounds(s,o,i){validateNumber(o,"offset"),void 0!==s[o]&&void 0!==s[o+i]||boundsError(o,s.length-(i+1))}(u,_,w)}function validateNumber(s,o){if("number"!=typeof s)throw new j.ERR_INVALID_ARG_TYPE(o,"number",s)}function boundsError(s,o,i){if(Math.floor(s)!==s)throw validateNumber(s,i),new j.ERR_OUT_OF_RANGE(i||"offset","an integer",s);if(o<0)throw new j.ERR_BUFFER_OUT_OF_BOUNDS;throw new j.ERR_OUT_OF_RANGE(i||"offset",`>= ${i?1:0} and <= ${o}`,s)}E("ERR_BUFFER_OUT_OF_BOUNDS",(function(s){return s?`${s} is outside of buffer bounds`:"Attempt to access memory outside buffer bounds"}),RangeError),E("ERR_INVALID_ARG_TYPE",(function(s,o){return`The "${s}" argument must be of type number. Received type ${typeof o}`}),TypeError),E("ERR_OUT_OF_RANGE",(function(s,o,i){let u=`The value of "${s}" is out of range.`,_=i;return Number.isInteger(i)&&Math.abs(i)>2**32?_=addNumericalSeparator(String(i)):"bigint"==typeof i&&(_=String(i),(i>BigInt(2)**BigInt(32)||i<-(BigInt(2)**BigInt(32)))&&(_=addNumericalSeparator(_)),_+="n"),u+=` It must be ${o}. Received ${_}`,u}),RangeError);const L=/[^+/0-9A-Za-z-_]/g;function utf8ToBytes(s,o){let i;o=o||1/0;const u=s.length;let _=null;const w=[];for(let x=0;x55295&&i<57344){if(!_){if(i>56319){(o-=3)>-1&&w.push(239,191,189);continue}if(x+1===u){(o-=3)>-1&&w.push(239,191,189);continue}_=i;continue}if(i<56320){(o-=3)>-1&&w.push(239,191,189),_=i;continue}i=65536+(_-55296<<10|i-56320)}else _&&(o-=3)>-1&&w.push(239,191,189);if(_=null,i<128){if((o-=1)<0)break;w.push(i)}else if(i<2048){if((o-=2)<0)break;w.push(i>>6|192,63&i|128)}else if(i<65536){if((o-=3)<0)break;w.push(i>>12|224,i>>6&63|128,63&i|128)}else{if(!(i<1114112))throw new Error("Invalid code point");if((o-=4)<0)break;w.push(i>>18|240,i>>12&63|128,i>>6&63|128,63&i|128)}}return w}function base64ToBytes(s){return u.toByteArray(function base64clean(s){if((s=(s=s.split("=")[0]).trim().replace(L,"")).length<2)return"";for(;s.length%4!=0;)s+="=";return s}(s))}function blitBuffer(s,o,i,u){let _;for(_=0;_=o.length||_>=s.length);++_)o[_+i]=s[_];return _}function isInstance(s,o){return s instanceof o||null!=s&&null!=s.constructor&&null!=s.constructor.name&&s.constructor.name===o.name}function numberIsNaN(s){return s!=s}const B=function(){const s="0123456789abcdef",o=new Array(256);for(let i=0;i<16;++i){const u=16*i;for(let _=0;_<16;++_)o[u+_]=s[i]+s[_]}return o}();function defineBigIntMethod(s){return"undefined"==typeof BigInt?BufferBigIntNotDefined:s}function BufferBigIntNotDefined(){throw new Error("BigInt not supported")}},17965:(s,o,i)=>{"use strict";var u=i(16426),_={"text/plain":"Text","text/html":"Url",default:"Text"};s.exports=function copy(s,o){var i,w,x,C,j,L,B=!1;o||(o={}),i=o.debug||!1;try{if(x=u(),C=document.createRange(),j=document.getSelection(),(L=document.createElement("span")).textContent=s,L.ariaHidden="true",L.style.all="unset",L.style.position="fixed",L.style.top=0,L.style.clip="rect(0, 0, 0, 0)",L.style.whiteSpace="pre",L.style.webkitUserSelect="text",L.style.MozUserSelect="text",L.style.msUserSelect="text",L.style.userSelect="text",L.addEventListener("copy",(function(u){if(u.stopPropagation(),o.format)if(u.preventDefault(),void 0===u.clipboardData){i&&console.warn("unable to use e.clipboardData"),i&&console.warn("trying IE specific stuff"),window.clipboardData.clearData();var w=_[o.format]||_.default;window.clipboardData.setData(w,s)}else u.clipboardData.clearData(),u.clipboardData.setData(o.format,s);o.onCopy&&(u.preventDefault(),o.onCopy(u.clipboardData))})),document.body.appendChild(L),C.selectNodeContents(L),j.addRange(C),!document.execCommand("copy"))throw new Error("copy command was unsuccessful");B=!0}catch(u){i&&console.error("unable to copy using execCommand: ",u),i&&console.warn("trying IE specific stuff");try{window.clipboardData.setData(o.format||"text",s),o.onCopy&&o.onCopy(window.clipboardData),B=!0}catch(u){i&&console.error("unable to copy using clipboardData: ",u),i&&console.error("falling back to prompt"),w=function format(s){var o=(/mac os x/i.test(navigator.userAgent)?"⌘":"Ctrl")+"+C";return s.replace(/#{\s*key\s*}/g,o)}("message"in o?o.message:"Copy to clipboard: #{key}, Enter"),window.prompt(w,s)}}finally{j&&("function"==typeof j.removeRange?j.removeRange(C):j.removeAllRanges()),L&&document.body.removeChild(L),x()}return B}},2205:function(s,o,i){var u;u=void 0!==i.g?i.g:this,s.exports=function(s){if(s.CSS&&s.CSS.escape)return s.CSS.escape;var cssEscape=function(s){if(0==arguments.length)throw new TypeError("`CSS.escape` requires an argument.");for(var o,i=String(s),u=i.length,_=-1,w="",x=i.charCodeAt(0);++_=1&&o<=31||127==o||0==_&&o>=48&&o<=57||1==_&&o>=48&&o<=57&&45==x?"\\"+o.toString(16)+" ":0==_&&1==u&&45==o||!(o>=128||45==o||95==o||o>=48&&o<=57||o>=65&&o<=90||o>=97&&o<=122)?"\\"+i.charAt(_):i.charAt(_):w+="�";return w};return s.CSS||(s.CSS={}),s.CSS.escape=cssEscape,cssEscape}(u)},81919:(s,o,i)=>{"use strict";var u=i(48287).Buffer;function isSpecificValue(s){return s instanceof u||s instanceof Date||s instanceof RegExp}function cloneSpecificValue(s){if(s instanceof u){var o=u.alloc?u.alloc(s.length):new u(s.length);return s.copy(o),o}if(s instanceof Date)return new Date(s.getTime());if(s instanceof RegExp)return new RegExp(s);throw new Error("Unexpected situation")}function deepCloneArray(s){var o=[];return s.forEach((function(s,i){"object"==typeof s&&null!==s?Array.isArray(s)?o[i]=deepCloneArray(s):isSpecificValue(s)?o[i]=cloneSpecificValue(s):o[i]=_({},s):o[i]=s})),o}function safeGetProperty(s,o){return"__proto__"===o?void 0:s[o]}var _=s.exports=function(){if(arguments.length<1||"object"!=typeof arguments[0])return!1;if(arguments.length<2)return arguments[0];var s,o,i=arguments[0];return Array.prototype.slice.call(arguments,1).forEach((function(u){"object"!=typeof u||null===u||Array.isArray(u)||Object.keys(u).forEach((function(w){return o=safeGetProperty(i,w),(s=safeGetProperty(u,w))===i?void 0:"object"!=typeof s||null===s?void(i[w]=s):Array.isArray(s)?void(i[w]=deepCloneArray(s)):isSpecificValue(s)?void(i[w]=cloneSpecificValue(s)):"object"!=typeof o||null===o||Array.isArray(o)?void(i[w]=_({},s)):void(i[w]=_(o,s))}))})),i}},14744:s=>{"use strict";var o=function isMergeableObject(s){return function isNonNullObject(s){return!!s&&"object"==typeof s}(s)&&!function isSpecial(s){var o=Object.prototype.toString.call(s);return"[object RegExp]"===o||"[object Date]"===o||function isReactElement(s){return s.$$typeof===i}(s)}(s)};var i="function"==typeof Symbol&&Symbol.for?Symbol.for("react.element"):60103;function cloneUnlessOtherwiseSpecified(s,o){return!1!==o.clone&&o.isMergeableObject(s)?deepmerge(function emptyTarget(s){return Array.isArray(s)?[]:{}}(s),s,o):s}function defaultArrayMerge(s,o,i){return s.concat(o).map((function(s){return cloneUnlessOtherwiseSpecified(s,i)}))}function getKeys(s){return Object.keys(s).concat(function getEnumerableOwnPropertySymbols(s){return Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(s).filter((function(o){return Object.propertyIsEnumerable.call(s,o)})):[]}(s))}function propertyIsOnObject(s,o){try{return o in s}catch(s){return!1}}function mergeObject(s,o,i){var u={};return i.isMergeableObject(s)&&getKeys(s).forEach((function(o){u[o]=cloneUnlessOtherwiseSpecified(s[o],i)})),getKeys(o).forEach((function(_){(function propertyIsUnsafe(s,o){return propertyIsOnObject(s,o)&&!(Object.hasOwnProperty.call(s,o)&&Object.propertyIsEnumerable.call(s,o))})(s,_)||(propertyIsOnObject(s,_)&&i.isMergeableObject(o[_])?u[_]=function getMergeFunction(s,o){if(!o.customMerge)return deepmerge;var i=o.customMerge(s);return"function"==typeof i?i:deepmerge}(_,i)(s[_],o[_],i):u[_]=cloneUnlessOtherwiseSpecified(o[_],i))})),u}function deepmerge(s,i,u){(u=u||{}).arrayMerge=u.arrayMerge||defaultArrayMerge,u.isMergeableObject=u.isMergeableObject||o,u.cloneUnlessOtherwiseSpecified=cloneUnlessOtherwiseSpecified;var _=Array.isArray(i);return _===Array.isArray(s)?_?u.arrayMerge(s,i,u):mergeObject(s,i,u):cloneUnlessOtherwiseSpecified(i,u)}deepmerge.all=function deepmergeAll(s,o){if(!Array.isArray(s))throw new Error("first argument should be an array");return s.reduce((function(s,i){return deepmerge(s,i,o)}),{})};var u=deepmerge;s.exports=u},42838:function(s){s.exports=function(){"use strict";const{entries:s,setPrototypeOf:o,isFrozen:i,getPrototypeOf:u,getOwnPropertyDescriptor:_}=Object;let{freeze:w,seal:x,create:C}=Object,{apply:j,construct:L}="undefined"!=typeof Reflect&&Reflect;w||(w=function freeze(s){return s}),x||(x=function seal(s){return s}),j||(j=function apply(s,o,i){return s.apply(o,i)}),L||(L=function construct(s,o){return new s(...o)});const B=unapply(Array.prototype.forEach),$=unapply(Array.prototype.pop),V=unapply(Array.prototype.push),U=unapply(String.prototype.toLowerCase),z=unapply(String.prototype.toString),Y=unapply(String.prototype.match),Z=unapply(String.prototype.replace),ee=unapply(String.prototype.indexOf),ie=unapply(String.prototype.trim),ae=unapply(Object.prototype.hasOwnProperty),le=unapply(RegExp.prototype.test),ce=unconstruct(TypeError);function unapply(s){return function(o){for(var i=arguments.length,u=new Array(i>1?i-1:0),_=1;_2&&void 0!==arguments[2]?arguments[2]:U;o&&o(s,null);let w=u.length;for(;w--;){let o=u[w];if("string"==typeof o){const s=_(o);s!==o&&(i(u)||(u[w]=s),o=s)}s[o]=!0}return s}function cleanArray(s){for(let o=0;o/gm),$e=x(/\${[\w\W]*}/gm),ze=x(/^data-[\-\w.\u00B7-\uFFFF]/),We=x(/^aria-[\-\w]+$/),He=x(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),Ye=x(/^(?:\w+script|data):/i),Xe=x(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),Qe=x(/^html$/i),et=x(/^[a-z][.\w]*(-[.\w]+)+$/i);var tt=Object.freeze({__proto__:null,MUSTACHE_EXPR:Re,ERB_EXPR:qe,TMPLIT_EXPR:$e,DATA_ATTR:ze,ARIA_ATTR:We,IS_ALLOWED_URI:He,IS_SCRIPT_OR_DATA:Ye,ATTR_WHITESPACE:Xe,DOCTYPE_NAME:Qe,CUSTOM_ELEMENT:et});const rt={element:1,attribute:2,text:3,cdataSection:4,entityReference:5,entityNode:6,progressingInstruction:7,comment:8,document:9,documentType:10,documentFragment:11,notation:12},nt=function getGlobal(){return"undefined"==typeof window?null:window},st=function _createTrustedTypesPolicy(s,o){if("object"!=typeof s||"function"!=typeof s.createPolicy)return null;let i=null;const u="data-tt-policy-suffix";o&&o.hasAttribute(u)&&(i=o.getAttribute(u));const _="dompurify"+(i?"#"+i:"");try{return s.createPolicy(_,{createHTML:s=>s,createScriptURL:s=>s})}catch(s){return console.warn("TrustedTypes policy "+_+" could not be created."),null}};function createDOMPurify(){let o=arguments.length>0&&void 0!==arguments[0]?arguments[0]:nt();const DOMPurify=s=>createDOMPurify(s);if(DOMPurify.version="3.1.6",DOMPurify.removed=[],!o||!o.document||o.document.nodeType!==rt.document)return DOMPurify.isSupported=!1,DOMPurify;let{document:i}=o;const u=i,_=u.currentScript,{DocumentFragment:x,HTMLTemplateElement:j,Node:L,Element:Re,NodeFilter:qe,NamedNodeMap:$e=o.NamedNodeMap||o.MozNamedAttrMap,HTMLFormElement:ze,DOMParser:We,trustedTypes:Ye}=o,Xe=Re.prototype,et=lookupGetter(Xe,"cloneNode"),ot=lookupGetter(Xe,"remove"),it=lookupGetter(Xe,"nextSibling"),at=lookupGetter(Xe,"childNodes"),lt=lookupGetter(Xe,"parentNode");if("function"==typeof j){const s=i.createElement("template");s.content&&s.content.ownerDocument&&(i=s.content.ownerDocument)}let ct,ut="";const{implementation:pt,createNodeIterator:ht,createDocumentFragment:dt,getElementsByTagName:mt}=i,{importNode:gt}=u;let yt={};DOMPurify.isSupported="function"==typeof s&&"function"==typeof lt&&pt&&void 0!==pt.createHTMLDocument;const{MUSTACHE_EXPR:vt,ERB_EXPR:bt,TMPLIT_EXPR:_t,DATA_ATTR:Et,ARIA_ATTR:wt,IS_SCRIPT_OR_DATA:St,ATTR_WHITESPACE:xt,CUSTOM_ELEMENT:kt}=tt;let{IS_ALLOWED_URI:Ct}=tt,Ot=null;const At=addToSet({},[...pe,...de,...fe,...be,...we]);let jt=null;const It=addToSet({},[...Se,...xe,...Pe,...Te]);let Pt=Object.seal(C(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),Mt=null,Tt=null,Nt=!0,Rt=!0,Dt=!1,Lt=!0,Bt=!1,Ft=!0,qt=!1,$t=!1,Vt=!1,Ut=!1,zt=!1,Wt=!1,Kt=!0,Ht=!1;const Jt="user-content-";let Gt=!0,Yt=!1,Xt={},Zt=null;const Qt=addToSet({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]);let er=null;const tr=addToSet({},["audio","video","img","source","image","track"]);let rr=null;const nr=addToSet({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),sr="http://www.w3.org/1998/Math/MathML",ir="http://www.w3.org/2000/svg",ar="http://www.w3.org/1999/xhtml";let lr=ar,cr=!1,ur=null;const pr=addToSet({},[sr,ir,ar],z);let dr=null;const fr=["application/xhtml+xml","text/html"],mr="text/html";let gr=null,yr=null;const vr=i.createElement("form"),br=function isRegexOrFunction(s){return s instanceof RegExp||s instanceof Function},_r=function _parseConfig(){let s=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(!yr||yr!==s){if(s&&"object"==typeof s||(s={}),s=clone(s),dr=-1===fr.indexOf(s.PARSER_MEDIA_TYPE)?mr:s.PARSER_MEDIA_TYPE,gr="application/xhtml+xml"===dr?z:U,Ot=ae(s,"ALLOWED_TAGS")?addToSet({},s.ALLOWED_TAGS,gr):At,jt=ae(s,"ALLOWED_ATTR")?addToSet({},s.ALLOWED_ATTR,gr):It,ur=ae(s,"ALLOWED_NAMESPACES")?addToSet({},s.ALLOWED_NAMESPACES,z):pr,rr=ae(s,"ADD_URI_SAFE_ATTR")?addToSet(clone(nr),s.ADD_URI_SAFE_ATTR,gr):nr,er=ae(s,"ADD_DATA_URI_TAGS")?addToSet(clone(tr),s.ADD_DATA_URI_TAGS,gr):tr,Zt=ae(s,"FORBID_CONTENTS")?addToSet({},s.FORBID_CONTENTS,gr):Qt,Mt=ae(s,"FORBID_TAGS")?addToSet({},s.FORBID_TAGS,gr):{},Tt=ae(s,"FORBID_ATTR")?addToSet({},s.FORBID_ATTR,gr):{},Xt=!!ae(s,"USE_PROFILES")&&s.USE_PROFILES,Nt=!1!==s.ALLOW_ARIA_ATTR,Rt=!1!==s.ALLOW_DATA_ATTR,Dt=s.ALLOW_UNKNOWN_PROTOCOLS||!1,Lt=!1!==s.ALLOW_SELF_CLOSE_IN_ATTR,Bt=s.SAFE_FOR_TEMPLATES||!1,Ft=!1!==s.SAFE_FOR_XML,qt=s.WHOLE_DOCUMENT||!1,Ut=s.RETURN_DOM||!1,zt=s.RETURN_DOM_FRAGMENT||!1,Wt=s.RETURN_TRUSTED_TYPE||!1,Vt=s.FORCE_BODY||!1,Kt=!1!==s.SANITIZE_DOM,Ht=s.SANITIZE_NAMED_PROPS||!1,Gt=!1!==s.KEEP_CONTENT,Yt=s.IN_PLACE||!1,Ct=s.ALLOWED_URI_REGEXP||He,lr=s.NAMESPACE||ar,Pt=s.CUSTOM_ELEMENT_HANDLING||{},s.CUSTOM_ELEMENT_HANDLING&&br(s.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(Pt.tagNameCheck=s.CUSTOM_ELEMENT_HANDLING.tagNameCheck),s.CUSTOM_ELEMENT_HANDLING&&br(s.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(Pt.attributeNameCheck=s.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),s.CUSTOM_ELEMENT_HANDLING&&"boolean"==typeof s.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements&&(Pt.allowCustomizedBuiltInElements=s.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),Bt&&(Rt=!1),zt&&(Ut=!0),Xt&&(Ot=addToSet({},we),jt=[],!0===Xt.html&&(addToSet(Ot,pe),addToSet(jt,Se)),!0===Xt.svg&&(addToSet(Ot,de),addToSet(jt,xe),addToSet(jt,Te)),!0===Xt.svgFilters&&(addToSet(Ot,fe),addToSet(jt,xe),addToSet(jt,Te)),!0===Xt.mathMl&&(addToSet(Ot,be),addToSet(jt,Pe),addToSet(jt,Te))),s.ADD_TAGS&&(Ot===At&&(Ot=clone(Ot)),addToSet(Ot,s.ADD_TAGS,gr)),s.ADD_ATTR&&(jt===It&&(jt=clone(jt)),addToSet(jt,s.ADD_ATTR,gr)),s.ADD_URI_SAFE_ATTR&&addToSet(rr,s.ADD_URI_SAFE_ATTR,gr),s.FORBID_CONTENTS&&(Zt===Qt&&(Zt=clone(Zt)),addToSet(Zt,s.FORBID_CONTENTS,gr)),Gt&&(Ot["#text"]=!0),qt&&addToSet(Ot,["html","head","body"]),Ot.table&&(addToSet(Ot,["tbody"]),delete Mt.tbody),s.TRUSTED_TYPES_POLICY){if("function"!=typeof s.TRUSTED_TYPES_POLICY.createHTML)throw ce('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if("function"!=typeof s.TRUSTED_TYPES_POLICY.createScriptURL)throw ce('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');ct=s.TRUSTED_TYPES_POLICY,ut=ct.createHTML("")}else void 0===ct&&(ct=st(Ye,_)),null!==ct&&"string"==typeof ut&&(ut=ct.createHTML(""));w&&w(s),yr=s}},Er=addToSet({},["mi","mo","mn","ms","mtext"]),wr=addToSet({},["foreignobject","annotation-xml"]),Sr=addToSet({},["title","style","font","a","script"]),xr=addToSet({},[...de,...fe,...ye]),kr=addToSet({},[...be,..._e]),Cr=function _checkValidNamespace(s){let o=lt(s);o&&o.tagName||(o={namespaceURI:lr,tagName:"template"});const i=U(s.tagName),u=U(o.tagName);return!!ur[s.namespaceURI]&&(s.namespaceURI===ir?o.namespaceURI===ar?"svg"===i:o.namespaceURI===sr?"svg"===i&&("annotation-xml"===u||Er[u]):Boolean(xr[i]):s.namespaceURI===sr?o.namespaceURI===ar?"math"===i:o.namespaceURI===ir?"math"===i&&wr[u]:Boolean(kr[i]):s.namespaceURI===ar?!(o.namespaceURI===ir&&!wr[u])&&!(o.namespaceURI===sr&&!Er[u])&&!kr[i]&&(Sr[i]||!xr[i]):!("application/xhtml+xml"!==dr||!ur[s.namespaceURI]))},Or=function _forceRemove(s){V(DOMPurify.removed,{element:s});try{lt(s).removeChild(s)}catch(o){ot(s)}},Ar=function _removeAttribute(s,o){try{V(DOMPurify.removed,{attribute:o.getAttributeNode(s),from:o})}catch(s){V(DOMPurify.removed,{attribute:null,from:o})}if(o.removeAttribute(s),"is"===s&&!jt[s])if(Ut||zt)try{Or(o)}catch(s){}else try{o.setAttribute(s,"")}catch(s){}},jr=function _initDocument(s){let o=null,u=null;if(Vt)s=""+s;else{const o=Y(s,/^[\r\n\t ]+/);u=o&&o[0]}"application/xhtml+xml"===dr&&lr===ar&&(s=''+s+"");const _=ct?ct.createHTML(s):s;if(lr===ar)try{o=(new We).parseFromString(_,dr)}catch(s){}if(!o||!o.documentElement){o=pt.createDocument(lr,"template",null);try{o.documentElement.innerHTML=cr?ut:_}catch(s){}}const w=o.body||o.documentElement;return s&&u&&w.insertBefore(i.createTextNode(u),w.childNodes[0]||null),lr===ar?mt.call(o,qt?"html":"body")[0]:qt?o.documentElement:w},Ir=function _createNodeIterator(s){return ht.call(s.ownerDocument||s,s,qe.SHOW_ELEMENT|qe.SHOW_COMMENT|qe.SHOW_TEXT|qe.SHOW_PROCESSING_INSTRUCTION|qe.SHOW_CDATA_SECTION,null)},Pr=function _isClobbered(s){return s instanceof ze&&("string"!=typeof s.nodeName||"string"!=typeof s.textContent||"function"!=typeof s.removeChild||!(s.attributes instanceof $e)||"function"!=typeof s.removeAttribute||"function"!=typeof s.setAttribute||"string"!=typeof s.namespaceURI||"function"!=typeof s.insertBefore||"function"!=typeof s.hasChildNodes)},Mr=function _isNode(s){return"function"==typeof L&&s instanceof L},Tr=function _executeHook(s,o,i){yt[s]&&B(yt[s],(s=>{s.call(DOMPurify,o,i,yr)}))},Nr=function _sanitizeElements(s){let o=null;if(Tr("beforeSanitizeElements",s,null),Pr(s))return Or(s),!0;const i=gr(s.nodeName);if(Tr("uponSanitizeElement",s,{tagName:i,allowedTags:Ot}),s.hasChildNodes()&&!Mr(s.firstElementChild)&&le(/<[/\w]/g,s.innerHTML)&&le(/<[/\w]/g,s.textContent))return Or(s),!0;if(s.nodeType===rt.progressingInstruction)return Or(s),!0;if(Ft&&s.nodeType===rt.comment&&le(/<[/\w]/g,s.data))return Or(s),!0;if(!Ot[i]||Mt[i]){if(!Mt[i]&&Dr(i)){if(Pt.tagNameCheck instanceof RegExp&&le(Pt.tagNameCheck,i))return!1;if(Pt.tagNameCheck instanceof Function&&Pt.tagNameCheck(i))return!1}if(Gt&&!Zt[i]){const o=lt(s)||s.parentNode,i=at(s)||s.childNodes;if(i&&o)for(let u=i.length-1;u>=0;--u){const _=et(i[u],!0);_.__removalCount=(s.__removalCount||0)+1,o.insertBefore(_,it(s))}}return Or(s),!0}return s instanceof Re&&!Cr(s)?(Or(s),!0):"noscript"!==i&&"noembed"!==i&&"noframes"!==i||!le(/<\/no(script|embed|frames)/i,s.innerHTML)?(Bt&&s.nodeType===rt.text&&(o=s.textContent,B([vt,bt,_t],(s=>{o=Z(o,s," ")})),s.textContent!==o&&(V(DOMPurify.removed,{element:s.cloneNode()}),s.textContent=o)),Tr("afterSanitizeElements",s,null),!1):(Or(s),!0)},Rr=function _isValidAttribute(s,o,u){if(Kt&&("id"===o||"name"===o)&&(u in i||u in vr))return!1;if(Rt&&!Tt[o]&&le(Et,o));else if(Nt&&le(wt,o));else if(!jt[o]||Tt[o]){if(!(Dr(s)&&(Pt.tagNameCheck instanceof RegExp&&le(Pt.tagNameCheck,s)||Pt.tagNameCheck instanceof Function&&Pt.tagNameCheck(s))&&(Pt.attributeNameCheck instanceof RegExp&&le(Pt.attributeNameCheck,o)||Pt.attributeNameCheck instanceof Function&&Pt.attributeNameCheck(o))||"is"===o&&Pt.allowCustomizedBuiltInElements&&(Pt.tagNameCheck instanceof RegExp&&le(Pt.tagNameCheck,u)||Pt.tagNameCheck instanceof Function&&Pt.tagNameCheck(u))))return!1}else if(rr[o]);else if(le(Ct,Z(u,xt,"")));else if("src"!==o&&"xlink:href"!==o&&"href"!==o||"script"===s||0!==ee(u,"data:")||!er[s])if(Dt&&!le(St,Z(u,xt,"")));else if(u)return!1;return!0},Dr=function _isBasicCustomElement(s){return"annotation-xml"!==s&&Y(s,kt)},Lr=function _sanitizeAttributes(s){Tr("beforeSanitizeAttributes",s,null);const{attributes:o}=s;if(!o)return;const i={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:jt};let u=o.length;for(;u--;){const _=o[u],{name:w,namespaceURI:x,value:C}=_,j=gr(w);let L="value"===w?C:ie(C);if(i.attrName=j,i.attrValue=L,i.keepAttr=!0,i.forceKeepAttr=void 0,Tr("uponSanitizeAttribute",s,i),L=i.attrValue,Ft&&le(/((--!?|])>)|<\/(style|title)/i,L)){Ar(w,s);continue}if(i.forceKeepAttr)continue;if(Ar(w,s),!i.keepAttr)continue;if(!Lt&&le(/\/>/i,L)){Ar(w,s);continue}Bt&&B([vt,bt,_t],(s=>{L=Z(L,s," ")}));const V=gr(s.nodeName);if(Rr(V,j,L)){if(!Ht||"id"!==j&&"name"!==j||(Ar(w,s),L=Jt+L),ct&&"object"==typeof Ye&&"function"==typeof Ye.getAttributeType)if(x);else switch(Ye.getAttributeType(V,j)){case"TrustedHTML":L=ct.createHTML(L);break;case"TrustedScriptURL":L=ct.createScriptURL(L)}try{x?s.setAttributeNS(x,w,L):s.setAttribute(w,L),Pr(s)?Or(s):$(DOMPurify.removed)}catch(s){}}}Tr("afterSanitizeAttributes",s,null)},Br=function _sanitizeShadowDOM(s){let o=null;const i=Ir(s);for(Tr("beforeSanitizeShadowDOM",s,null);o=i.nextNode();)Tr("uponSanitizeShadowNode",o,null),Nr(o)||(o.content instanceof x&&_sanitizeShadowDOM(o.content),Lr(o));Tr("afterSanitizeShadowDOM",s,null)};return DOMPurify.sanitize=function(s){let o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},i=null,_=null,w=null,C=null;if(cr=!s,cr&&(s="\x3c!--\x3e"),"string"!=typeof s&&!Mr(s)){if("function"!=typeof s.toString)throw ce("toString is not a function");if("string"!=typeof(s=s.toString()))throw ce("dirty is not a string, aborting")}if(!DOMPurify.isSupported)return s;if($t||_r(o),DOMPurify.removed=[],"string"==typeof s&&(Yt=!1),Yt){if(s.nodeName){const o=gr(s.nodeName);if(!Ot[o]||Mt[o])throw ce("root node is forbidden and cannot be sanitized in-place")}}else if(s instanceof L)i=jr("\x3c!----\x3e"),_=i.ownerDocument.importNode(s,!0),_.nodeType===rt.element&&"BODY"===_.nodeName||"HTML"===_.nodeName?i=_:i.appendChild(_);else{if(!Ut&&!Bt&&!qt&&-1===s.indexOf("<"))return ct&&Wt?ct.createHTML(s):s;if(i=jr(s),!i)return Ut?null:Wt?ut:""}i&&Vt&&Or(i.firstChild);const j=Ir(Yt?s:i);for(;w=j.nextNode();)Nr(w)||(w.content instanceof x&&Br(w.content),Lr(w));if(Yt)return s;if(Ut){if(zt)for(C=dt.call(i.ownerDocument);i.firstChild;)C.appendChild(i.firstChild);else C=i;return(jt.shadowroot||jt.shadowrootmode)&&(C=gt.call(u,C,!0)),C}let $=qt?i.outerHTML:i.innerHTML;return qt&&Ot["!doctype"]&&i.ownerDocument&&i.ownerDocument.doctype&&i.ownerDocument.doctype.name&&le(Qe,i.ownerDocument.doctype.name)&&($="\n"+$),Bt&&B([vt,bt,_t],(s=>{$=Z($,s," ")})),ct&&Wt?ct.createHTML($):$},DOMPurify.setConfig=function(){_r(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}),$t=!0},DOMPurify.clearConfig=function(){yr=null,$t=!1},DOMPurify.isValidAttribute=function(s,o,i){yr||_r({});const u=gr(s),_=gr(o);return Rr(u,_,i)},DOMPurify.addHook=function(s,o){"function"==typeof o&&(yt[s]=yt[s]||[],V(yt[s],o))},DOMPurify.removeHook=function(s){if(yt[s])return $(yt[s])},DOMPurify.removeHooks=function(s){yt[s]&&(yt[s]=[])},DOMPurify.removeAllHooks=function(){yt={}},DOMPurify}return createDOMPurify()}()},78004:s=>{"use strict";class SubRange{constructor(s,o){this.low=s,this.high=o,this.length=1+o-s}overlaps(s){return!(this.highs.high)}touches(s){return!(this.high+1s.high)}add(s){return new SubRange(Math.min(this.low,s.low),Math.max(this.high,s.high))}subtract(s){return s.low<=this.low&&s.high>=this.high?[]:s.low>this.low&&s.highs+o.length),0)}add(s,o){var _add=s=>{for(var o=0;o{for(var o=0;o{for(var o=0;o{for(var i=o.low;i<=o.high;)s.push(i),i++;return s}),[])}subranges(){return this.ranges.map((s=>({low:s.low,high:s.high,length:1+s.high-s.low})))}}s.exports=DRange},37007:s=>{"use strict";var o,i="object"==typeof Reflect?Reflect:null,u=i&&"function"==typeof i.apply?i.apply:function ReflectApply(s,o,i){return Function.prototype.apply.call(s,o,i)};o=i&&"function"==typeof i.ownKeys?i.ownKeys:Object.getOwnPropertySymbols?function ReflectOwnKeys(s){return Object.getOwnPropertyNames(s).concat(Object.getOwnPropertySymbols(s))}:function ReflectOwnKeys(s){return Object.getOwnPropertyNames(s)};var _=Number.isNaN||function NumberIsNaN(s){return s!=s};function EventEmitter(){EventEmitter.init.call(this)}s.exports=EventEmitter,s.exports.once=function once(s,o){return new Promise((function(i,u){function errorListener(i){s.removeListener(o,resolver),u(i)}function resolver(){"function"==typeof s.removeListener&&s.removeListener("error",errorListener),i([].slice.call(arguments))}eventTargetAgnosticAddListener(s,o,resolver,{once:!0}),"error"!==o&&function addErrorHandlerIfEventEmitter(s,o,i){"function"==typeof s.on&&eventTargetAgnosticAddListener(s,"error",o,i)}(s,errorListener,{once:!0})}))},EventEmitter.EventEmitter=EventEmitter,EventEmitter.prototype._events=void 0,EventEmitter.prototype._eventsCount=0,EventEmitter.prototype._maxListeners=void 0;var w=10;function checkListener(s){if("function"!=typeof s)throw new TypeError('The "listener" argument must be of type Function. Received type '+typeof s)}function _getMaxListeners(s){return void 0===s._maxListeners?EventEmitter.defaultMaxListeners:s._maxListeners}function _addListener(s,o,i,u){var _,w,x;if(checkListener(i),void 0===(w=s._events)?(w=s._events=Object.create(null),s._eventsCount=0):(void 0!==w.newListener&&(s.emit("newListener",o,i.listener?i.listener:i),w=s._events),x=w[o]),void 0===x)x=w[o]=i,++s._eventsCount;else if("function"==typeof x?x=w[o]=u?[i,x]:[x,i]:u?x.unshift(i):x.push(i),(_=_getMaxListeners(s))>0&&x.length>_&&!x.warned){x.warned=!0;var C=new Error("Possible EventEmitter memory leak detected. "+x.length+" "+String(o)+" listeners added. Use emitter.setMaxListeners() to increase limit");C.name="MaxListenersExceededWarning",C.emitter=s,C.type=o,C.count=x.length,function ProcessEmitWarning(s){console&&console.warn&&console.warn(s)}(C)}return s}function onceWrapper(){if(!this.fired)return this.target.removeListener(this.type,this.wrapFn),this.fired=!0,0===arguments.length?this.listener.call(this.target):this.listener.apply(this.target,arguments)}function _onceWrap(s,o,i){var u={fired:!1,wrapFn:void 0,target:s,type:o,listener:i},_=onceWrapper.bind(u);return _.listener=i,u.wrapFn=_,_}function _listeners(s,o,i){var u=s._events;if(void 0===u)return[];var _=u[o];return void 0===_?[]:"function"==typeof _?i?[_.listener||_]:[_]:i?function unwrapListeners(s){for(var o=new Array(s.length),i=0;i0&&(x=o[0]),x instanceof Error)throw x;var C=new Error("Unhandled error."+(x?" ("+x.message+")":""));throw C.context=x,C}var j=w[s];if(void 0===j)return!1;if("function"==typeof j)u(j,this,o);else{var L=j.length,B=arrayClone(j,L);for(i=0;i=0;w--)if(i[w]===o||i[w].listener===o){x=i[w].listener,_=w;break}if(_<0)return this;0===_?i.shift():function spliceOne(s,o){for(;o+1=0;u--)this.removeListener(s,o[u]);return this},EventEmitter.prototype.listeners=function listeners(s){return _listeners(this,s,!0)},EventEmitter.prototype.rawListeners=function rawListeners(s){return _listeners(this,s,!1)},EventEmitter.listenerCount=function(s,o){return"function"==typeof s.listenerCount?s.listenerCount(o):listenerCount.call(s,o)},EventEmitter.prototype.listenerCount=listenerCount,EventEmitter.prototype.eventNames=function eventNames(){return this._eventsCount>0?o(this._events):[]}},85587:(s,o,i)=>{"use strict";var u=i(26311),_=create(Error);function create(s){return FormattedError.displayName=s.displayName||s.name,FormattedError;function FormattedError(o){return o&&(o=u.apply(null,arguments)),new s(o)}}s.exports=_,_.eval=create(EvalError),_.range=create(RangeError),_.reference=create(ReferenceError),_.syntax=create(SyntaxError),_.type=create(TypeError),_.uri=create(URIError),_.create=create},26311:s=>{!function(){var o;function format(s){for(var o,i,u,_,w=1,x=[].slice.call(arguments),C=0,j=s.length,L="",B=!1,$=!1,nextArg=function(){return x[w++]},slurpNumber=function(){for(var i="";/\d/.test(s[C]);)i+=s[C++],o=s[C];return i.length>0?parseInt(i):null};C{function deepFreeze(s){return s instanceof Map?s.clear=s.delete=s.set=function(){throw new Error("map is read-only")}:s instanceof Set&&(s.add=s.clear=s.delete=function(){throw new Error("set is read-only")}),Object.freeze(s),Object.getOwnPropertyNames(s).forEach((function(o){var i=s[o];"object"!=typeof i||Object.isFrozen(i)||deepFreeze(i)})),s}var o=deepFreeze,i=deepFreeze;o.default=i;class Response{constructor(s){void 0===s.data&&(s.data={}),this.data=s.data,this.isMatchIgnored=!1}ignoreMatch(){this.isMatchIgnored=!0}}function escapeHTML(s){return s.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function inherit(s,...o){const i=Object.create(null);for(const o in s)i[o]=s[o];return o.forEach((function(s){for(const o in s)i[o]=s[o]})),i}const emitsWrappingTags=s=>!!s.kind;class HTMLRenderer{constructor(s,o){this.buffer="",this.classPrefix=o.classPrefix,s.walk(this)}addText(s){this.buffer+=escapeHTML(s)}openNode(s){if(!emitsWrappingTags(s))return;let o=s.kind;s.sublanguage||(o=`${this.classPrefix}${o}`),this.span(o)}closeNode(s){emitsWrappingTags(s)&&(this.buffer+="")}value(){return this.buffer}span(s){this.buffer+=``}}class TokenTree{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(s){this.top.children.push(s)}openNode(s){const o={kind:s,children:[]};this.add(o),this.stack.push(o)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(s){return this.constructor._walk(s,this.rootNode)}static _walk(s,o){return"string"==typeof o?s.addText(o):o.children&&(s.openNode(o),o.children.forEach((o=>this._walk(s,o))),s.closeNode(o)),s}static _collapse(s){"string"!=typeof s&&s.children&&(s.children.every((s=>"string"==typeof s))?s.children=[s.children.join("")]:s.children.forEach((s=>{TokenTree._collapse(s)})))}}class TokenTreeEmitter extends TokenTree{constructor(s){super(),this.options=s}addKeyword(s,o){""!==s&&(this.openNode(o),this.addText(s),this.closeNode())}addText(s){""!==s&&this.add(s)}addSublanguage(s,o){const i=s.root;i.kind=o,i.sublanguage=!0,this.add(i)}toHTML(){return new HTMLRenderer(this,this.options).value()}finalize(){return!0}}function source(s){return s?"string"==typeof s?s:s.source:null}const u=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;const _="[a-zA-Z]\\w*",w="[a-zA-Z_]\\w*",x="\\b\\d+(\\.\\d+)?",C="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",j="\\b(0b[01]+)",L={begin:"\\\\[\\s\\S]",relevance:0},B={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[L]},$={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[L]},V={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},COMMENT=function(s,o,i={}){const u=inherit({className:"comment",begin:s,end:o,contains:[]},i);return u.contains.push(V),u.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),u},U=COMMENT("//","$"),z=COMMENT("/\\*","\\*/"),Y=COMMENT("#","$"),Z={className:"number",begin:x,relevance:0},ee={className:"number",begin:C,relevance:0},ie={className:"number",begin:j,relevance:0},ae={className:"number",begin:x+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},le={begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[L,{begin:/\[/,end:/\]/,relevance:0,contains:[L]}]}]},ce={className:"title",begin:_,relevance:0},pe={className:"title",begin:w,relevance:0},de={begin:"\\.\\s*"+w,relevance:0};var fe=Object.freeze({__proto__:null,MATCH_NOTHING_RE:/\b\B/,IDENT_RE:_,UNDERSCORE_IDENT_RE:w,NUMBER_RE:x,C_NUMBER_RE:C,BINARY_NUMBER_RE:j,RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",SHEBANG:(s={})=>{const o=/^#![ ]*\//;return s.binary&&(s.begin=function concat(...s){return s.map((s=>source(s))).join("")}(o,/.*\b/,s.binary,/\b.*/)),inherit({className:"meta",begin:o,end:/$/,relevance:0,"on:begin":(s,o)=>{0!==s.index&&o.ignoreMatch()}},s)},BACKSLASH_ESCAPE:L,APOS_STRING_MODE:B,QUOTE_STRING_MODE:$,PHRASAL_WORDS_MODE:V,COMMENT,C_LINE_COMMENT_MODE:U,C_BLOCK_COMMENT_MODE:z,HASH_COMMENT_MODE:Y,NUMBER_MODE:Z,C_NUMBER_MODE:ee,BINARY_NUMBER_MODE:ie,CSS_NUMBER_MODE:ae,REGEXP_MODE:le,TITLE_MODE:ce,UNDERSCORE_TITLE_MODE:pe,METHOD_GUARD:de,END_SAME_AS_BEGIN:function(s){return Object.assign(s,{"on:begin":(s,o)=>{o.data._beginMatch=s[1]},"on:end":(s,o)=>{o.data._beginMatch!==s[1]&&o.ignoreMatch()}})}});function skipIfhasPrecedingDot(s,o){"."===s.input[s.index-1]&&o.ignoreMatch()}function beginKeywords(s,o){o&&s.beginKeywords&&(s.begin="\\b("+s.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)",s.__beforeBegin=skipIfhasPrecedingDot,s.keywords=s.keywords||s.beginKeywords,delete s.beginKeywords,void 0===s.relevance&&(s.relevance=0))}function compileIllegal(s,o){Array.isArray(s.illegal)&&(s.illegal=function either(...s){return"("+s.map((s=>source(s))).join("|")+")"}(...s.illegal))}function compileMatch(s,o){if(s.match){if(s.begin||s.end)throw new Error("begin & end are not supported with match");s.begin=s.match,delete s.match}}function compileRelevance(s,o){void 0===s.relevance&&(s.relevance=1)}const ye=["of","and","for","in","not","or","if","then","parent","list","value"];function compileKeywords(s,o,i="keyword"){const u={};return"string"==typeof s?compileList(i,s.split(" ")):Array.isArray(s)?compileList(i,s):Object.keys(s).forEach((function(i){Object.assign(u,compileKeywords(s[i],o,i))})),u;function compileList(s,i){o&&(i=i.map((s=>s.toLowerCase()))),i.forEach((function(o){const i=o.split("|");u[i[0]]=[s,scoreForKeyword(i[0],i[1])]}))}}function scoreForKeyword(s,o){return o?Number(o):function commonKeyword(s){return ye.includes(s.toLowerCase())}(s)?0:1}function compileLanguage(s,{plugins:o}){function langRe(o,i){return new RegExp(source(o),"m"+(s.case_insensitive?"i":"")+(i?"g":""))}class MultiRegex{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(s,o){o.position=this.position++,this.matchIndexes[this.matchAt]=o,this.regexes.push([o,s]),this.matchAt+=function countMatchGroups(s){return new RegExp(s.toString()+"|").exec("").length-1}(s)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);const s=this.regexes.map((s=>s[1]));this.matcherRe=langRe(function join(s,o="|"){let i=0;return s.map((s=>{i+=1;const o=i;let _=source(s),w="";for(;_.length>0;){const s=u.exec(_);if(!s){w+=_;break}w+=_.substring(0,s.index),_=_.substring(s.index+s[0].length),"\\"===s[0][0]&&s[1]?w+="\\"+String(Number(s[1])+o):(w+=s[0],"("===s[0]&&i++)}return w})).map((s=>`(${s})`)).join(o)}(s),!0),this.lastIndex=0}exec(s){this.matcherRe.lastIndex=this.lastIndex;const o=this.matcherRe.exec(s);if(!o)return null;const i=o.findIndex(((s,o)=>o>0&&void 0!==s)),u=this.matchIndexes[i];return o.splice(0,i),Object.assign(o,u)}}class ResumableMultiRegex{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(s){if(this.multiRegexes[s])return this.multiRegexes[s];const o=new MultiRegex;return this.rules.slice(s).forEach((([s,i])=>o.addRule(s,i))),o.compile(),this.multiRegexes[s]=o,o}resumingScanAtSamePosition(){return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(s,o){this.rules.push([s,o]),"begin"===o.type&&this.count++}exec(s){const o=this.getMatcher(this.regexIndex);o.lastIndex=this.lastIndex;let i=o.exec(s);if(this.resumingScanAtSamePosition())if(i&&i.index===this.lastIndex);else{const o=this.getMatcher(0);o.lastIndex=this.lastIndex+1,i=o.exec(s)}return i&&(this.regexIndex+=i.position+1,this.regexIndex===this.count&&this.considerAll()),i}}if(s.compilerExtensions||(s.compilerExtensions=[]),s.contains&&s.contains.includes("self"))throw new Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return s.classNameAliases=inherit(s.classNameAliases||{}),function compileMode(o,i){const u=o;if(o.isCompiled)return u;[compileMatch].forEach((s=>s(o,i))),s.compilerExtensions.forEach((s=>s(o,i))),o.__beforeBegin=null,[beginKeywords,compileIllegal,compileRelevance].forEach((s=>s(o,i))),o.isCompiled=!0;let _=null;if("object"==typeof o.keywords&&(_=o.keywords.$pattern,delete o.keywords.$pattern),o.keywords&&(o.keywords=compileKeywords(o.keywords,s.case_insensitive)),o.lexemes&&_)throw new Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");return _=_||o.lexemes||/\w+/,u.keywordPatternRe=langRe(_,!0),i&&(o.begin||(o.begin=/\B|\b/),u.beginRe=langRe(o.begin),o.endSameAsBegin&&(o.end=o.begin),o.end||o.endsWithParent||(o.end=/\B|\b/),o.end&&(u.endRe=langRe(o.end)),u.terminatorEnd=source(o.end)||"",o.endsWithParent&&i.terminatorEnd&&(u.terminatorEnd+=(o.end?"|":"")+i.terminatorEnd)),o.illegal&&(u.illegalRe=langRe(o.illegal)),o.contains||(o.contains=[]),o.contains=[].concat(...o.contains.map((function(s){return function expandOrCloneMode(s){s.variants&&!s.cachedVariants&&(s.cachedVariants=s.variants.map((function(o){return inherit(s,{variants:null},o)})));if(s.cachedVariants)return s.cachedVariants;if(dependencyOnParent(s))return inherit(s,{starts:s.starts?inherit(s.starts):null});if(Object.isFrozen(s))return inherit(s);return s}("self"===s?o:s)}))),o.contains.forEach((function(s){compileMode(s,u)})),o.starts&&compileMode(o.starts,i),u.matcher=function buildModeRegex(s){const o=new ResumableMultiRegex;return s.contains.forEach((s=>o.addRule(s.begin,{rule:s,type:"begin"}))),s.terminatorEnd&&o.addRule(s.terminatorEnd,{type:"end"}),s.illegal&&o.addRule(s.illegal,{type:"illegal"}),o}(u),u}(s)}function dependencyOnParent(s){return!!s&&(s.endsWithParent||dependencyOnParent(s.starts))}function BuildVuePlugin(s){const o={props:["language","code","autodetect"],data:function(){return{detectedLanguage:"",unknownLanguage:!1}},computed:{className(){return this.unknownLanguage?"":"hljs "+this.detectedLanguage},highlighted(){if(!this.autoDetect&&!s.getLanguage(this.language))return console.warn(`The language "${this.language}" you specified could not be found.`),this.unknownLanguage=!0,escapeHTML(this.code);let o={};return this.autoDetect?(o=s.highlightAuto(this.code),this.detectedLanguage=o.language):(o=s.highlight(this.language,this.code,this.ignoreIllegals),this.detectedLanguage=this.language),o.value},autoDetect(){return!this.language||function hasValueOrEmptyAttribute(s){return Boolean(s||""===s)}(this.autodetect)},ignoreIllegals:()=>!0},render(s){return s("pre",{},[s("code",{class:this.className,domProps:{innerHTML:this.highlighted}})])}};return{Component:o,VuePlugin:{install(s){s.component("highlightjs",o)}}}}const be={"after:highlightElement":({el:s,result:o,text:i})=>{const u=nodeStream(s);if(!u.length)return;const _=document.createElement("div");_.innerHTML=o.value,o.value=function mergeStreams(s,o,i){let u=0,_="";const w=[];function selectStream(){return s.length&&o.length?s[0].offset!==o[0].offset?s[0].offset"}function close(s){_+=""}function render(s){("start"===s.event?open:close)(s.node)}for(;s.length||o.length;){let o=selectStream();if(_+=escapeHTML(i.substring(u,o[0].offset)),u=o[0].offset,o===s){w.reverse().forEach(close);do{render(o.splice(0,1)[0]),o=selectStream()}while(o===s&&o.length&&o[0].offset===u);w.reverse().forEach(open)}else"start"===o[0].event?w.push(o[0].node):w.pop(),render(o.splice(0,1)[0])}return _+escapeHTML(i.substr(u))}(u,nodeStream(_),i)}};function tag(s){return s.nodeName.toLowerCase()}function nodeStream(s){const o=[];return function _nodeStream(s,i){for(let u=s.firstChild;u;u=u.nextSibling)3===u.nodeType?i+=u.nodeValue.length:1===u.nodeType&&(o.push({event:"start",offset:i,node:u}),i=_nodeStream(u,i),tag(u).match(/br|hr|img|input/)||o.push({event:"stop",offset:i,node:u}));return i}(s,0),o}const _e={},error=s=>{console.error(s)},warn=(s,...o)=>{console.log(`WARN: ${s}`,...o)},deprecated=(s,o)=>{_e[`${s}/${o}`]||(console.log(`Deprecated as of ${s}. ${o}`),_e[`${s}/${o}`]=!0)},we=escapeHTML,Se=inherit,xe=Symbol("nomatch");var Pe=function(s){const i=Object.create(null),u=Object.create(null),_=[];let w=!0;const x=/(^(<[^>]+>|\t|)+|\n)/gm,C="Could not find the language '{}', did you forget to load/include a language module?",j={disableAutodetect:!0,name:"Plain text",contains:[]};let L={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:null,__emitter:TokenTreeEmitter};function shouldNotHighlight(s){return L.noHighlightRe.test(s)}function highlight(s,o,i,u){let _="",w="";"object"==typeof o?(_=s,i=o.ignoreIllegals,w=o.language,u=void 0):(deprecated("10.7.0","highlight(lang, code, ...args) has been deprecated."),deprecated("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"),w=s,_=o);const x={code:_,language:w};fire("before:highlight",x);const C=x.result?x.result:_highlight(x.language,x.code,i,u);return C.code=x.code,fire("after:highlight",C),C}function _highlight(s,o,u,x){function keywordData(s,o){const i=B.case_insensitive?o[0].toLowerCase():o[0];return Object.prototype.hasOwnProperty.call(s.keywords,i)&&s.keywords[i]}function processBuffer(){null!=U.subLanguage?function processSubLanguage(){if(""===Z)return;let s=null;if("string"==typeof U.subLanguage){if(!i[U.subLanguage])return void Y.addText(Z);s=_highlight(U.subLanguage,Z,!0,z[U.subLanguage]),z[U.subLanguage]=s.top}else s=highlightAuto(Z,U.subLanguage.length?U.subLanguage:null);U.relevance>0&&(ee+=s.relevance),Y.addSublanguage(s.emitter,s.language)}():function processKeywords(){if(!U.keywords)return void Y.addText(Z);let s=0;U.keywordPatternRe.lastIndex=0;let o=U.keywordPatternRe.exec(Z),i="";for(;o;){i+=Z.substring(s,o.index);const u=keywordData(U,o);if(u){const[s,_]=u;if(Y.addText(i),i="",ee+=_,s.startsWith("_"))i+=o[0];else{const i=B.classNameAliases[s]||s;Y.addKeyword(o[0],i)}}else i+=o[0];s=U.keywordPatternRe.lastIndex,o=U.keywordPatternRe.exec(Z)}i+=Z.substr(s),Y.addText(i)}(),Z=""}function startNewMode(s){return s.className&&Y.openNode(B.classNameAliases[s.className]||s.className),U=Object.create(s,{parent:{value:U}}),U}function endOfMode(s,o,i){let u=function startsWith(s,o){const i=s&&s.exec(o);return i&&0===i.index}(s.endRe,i);if(u){if(s["on:end"]){const i=new Response(s);s["on:end"](o,i),i.isMatchIgnored&&(u=!1)}if(u){for(;s.endsParent&&s.parent;)s=s.parent;return s}}if(s.endsWithParent)return endOfMode(s.parent,o,i)}function doIgnore(s){return 0===U.matcher.regexIndex?(Z+=s[0],1):(le=!0,0)}function doBeginMatch(s){const o=s[0],i=s.rule,u=new Response(i),_=[i.__beforeBegin,i["on:begin"]];for(const i of _)if(i&&(i(s,u),u.isMatchIgnored))return doIgnore(o);return i&&i.endSameAsBegin&&(i.endRe=function escape(s){return new RegExp(s.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")}(o)),i.skip?Z+=o:(i.excludeBegin&&(Z+=o),processBuffer(),i.returnBegin||i.excludeBegin||(Z=o)),startNewMode(i),i.returnBegin?0:o.length}function doEndMatch(s){const i=s[0],u=o.substr(s.index),_=endOfMode(U,s,u);if(!_)return xe;const w=U;w.skip?Z+=i:(w.returnEnd||w.excludeEnd||(Z+=i),processBuffer(),w.excludeEnd&&(Z=i));do{U.className&&Y.closeNode(),U.skip||U.subLanguage||(ee+=U.relevance),U=U.parent}while(U!==_.parent);return _.starts&&(_.endSameAsBegin&&(_.starts.endRe=_.endRe),startNewMode(_.starts)),w.returnEnd?0:i.length}let j={};function processLexeme(i,_){const x=_&&_[0];if(Z+=i,null==x)return processBuffer(),0;if("begin"===j.type&&"end"===_.type&&j.index===_.index&&""===x){if(Z+=o.slice(_.index,_.index+1),!w){const o=new Error("0 width match regex");throw o.languageName=s,o.badRule=j.rule,o}return 1}if(j=_,"begin"===_.type)return doBeginMatch(_);if("illegal"===_.type&&!u){const s=new Error('Illegal lexeme "'+x+'" for mode "'+(U.className||"")+'"');throw s.mode=U,s}if("end"===_.type){const s=doEndMatch(_);if(s!==xe)return s}if("illegal"===_.type&&""===x)return 1;if(ae>1e5&&ae>3*_.index){throw new Error("potential infinite loop, way more iterations than matches")}return Z+=x,x.length}const B=getLanguage(s);if(!B)throw error(C.replace("{}",s)),new Error('Unknown language: "'+s+'"');const $=compileLanguage(B,{plugins:_});let V="",U=x||$;const z={},Y=new L.__emitter(L);!function processContinuations(){const s=[];for(let o=U;o!==B;o=o.parent)o.className&&s.unshift(o.className);s.forEach((s=>Y.openNode(s)))}();let Z="",ee=0,ie=0,ae=0,le=!1;try{for(U.matcher.considerAll();;){ae++,le?le=!1:U.matcher.considerAll(),U.matcher.lastIndex=ie;const s=U.matcher.exec(o);if(!s)break;const i=processLexeme(o.substring(ie,s.index),s);ie=s.index+i}return processLexeme(o.substr(ie)),Y.closeAllNodes(),Y.finalize(),V=Y.toHTML(),{relevance:Math.floor(ee),value:V,language:s,illegal:!1,emitter:Y,top:U}}catch(i){if(i.message&&i.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:i.message,context:o.slice(ie-100,ie+100),mode:i.mode},sofar:V,relevance:0,value:we(o),emitter:Y};if(w)return{illegal:!1,relevance:0,value:we(o),emitter:Y,language:s,top:U,errorRaised:i};throw i}}function highlightAuto(s,o){o=o||L.languages||Object.keys(i);const u=function justTextHighlightResult(s){const o={relevance:0,emitter:new L.__emitter(L),value:we(s),illegal:!1,top:j};return o.emitter.addText(s),o}(s),_=o.filter(getLanguage).filter(autoDetection).map((o=>_highlight(o,s,!1)));_.unshift(u);const w=_.sort(((s,o)=>{if(s.relevance!==o.relevance)return o.relevance-s.relevance;if(s.language&&o.language){if(getLanguage(s.language).supersetOf===o.language)return 1;if(getLanguage(o.language).supersetOf===s.language)return-1}return 0})),[x,C]=w,B=x;return B.second_best=C,B}const B={"before:highlightElement":({el:s})=>{L.useBR&&(s.innerHTML=s.innerHTML.replace(/\n/g,"").replace(//g,"\n"))},"after:highlightElement":({result:s})=>{L.useBR&&(s.value=s.value.replace(/\n/g,"
    "))}},$=/^(<[^>]+>|\t)+/gm,V={"after:highlightElement":({result:s})=>{L.tabReplace&&(s.value=s.value.replace($,(s=>s.replace(/\t/g,L.tabReplace))))}};function highlightElement(s){let o=null;const i=function blockLanguage(s){let o=s.className+" ";o+=s.parentNode?s.parentNode.className:"";const i=L.languageDetectRe.exec(o);if(i){const o=getLanguage(i[1]);return o||(warn(C.replace("{}",i[1])),warn("Falling back to no-highlight mode for this block.",s)),o?i[1]:"no-highlight"}return o.split(/\s+/).find((s=>shouldNotHighlight(s)||getLanguage(s)))}(s);if(shouldNotHighlight(i))return;fire("before:highlightElement",{el:s,language:i}),o=s;const _=o.textContent,w=i?highlight(_,{language:i,ignoreIllegals:!0}):highlightAuto(_);fire("after:highlightElement",{el:s,result:w,text:_}),s.innerHTML=w.value,function updateClassName(s,o,i){const _=o?u[o]:i;s.classList.add("hljs"),_&&s.classList.add(_)}(s,i,w.language),s.result={language:w.language,re:w.relevance,relavance:w.relevance},w.second_best&&(s.second_best={language:w.second_best.language,re:w.second_best.relevance,relavance:w.second_best.relevance})}const initHighlighting=()=>{if(initHighlighting.called)return;initHighlighting.called=!0,deprecated("10.6.0","initHighlighting() is deprecated. Use highlightAll() instead.");document.querySelectorAll("pre code").forEach(highlightElement)};let U=!1;function highlightAll(){if("loading"===document.readyState)return void(U=!0);document.querySelectorAll("pre code").forEach(highlightElement)}function getLanguage(s){return s=(s||"").toLowerCase(),i[s]||i[u[s]]}function registerAliases(s,{languageName:o}){"string"==typeof s&&(s=[s]),s.forEach((s=>{u[s.toLowerCase()]=o}))}function autoDetection(s){const o=getLanguage(s);return o&&!o.disableAutodetect}function fire(s,o){const i=s;_.forEach((function(s){s[i]&&s[i](o)}))}"undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(function boot(){U&&highlightAll()}),!1),Object.assign(s,{highlight,highlightAuto,highlightAll,fixMarkup:function deprecateFixMarkup(s){return deprecated("10.2.0","fixMarkup will be removed entirely in v11.0"),deprecated("10.2.0","Please see https://github.com/highlightjs/highlight.js/issues/2534"),function fixMarkup(s){return L.tabReplace||L.useBR?s.replace(x,(s=>"\n"===s?L.useBR?"
    ":s:L.tabReplace?s.replace(/\t/g,L.tabReplace):s)):s}(s)},highlightElement,highlightBlock:function deprecateHighlightBlock(s){return deprecated("10.7.0","highlightBlock will be removed entirely in v12.0"),deprecated("10.7.0","Please use highlightElement now."),highlightElement(s)},configure:function configure(s){s.useBR&&(deprecated("10.3.0","'useBR' will be removed entirely in v11.0"),deprecated("10.3.0","Please see https://github.com/highlightjs/highlight.js/issues/2559")),L=Se(L,s)},initHighlighting,initHighlightingOnLoad:function initHighlightingOnLoad(){deprecated("10.6.0","initHighlightingOnLoad() is deprecated. Use highlightAll() instead."),U=!0},registerLanguage:function registerLanguage(o,u){let _=null;try{_=u(s)}catch(s){if(error("Language definition for '{}' could not be registered.".replace("{}",o)),!w)throw s;error(s),_=j}_.name||(_.name=o),i[o]=_,_.rawDefinition=u.bind(null,s),_.aliases&®isterAliases(_.aliases,{languageName:o})},unregisterLanguage:function unregisterLanguage(s){delete i[s];for(const o of Object.keys(u))u[o]===s&&delete u[o]},listLanguages:function listLanguages(){return Object.keys(i)},getLanguage,registerAliases,requireLanguage:function requireLanguage(s){deprecated("10.4.0","requireLanguage will be removed entirely in v11."),deprecated("10.4.0","Please see https://github.com/highlightjs/highlight.js/pull/2844");const o=getLanguage(s);if(o)return o;throw new Error("The '{}' language is required, but not loaded.".replace("{}",s))},autoDetection,inherit:Se,addPlugin:function addPlugin(s){!function upgradePluginAPI(s){s["before:highlightBlock"]&&!s["before:highlightElement"]&&(s["before:highlightElement"]=o=>{s["before:highlightBlock"](Object.assign({block:o.el},o))}),s["after:highlightBlock"]&&!s["after:highlightElement"]&&(s["after:highlightElement"]=o=>{s["after:highlightBlock"](Object.assign({block:o.el},o))})}(s),_.push(s)},vuePlugin:BuildVuePlugin(s).VuePlugin}),s.debugMode=function(){w=!1},s.safeMode=function(){w=!0},s.versionString="10.7.3";for(const s in fe)"object"==typeof fe[s]&&o(fe[s]);return Object.assign(s,fe),s.addPlugin(B),s.addPlugin(be),s.addPlugin(V),s}({});s.exports=Pe},35344:s=>{function concat(...s){return s.map((s=>function source(s){return s?"string"==typeof s?s:s.source:null}(s))).join("")}s.exports=function bash(s){const o={},i={begin:/\$\{/,end:/\}/,contains:["self",{begin:/:-/,contains:[o]}]};Object.assign(o,{className:"variable",variants:[{begin:concat(/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])")},i]});const u={className:"subst",begin:/\$\(/,end:/\)/,contains:[s.BACKSLASH_ESCAPE]},_={begin:/<<-?\s*(?=\w+)/,starts:{contains:[s.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,className:"string"})]}},w={className:"string",begin:/"/,end:/"/,contains:[s.BACKSLASH_ESCAPE,o,u]};u.contains.push(w);const x={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},s.NUMBER_MODE,o]},C=s.SHEBANG({binary:`(${["fish","bash","zsh","sh","csh","ksh","tcsh","dash","scsh"].join("|")})`,relevance:10}),j={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[s.inherit(s.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b[a-z._-]+\b/,keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp"},contains:[C,s.SHEBANG(),j,x,s.HASH_COMMENT_MODE,_,w,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},o]}}},73402:s=>{function concat(...s){return s.map((s=>function source(s){return s?"string"==typeof s?s:s.source:null}(s))).join("")}s.exports=function http(s){const o="HTTP/(2|1\\.[01])",i={className:"attribute",begin:concat("^",/[A-Za-z][A-Za-z0-9-]*/,"(?=\\:\\s)"),starts:{contains:[{className:"punctuation",begin:/: /,relevance:0,starts:{end:"$",relevance:0}}]}},u=[i,{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}];return{name:"HTTP",aliases:["https"],illegal:/\S/,contains:[{begin:"^(?="+o+" \\d{3})",end:/$/,contains:[{className:"meta",begin:o},{className:"number",begin:"\\b\\d{3}\\b"}],starts:{end:/\b\B/,illegal:/\S/,contains:u}},{begin:"(?=^[A-Z]+ (.*?) "+o+"$)",end:/$/,contains:[{className:"string",begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{className:"meta",begin:o},{className:"keyword",begin:"[A-Z]+"}],starts:{end:/\b\B/,illegal:/\S/,contains:u}},s.inherit(i,{relevance:0})]}}},95089:s=>{const o="[A-Za-z$_][0-9A-Za-z$_]*",i=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],u=["true","false","null","undefined","NaN","Infinity"],_=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer","BigInt64Array","BigUint64Array","BigInt"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);function lookahead(s){return concat("(?=",s,")")}function concat(...s){return s.map((s=>function source(s){return s?"string"==typeof s?s:s.source:null}(s))).join("")}s.exports=function javascript(s){const w=o,x="<>",C="",j={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(s,o)=>{const i=s[0].length+s.index,u=s.input[i];"<"!==u?">"===u&&(((s,{after:o})=>{const i="",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:s.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:L,contains:ce}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:x,end:C},{begin:j.begin,"on:begin":j.isTrulyOpeningTag,end:j.end}],subLanguage:"xml",contains:[{begin:j.begin,end:j.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[{;]/,excludeEnd:!0,keywords:L,contains:["self",s.inherit(s.TITLE_MODE,{begin:w}),pe],illegal:/%/},{beginKeywords:"while if switch catch for"},{className:"function",begin:s.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{",returnBegin:!0,contains:[pe,s.inherit(s.TITLE_MODE,{begin:w})]},{variants:[{begin:"\\."+w},{begin:"\\$"+w}],relevance:0},{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"[\]]/,contains:[{beginKeywords:"extends"},s.UNDERSCORE_TITLE_MODE]},{begin:/\b(?=constructor)/,end:/[{;]/,excludeEnd:!0,contains:[s.inherit(s.TITLE_MODE,{begin:w}),"self",pe]},{begin:"(get|set)\\s+(?="+w+"\\()",end:/\{/,keywords:"get set",contains:[s.inherit(s.TITLE_MODE,{begin:w}),{begin:/\(\)/},pe]},{begin:/\$[(.]/}]}}},65772:s=>{s.exports=function json(s){const o={literal:"true false null"},i=[s.C_LINE_COMMENT_MODE,s.C_BLOCK_COMMENT_MODE],u=[s.QUOTE_STRING_MODE,s.C_NUMBER_MODE],_={end:",",endsWithParent:!0,excludeEnd:!0,contains:u,keywords:o},w={begin:/\{/,end:/\}/,contains:[{className:"attr",begin:/"/,end:/"/,contains:[s.BACKSLASH_ESCAPE],illegal:"\\n"},s.inherit(_,{begin:/:/})].concat(i),illegal:"\\S"},x={begin:"\\[",end:"\\]",contains:[s.inherit(_)],illegal:"\\S"};return u.push(w,x),i.forEach((function(s){u.push(s)})),{name:"JSON",contains:u,keywords:o,illegal:"\\S"}}},26571:s=>{s.exports=function powershell(s){const o={$pattern:/-?[A-z\.\-]+\b/,keyword:"if else foreach return do while until elseif begin for trap data dynamicparam end break throw param continue finally in switch exit filter try process catch hidden static parameter",built_in:"ac asnp cat cd CFS chdir clc clear clhy cli clp cls clv cnsn compare copy cp cpi cpp curl cvpa dbp del diff dir dnsn ebp echo|0 epal epcsv epsn erase etsn exsn fc fhx fl ft fw gal gbp gc gcb gci gcm gcs gdr gerr ghy gi gin gjb gl gm gmo gp gps gpv group gsn gsnp gsv gtz gu gv gwmi h history icm iex ihy ii ipal ipcsv ipmo ipsn irm ise iwmi iwr kill lp ls man md measure mi mount move mp mv nal ndr ni nmo npssc nsn nv ogv oh popd ps pushd pwd r rbp rcjb rcsn rd rdr ren ri rjb rm rmdir rmo rni rnp rp rsn rsnp rujb rv rvpa rwmi sajb sal saps sasv sbp sc scb select set shcm si sl sleep sls sort sp spjb spps spsv start stz sujb sv swmi tee trcm type wget where wjb write"},i={begin:"`[\\s\\S]",relevance:0},u={className:"variable",variants:[{begin:/\$\B/},{className:"keyword",begin:/\$this/},{begin:/\$[\w\d][\w\d_:]*/}]},_={className:"string",variants:[{begin:/"/,end:/"/},{begin:/@"/,end:/^"@/}],contains:[i,u,{className:"variable",begin:/\$[A-z]/,end:/[^A-z]/}]},w={className:"string",variants:[{begin:/'/,end:/'/},{begin:/@'/,end:/^'@/}]},x=s.inherit(s.COMMENT(null,null),{variants:[{begin:/#/,end:/$/},{begin:/<#/,end:/#>/}],contains:[{className:"doctag",variants:[{begin:/\.(synopsis|description|example|inputs|outputs|notes|link|component|role|functionality)/},{begin:/\.(parameter|forwardhelptargetname|forwardhelpcategory|remotehelprunspace|externalhelp)\s+\S+/}]}]}),C={className:"built_in",variants:[{begin:"(".concat("Add|Clear|Close|Copy|Enter|Exit|Find|Format|Get|Hide|Join|Lock|Move|New|Open|Optimize|Pop|Push|Redo|Remove|Rename|Reset|Resize|Search|Select|Set|Show|Skip|Split|Step|Switch|Undo|Unlock|Watch|Backup|Checkpoint|Compare|Compress|Convert|ConvertFrom|ConvertTo|Dismount|Edit|Expand|Export|Group|Import|Initialize|Limit|Merge|Mount|Out|Publish|Restore|Save|Sync|Unpublish|Update|Approve|Assert|Build|Complete|Confirm|Deny|Deploy|Disable|Enable|Install|Invoke|Register|Request|Restart|Resume|Start|Stop|Submit|Suspend|Uninstall|Unregister|Wait|Debug|Measure|Ping|Repair|Resolve|Test|Trace|Connect|Disconnect|Read|Receive|Send|Write|Block|Grant|Protect|Revoke|Unblock|Unprotect|Use|ForEach|Sort|Tee|Where",")+(-)[\\w\\d]+")}]},j={className:"class",beginKeywords:"class enum",end:/\s*[{]/,excludeEnd:!0,relevance:0,contains:[s.TITLE_MODE]},L={className:"function",begin:/function\s+/,end:/\s*\{|$/,excludeEnd:!0,returnBegin:!0,relevance:0,contains:[{begin:"function",relevance:0,className:"keyword"},{className:"title",begin:/\w[\w\d]*((-)[\w\d]+)*/,relevance:0},{begin:/\(/,end:/\)/,className:"params",relevance:0,contains:[u]}]},B={begin:/using\s/,end:/$/,returnBegin:!0,contains:[_,w,{className:"keyword",begin:/(using|assembly|command|module|namespace|type)/}]},$={variants:[{className:"operator",begin:"(".concat("-and|-as|-band|-bnot|-bor|-bxor|-casesensitive|-ccontains|-ceq|-cge|-cgt|-cle|-clike|-clt|-cmatch|-cne|-cnotcontains|-cnotlike|-cnotmatch|-contains|-creplace|-csplit|-eq|-exact|-f|-file|-ge|-gt|-icontains|-ieq|-ige|-igt|-ile|-ilike|-ilt|-imatch|-in|-ine|-inotcontains|-inotlike|-inotmatch|-ireplace|-is|-isnot|-isplit|-join|-le|-like|-lt|-match|-ne|-not|-notcontains|-notin|-notlike|-notmatch|-or|-regex|-replace|-shl|-shr|-split|-wildcard|-xor",")\\b")},{className:"literal",begin:/(-)[\w\d]+/,relevance:0}]},V={className:"function",begin:/\[.*\]\s*[\w]+[ ]??\(/,end:/$/,returnBegin:!0,relevance:0,contains:[{className:"keyword",begin:"(".concat(o.keyword.toString().replace(/\s/g,"|"),")\\b"),endsParent:!0,relevance:0},s.inherit(s.TITLE_MODE,{endsParent:!0})]},U=[V,x,i,s.NUMBER_MODE,_,w,C,u,{className:"literal",begin:/\$(null|true|false)\b/},{className:"selector-tag",begin:/@\B/,relevance:0}],z={begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[].concat("self",U,{begin:"("+["string","char","byte","int","long","bool","decimal","single","double","DateTime","xml","array","hashtable","void"].join("|")+")",className:"built_in",relevance:0},{className:"type",begin:/[\.\w\d]+/,relevance:0})};return V.contains.unshift(z),{name:"PowerShell",aliases:["ps","ps1"],case_insensitive:!0,keywords:o,contains:U.concat(j,L,B,$,z)}}},17285:s=>{function source(s){return s?"string"==typeof s?s:s.source:null}function lookahead(s){return concat("(?=",s,")")}function concat(...s){return s.map((s=>source(s))).join("")}function either(...s){return"("+s.map((s=>source(s))).join("|")+")"}s.exports=function xml(s){const o=concat(/[A-Z_]/,function optional(s){return concat("(",s,")?")}(/[A-Z0-9_.-]*:/),/[A-Z0-9_.-]*/),i={className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},u={begin:/\s/,contains:[{className:"meta-keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}]},_=s.inherit(u,{begin:/\(/,end:/\)/}),w=s.inherit(s.APOS_STRING_MODE,{className:"meta-string"}),x=s.inherit(s.QUOTE_STRING_MODE,{className:"meta-string"}),C={endsWithParent:!0,illegal:/`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin://,relevance:10,contains:[u,x,w,_,{begin:/\[/,end:/\]/,contains:[{className:"meta",begin://,contains:[u,_,x,w]}]}]},s.COMMENT(//,{relevance:10}),{begin://,relevance:10},i,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:/)/,end:/>/,keywords:{name:"style"},contains:[C],starts:{end:/<\/style>/,returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:/)/,end:/>/,keywords:{name:"script"},contains:[C],starts:{end:/<\/script>/,returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:/<>|<\/>/},{className:"tag",begin:concat(//,/>/,/\s/)))),end:/\/?>/,contains:[{className:"name",begin:o,relevance:0,starts:C}]},{className:"tag",begin:concat(/<\//,lookahead(concat(o,/>/))),contains:[{className:"name",begin:o,relevance:0},{begin:/>/,relevance:0,endsParent:!0}]}]}}},17533:s=>{s.exports=function yaml(s){var o="true false yes no null",i="[\\w#;/?:@&=+$,.~*'()[\\]]+",u={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[s.BACKSLASH_ESCAPE,{className:"template-variable",variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]}]},_=s.inherit(u,{variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),w={className:"number",begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"},x={end:",",endsWithParent:!0,excludeEnd:!0,keywords:o,relevance:0},C={begin:/\{/,end:/\}/,contains:[x],illegal:"\\n",relevance:0},j={begin:"\\[",end:"\\]",contains:[x],illegal:"\\n",relevance:0},L=[{className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---\\s*$",relevance:10},{className:"string",begin:"[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!\\w+!"+i},{className:"type",begin:"!<"+i+">"},{className:"type",begin:"!"+i},{className:"type",begin:"!!"+i},{className:"meta",begin:"&"+s.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+s.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"-(?=[ ]|$)",relevance:0},s.HASH_COMMENT_MODE,{beginKeywords:o,keywords:{literal:o}},w,{className:"number",begin:s.C_NUMBER_RE+"\\b",relevance:0},C,j,u],B=[...L];return B.pop(),B.push(_),x.contains=B,{name:"YAML",case_insensitive:!0,aliases:["yml"],contains:L}}},251:(s,o)=>{o.read=function(s,o,i,u,_){var w,x,C=8*_-u-1,j=(1<>1,B=-7,$=i?_-1:0,V=i?-1:1,U=s[o+$];for($+=V,w=U&(1<<-B)-1,U>>=-B,B+=C;B>0;w=256*w+s[o+$],$+=V,B-=8);for(x=w&(1<<-B)-1,w>>=-B,B+=u;B>0;x=256*x+s[o+$],$+=V,B-=8);if(0===w)w=1-L;else{if(w===j)return x?NaN:1/0*(U?-1:1);x+=Math.pow(2,u),w-=L}return(U?-1:1)*x*Math.pow(2,w-u)},o.write=function(s,o,i,u,_,w){var x,C,j,L=8*w-_-1,B=(1<>1,V=23===_?Math.pow(2,-24)-Math.pow(2,-77):0,U=u?0:w-1,z=u?1:-1,Y=o<0||0===o&&1/o<0?1:0;for(o=Math.abs(o),isNaN(o)||o===1/0?(C=isNaN(o)?1:0,x=B):(x=Math.floor(Math.log(o)/Math.LN2),o*(j=Math.pow(2,-x))<1&&(x--,j*=2),(o+=x+$>=1?V/j:V*Math.pow(2,1-$))*j>=2&&(x++,j/=2),x+$>=B?(C=0,x=B):x+$>=1?(C=(o*j-1)*Math.pow(2,_),x+=$):(C=o*Math.pow(2,$-1)*Math.pow(2,_),x=0));_>=8;s[i+U]=255&C,U+=z,C/=256,_-=8);for(x=x<<_|C,L+=_;L>0;s[i+U]=255&x,U+=z,x/=256,L-=8);s[i+U-z]|=128*Y}},9404:function(s){s.exports=function(){"use strict";var s=Array.prototype.slice;function createClass(s,o){o&&(s.prototype=Object.create(o.prototype)),s.prototype.constructor=s}function Iterable(s){return isIterable(s)?s:Seq(s)}function KeyedIterable(s){return isKeyed(s)?s:KeyedSeq(s)}function IndexedIterable(s){return isIndexed(s)?s:IndexedSeq(s)}function SetIterable(s){return isIterable(s)&&!isAssociative(s)?s:SetSeq(s)}function isIterable(s){return!(!s||!s[o])}function isKeyed(s){return!(!s||!s[i])}function isIndexed(s){return!(!s||!s[u])}function isAssociative(s){return isKeyed(s)||isIndexed(s)}function isOrdered(s){return!(!s||!s[_])}createClass(KeyedIterable,Iterable),createClass(IndexedIterable,Iterable),createClass(SetIterable,Iterable),Iterable.isIterable=isIterable,Iterable.isKeyed=isKeyed,Iterable.isIndexed=isIndexed,Iterable.isAssociative=isAssociative,Iterable.isOrdered=isOrdered,Iterable.Keyed=KeyedIterable,Iterable.Indexed=IndexedIterable,Iterable.Set=SetIterable;var o="@@__IMMUTABLE_ITERABLE__@@",i="@@__IMMUTABLE_KEYED__@@",u="@@__IMMUTABLE_INDEXED__@@",_="@@__IMMUTABLE_ORDERED__@@",w="delete",x=5,C=1<>>0;if(""+i!==o||4294967295===i)return NaN;o=i}return o<0?ensureSize(s)+o:o}function returnTrue(){return!0}function wholeSlice(s,o,i){return(0===s||void 0!==i&&s<=-i)&&(void 0===o||void 0!==i&&o>=i)}function resolveBegin(s,o){return resolveIndex(s,o,0)}function resolveEnd(s,o){return resolveIndex(s,o,o)}function resolveIndex(s,o,i){return void 0===s?i:s<0?Math.max(0,o+s):void 0===o?s:Math.min(o,s)}var V=0,U=1,z=2,Y="function"==typeof Symbol&&Symbol.iterator,Z="@@iterator",ee=Y||Z;function Iterator(s){this.next=s}function iteratorValue(s,o,i,u){var _=0===s?o:1===s?i:[o,i];return u?u.value=_:u={value:_,done:!1},u}function iteratorDone(){return{value:void 0,done:!0}}function hasIterator(s){return!!getIteratorFn(s)}function isIterator(s){return s&&"function"==typeof s.next}function getIterator(s){var o=getIteratorFn(s);return o&&o.call(s)}function getIteratorFn(s){var o=s&&(Y&&s[Y]||s[Z]);if("function"==typeof o)return o}function isArrayLike(s){return s&&"number"==typeof s.length}function Seq(s){return null==s?emptySequence():isIterable(s)?s.toSeq():seqFromValue(s)}function KeyedSeq(s){return null==s?emptySequence().toKeyedSeq():isIterable(s)?isKeyed(s)?s.toSeq():s.fromEntrySeq():keyedSeqFromValue(s)}function IndexedSeq(s){return null==s?emptySequence():isIterable(s)?isKeyed(s)?s.entrySeq():s.toIndexedSeq():indexedSeqFromValue(s)}function SetSeq(s){return(null==s?emptySequence():isIterable(s)?isKeyed(s)?s.entrySeq():s:indexedSeqFromValue(s)).toSetSeq()}Iterator.prototype.toString=function(){return"[Iterator]"},Iterator.KEYS=V,Iterator.VALUES=U,Iterator.ENTRIES=z,Iterator.prototype.inspect=Iterator.prototype.toSource=function(){return this.toString()},Iterator.prototype[ee]=function(){return this},createClass(Seq,Iterable),Seq.of=function(){return Seq(arguments)},Seq.prototype.toSeq=function(){return this},Seq.prototype.toString=function(){return this.__toString("Seq {","}")},Seq.prototype.cacheResult=function(){return!this._cache&&this.__iterateUncached&&(this._cache=this.entrySeq().toArray(),this.size=this._cache.length),this},Seq.prototype.__iterate=function(s,o){return seqIterate(this,s,o,!0)},Seq.prototype.__iterator=function(s,o){return seqIterator(this,s,o,!0)},createClass(KeyedSeq,Seq),KeyedSeq.prototype.toKeyedSeq=function(){return this},createClass(IndexedSeq,Seq),IndexedSeq.of=function(){return IndexedSeq(arguments)},IndexedSeq.prototype.toIndexedSeq=function(){return this},IndexedSeq.prototype.toString=function(){return this.__toString("Seq [","]")},IndexedSeq.prototype.__iterate=function(s,o){return seqIterate(this,s,o,!1)},IndexedSeq.prototype.__iterator=function(s,o){return seqIterator(this,s,o,!1)},createClass(SetSeq,Seq),SetSeq.of=function(){return SetSeq(arguments)},SetSeq.prototype.toSetSeq=function(){return this},Seq.isSeq=isSeq,Seq.Keyed=KeyedSeq,Seq.Set=SetSeq,Seq.Indexed=IndexedSeq;var ie,ae,le,ce="@@__IMMUTABLE_SEQ__@@";function ArraySeq(s){this._array=s,this.size=s.length}function ObjectSeq(s){var o=Object.keys(s);this._object=s,this._keys=o,this.size=o.length}function IterableSeq(s){this._iterable=s,this.size=s.length||s.size}function IteratorSeq(s){this._iterator=s,this._iteratorCache=[]}function isSeq(s){return!(!s||!s[ce])}function emptySequence(){return ie||(ie=new ArraySeq([]))}function keyedSeqFromValue(s){var o=Array.isArray(s)?new ArraySeq(s).fromEntrySeq():isIterator(s)?new IteratorSeq(s).fromEntrySeq():hasIterator(s)?new IterableSeq(s).fromEntrySeq():"object"==typeof s?new ObjectSeq(s):void 0;if(!o)throw new TypeError("Expected Array or iterable object of [k, v] entries, or keyed object: "+s);return o}function indexedSeqFromValue(s){var o=maybeIndexedSeqFromValue(s);if(!o)throw new TypeError("Expected Array or iterable object of values: "+s);return o}function seqFromValue(s){var o=maybeIndexedSeqFromValue(s)||"object"==typeof s&&new ObjectSeq(s);if(!o)throw new TypeError("Expected Array or iterable object of values, or keyed object: "+s);return o}function maybeIndexedSeqFromValue(s){return isArrayLike(s)?new ArraySeq(s):isIterator(s)?new IteratorSeq(s):hasIterator(s)?new IterableSeq(s):void 0}function seqIterate(s,o,i,u){var _=s._cache;if(_){for(var w=_.length-1,x=0;x<=w;x++){var C=_[i?w-x:x];if(!1===o(C[1],u?C[0]:x,s))return x+1}return x}return s.__iterateUncached(o,i)}function seqIterator(s,o,i,u){var _=s._cache;if(_){var w=_.length-1,x=0;return new Iterator((function(){var s=_[i?w-x:x];return x++>w?iteratorDone():iteratorValue(o,u?s[0]:x-1,s[1])}))}return s.__iteratorUncached(o,i)}function fromJS(s,o){return o?fromJSWith(o,s,"",{"":s}):fromJSDefault(s)}function fromJSWith(s,o,i,u){return Array.isArray(o)?s.call(u,i,IndexedSeq(o).map((function(i,u){return fromJSWith(s,i,u,o)}))):isPlainObj(o)?s.call(u,i,KeyedSeq(o).map((function(i,u){return fromJSWith(s,i,u,o)}))):o}function fromJSDefault(s){return Array.isArray(s)?IndexedSeq(s).map(fromJSDefault).toList():isPlainObj(s)?KeyedSeq(s).map(fromJSDefault).toMap():s}function isPlainObj(s){return s&&(s.constructor===Object||void 0===s.constructor)}function is(s,o){if(s===o||s!=s&&o!=o)return!0;if(!s||!o)return!1;if("function"==typeof s.valueOf&&"function"==typeof o.valueOf){if((s=s.valueOf())===(o=o.valueOf())||s!=s&&o!=o)return!0;if(!s||!o)return!1}return!("function"!=typeof s.equals||"function"!=typeof o.equals||!s.equals(o))}function deepEqual(s,o){if(s===o)return!0;if(!isIterable(o)||void 0!==s.size&&void 0!==o.size&&s.size!==o.size||void 0!==s.__hash&&void 0!==o.__hash&&s.__hash!==o.__hash||isKeyed(s)!==isKeyed(o)||isIndexed(s)!==isIndexed(o)||isOrdered(s)!==isOrdered(o))return!1;if(0===s.size&&0===o.size)return!0;var i=!isAssociative(s);if(isOrdered(s)){var u=s.entries();return o.every((function(s,o){var _=u.next().value;return _&&is(_[1],s)&&(i||is(_[0],o))}))&&u.next().done}var _=!1;if(void 0===s.size)if(void 0===o.size)"function"==typeof s.cacheResult&&s.cacheResult();else{_=!0;var w=s;s=o,o=w}var x=!0,C=o.__iterate((function(o,u){if(i?!s.has(o):_?!is(o,s.get(u,L)):!is(s.get(u,L),o))return x=!1,!1}));return x&&s.size===C}function Repeat(s,o){if(!(this instanceof Repeat))return new Repeat(s,o);if(this._value=s,this.size=void 0===o?1/0:Math.max(0,o),0===this.size){if(ae)return ae;ae=this}}function invariant(s,o){if(!s)throw new Error(o)}function Range(s,o,i){if(!(this instanceof Range))return new Range(s,o,i);if(invariant(0!==i,"Cannot step a Range by 0"),s=s||0,void 0===o&&(o=1/0),i=void 0===i?1:Math.abs(i),ou?iteratorDone():iteratorValue(s,_,i[o?u-_++:_++])}))},createClass(ObjectSeq,KeyedSeq),ObjectSeq.prototype.get=function(s,o){return void 0===o||this.has(s)?this._object[s]:o},ObjectSeq.prototype.has=function(s){return this._object.hasOwnProperty(s)},ObjectSeq.prototype.__iterate=function(s,o){for(var i=this._object,u=this._keys,_=u.length-1,w=0;w<=_;w++){var x=u[o?_-w:w];if(!1===s(i[x],x,this))return w+1}return w},ObjectSeq.prototype.__iterator=function(s,o){var i=this._object,u=this._keys,_=u.length-1,w=0;return new Iterator((function(){var x=u[o?_-w:w];return w++>_?iteratorDone():iteratorValue(s,x,i[x])}))},ObjectSeq.prototype[_]=!0,createClass(IterableSeq,IndexedSeq),IterableSeq.prototype.__iterateUncached=function(s,o){if(o)return this.cacheResult().__iterate(s,o);var i=getIterator(this._iterable),u=0;if(isIterator(i))for(var _;!(_=i.next()).done&&!1!==s(_.value,u++,this););return u},IterableSeq.prototype.__iteratorUncached=function(s,o){if(o)return this.cacheResult().__iterator(s,o);var i=getIterator(this._iterable);if(!isIterator(i))return new Iterator(iteratorDone);var u=0;return new Iterator((function(){var o=i.next();return o.done?o:iteratorValue(s,u++,o.value)}))},createClass(IteratorSeq,IndexedSeq),IteratorSeq.prototype.__iterateUncached=function(s,o){if(o)return this.cacheResult().__iterate(s,o);for(var i,u=this._iterator,_=this._iteratorCache,w=0;w<_.length;)if(!1===s(_[w],w++,this))return w;for(;!(i=u.next()).done;){var x=i.value;if(_[w]=x,!1===s(x,w++,this))break}return w},IteratorSeq.prototype.__iteratorUncached=function(s,o){if(o)return this.cacheResult().__iterator(s,o);var i=this._iterator,u=this._iteratorCache,_=0;return new Iterator((function(){if(_>=u.length){var o=i.next();if(o.done)return o;u[_]=o.value}return iteratorValue(s,_,u[_++])}))},createClass(Repeat,IndexedSeq),Repeat.prototype.toString=function(){return 0===this.size?"Repeat []":"Repeat [ "+this._value+" "+this.size+" times ]"},Repeat.prototype.get=function(s,o){return this.has(s)?this._value:o},Repeat.prototype.includes=function(s){return is(this._value,s)},Repeat.prototype.slice=function(s,o){var i=this.size;return wholeSlice(s,o,i)?this:new Repeat(this._value,resolveEnd(o,i)-resolveBegin(s,i))},Repeat.prototype.reverse=function(){return this},Repeat.prototype.indexOf=function(s){return is(this._value,s)?0:-1},Repeat.prototype.lastIndexOf=function(s){return is(this._value,s)?this.size:-1},Repeat.prototype.__iterate=function(s,o){for(var i=0;i=0&&o=0&&ii?iteratorDone():iteratorValue(s,w++,x)}))},Range.prototype.equals=function(s){return s instanceof Range?this._start===s._start&&this._end===s._end&&this._step===s._step:deepEqual(this,s)},createClass(Collection,Iterable),createClass(KeyedCollection,Collection),createClass(IndexedCollection,Collection),createClass(SetCollection,Collection),Collection.Keyed=KeyedCollection,Collection.Indexed=IndexedCollection,Collection.Set=SetCollection;var pe="function"==typeof Math.imul&&-2===Math.imul(4294967295,2)?Math.imul:function imul(s,o){var i=65535&(s|=0),u=65535&(o|=0);return i*u+((s>>>16)*u+i*(o>>>16)<<16>>>0)|0};function smi(s){return s>>>1&1073741824|3221225471&s}function hash(s){if(!1===s||null==s)return 0;if("function"==typeof s.valueOf&&(!1===(s=s.valueOf())||null==s))return 0;if(!0===s)return 1;var o=typeof s;if("number"===o){if(s!=s||s===1/0)return 0;var i=0|s;for(i!==s&&(i^=4294967295*s);s>4294967295;)i^=s/=4294967295;return smi(i)}if("string"===o)return s.length>Se?cachedHashString(s):hashString(s);if("function"==typeof s.hashCode)return s.hashCode();if("object"===o)return hashJSObj(s);if("function"==typeof s.toString)return hashString(s.toString());throw new Error("Value type "+o+" cannot be hashed.")}function cachedHashString(s){var o=Te[s];return void 0===o&&(o=hashString(s),Pe===xe&&(Pe=0,Te={}),Pe++,Te[s]=o),o}function hashString(s){for(var o=0,i=0;i0)switch(s.nodeType){case 1:return s.uniqueID;case 9:return s.documentElement&&s.documentElement.uniqueID}}var ye,be="function"==typeof WeakMap;be&&(ye=new WeakMap);var _e=0,we="__immutablehash__";"function"==typeof Symbol&&(we=Symbol(we));var Se=16,xe=255,Pe=0,Te={};function assertNotInfinite(s){invariant(s!==1/0,"Cannot perform this action with an infinite size.")}function Map(s){return null==s?emptyMap():isMap(s)&&!isOrdered(s)?s:emptyMap().withMutations((function(o){var i=KeyedIterable(s);assertNotInfinite(i.size),i.forEach((function(s,i){return o.set(i,s)}))}))}function isMap(s){return!(!s||!s[qe])}createClass(Map,KeyedCollection),Map.of=function(){var o=s.call(arguments,0);return emptyMap().withMutations((function(s){for(var i=0;i=o.length)throw new Error("Missing value for key: "+o[i]);s.set(o[i],o[i+1])}}))},Map.prototype.toString=function(){return this.__toString("Map {","}")},Map.prototype.get=function(s,o){return this._root?this._root.get(0,void 0,s,o):o},Map.prototype.set=function(s,o){return updateMap(this,s,o)},Map.prototype.setIn=function(s,o){return this.updateIn(s,L,(function(){return o}))},Map.prototype.remove=function(s){return updateMap(this,s,L)},Map.prototype.deleteIn=function(s){return this.updateIn(s,(function(){return L}))},Map.prototype.update=function(s,o,i){return 1===arguments.length?s(this):this.updateIn([s],o,i)},Map.prototype.updateIn=function(s,o,i){i||(i=o,o=void 0);var u=updateInDeepMap(this,forceIterator(s),o,i);return u===L?void 0:u},Map.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._root=null,this.__hash=void 0,this.__altered=!0,this):emptyMap()},Map.prototype.merge=function(){return mergeIntoMapWith(this,void 0,arguments)},Map.prototype.mergeWith=function(o){return mergeIntoMapWith(this,o,s.call(arguments,1))},Map.prototype.mergeIn=function(o){var i=s.call(arguments,1);return this.updateIn(o,emptyMap(),(function(s){return"function"==typeof s.merge?s.merge.apply(s,i):i[i.length-1]}))},Map.prototype.mergeDeep=function(){return mergeIntoMapWith(this,deepMerger,arguments)},Map.prototype.mergeDeepWith=function(o){var i=s.call(arguments,1);return mergeIntoMapWith(this,deepMergerWith(o),i)},Map.prototype.mergeDeepIn=function(o){var i=s.call(arguments,1);return this.updateIn(o,emptyMap(),(function(s){return"function"==typeof s.mergeDeep?s.mergeDeep.apply(s,i):i[i.length-1]}))},Map.prototype.sort=function(s){return OrderedMap(sortFactory(this,s))},Map.prototype.sortBy=function(s,o){return OrderedMap(sortFactory(this,o,s))},Map.prototype.withMutations=function(s){var o=this.asMutable();return s(o),o.wasAltered()?o.__ensureOwner(this.__ownerID):this},Map.prototype.asMutable=function(){return this.__ownerID?this:this.__ensureOwner(new OwnerID)},Map.prototype.asImmutable=function(){return this.__ensureOwner()},Map.prototype.wasAltered=function(){return this.__altered},Map.prototype.__iterator=function(s,o){return new MapIterator(this,s,o)},Map.prototype.__iterate=function(s,o){var i=this,u=0;return this._root&&this._root.iterate((function(o){return u++,s(o[1],o[0],i)}),o),u},Map.prototype.__ensureOwner=function(s){return s===this.__ownerID?this:s?makeMap(this.size,this._root,s,this.__hash):(this.__ownerID=s,this.__altered=!1,this)},Map.isMap=isMap;var Re,qe="@@__IMMUTABLE_MAP__@@",$e=Map.prototype;function ArrayMapNode(s,o){this.ownerID=s,this.entries=o}function BitmapIndexedNode(s,o,i){this.ownerID=s,this.bitmap=o,this.nodes=i}function HashArrayMapNode(s,o,i){this.ownerID=s,this.count=o,this.nodes=i}function HashCollisionNode(s,o,i){this.ownerID=s,this.keyHash=o,this.entries=i}function ValueNode(s,o,i){this.ownerID=s,this.keyHash=o,this.entry=i}function MapIterator(s,o,i){this._type=o,this._reverse=i,this._stack=s._root&&mapIteratorFrame(s._root)}function mapIteratorValue(s,o){return iteratorValue(s,o[0],o[1])}function mapIteratorFrame(s,o){return{node:s,index:0,__prev:o}}function makeMap(s,o,i,u){var _=Object.create($e);return _.size=s,_._root=o,_.__ownerID=i,_.__hash=u,_.__altered=!1,_}function emptyMap(){return Re||(Re=makeMap(0))}function updateMap(s,o,i){var u,_;if(s._root){var w=MakeRef(B),x=MakeRef($);if(u=updateNode(s._root,s.__ownerID,0,void 0,o,i,w,x),!x.value)return s;_=s.size+(w.value?i===L?-1:1:0)}else{if(i===L)return s;_=1,u=new ArrayMapNode(s.__ownerID,[[o,i]])}return s.__ownerID?(s.size=_,s._root=u,s.__hash=void 0,s.__altered=!0,s):u?makeMap(_,u):emptyMap()}function updateNode(s,o,i,u,_,w,x,C){return s?s.update(o,i,u,_,w,x,C):w===L?s:(SetRef(C),SetRef(x),new ValueNode(o,u,[_,w]))}function isLeafNode(s){return s.constructor===ValueNode||s.constructor===HashCollisionNode}function mergeIntoNode(s,o,i,u,_){if(s.keyHash===u)return new HashCollisionNode(o,u,[s.entry,_]);var w,C=(0===i?s.keyHash:s.keyHash>>>i)&j,L=(0===i?u:u>>>i)&j;return new BitmapIndexedNode(o,1<>>=1)x[j]=1&i?o[w++]:void 0;return x[u]=_,new HashArrayMapNode(s,w+1,x)}function mergeIntoMapWith(s,o,i){for(var u=[],_=0;_>1&1431655765))+(s>>2&858993459))+(s>>4)&252645135,s+=s>>8,127&(s+=s>>16)}function setIn(s,o,i,u){var _=u?s:arrCopy(s);return _[o]=i,_}function spliceIn(s,o,i,u){var _=s.length+1;if(u&&o+1===_)return s[o]=i,s;for(var w=new Array(_),x=0,C=0;C<_;C++)C===o?(w[C]=i,x=-1):w[C]=s[C+x];return w}function spliceOut(s,o,i){var u=s.length-1;if(i&&o===u)return s.pop(),s;for(var _=new Array(u),w=0,x=0;x=ze)return createNodes(s,j,u,_);var U=s&&s===this.ownerID,z=U?j:arrCopy(j);return V?C?B===$-1?z.pop():z[B]=z.pop():z[B]=[u,_]:z.push([u,_]),U?(this.entries=z,this):new ArrayMapNode(s,z)}},BitmapIndexedNode.prototype.get=function(s,o,i,u){void 0===o&&(o=hash(i));var _=1<<((0===s?o:o>>>s)&j),w=this.bitmap;return w&_?this.nodes[popCount(w&_-1)].get(s+x,o,i,u):u},BitmapIndexedNode.prototype.update=function(s,o,i,u,_,w,C){void 0===i&&(i=hash(u));var B=(0===o?i:i>>>o)&j,$=1<=We)return expandNodes(s,Y,V,B,ee);if(U&&!ee&&2===Y.length&&isLeafNode(Y[1^z]))return Y[1^z];if(U&&ee&&1===Y.length&&isLeafNode(ee))return ee;var ie=s&&s===this.ownerID,ae=U?ee?V:V^$:V|$,le=U?ee?setIn(Y,z,ee,ie):spliceOut(Y,z,ie):spliceIn(Y,z,ee,ie);return ie?(this.bitmap=ae,this.nodes=le,this):new BitmapIndexedNode(s,ae,le)},HashArrayMapNode.prototype.get=function(s,o,i,u){void 0===o&&(o=hash(i));var _=(0===s?o:o>>>s)&j,w=this.nodes[_];return w?w.get(s+x,o,i,u):u},HashArrayMapNode.prototype.update=function(s,o,i,u,_,w,C){void 0===i&&(i=hash(u));var B=(0===o?i:i>>>o)&j,$=_===L,V=this.nodes,U=V[B];if($&&!U)return this;var z=updateNode(U,s,o+x,i,u,_,w,C);if(z===U)return this;var Y=this.count;if(U){if(!z&&--Y0&&u=0&&s>>o&j;if(u>=this.array.length)return new VNode([],s);var _,w=0===u;if(o>0){var C=this.array[u];if((_=C&&C.removeBefore(s,o-x,i))===C&&w)return this}if(w&&!_)return this;var L=editableVNode(this,s);if(!w)for(var B=0;B>>o&j;if(_>=this.array.length)return this;if(o>0){var w=this.array[_];if((u=w&&w.removeAfter(s,o-x,i))===w&&_===this.array.length-1)return this}var C=editableVNode(this,s);return C.array.splice(_+1),u&&(C.array[_]=u),C};var Qe,et,tt={};function iterateList(s,o){var i=s._origin,u=s._capacity,_=getTailOffset(u),w=s._tail;return iterateNodeOrLeaf(s._root,s._level,0);function iterateNodeOrLeaf(s,o,i){return 0===o?iterateLeaf(s,i):iterateNode(s,o,i)}function iterateLeaf(s,x){var j=x===_?w&&w.array:s&&s.array,L=x>i?0:i-x,B=u-x;return B>C&&(B=C),function(){if(L===B)return tt;var s=o?--B:L++;return j&&j[s]}}function iterateNode(s,_,w){var j,L=s&&s.array,B=w>i?0:i-w>>_,$=1+(u-w>>_);return $>C&&($=C),function(){for(;;){if(j){var s=j();if(s!==tt)return s;j=null}if(B===$)return tt;var i=o?--$:B++;j=iterateNodeOrLeaf(L&&L[i],_-x,w+(i<<_))}}}}function makeList(s,o,i,u,_,w,x){var C=Object.create(Xe);return C.size=o-s,C._origin=s,C._capacity=o,C._level=i,C._root=u,C._tail=_,C.__ownerID=w,C.__hash=x,C.__altered=!1,C}function emptyList(){return Qe||(Qe=makeList(0,0,x))}function updateList(s,o,i){if((o=wrapIndex(s,o))!=o)return s;if(o>=s.size||o<0)return s.withMutations((function(s){o<0?setListBounds(s,o).set(0,i):setListBounds(s,0,o+1).set(o,i)}));o+=s._origin;var u=s._tail,_=s._root,w=MakeRef($);return o>=getTailOffset(s._capacity)?u=updateVNode(u,s.__ownerID,0,o,i,w):_=updateVNode(_,s.__ownerID,s._level,o,i,w),w.value?s.__ownerID?(s._root=_,s._tail=u,s.__hash=void 0,s.__altered=!0,s):makeList(s._origin,s._capacity,s._level,_,u):s}function updateVNode(s,o,i,u,_,w){var C,L=u>>>i&j,B=s&&L0){var $=s&&s.array[L],V=updateVNode($,o,i-x,u,_,w);return V===$?s:((C=editableVNode(s,o)).array[L]=V,C)}return B&&s.array[L]===_?s:(SetRef(w),C=editableVNode(s,o),void 0===_&&L===C.array.length-1?C.array.pop():C.array[L]=_,C)}function editableVNode(s,o){return o&&s&&o===s.ownerID?s:new VNode(s?s.array.slice():[],o)}function listNodeFor(s,o){if(o>=getTailOffset(s._capacity))return s._tail;if(o<1<0;)i=i.array[o>>>u&j],u-=x;return i}}function setListBounds(s,o,i){void 0!==o&&(o|=0),void 0!==i&&(i|=0);var u=s.__ownerID||new OwnerID,_=s._origin,w=s._capacity,C=_+o,L=void 0===i?w:i<0?w+i:_+i;if(C===_&&L===w)return s;if(C>=L)return s.clear();for(var B=s._level,$=s._root,V=0;C+V<0;)$=new VNode($&&$.array.length?[void 0,$]:[],u),V+=1<<(B+=x);V&&(C+=V,_+=V,L+=V,w+=V);for(var U=getTailOffset(w),z=getTailOffset(L);z>=1<U?new VNode([],u):Y;if(Y&&z>U&&Cx;ie-=x){var ae=U>>>ie&j;ee=ee.array[ae]=editableVNode(ee.array[ae],u)}ee.array[U>>>x&j]=Y}if(L=z)C-=z,L-=z,B=x,$=null,Z=Z&&Z.removeBefore(u,0,C);else if(C>_||z>>B&j;if(le!==z>>>B&j)break;le&&(V+=(1<_&&($=$.removeBefore(u,B,C-V)),$&&z_&&(_=C.size),isIterable(x)||(C=C.map((function(s){return fromJS(s)}))),u.push(C)}return _>s.size&&(s=s.setSize(_)),mergeIntoCollectionWith(s,o,u)}function getTailOffset(s){return s>>x<=C&&x.size>=2*w.size?(u=(_=x.filter((function(s,o){return void 0!==s&&j!==o}))).toKeyedSeq().map((function(s){return s[0]})).flip().toMap(),s.__ownerID&&(u.__ownerID=_.__ownerID=s.__ownerID)):(u=w.remove(o),_=j===x.size-1?x.pop():x.set(j,void 0))}else if(B){if(i===x.get(j)[1])return s;u=w,_=x.set(j,[o,i])}else u=w.set(o,x.size),_=x.set(x.size,[o,i]);return s.__ownerID?(s.size=u.size,s._map=u,s._list=_,s.__hash=void 0,s):makeOrderedMap(u,_)}function ToKeyedSequence(s,o){this._iter=s,this._useKeys=o,this.size=s.size}function ToIndexedSequence(s){this._iter=s,this.size=s.size}function ToSetSequence(s){this._iter=s,this.size=s.size}function FromEntriesSequence(s){this._iter=s,this.size=s.size}function flipFactory(s){var o=makeSequence(s);return o._iter=s,o.size=s.size,o.flip=function(){return s},o.reverse=function(){var o=s.reverse.apply(this);return o.flip=function(){return s.reverse()},o},o.has=function(o){return s.includes(o)},o.includes=function(o){return s.has(o)},o.cacheResult=cacheResultThrough,o.__iterateUncached=function(o,i){var u=this;return s.__iterate((function(s,i){return!1!==o(i,s,u)}),i)},o.__iteratorUncached=function(o,i){if(o===z){var u=s.__iterator(o,i);return new Iterator((function(){var s=u.next();if(!s.done){var o=s.value[0];s.value[0]=s.value[1],s.value[1]=o}return s}))}return s.__iterator(o===U?V:U,i)},o}function mapFactory(s,o,i){var u=makeSequence(s);return u.size=s.size,u.has=function(o){return s.has(o)},u.get=function(u,_){var w=s.get(u,L);return w===L?_:o.call(i,w,u,s)},u.__iterateUncached=function(u,_){var w=this;return s.__iterate((function(s,_,x){return!1!==u(o.call(i,s,_,x),_,w)}),_)},u.__iteratorUncached=function(u,_){var w=s.__iterator(z,_);return new Iterator((function(){var _=w.next();if(_.done)return _;var x=_.value,C=x[0];return iteratorValue(u,C,o.call(i,x[1],C,s),_)}))},u}function reverseFactory(s,o){var i=makeSequence(s);return i._iter=s,i.size=s.size,i.reverse=function(){return s},s.flip&&(i.flip=function(){var o=flipFactory(s);return o.reverse=function(){return s.flip()},o}),i.get=function(i,u){return s.get(o?i:-1-i,u)},i.has=function(i){return s.has(o?i:-1-i)},i.includes=function(o){return s.includes(o)},i.cacheResult=cacheResultThrough,i.__iterate=function(o,i){var u=this;return s.__iterate((function(s,i){return o(s,i,u)}),!i)},i.__iterator=function(o,i){return s.__iterator(o,!i)},i}function filterFactory(s,o,i,u){var _=makeSequence(s);return u&&(_.has=function(u){var _=s.get(u,L);return _!==L&&!!o.call(i,_,u,s)},_.get=function(u,_){var w=s.get(u,L);return w!==L&&o.call(i,w,u,s)?w:_}),_.__iterateUncached=function(_,w){var x=this,C=0;return s.__iterate((function(s,w,j){if(o.call(i,s,w,j))return C++,_(s,u?w:C-1,x)}),w),C},_.__iteratorUncached=function(_,w){var x=s.__iterator(z,w),C=0;return new Iterator((function(){for(;;){var w=x.next();if(w.done)return w;var j=w.value,L=j[0],B=j[1];if(o.call(i,B,L,s))return iteratorValue(_,u?L:C++,B,w)}}))},_}function countByFactory(s,o,i){var u=Map().asMutable();return s.__iterate((function(_,w){u.update(o.call(i,_,w,s),0,(function(s){return s+1}))})),u.asImmutable()}function groupByFactory(s,o,i){var u=isKeyed(s),_=(isOrdered(s)?OrderedMap():Map()).asMutable();s.__iterate((function(w,x){_.update(o.call(i,w,x,s),(function(s){return(s=s||[]).push(u?[x,w]:w),s}))}));var w=iterableClass(s);return _.map((function(o){return reify(s,w(o))}))}function sliceFactory(s,o,i,u){var _=s.size;if(void 0!==o&&(o|=0),void 0!==i&&(i===1/0?i=_:i|=0),wholeSlice(o,i,_))return s;var w=resolveBegin(o,_),x=resolveEnd(i,_);if(w!=w||x!=x)return sliceFactory(s.toSeq().cacheResult(),o,i,u);var C,j=x-w;j==j&&(C=j<0?0:j);var L=makeSequence(s);return L.size=0===C?C:s.size&&C||void 0,!u&&isSeq(s)&&C>=0&&(L.get=function(o,i){return(o=wrapIndex(this,o))>=0&&oC)return iteratorDone();var s=_.next();return u||o===U?s:iteratorValue(o,j-1,o===V?void 0:s.value[1],s)}))},L}function takeWhileFactory(s,o,i){var u=makeSequence(s);return u.__iterateUncached=function(u,_){var w=this;if(_)return this.cacheResult().__iterate(u,_);var x=0;return s.__iterate((function(s,_,C){return o.call(i,s,_,C)&&++x&&u(s,_,w)})),x},u.__iteratorUncached=function(u,_){var w=this;if(_)return this.cacheResult().__iterator(u,_);var x=s.__iterator(z,_),C=!0;return new Iterator((function(){if(!C)return iteratorDone();var s=x.next();if(s.done)return s;var _=s.value,j=_[0],L=_[1];return o.call(i,L,j,w)?u===z?s:iteratorValue(u,j,L,s):(C=!1,iteratorDone())}))},u}function skipWhileFactory(s,o,i,u){var _=makeSequence(s);return _.__iterateUncached=function(_,w){var x=this;if(w)return this.cacheResult().__iterate(_,w);var C=!0,j=0;return s.__iterate((function(s,w,L){if(!C||!(C=o.call(i,s,w,L)))return j++,_(s,u?w:j-1,x)})),j},_.__iteratorUncached=function(_,w){var x=this;if(w)return this.cacheResult().__iterator(_,w);var C=s.__iterator(z,w),j=!0,L=0;return new Iterator((function(){var s,w,B;do{if((s=C.next()).done)return u||_===U?s:iteratorValue(_,L++,_===V?void 0:s.value[1],s);var $=s.value;w=$[0],B=$[1],j&&(j=o.call(i,B,w,x))}while(j);return _===z?s:iteratorValue(_,w,B,s)}))},_}function concatFactory(s,o){var i=isKeyed(s),u=[s].concat(o).map((function(s){return isIterable(s)?i&&(s=KeyedIterable(s)):s=i?keyedSeqFromValue(s):indexedSeqFromValue(Array.isArray(s)?s:[s]),s})).filter((function(s){return 0!==s.size}));if(0===u.length)return s;if(1===u.length){var _=u[0];if(_===s||i&&isKeyed(_)||isIndexed(s)&&isIndexed(_))return _}var w=new ArraySeq(u);return i?w=w.toKeyedSeq():isIndexed(s)||(w=w.toSetSeq()),(w=w.flatten(!0)).size=u.reduce((function(s,o){if(void 0!==s){var i=o.size;if(void 0!==i)return s+i}}),0),w}function flattenFactory(s,o,i){var u=makeSequence(s);return u.__iterateUncached=function(u,_){var w=0,x=!1;function flatDeep(s,C){var j=this;s.__iterate((function(s,_){return(!o||C0}function zipWithFactory(s,o,i){var u=makeSequence(s);return u.size=new ArraySeq(i).map((function(s){return s.size})).min(),u.__iterate=function(s,o){for(var i,u=this.__iterator(U,o),_=0;!(i=u.next()).done&&!1!==s(i.value,_++,this););return _},u.__iteratorUncached=function(s,u){var _=i.map((function(s){return s=Iterable(s),getIterator(u?s.reverse():s)})),w=0,x=!1;return new Iterator((function(){var i;return x||(i=_.map((function(s){return s.next()})),x=i.some((function(s){return s.done}))),x?iteratorDone():iteratorValue(s,w++,o.apply(null,i.map((function(s){return s.value}))))}))},u}function reify(s,o){return isSeq(s)?o:s.constructor(o)}function validateEntry(s){if(s!==Object(s))throw new TypeError("Expected [K, V] tuple: "+s)}function resolveSize(s){return assertNotInfinite(s.size),ensureSize(s)}function iterableClass(s){return isKeyed(s)?KeyedIterable:isIndexed(s)?IndexedIterable:SetIterable}function makeSequence(s){return Object.create((isKeyed(s)?KeyedSeq:isIndexed(s)?IndexedSeq:SetSeq).prototype)}function cacheResultThrough(){return this._iter.cacheResult?(this._iter.cacheResult(),this.size=this._iter.size,this):Seq.prototype.cacheResult.call(this)}function defaultComparator(s,o){return s>o?1:s=0;i--)o={value:arguments[i],next:o};return this.__ownerID?(this.size=s,this._head=o,this.__hash=void 0,this.__altered=!0,this):makeStack(s,o)},Stack.prototype.pushAll=function(s){if(0===(s=IndexedIterable(s)).size)return this;assertNotInfinite(s.size);var o=this.size,i=this._head;return s.reverse().forEach((function(s){o++,i={value:s,next:i}})),this.__ownerID?(this.size=o,this._head=i,this.__hash=void 0,this.__altered=!0,this):makeStack(o,i)},Stack.prototype.pop=function(){return this.slice(1)},Stack.prototype.unshift=function(){return this.push.apply(this,arguments)},Stack.prototype.unshiftAll=function(s){return this.pushAll(s)},Stack.prototype.shift=function(){return this.pop.apply(this,arguments)},Stack.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._head=void 0,this.__hash=void 0,this.__altered=!0,this):emptyStack()},Stack.prototype.slice=function(s,o){if(wholeSlice(s,o,this.size))return this;var i=resolveBegin(s,this.size);if(resolveEnd(o,this.size)!==this.size)return IndexedCollection.prototype.slice.call(this,s,o);for(var u=this.size-i,_=this._head;i--;)_=_.next;return this.__ownerID?(this.size=u,this._head=_,this.__hash=void 0,this.__altered=!0,this):makeStack(u,_)},Stack.prototype.__ensureOwner=function(s){return s===this.__ownerID?this:s?makeStack(this.size,this._head,s,this.__hash):(this.__ownerID=s,this.__altered=!1,this)},Stack.prototype.__iterate=function(s,o){if(o)return this.reverse().__iterate(s);for(var i=0,u=this._head;u&&!1!==s(u.value,i++,this);)u=u.next;return i},Stack.prototype.__iterator=function(s,o){if(o)return this.reverse().__iterator(s);var i=0,u=this._head;return new Iterator((function(){if(u){var o=u.value;return u=u.next,iteratorValue(s,i++,o)}return iteratorDone()}))},Stack.isStack=isStack;var lt,ct="@@__IMMUTABLE_STACK__@@",ut=Stack.prototype;function makeStack(s,o,i,u){var _=Object.create(ut);return _.size=s,_._head=o,_.__ownerID=i,_.__hash=u,_.__altered=!1,_}function emptyStack(){return lt||(lt=makeStack(0))}function mixin(s,o){var keyCopier=function(i){s.prototype[i]=o[i]};return Object.keys(o).forEach(keyCopier),Object.getOwnPropertySymbols&&Object.getOwnPropertySymbols(o).forEach(keyCopier),s}ut[ct]=!0,ut.withMutations=$e.withMutations,ut.asMutable=$e.asMutable,ut.asImmutable=$e.asImmutable,ut.wasAltered=$e.wasAltered,Iterable.Iterator=Iterator,mixin(Iterable,{toArray:function(){assertNotInfinite(this.size);var s=new Array(this.size||0);return this.valueSeq().__iterate((function(o,i){s[i]=o})),s},toIndexedSeq:function(){return new ToIndexedSequence(this)},toJS:function(){return this.toSeq().map((function(s){return s&&"function"==typeof s.toJS?s.toJS():s})).__toJS()},toJSON:function(){return this.toSeq().map((function(s){return s&&"function"==typeof s.toJSON?s.toJSON():s})).__toJS()},toKeyedSeq:function(){return new ToKeyedSequence(this,!0)},toMap:function(){return Map(this.toKeyedSeq())},toObject:function(){assertNotInfinite(this.size);var s={};return this.__iterate((function(o,i){s[i]=o})),s},toOrderedMap:function(){return OrderedMap(this.toKeyedSeq())},toOrderedSet:function(){return OrderedSet(isKeyed(this)?this.valueSeq():this)},toSet:function(){return Set(isKeyed(this)?this.valueSeq():this)},toSetSeq:function(){return new ToSetSequence(this)},toSeq:function(){return isIndexed(this)?this.toIndexedSeq():isKeyed(this)?this.toKeyedSeq():this.toSetSeq()},toStack:function(){return Stack(isKeyed(this)?this.valueSeq():this)},toList:function(){return List(isKeyed(this)?this.valueSeq():this)},toString:function(){return"[Iterable]"},__toString:function(s,o){return 0===this.size?s+o:s+" "+this.toSeq().map(this.__toStringMapper).join(", ")+" "+o},concat:function(){return reify(this,concatFactory(this,s.call(arguments,0)))},includes:function(s){return this.some((function(o){return is(o,s)}))},entries:function(){return this.__iterator(z)},every:function(s,o){assertNotInfinite(this.size);var i=!0;return this.__iterate((function(u,_,w){if(!s.call(o,u,_,w))return i=!1,!1})),i},filter:function(s,o){return reify(this,filterFactory(this,s,o,!0))},find:function(s,o,i){var u=this.findEntry(s,o);return u?u[1]:i},forEach:function(s,o){return assertNotInfinite(this.size),this.__iterate(o?s.bind(o):s)},join:function(s){assertNotInfinite(this.size),s=void 0!==s?""+s:",";var o="",i=!0;return this.__iterate((function(u){i?i=!1:o+=s,o+=null!=u?u.toString():""})),o},keys:function(){return this.__iterator(V)},map:function(s,o){return reify(this,mapFactory(this,s,o))},reduce:function(s,o,i){var u,_;return assertNotInfinite(this.size),arguments.length<2?_=!0:u=o,this.__iterate((function(o,w,x){_?(_=!1,u=o):u=s.call(i,u,o,w,x)})),u},reduceRight:function(s,o,i){var u=this.toKeyedSeq().reverse();return u.reduce.apply(u,arguments)},reverse:function(){return reify(this,reverseFactory(this,!0))},slice:function(s,o){return reify(this,sliceFactory(this,s,o,!0))},some:function(s,o){return!this.every(not(s),o)},sort:function(s){return reify(this,sortFactory(this,s))},values:function(){return this.__iterator(U)},butLast:function(){return this.slice(0,-1)},isEmpty:function(){return void 0!==this.size?0===this.size:!this.some((function(){return!0}))},count:function(s,o){return ensureSize(s?this.toSeq().filter(s,o):this)},countBy:function(s,o){return countByFactory(this,s,o)},equals:function(s){return deepEqual(this,s)},entrySeq:function(){var s=this;if(s._cache)return new ArraySeq(s._cache);var o=s.toSeq().map(entryMapper).toIndexedSeq();return o.fromEntrySeq=function(){return s.toSeq()},o},filterNot:function(s,o){return this.filter(not(s),o)},findEntry:function(s,o,i){var u=i;return this.__iterate((function(i,_,w){if(s.call(o,i,_,w))return u=[_,i],!1})),u},findKey:function(s,o){var i=this.findEntry(s,o);return i&&i[0]},findLast:function(s,o,i){return this.toKeyedSeq().reverse().find(s,o,i)},findLastEntry:function(s,o,i){return this.toKeyedSeq().reverse().findEntry(s,o,i)},findLastKey:function(s,o){return this.toKeyedSeq().reverse().findKey(s,o)},first:function(){return this.find(returnTrue)},flatMap:function(s,o){return reify(this,flatMapFactory(this,s,o))},flatten:function(s){return reify(this,flattenFactory(this,s,!0))},fromEntrySeq:function(){return new FromEntriesSequence(this)},get:function(s,o){return this.find((function(o,i){return is(i,s)}),void 0,o)},getIn:function(s,o){for(var i,u=this,_=forceIterator(s);!(i=_.next()).done;){var w=i.value;if((u=u&&u.get?u.get(w,L):L)===L)return o}return u},groupBy:function(s,o){return groupByFactory(this,s,o)},has:function(s){return this.get(s,L)!==L},hasIn:function(s){return this.getIn(s,L)!==L},isSubset:function(s){return s="function"==typeof s.includes?s:Iterable(s),this.every((function(o){return s.includes(o)}))},isSuperset:function(s){return(s="function"==typeof s.isSubset?s:Iterable(s)).isSubset(this)},keyOf:function(s){return this.findKey((function(o){return is(o,s)}))},keySeq:function(){return this.toSeq().map(keyMapper).toIndexedSeq()},last:function(){return this.toSeq().reverse().first()},lastKeyOf:function(s){return this.toKeyedSeq().reverse().keyOf(s)},max:function(s){return maxFactory(this,s)},maxBy:function(s,o){return maxFactory(this,o,s)},min:function(s){return maxFactory(this,s?neg(s):defaultNegComparator)},minBy:function(s,o){return maxFactory(this,o?neg(o):defaultNegComparator,s)},rest:function(){return this.slice(1)},skip:function(s){return this.slice(Math.max(0,s))},skipLast:function(s){return reify(this,this.toSeq().reverse().skip(s).reverse())},skipWhile:function(s,o){return reify(this,skipWhileFactory(this,s,o,!0))},skipUntil:function(s,o){return this.skipWhile(not(s),o)},sortBy:function(s,o){return reify(this,sortFactory(this,o,s))},take:function(s){return this.slice(0,Math.max(0,s))},takeLast:function(s){return reify(this,this.toSeq().reverse().take(s).reverse())},takeWhile:function(s,o){return reify(this,takeWhileFactory(this,s,o))},takeUntil:function(s,o){return this.takeWhile(not(s),o)},valueSeq:function(){return this.toIndexedSeq()},hashCode:function(){return this.__hash||(this.__hash=hashIterable(this))}});var pt=Iterable.prototype;pt[o]=!0,pt[ee]=pt.values,pt.__toJS=pt.toArray,pt.__toStringMapper=quoteString,pt.inspect=pt.toSource=function(){return this.toString()},pt.chain=pt.flatMap,pt.contains=pt.includes,mixin(KeyedIterable,{flip:function(){return reify(this,flipFactory(this))},mapEntries:function(s,o){var i=this,u=0;return reify(this,this.toSeq().map((function(_,w){return s.call(o,[w,_],u++,i)})).fromEntrySeq())},mapKeys:function(s,o){var i=this;return reify(this,this.toSeq().flip().map((function(u,_){return s.call(o,u,_,i)})).flip())}});var ht=KeyedIterable.prototype;function keyMapper(s,o){return o}function entryMapper(s,o){return[o,s]}function not(s){return function(){return!s.apply(this,arguments)}}function neg(s){return function(){return-s.apply(this,arguments)}}function quoteString(s){return"string"==typeof s?JSON.stringify(s):String(s)}function defaultZipper(){return arrCopy(arguments)}function defaultNegComparator(s,o){return so?-1:0}function hashIterable(s){if(s.size===1/0)return 0;var o=isOrdered(s),i=isKeyed(s),u=o?1:0;return murmurHashOfSize(s.__iterate(i?o?function(s,o){u=31*u+hashMerge(hash(s),hash(o))|0}:function(s,o){u=u+hashMerge(hash(s),hash(o))|0}:o?function(s){u=31*u+hash(s)|0}:function(s){u=u+hash(s)|0}),u)}function murmurHashOfSize(s,o){return o=pe(o,3432918353),o=pe(o<<15|o>>>-15,461845907),o=pe(o<<13|o>>>-13,5),o=pe((o=o+3864292196^s)^o>>>16,2246822507),o=smi((o=pe(o^o>>>13,3266489909))^o>>>16)}function hashMerge(s,o){return s^o+2654435769+(s<<6)+(s>>2)}return ht[i]=!0,ht[ee]=pt.entries,ht.__toJS=pt.toObject,ht.__toStringMapper=function(s,o){return JSON.stringify(o)+": "+quoteString(s)},mixin(IndexedIterable,{toKeyedSeq:function(){return new ToKeyedSequence(this,!1)},filter:function(s,o){return reify(this,filterFactory(this,s,o,!1))},findIndex:function(s,o){var i=this.findEntry(s,o);return i?i[0]:-1},indexOf:function(s){var o=this.keyOf(s);return void 0===o?-1:o},lastIndexOf:function(s){var o=this.lastKeyOf(s);return void 0===o?-1:o},reverse:function(){return reify(this,reverseFactory(this,!1))},slice:function(s,o){return reify(this,sliceFactory(this,s,o,!1))},splice:function(s,o){var i=arguments.length;if(o=Math.max(0|o,0),0===i||2===i&&!o)return this;s=resolveBegin(s,s<0?this.count():this.size);var u=this.slice(0,s);return reify(this,1===i?u:u.concat(arrCopy(arguments,2),this.slice(s+o)))},findLastIndex:function(s,o){var i=this.findLastEntry(s,o);return i?i[0]:-1},first:function(){return this.get(0)},flatten:function(s){return reify(this,flattenFactory(this,s,!1))},get:function(s,o){return(s=wrapIndex(this,s))<0||this.size===1/0||void 0!==this.size&&s>this.size?o:this.find((function(o,i){return i===s}),void 0,o)},has:function(s){return(s=wrapIndex(this,s))>=0&&(void 0!==this.size?this.size===1/0||s{"function"==typeof Object.create?s.exports=function inherits(s,o){o&&(s.super_=o,s.prototype=Object.create(o.prototype,{constructor:{value:s,enumerable:!1,writable:!0,configurable:!0}}))}:s.exports=function inherits(s,o){if(o){s.super_=o;var TempCtor=function(){};TempCtor.prototype=o.prototype,s.prototype=new TempCtor,s.prototype.constructor=s}}},5419:s=>{s.exports=function(s,o,i,u){var _=new Blob(void 0!==u?[u,s]:[s],{type:i||"application/octet-stream"});if(void 0!==window.navigator.msSaveBlob)window.navigator.msSaveBlob(_,o);else{var w=window.URL&&window.URL.createObjectURL?window.URL.createObjectURL(_):window.webkitURL.createObjectURL(_),x=document.createElement("a");x.style.display="none",x.href=w,x.setAttribute("download",o),void 0===x.download&&x.setAttribute("target","_blank"),document.body.appendChild(x),x.click(),setTimeout((function(){document.body.removeChild(x),window.URL.revokeObjectURL(w)}),200)}}},20181:(s,o,i)=>{var u=/^\s+|\s+$/g,_=/^[-+]0x[0-9a-f]+$/i,w=/^0b[01]+$/i,x=/^0o[0-7]+$/i,C=parseInt,j="object"==typeof i.g&&i.g&&i.g.Object===Object&&i.g,L="object"==typeof self&&self&&self.Object===Object&&self,B=j||L||Function("return this")(),$=Object.prototype.toString,V=Math.max,U=Math.min,now=function(){return B.Date.now()};function isObject(s){var o=typeof s;return!!s&&("object"==o||"function"==o)}function toNumber(s){if("number"==typeof s)return s;if(function isSymbol(s){return"symbol"==typeof s||function isObjectLike(s){return!!s&&"object"==typeof s}(s)&&"[object Symbol]"==$.call(s)}(s))return NaN;if(isObject(s)){var o="function"==typeof s.valueOf?s.valueOf():s;s=isObject(o)?o+"":o}if("string"!=typeof s)return 0===s?s:+s;s=s.replace(u,"");var i=w.test(s);return i||x.test(s)?C(s.slice(2),i?2:8):_.test(s)?NaN:+s}s.exports=function debounce(s,o,i){var u,_,w,x,C,j,L=0,B=!1,$=!1,z=!0;if("function"!=typeof s)throw new TypeError("Expected a function");function invokeFunc(o){var i=u,w=_;return u=_=void 0,L=o,x=s.apply(w,i)}function shouldInvoke(s){var i=s-j;return void 0===j||i>=o||i<0||$&&s-L>=w}function timerExpired(){var s=now();if(shouldInvoke(s))return trailingEdge(s);C=setTimeout(timerExpired,function remainingWait(s){var i=o-(s-j);return $?U(i,w-(s-L)):i}(s))}function trailingEdge(s){return C=void 0,z&&u?invokeFunc(s):(u=_=void 0,x)}function debounced(){var s=now(),i=shouldInvoke(s);if(u=arguments,_=this,j=s,i){if(void 0===C)return function leadingEdge(s){return L=s,C=setTimeout(timerExpired,o),B?invokeFunc(s):x}(j);if($)return C=setTimeout(timerExpired,o),invokeFunc(j)}return void 0===C&&(C=setTimeout(timerExpired,o)),x}return o=toNumber(o)||0,isObject(i)&&(B=!!i.leading,w=($="maxWait"in i)?V(toNumber(i.maxWait)||0,o):w,z="trailing"in i?!!i.trailing:z),debounced.cancel=function cancel(){void 0!==C&&clearTimeout(C),L=0,u=j=_=C=void 0},debounced.flush=function flush(){return void 0===C?x:trailingEdge(now())},debounced}},55580:(s,o,i)=>{var u=i(56110)(i(9325),"DataView");s.exports=u},21549:(s,o,i)=>{var u=i(22032),_=i(63862),w=i(66721),x=i(12749),C=i(35749);function Hash(s){var o=-1,i=null==s?0:s.length;for(this.clear();++o{var u=i(39344),_=i(94033);function LazyWrapper(s){this.__wrapped__=s,this.__actions__=[],this.__dir__=1,this.__filtered__=!1,this.__iteratees__=[],this.__takeCount__=4294967295,this.__views__=[]}LazyWrapper.prototype=u(_.prototype),LazyWrapper.prototype.constructor=LazyWrapper,s.exports=LazyWrapper},80079:(s,o,i)=>{var u=i(63702),_=i(70080),w=i(24739),x=i(48655),C=i(31175);function ListCache(s){var o=-1,i=null==s?0:s.length;for(this.clear();++o{var u=i(39344),_=i(94033);function LodashWrapper(s,o){this.__wrapped__=s,this.__actions__=[],this.__chain__=!!o,this.__index__=0,this.__values__=void 0}LodashWrapper.prototype=u(_.prototype),LodashWrapper.prototype.constructor=LodashWrapper,s.exports=LodashWrapper},68223:(s,o,i)=>{var u=i(56110)(i(9325),"Map");s.exports=u},53661:(s,o,i)=>{var u=i(63040),_=i(17670),w=i(90289),x=i(4509),C=i(72949);function MapCache(s){var o=-1,i=null==s?0:s.length;for(this.clear();++o{var u=i(56110)(i(9325),"Promise");s.exports=u},76545:(s,o,i)=>{var u=i(56110)(i(9325),"Set");s.exports=u},38859:(s,o,i)=>{var u=i(53661),_=i(31380),w=i(51459);function SetCache(s){var o=-1,i=null==s?0:s.length;for(this.__data__=new u;++o{var u=i(80079),_=i(51420),w=i(90938),x=i(63605),C=i(29817),j=i(80945);function Stack(s){var o=this.__data__=new u(s);this.size=o.size}Stack.prototype.clear=_,Stack.prototype.delete=w,Stack.prototype.get=x,Stack.prototype.has=C,Stack.prototype.set=j,s.exports=Stack},51873:(s,o,i)=>{var u=i(9325).Symbol;s.exports=u},37828:(s,o,i)=>{var u=i(9325).Uint8Array;s.exports=u},28303:(s,o,i)=>{var u=i(56110)(i(9325),"WeakMap");s.exports=u},91033:s=>{s.exports=function apply(s,o,i){switch(i.length){case 0:return s.call(o);case 1:return s.call(o,i[0]);case 2:return s.call(o,i[0],i[1]);case 3:return s.call(o,i[0],i[1],i[2])}return s.apply(o,i)}},83729:s=>{s.exports=function arrayEach(s,o){for(var i=-1,u=null==s?0:s.length;++i{s.exports=function arrayFilter(s,o){for(var i=-1,u=null==s?0:s.length,_=0,w=[];++i{var u=i(96131);s.exports=function arrayIncludes(s,o){return!!(null==s?0:s.length)&&u(s,o,0)>-1}},70695:(s,o,i)=>{var u=i(78096),_=i(72428),w=i(56449),x=i(3656),C=i(30361),j=i(37167),L=Object.prototype.hasOwnProperty;s.exports=function arrayLikeKeys(s,o){var i=w(s),B=!i&&_(s),$=!i&&!B&&x(s),V=!i&&!B&&!$&&j(s),U=i||B||$||V,z=U?u(s.length,String):[],Y=z.length;for(var Z in s)!o&&!L.call(s,Z)||U&&("length"==Z||$&&("offset"==Z||"parent"==Z)||V&&("buffer"==Z||"byteLength"==Z||"byteOffset"==Z)||C(Z,Y))||z.push(Z);return z}},34932:s=>{s.exports=function arrayMap(s,o){for(var i=-1,u=null==s?0:s.length,_=Array(u);++i{s.exports=function arrayPush(s,o){for(var i=-1,u=o.length,_=s.length;++i{s.exports=function arrayReduce(s,o,i,u){var _=-1,w=null==s?0:s.length;for(u&&w&&(i=s[++_]);++_{s.exports=function arraySome(s,o){for(var i=-1,u=null==s?0:s.length;++i{s.exports=function asciiToArray(s){return s.split("")}},1733:s=>{var o=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g;s.exports=function asciiWords(s){return s.match(o)||[]}},87805:(s,o,i)=>{var u=i(43360),_=i(75288);s.exports=function assignMergeValue(s,o,i){(void 0!==i&&!_(s[o],i)||void 0===i&&!(o in s))&&u(s,o,i)}},16547:(s,o,i)=>{var u=i(43360),_=i(75288),w=Object.prototype.hasOwnProperty;s.exports=function assignValue(s,o,i){var x=s[o];w.call(s,o)&&_(x,i)&&(void 0!==i||o in s)||u(s,o,i)}},26025:(s,o,i)=>{var u=i(75288);s.exports=function assocIndexOf(s,o){for(var i=s.length;i--;)if(u(s[i][0],o))return i;return-1}},74733:(s,o,i)=>{var u=i(21791),_=i(95950);s.exports=function baseAssign(s,o){return s&&u(o,_(o),s)}},43838:(s,o,i)=>{var u=i(21791),_=i(37241);s.exports=function baseAssignIn(s,o){return s&&u(o,_(o),s)}},43360:(s,o,i)=>{var u=i(93243);s.exports=function baseAssignValue(s,o,i){"__proto__"==o&&u?u(s,o,{configurable:!0,enumerable:!0,value:i,writable:!0}):s[o]=i}},9999:(s,o,i)=>{var u=i(37217),_=i(83729),w=i(16547),x=i(74733),C=i(43838),j=i(93290),L=i(23007),B=i(92271),$=i(48948),V=i(50002),U=i(83349),z=i(5861),Y=i(76189),Z=i(77199),ee=i(35529),ie=i(56449),ae=i(3656),le=i(87730),ce=i(23805),pe=i(38440),de=i(95950),fe=i(37241),ye="[object Arguments]",be="[object Function]",_e="[object Object]",we={};we[ye]=we["[object Array]"]=we["[object ArrayBuffer]"]=we["[object DataView]"]=we["[object Boolean]"]=we["[object Date]"]=we["[object Float32Array]"]=we["[object Float64Array]"]=we["[object Int8Array]"]=we["[object Int16Array]"]=we["[object Int32Array]"]=we["[object Map]"]=we["[object Number]"]=we[_e]=we["[object RegExp]"]=we["[object Set]"]=we["[object String]"]=we["[object Symbol]"]=we["[object Uint8Array]"]=we["[object Uint8ClampedArray]"]=we["[object Uint16Array]"]=we["[object Uint32Array]"]=!0,we["[object Error]"]=we[be]=we["[object WeakMap]"]=!1,s.exports=function baseClone(s,o,i,Se,xe,Pe){var Te,Re=1&o,qe=2&o,$e=4&o;if(i&&(Te=xe?i(s,Se,xe,Pe):i(s)),void 0!==Te)return Te;if(!ce(s))return s;var ze=ie(s);if(ze){if(Te=Y(s),!Re)return L(s,Te)}else{var We=z(s),He=We==be||"[object GeneratorFunction]"==We;if(ae(s))return j(s,Re);if(We==_e||We==ye||He&&!xe){if(Te=qe||He?{}:ee(s),!Re)return qe?$(s,C(Te,s)):B(s,x(Te,s))}else{if(!we[We])return xe?s:{};Te=Z(s,We,Re)}}Pe||(Pe=new u);var Ye=Pe.get(s);if(Ye)return Ye;Pe.set(s,Te),pe(s)?s.forEach((function(u){Te.add(baseClone(u,o,i,u,s,Pe))})):le(s)&&s.forEach((function(u,_){Te.set(_,baseClone(u,o,i,_,s,Pe))}));var Xe=ze?void 0:($e?qe?U:V:qe?fe:de)(s);return _(Xe||s,(function(u,_){Xe&&(u=s[_=u]),w(Te,_,baseClone(u,o,i,_,s,Pe))})),Te}},39344:(s,o,i)=>{var u=i(23805),_=Object.create,w=function(){function object(){}return function(s){if(!u(s))return{};if(_)return _(s);object.prototype=s;var o=new object;return object.prototype=void 0,o}}();s.exports=w},80909:(s,o,i)=>{var u=i(30641),_=i(38329)(u);s.exports=_},2523:s=>{s.exports=function baseFindIndex(s,o,i,u){for(var _=s.length,w=i+(u?1:-1);u?w--:++w<_;)if(o(s[w],w,s))return w;return-1}},83120:(s,o,i)=>{var u=i(14528),_=i(45891);s.exports=function baseFlatten(s,o,i,w,x){var C=-1,j=s.length;for(i||(i=_),x||(x=[]);++C0&&i(L)?o>1?baseFlatten(L,o-1,i,w,x):u(x,L):w||(x[x.length]=L)}return x}},86649:(s,o,i)=>{var u=i(83221)();s.exports=u},30641:(s,o,i)=>{var u=i(86649),_=i(95950);s.exports=function baseForOwn(s,o){return s&&u(s,o,_)}},47422:(s,o,i)=>{var u=i(31769),_=i(77797);s.exports=function baseGet(s,o){for(var i=0,w=(o=u(o,s)).length;null!=s&&i{var u=i(14528),_=i(56449);s.exports=function baseGetAllKeys(s,o,i){var w=o(s);return _(s)?w:u(w,i(s))}},72552:(s,o,i)=>{var u=i(51873),_=i(659),w=i(59350),x=u?u.toStringTag:void 0;s.exports=function baseGetTag(s){return null==s?void 0===s?"[object Undefined]":"[object Null]":x&&x in Object(s)?_(s):w(s)}},20426:s=>{var o=Object.prototype.hasOwnProperty;s.exports=function baseHas(s,i){return null!=s&&o.call(s,i)}},28077:s=>{s.exports=function baseHasIn(s,o){return null!=s&&o in Object(s)}},96131:(s,o,i)=>{var u=i(2523),_=i(85463),w=i(76959);s.exports=function baseIndexOf(s,o,i){return o==o?w(s,o,i):u(s,_,i)}},27534:(s,o,i)=>{var u=i(72552),_=i(40346);s.exports=function baseIsArguments(s){return _(s)&&"[object Arguments]"==u(s)}},60270:(s,o,i)=>{var u=i(87068),_=i(40346);s.exports=function baseIsEqual(s,o,i,w,x){return s===o||(null==s||null==o||!_(s)&&!_(o)?s!=s&&o!=o:u(s,o,i,w,baseIsEqual,x))}},87068:(s,o,i)=>{var u=i(37217),_=i(25911),w=i(21986),x=i(50689),C=i(5861),j=i(56449),L=i(3656),B=i(37167),$="[object Arguments]",V="[object Array]",U="[object Object]",z=Object.prototype.hasOwnProperty;s.exports=function baseIsEqualDeep(s,o,i,Y,Z,ee){var ie=j(s),ae=j(o),le=ie?V:C(s),ce=ae?V:C(o),pe=(le=le==$?U:le)==U,de=(ce=ce==$?U:ce)==U,fe=le==ce;if(fe&&L(s)){if(!L(o))return!1;ie=!0,pe=!1}if(fe&&!pe)return ee||(ee=new u),ie||B(s)?_(s,o,i,Y,Z,ee):w(s,o,le,i,Y,Z,ee);if(!(1&i)){var ye=pe&&z.call(s,"__wrapped__"),be=de&&z.call(o,"__wrapped__");if(ye||be){var _e=ye?s.value():s,we=be?o.value():o;return ee||(ee=new u),Z(_e,we,i,Y,ee)}}return!!fe&&(ee||(ee=new u),x(s,o,i,Y,Z,ee))}},29172:(s,o,i)=>{var u=i(5861),_=i(40346);s.exports=function baseIsMap(s){return _(s)&&"[object Map]"==u(s)}},41799:(s,o,i)=>{var u=i(37217),_=i(60270);s.exports=function baseIsMatch(s,o,i,w){var x=i.length,C=x,j=!w;if(null==s)return!C;for(s=Object(s);x--;){var L=i[x];if(j&&L[2]?L[1]!==s[L[0]]:!(L[0]in s))return!1}for(;++x{s.exports=function baseIsNaN(s){return s!=s}},45083:(s,o,i)=>{var u=i(1882),_=i(87296),w=i(23805),x=i(47473),C=/^\[object .+?Constructor\]$/,j=Function.prototype,L=Object.prototype,B=j.toString,$=L.hasOwnProperty,V=RegExp("^"+B.call($).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");s.exports=function baseIsNative(s){return!(!w(s)||_(s))&&(u(s)?V:C).test(x(s))}},16038:(s,o,i)=>{var u=i(5861),_=i(40346);s.exports=function baseIsSet(s){return _(s)&&"[object Set]"==u(s)}},4901:(s,o,i)=>{var u=i(72552),_=i(30294),w=i(40346),x={};x["[object Float32Array]"]=x["[object Float64Array]"]=x["[object Int8Array]"]=x["[object Int16Array]"]=x["[object Int32Array]"]=x["[object Uint8Array]"]=x["[object Uint8ClampedArray]"]=x["[object Uint16Array]"]=x["[object Uint32Array]"]=!0,x["[object Arguments]"]=x["[object Array]"]=x["[object ArrayBuffer]"]=x["[object Boolean]"]=x["[object DataView]"]=x["[object Date]"]=x["[object Error]"]=x["[object Function]"]=x["[object Map]"]=x["[object Number]"]=x["[object Object]"]=x["[object RegExp]"]=x["[object Set]"]=x["[object String]"]=x["[object WeakMap]"]=!1,s.exports=function baseIsTypedArray(s){return w(s)&&_(s.length)&&!!x[u(s)]}},15389:(s,o,i)=>{var u=i(93663),_=i(87978),w=i(83488),x=i(56449),C=i(50583);s.exports=function baseIteratee(s){return"function"==typeof s?s:null==s?w:"object"==typeof s?x(s)?_(s[0],s[1]):u(s):C(s)}},88984:(s,o,i)=>{var u=i(55527),_=i(3650),w=Object.prototype.hasOwnProperty;s.exports=function baseKeys(s){if(!u(s))return _(s);var o=[];for(var i in Object(s))w.call(s,i)&&"constructor"!=i&&o.push(i);return o}},72903:(s,o,i)=>{var u=i(23805),_=i(55527),w=i(90181),x=Object.prototype.hasOwnProperty;s.exports=function baseKeysIn(s){if(!u(s))return w(s);var o=_(s),i=[];for(var C in s)("constructor"!=C||!o&&x.call(s,C))&&i.push(C);return i}},94033:s=>{s.exports=function baseLodash(){}},93663:(s,o,i)=>{var u=i(41799),_=i(10776),w=i(67197);s.exports=function baseMatches(s){var o=_(s);return 1==o.length&&o[0][2]?w(o[0][0],o[0][1]):function(i){return i===s||u(i,s,o)}}},87978:(s,o,i)=>{var u=i(60270),_=i(58156),w=i(80631),x=i(28586),C=i(30756),j=i(67197),L=i(77797);s.exports=function baseMatchesProperty(s,o){return x(s)&&C(o)?j(L(s),o):function(i){var x=_(i,s);return void 0===x&&x===o?w(i,s):u(o,x,3)}}},85250:(s,o,i)=>{var u=i(37217),_=i(87805),w=i(86649),x=i(42824),C=i(23805),j=i(37241),L=i(14974);s.exports=function baseMerge(s,o,i,B,$){s!==o&&w(o,(function(w,j){if($||($=new u),C(w))x(s,o,j,i,baseMerge,B,$);else{var V=B?B(L(s,j),w,j+"",s,o,$):void 0;void 0===V&&(V=w),_(s,j,V)}}),j)}},42824:(s,o,i)=>{var u=i(87805),_=i(93290),w=i(71961),x=i(23007),C=i(35529),j=i(72428),L=i(56449),B=i(83693),$=i(3656),V=i(1882),U=i(23805),z=i(11331),Y=i(37167),Z=i(14974),ee=i(69884);s.exports=function baseMergeDeep(s,o,i,ie,ae,le,ce){var pe=Z(s,i),de=Z(o,i),fe=ce.get(de);if(fe)u(s,i,fe);else{var ye=le?le(pe,de,i+"",s,o,ce):void 0,be=void 0===ye;if(be){var _e=L(de),we=!_e&&$(de),Se=!_e&&!we&&Y(de);ye=de,_e||we||Se?L(pe)?ye=pe:B(pe)?ye=x(pe):we?(be=!1,ye=_(de,!0)):Se?(be=!1,ye=w(de,!0)):ye=[]:z(de)||j(de)?(ye=pe,j(pe)?ye=ee(pe):U(pe)&&!V(pe)||(ye=C(de))):be=!1}be&&(ce.set(de,ye),ae(ye,de,ie,le,ce),ce.delete(de)),u(s,i,ye)}}},47237:s=>{s.exports=function baseProperty(s){return function(o){return null==o?void 0:o[s]}}},17255:(s,o,i)=>{var u=i(47422);s.exports=function basePropertyDeep(s){return function(o){return u(o,s)}}},54552:s=>{s.exports=function basePropertyOf(s){return function(o){return null==s?void 0:s[o]}}},85558:s=>{s.exports=function baseReduce(s,o,i,u,_){return _(s,(function(s,_,w){i=u?(u=!1,s):o(i,s,_,w)})),i}},69302:(s,o,i)=>{var u=i(83488),_=i(56757),w=i(32865);s.exports=function baseRest(s,o){return w(_(s,o,u),s+"")}},73170:(s,o,i)=>{var u=i(16547),_=i(31769),w=i(30361),x=i(23805),C=i(77797);s.exports=function baseSet(s,o,i,j){if(!x(s))return s;for(var L=-1,B=(o=_(o,s)).length,$=B-1,V=s;null!=V&&++L{var u=i(83488),_=i(48152),w=_?function(s,o){return _.set(s,o),s}:u;s.exports=w},19570:(s,o,i)=>{var u=i(37334),_=i(93243),w=i(83488),x=_?function(s,o){return _(s,"toString",{configurable:!0,enumerable:!1,value:u(o),writable:!0})}:w;s.exports=x},25160:s=>{s.exports=function baseSlice(s,o,i){var u=-1,_=s.length;o<0&&(o=-o>_?0:_+o),(i=i>_?_:i)<0&&(i+=_),_=o>i?0:i-o>>>0,o>>>=0;for(var w=Array(_);++u<_;)w[u]=s[u+o];return w}},90916:(s,o,i)=>{var u=i(80909);s.exports=function baseSome(s,o){var i;return u(s,(function(s,u,_){return!(i=o(s,u,_))})),!!i}},78096:s=>{s.exports=function baseTimes(s,o){for(var i=-1,u=Array(s);++i{var u=i(51873),_=i(34932),w=i(56449),x=i(44394),C=u?u.prototype:void 0,j=C?C.toString:void 0;s.exports=function baseToString(s){if("string"==typeof s)return s;if(w(s))return _(s,baseToString)+"";if(x(s))return j?j.call(s):"";var o=s+"";return"0"==o&&1/s==-1/0?"-0":o}},54128:(s,o,i)=>{var u=i(31800),_=/^\s+/;s.exports=function baseTrim(s){return s?s.slice(0,u(s)+1).replace(_,""):s}},27301:s=>{s.exports=function baseUnary(s){return function(o){return s(o)}}},19931:(s,o,i)=>{var u=i(31769),_=i(68090),w=i(68969),x=i(77797);s.exports=function baseUnset(s,o){return o=u(o,s),null==(s=w(s,o))||delete s[x(_(o))]}},51234:s=>{s.exports=function baseZipObject(s,o,i){for(var u=-1,_=s.length,w=o.length,x={};++u<_;){var C=u{s.exports=function cacheHas(s,o){return s.has(o)}},31769:(s,o,i)=>{var u=i(56449),_=i(28586),w=i(61802),x=i(13222);s.exports=function castPath(s,o){return u(s)?s:_(s,o)?[s]:w(x(s))}},28754:(s,o,i)=>{var u=i(25160);s.exports=function castSlice(s,o,i){var _=s.length;return i=void 0===i?_:i,!o&&i>=_?s:u(s,o,i)}},49653:(s,o,i)=>{var u=i(37828);s.exports=function cloneArrayBuffer(s){var o=new s.constructor(s.byteLength);return new u(o).set(new u(s)),o}},93290:(s,o,i)=>{s=i.nmd(s);var u=i(9325),_=o&&!o.nodeType&&o,w=_&&s&&!s.nodeType&&s,x=w&&w.exports===_?u.Buffer:void 0,C=x?x.allocUnsafe:void 0;s.exports=function cloneBuffer(s,o){if(o)return s.slice();var i=s.length,u=C?C(i):new s.constructor(i);return s.copy(u),u}},76169:(s,o,i)=>{var u=i(49653);s.exports=function cloneDataView(s,o){var i=o?u(s.buffer):s.buffer;return new s.constructor(i,s.byteOffset,s.byteLength)}},73201:s=>{var o=/\w*$/;s.exports=function cloneRegExp(s){var i=new s.constructor(s.source,o.exec(s));return i.lastIndex=s.lastIndex,i}},93736:(s,o,i)=>{var u=i(51873),_=u?u.prototype:void 0,w=_?_.valueOf:void 0;s.exports=function cloneSymbol(s){return w?Object(w.call(s)):{}}},71961:(s,o,i)=>{var u=i(49653);s.exports=function cloneTypedArray(s,o){var i=o?u(s.buffer):s.buffer;return new s.constructor(i,s.byteOffset,s.length)}},91596:s=>{var o=Math.max;s.exports=function composeArgs(s,i,u,_){for(var w=-1,x=s.length,C=u.length,j=-1,L=i.length,B=o(x-C,0),$=Array(L+B),V=!_;++j{var o=Math.max;s.exports=function composeArgsRight(s,i,u,_){for(var w=-1,x=s.length,C=-1,j=u.length,L=-1,B=i.length,$=o(x-j,0),V=Array($+B),U=!_;++w<$;)V[w]=s[w];for(var z=w;++L{s.exports=function copyArray(s,o){var i=-1,u=s.length;for(o||(o=Array(u));++i{var u=i(16547),_=i(43360);s.exports=function copyObject(s,o,i,w){var x=!i;i||(i={});for(var C=-1,j=o.length;++C{var u=i(21791),_=i(4664);s.exports=function copySymbols(s,o){return u(s,_(s),o)}},48948:(s,o,i)=>{var u=i(21791),_=i(86375);s.exports=function copySymbolsIn(s,o){return u(s,_(s),o)}},55481:(s,o,i)=>{var u=i(9325)["__core-js_shared__"];s.exports=u},58523:s=>{s.exports=function countHolders(s,o){for(var i=s.length,u=0;i--;)s[i]===o&&++u;return u}},20999:(s,o,i)=>{var u=i(69302),_=i(36800);s.exports=function createAssigner(s){return u((function(o,i){var u=-1,w=i.length,x=w>1?i[w-1]:void 0,C=w>2?i[2]:void 0;for(x=s.length>3&&"function"==typeof x?(w--,x):void 0,C&&_(i[0],i[1],C)&&(x=w<3?void 0:x,w=1),o=Object(o);++u{var u=i(64894);s.exports=function createBaseEach(s,o){return function(i,_){if(null==i)return i;if(!u(i))return s(i,_);for(var w=i.length,x=o?w:-1,C=Object(i);(o?x--:++x{s.exports=function createBaseFor(s){return function(o,i,u){for(var _=-1,w=Object(o),x=u(o),C=x.length;C--;){var j=x[s?C:++_];if(!1===i(w[j],j,w))break}return o}}},11842:(s,o,i)=>{var u=i(82819),_=i(9325);s.exports=function createBind(s,o,i){var w=1&o,x=u(s);return function wrapper(){return(this&&this!==_&&this instanceof wrapper?x:s).apply(w?i:this,arguments)}}},12507:(s,o,i)=>{var u=i(28754),_=i(49698),w=i(63912),x=i(13222);s.exports=function createCaseFirst(s){return function(o){o=x(o);var i=_(o)?w(o):void 0,C=i?i[0]:o.charAt(0),j=i?u(i,1).join(""):o.slice(1);return C[s]()+j}}},45539:(s,o,i)=>{var u=i(40882),_=i(50828),w=i(66645),x=RegExp("['’]","g");s.exports=function createCompounder(s){return function(o){return u(w(_(o).replace(x,"")),s,"")}}},82819:(s,o,i)=>{var u=i(39344),_=i(23805);s.exports=function createCtor(s){return function(){var o=arguments;switch(o.length){case 0:return new s;case 1:return new s(o[0]);case 2:return new s(o[0],o[1]);case 3:return new s(o[0],o[1],o[2]);case 4:return new s(o[0],o[1],o[2],o[3]);case 5:return new s(o[0],o[1],o[2],o[3],o[4]);case 6:return new s(o[0],o[1],o[2],o[3],o[4],o[5]);case 7:return new s(o[0],o[1],o[2],o[3],o[4],o[5],o[6])}var i=u(s.prototype),w=s.apply(i,o);return _(w)?w:i}}},77078:(s,o,i)=>{var u=i(91033),_=i(82819),w=i(37471),x=i(18073),C=i(11287),j=i(36306),L=i(9325);s.exports=function createCurry(s,o,i){var B=_(s);return function wrapper(){for(var _=arguments.length,$=Array(_),V=_,U=C(wrapper);V--;)$[V]=arguments[V];var z=_<3&&$[0]!==U&&$[_-1]!==U?[]:j($,U);return(_-=z.length){var u=i(15389),_=i(64894),w=i(95950);s.exports=function createFind(s){return function(o,i,x){var C=Object(o);if(!_(o)){var j=u(i,3);o=w(o),i=function(s){return j(C[s],s,C)}}var L=s(o,i,x);return L>-1?C[j?o[L]:L]:void 0}}},37471:(s,o,i)=>{var u=i(91596),_=i(53320),w=i(58523),x=i(82819),C=i(18073),j=i(11287),L=i(68294),B=i(36306),$=i(9325);s.exports=function createHybrid(s,o,i,V,U,z,Y,Z,ee,ie){var ae=128&o,le=1&o,ce=2&o,pe=24&o,de=512&o,fe=ce?void 0:x(s);return function wrapper(){for(var ye=arguments.length,be=Array(ye),_e=ye;_e--;)be[_e]=arguments[_e];if(pe)var we=j(wrapper),Se=w(be,we);if(V&&(be=u(be,V,U,pe)),z&&(be=_(be,z,Y,pe)),ye-=Se,pe&&ye1&&be.reverse(),ae&&ee{var u=i(91033),_=i(82819),w=i(9325);s.exports=function createPartial(s,o,i,x){var C=1&o,j=_(s);return function wrapper(){for(var o=-1,_=arguments.length,L=-1,B=x.length,$=Array(B+_),V=this&&this!==w&&this instanceof wrapper?j:s;++L{var u=i(85087),_=i(54641),w=i(70981);s.exports=function createRecurry(s,o,i,x,C,j,L,B,$,V){var U=8&o;o|=U?32:64,4&(o&=~(U?64:32))||(o&=-4);var z=[s,o,C,U?j:void 0,U?L:void 0,U?void 0:j,U?void 0:L,B,$,V],Y=i.apply(void 0,z);return u(s)&&_(Y,z),Y.placeholder=x,w(Y,s,o)}},66977:(s,o,i)=>{var u=i(68882),_=i(11842),w=i(77078),x=i(37471),C=i(24168),j=i(37381),L=i(3209),B=i(54641),$=i(70981),V=i(61489),U=Math.max;s.exports=function createWrap(s,o,i,z,Y,Z,ee,ie){var ae=2&o;if(!ae&&"function"!=typeof s)throw new TypeError("Expected a function");var le=z?z.length:0;if(le||(o&=-97,z=Y=void 0),ee=void 0===ee?ee:U(V(ee),0),ie=void 0===ie?ie:V(ie),le-=Y?Y.length:0,64&o){var ce=z,pe=Y;z=Y=void 0}var de=ae?void 0:j(s),fe=[s,o,i,z,Y,ce,pe,Z,ee,ie];if(de&&L(fe,de),s=fe[0],o=fe[1],i=fe[2],z=fe[3],Y=fe[4],!(ie=fe[9]=void 0===fe[9]?ae?0:s.length:U(fe[9]-le,0))&&24&o&&(o&=-25),o&&1!=o)ye=8==o||16==o?w(s,o,ie):32!=o&&33!=o||Y.length?x.apply(void 0,fe):C(s,o,i,z);else var ye=_(s,o,i);return $((de?u:B)(ye,fe),s,o)}},53138:(s,o,i)=>{var u=i(11331);s.exports=function customOmitClone(s){return u(s)?void 0:s}},24647:(s,o,i)=>{var u=i(54552)({À:"A",Á:"A",Â:"A",Ã:"A",Ä:"A",Å:"A",à:"a",á:"a",â:"a",ã:"a",ä:"a",å:"a",Ç:"C",ç:"c",Ð:"D",ð:"d",È:"E",É:"E",Ê:"E",Ë:"E",è:"e",é:"e",ê:"e",ë:"e",Ì:"I",Í:"I",Î:"I",Ï:"I",ì:"i",í:"i",î:"i",ï:"i",Ñ:"N",ñ:"n",Ò:"O",Ó:"O",Ô:"O",Õ:"O",Ö:"O",Ø:"O",ò:"o",ó:"o",ô:"o",õ:"o",ö:"o",ø:"o",Ù:"U",Ú:"U",Û:"U",Ü:"U",ù:"u",ú:"u",û:"u",ü:"u",Ý:"Y",ý:"y",ÿ:"y",Æ:"Ae",æ:"ae",Þ:"Th",þ:"th",ß:"ss",Ā:"A",Ă:"A",Ą:"A",ā:"a",ă:"a",ą:"a",Ć:"C",Ĉ:"C",Ċ:"C",Č:"C",ć:"c",ĉ:"c",ċ:"c",č:"c",Ď:"D",Đ:"D",ď:"d",đ:"d",Ē:"E",Ĕ:"E",Ė:"E",Ę:"E",Ě:"E",ē:"e",ĕ:"e",ė:"e",ę:"e",ě:"e",Ĝ:"G",Ğ:"G",Ġ:"G",Ģ:"G",ĝ:"g",ğ:"g",ġ:"g",ģ:"g",Ĥ:"H",Ħ:"H",ĥ:"h",ħ:"h",Ĩ:"I",Ī:"I",Ĭ:"I",Į:"I",İ:"I",ĩ:"i",ī:"i",ĭ:"i",į:"i",ı:"i",Ĵ:"J",ĵ:"j",Ķ:"K",ķ:"k",ĸ:"k",Ĺ:"L",Ļ:"L",Ľ:"L",Ŀ:"L",Ł:"L",ĺ:"l",ļ:"l",ľ:"l",ŀ:"l",ł:"l",Ń:"N",Ņ:"N",Ň:"N",Ŋ:"N",ń:"n",ņ:"n",ň:"n",ŋ:"n",Ō:"O",Ŏ:"O",Ő:"O",ō:"o",ŏ:"o",ő:"o",Ŕ:"R",Ŗ:"R",Ř:"R",ŕ:"r",ŗ:"r",ř:"r",Ś:"S",Ŝ:"S",Ş:"S",Š:"S",ś:"s",ŝ:"s",ş:"s",š:"s",Ţ:"T",Ť:"T",Ŧ:"T",ţ:"t",ť:"t",ŧ:"t",Ũ:"U",Ū:"U",Ŭ:"U",Ů:"U",Ű:"U",Ų:"U",ũ:"u",ū:"u",ŭ:"u",ů:"u",ű:"u",ų:"u",Ŵ:"W",ŵ:"w",Ŷ:"Y",ŷ:"y",Ÿ:"Y",Ź:"Z",Ż:"Z",Ž:"Z",ź:"z",ż:"z",ž:"z",IJ:"IJ",ij:"ij",Œ:"Oe",œ:"oe",ʼn:"'n",ſ:"s"});s.exports=u},93243:(s,o,i)=>{var u=i(56110),_=function(){try{var s=u(Object,"defineProperty");return s({},"",{}),s}catch(s){}}();s.exports=_},25911:(s,o,i)=>{var u=i(38859),_=i(14248),w=i(19219);s.exports=function equalArrays(s,o,i,x,C,j){var L=1&i,B=s.length,$=o.length;if(B!=$&&!(L&&$>B))return!1;var V=j.get(s),U=j.get(o);if(V&&U)return V==o&&U==s;var z=-1,Y=!0,Z=2&i?new u:void 0;for(j.set(s,o),j.set(o,s);++z{var u=i(51873),_=i(37828),w=i(75288),x=i(25911),C=i(20317),j=i(84247),L=u?u.prototype:void 0,B=L?L.valueOf:void 0;s.exports=function equalByTag(s,o,i,u,L,$,V){switch(i){case"[object DataView]":if(s.byteLength!=o.byteLength||s.byteOffset!=o.byteOffset)return!1;s=s.buffer,o=o.buffer;case"[object ArrayBuffer]":return!(s.byteLength!=o.byteLength||!$(new _(s),new _(o)));case"[object Boolean]":case"[object Date]":case"[object Number]":return w(+s,+o);case"[object Error]":return s.name==o.name&&s.message==o.message;case"[object RegExp]":case"[object String]":return s==o+"";case"[object Map]":var U=C;case"[object Set]":var z=1&u;if(U||(U=j),s.size!=o.size&&!z)return!1;var Y=V.get(s);if(Y)return Y==o;u|=2,V.set(s,o);var Z=x(U(s),U(o),u,L,$,V);return V.delete(s),Z;case"[object Symbol]":if(B)return B.call(s)==B.call(o)}return!1}},50689:(s,o,i)=>{var u=i(50002),_=Object.prototype.hasOwnProperty;s.exports=function equalObjects(s,o,i,w,x,C){var j=1&i,L=u(s),B=L.length;if(B!=u(o).length&&!j)return!1;for(var $=B;$--;){var V=L[$];if(!(j?V in o:_.call(o,V)))return!1}var U=C.get(s),z=C.get(o);if(U&&z)return U==o&&z==s;var Y=!0;C.set(s,o),C.set(o,s);for(var Z=j;++${var u=i(35970),_=i(56757),w=i(32865);s.exports=function flatRest(s){return w(_(s,void 0,u),s+"")}},34840:(s,o,i)=>{var u="object"==typeof i.g&&i.g&&i.g.Object===Object&&i.g;s.exports=u},50002:(s,o,i)=>{var u=i(82199),_=i(4664),w=i(95950);s.exports=function getAllKeys(s){return u(s,w,_)}},83349:(s,o,i)=>{var u=i(82199),_=i(86375),w=i(37241);s.exports=function getAllKeysIn(s){return u(s,w,_)}},37381:(s,o,i)=>{var u=i(48152),_=i(63950),w=u?function(s){return u.get(s)}:_;s.exports=w},62284:(s,o,i)=>{var u=i(84629),_=Object.prototype.hasOwnProperty;s.exports=function getFuncName(s){for(var o=s.name+"",i=u[o],w=_.call(u,o)?i.length:0;w--;){var x=i[w],C=x.func;if(null==C||C==s)return x.name}return o}},11287:s=>{s.exports=function getHolder(s){return s.placeholder}},12651:(s,o,i)=>{var u=i(74218);s.exports=function getMapData(s,o){var i=s.__data__;return u(o)?i["string"==typeof o?"string":"hash"]:i.map}},10776:(s,o,i)=>{var u=i(30756),_=i(95950);s.exports=function getMatchData(s){for(var o=_(s),i=o.length;i--;){var w=o[i],x=s[w];o[i]=[w,x,u(x)]}return o}},56110:(s,o,i)=>{var u=i(45083),_=i(10392);s.exports=function getNative(s,o){var i=_(s,o);return u(i)?i:void 0}},28879:(s,o,i)=>{var u=i(74335)(Object.getPrototypeOf,Object);s.exports=u},659:(s,o,i)=>{var u=i(51873),_=Object.prototype,w=_.hasOwnProperty,x=_.toString,C=u?u.toStringTag:void 0;s.exports=function getRawTag(s){var o=w.call(s,C),i=s[C];try{s[C]=void 0;var u=!0}catch(s){}var _=x.call(s);return u&&(o?s[C]=i:delete s[C]),_}},4664:(s,o,i)=>{var u=i(79770),_=i(63345),w=Object.prototype.propertyIsEnumerable,x=Object.getOwnPropertySymbols,C=x?function(s){return null==s?[]:(s=Object(s),u(x(s),(function(o){return w.call(s,o)})))}:_;s.exports=C},86375:(s,o,i)=>{var u=i(14528),_=i(28879),w=i(4664),x=i(63345),C=Object.getOwnPropertySymbols?function(s){for(var o=[];s;)u(o,w(s)),s=_(s);return o}:x;s.exports=C},5861:(s,o,i)=>{var u=i(55580),_=i(68223),w=i(32804),x=i(76545),C=i(28303),j=i(72552),L=i(47473),B="[object Map]",$="[object Promise]",V="[object Set]",U="[object WeakMap]",z="[object DataView]",Y=L(u),Z=L(_),ee=L(w),ie=L(x),ae=L(C),le=j;(u&&le(new u(new ArrayBuffer(1)))!=z||_&&le(new _)!=B||w&&le(w.resolve())!=$||x&&le(new x)!=V||C&&le(new C)!=U)&&(le=function(s){var o=j(s),i="[object Object]"==o?s.constructor:void 0,u=i?L(i):"";if(u)switch(u){case Y:return z;case Z:return B;case ee:return $;case ie:return V;case ae:return U}return o}),s.exports=le},10392:s=>{s.exports=function getValue(s,o){return null==s?void 0:s[o]}},75251:s=>{var o=/\{\n\/\* \[wrapped with (.+)\] \*/,i=/,? & /;s.exports=function getWrapDetails(s){var u=s.match(o);return u?u[1].split(i):[]}},49326:(s,o,i)=>{var u=i(31769),_=i(72428),w=i(56449),x=i(30361),C=i(30294),j=i(77797);s.exports=function hasPath(s,o,i){for(var L=-1,B=(o=u(o,s)).length,$=!1;++L{var o=RegExp("[\\u200d\\ud800-\\udfff\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff\\ufe0e\\ufe0f]");s.exports=function hasUnicode(s){return o.test(s)}},45434:s=>{var o=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/;s.exports=function hasUnicodeWord(s){return o.test(s)}},22032:(s,o,i)=>{var u=i(81042);s.exports=function hashClear(){this.__data__=u?u(null):{},this.size=0}},63862:s=>{s.exports=function hashDelete(s){var o=this.has(s)&&delete this.__data__[s];return this.size-=o?1:0,o}},66721:(s,o,i)=>{var u=i(81042),_=Object.prototype.hasOwnProperty;s.exports=function hashGet(s){var o=this.__data__;if(u){var i=o[s];return"__lodash_hash_undefined__"===i?void 0:i}return _.call(o,s)?o[s]:void 0}},12749:(s,o,i)=>{var u=i(81042),_=Object.prototype.hasOwnProperty;s.exports=function hashHas(s){var o=this.__data__;return u?void 0!==o[s]:_.call(o,s)}},35749:(s,o,i)=>{var u=i(81042);s.exports=function hashSet(s,o){var i=this.__data__;return this.size+=this.has(s)?0:1,i[s]=u&&void 0===o?"__lodash_hash_undefined__":o,this}},76189:s=>{var o=Object.prototype.hasOwnProperty;s.exports=function initCloneArray(s){var i=s.length,u=new s.constructor(i);return i&&"string"==typeof s[0]&&o.call(s,"index")&&(u.index=s.index,u.input=s.input),u}},77199:(s,o,i)=>{var u=i(49653),_=i(76169),w=i(73201),x=i(93736),C=i(71961);s.exports=function initCloneByTag(s,o,i){var j=s.constructor;switch(o){case"[object ArrayBuffer]":return u(s);case"[object Boolean]":case"[object Date]":return new j(+s);case"[object DataView]":return _(s,i);case"[object Float32Array]":case"[object Float64Array]":case"[object Int8Array]":case"[object Int16Array]":case"[object Int32Array]":case"[object Uint8Array]":case"[object Uint8ClampedArray]":case"[object Uint16Array]":case"[object Uint32Array]":return C(s,i);case"[object Map]":case"[object Set]":return new j;case"[object Number]":case"[object String]":return new j(s);case"[object RegExp]":return w(s);case"[object Symbol]":return x(s)}}},35529:(s,o,i)=>{var u=i(39344),_=i(28879),w=i(55527);s.exports=function initCloneObject(s){return"function"!=typeof s.constructor||w(s)?{}:u(_(s))}},62060:s=>{var o=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/;s.exports=function insertWrapDetails(s,i){var u=i.length;if(!u)return s;var _=u-1;return i[_]=(u>1?"& ":"")+i[_],i=i.join(u>2?", ":" "),s.replace(o,"{\n/* [wrapped with "+i+"] */\n")}},45891:(s,o,i)=>{var u=i(51873),_=i(72428),w=i(56449),x=u?u.isConcatSpreadable:void 0;s.exports=function isFlattenable(s){return w(s)||_(s)||!!(x&&s&&s[x])}},30361:s=>{var o=/^(?:0|[1-9]\d*)$/;s.exports=function isIndex(s,i){var u=typeof s;return!!(i=null==i?9007199254740991:i)&&("number"==u||"symbol"!=u&&o.test(s))&&s>-1&&s%1==0&&s{var u=i(75288),_=i(64894),w=i(30361),x=i(23805);s.exports=function isIterateeCall(s,o,i){if(!x(i))return!1;var C=typeof o;return!!("number"==C?_(i)&&w(o,i.length):"string"==C&&o in i)&&u(i[o],s)}},28586:(s,o,i)=>{var u=i(56449),_=i(44394),w=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,x=/^\w*$/;s.exports=function isKey(s,o){if(u(s))return!1;var i=typeof s;return!("number"!=i&&"symbol"!=i&&"boolean"!=i&&null!=s&&!_(s))||(x.test(s)||!w.test(s)||null!=o&&s in Object(o))}},74218:s=>{s.exports=function isKeyable(s){var o=typeof s;return"string"==o||"number"==o||"symbol"==o||"boolean"==o?"__proto__"!==s:null===s}},85087:(s,o,i)=>{var u=i(30980),_=i(37381),w=i(62284),x=i(53758);s.exports=function isLaziable(s){var o=w(s),i=x[o];if("function"!=typeof i||!(o in u.prototype))return!1;if(s===i)return!0;var C=_(i);return!!C&&s===C[0]}},87296:(s,o,i)=>{var u,_=i(55481),w=(u=/[^.]+$/.exec(_&&_.keys&&_.keys.IE_PROTO||""))?"Symbol(src)_1."+u:"";s.exports=function isMasked(s){return!!w&&w in s}},55527:s=>{var o=Object.prototype;s.exports=function isPrototype(s){var i=s&&s.constructor;return s===("function"==typeof i&&i.prototype||o)}},30756:(s,o,i)=>{var u=i(23805);s.exports=function isStrictComparable(s){return s==s&&!u(s)}},63702:s=>{s.exports=function listCacheClear(){this.__data__=[],this.size=0}},70080:(s,o,i)=>{var u=i(26025),_=Array.prototype.splice;s.exports=function listCacheDelete(s){var o=this.__data__,i=u(o,s);return!(i<0)&&(i==o.length-1?o.pop():_.call(o,i,1),--this.size,!0)}},24739:(s,o,i)=>{var u=i(26025);s.exports=function listCacheGet(s){var o=this.__data__,i=u(o,s);return i<0?void 0:o[i][1]}},48655:(s,o,i)=>{var u=i(26025);s.exports=function listCacheHas(s){return u(this.__data__,s)>-1}},31175:(s,o,i)=>{var u=i(26025);s.exports=function listCacheSet(s,o){var i=this.__data__,_=u(i,s);return _<0?(++this.size,i.push([s,o])):i[_][1]=o,this}},63040:(s,o,i)=>{var u=i(21549),_=i(80079),w=i(68223);s.exports=function mapCacheClear(){this.size=0,this.__data__={hash:new u,map:new(w||_),string:new u}}},17670:(s,o,i)=>{var u=i(12651);s.exports=function mapCacheDelete(s){var o=u(this,s).delete(s);return this.size-=o?1:0,o}},90289:(s,o,i)=>{var u=i(12651);s.exports=function mapCacheGet(s){return u(this,s).get(s)}},4509:(s,o,i)=>{var u=i(12651);s.exports=function mapCacheHas(s){return u(this,s).has(s)}},72949:(s,o,i)=>{var u=i(12651);s.exports=function mapCacheSet(s,o){var i=u(this,s),_=i.size;return i.set(s,o),this.size+=i.size==_?0:1,this}},20317:s=>{s.exports=function mapToArray(s){var o=-1,i=Array(s.size);return s.forEach((function(s,u){i[++o]=[u,s]})),i}},67197:s=>{s.exports=function matchesStrictComparable(s,o){return function(i){return null!=i&&(i[s]===o&&(void 0!==o||s in Object(i)))}}},62224:(s,o,i)=>{var u=i(50104);s.exports=function memoizeCapped(s){var o=u(s,(function(s){return 500===i.size&&i.clear(),s})),i=o.cache;return o}},3209:(s,o,i)=>{var u=i(91596),_=i(53320),w=i(36306),x="__lodash_placeholder__",C=128,j=Math.min;s.exports=function mergeData(s,o){var i=s[1],L=o[1],B=i|L,$=B<131,V=L==C&&8==i||L==C&&256==i&&s[7].length<=o[8]||384==L&&o[7].length<=o[8]&&8==i;if(!$&&!V)return s;1&L&&(s[2]=o[2],B|=1&i?0:4);var U=o[3];if(U){var z=s[3];s[3]=z?u(z,U,o[4]):U,s[4]=z?w(s[3],x):o[4]}return(U=o[5])&&(z=s[5],s[5]=z?_(z,U,o[6]):U,s[6]=z?w(s[5],x):o[6]),(U=o[7])&&(s[7]=U),L&C&&(s[8]=null==s[8]?o[8]:j(s[8],o[8])),null==s[9]&&(s[9]=o[9]),s[0]=o[0],s[1]=B,s}},48152:(s,o,i)=>{var u=i(28303),_=u&&new u;s.exports=_},81042:(s,o,i)=>{var u=i(56110)(Object,"create");s.exports=u},3650:(s,o,i)=>{var u=i(74335)(Object.keys,Object);s.exports=u},90181:s=>{s.exports=function nativeKeysIn(s){var o=[];if(null!=s)for(var i in Object(s))o.push(i);return o}},86009:(s,o,i)=>{s=i.nmd(s);var u=i(34840),_=o&&!o.nodeType&&o,w=_&&s&&!s.nodeType&&s,x=w&&w.exports===_&&u.process,C=function(){try{var s=w&&w.require&&w.require("util").types;return s||x&&x.binding&&x.binding("util")}catch(s){}}();s.exports=C},59350:s=>{var o=Object.prototype.toString;s.exports=function objectToString(s){return o.call(s)}},74335:s=>{s.exports=function overArg(s,o){return function(i){return s(o(i))}}},56757:(s,o,i)=>{var u=i(91033),_=Math.max;s.exports=function overRest(s,o,i){return o=_(void 0===o?s.length-1:o,0),function(){for(var w=arguments,x=-1,C=_(w.length-o,0),j=Array(C);++x{var u=i(47422),_=i(25160);s.exports=function parent(s,o){return o.length<2?s:u(s,_(o,0,-1))}},84629:s=>{s.exports={}},68294:(s,o,i)=>{var u=i(23007),_=i(30361),w=Math.min;s.exports=function reorder(s,o){for(var i=s.length,x=w(o.length,i),C=u(s);x--;){var j=o[x];s[x]=_(j,i)?C[j]:void 0}return s}},36306:s=>{var o="__lodash_placeholder__";s.exports=function replaceHolders(s,i){for(var u=-1,_=s.length,w=0,x=[];++u<_;){var C=s[u];C!==i&&C!==o||(s[u]=o,x[w++]=u)}return x}},9325:(s,o,i)=>{var u=i(34840),_="object"==typeof self&&self&&self.Object===Object&&self,w=u||_||Function("return this")();s.exports=w},14974:s=>{s.exports=function safeGet(s,o){if(("constructor"!==o||"function"!=typeof s[o])&&"__proto__"!=o)return s[o]}},31380:s=>{s.exports=function setCacheAdd(s){return this.__data__.set(s,"__lodash_hash_undefined__"),this}},51459:s=>{s.exports=function setCacheHas(s){return this.__data__.has(s)}},54641:(s,o,i)=>{var u=i(68882),_=i(51811)(u);s.exports=_},84247:s=>{s.exports=function setToArray(s){var o=-1,i=Array(s.size);return s.forEach((function(s){i[++o]=s})),i}},32865:(s,o,i)=>{var u=i(19570),_=i(51811)(u);s.exports=_},70981:(s,o,i)=>{var u=i(75251),_=i(62060),w=i(32865),x=i(75948);s.exports=function setWrapToString(s,o,i){var C=o+"";return w(s,_(C,x(u(C),i)))}},51811:s=>{var o=Date.now;s.exports=function shortOut(s){var i=0,u=0;return function(){var _=o(),w=16-(_-u);if(u=_,w>0){if(++i>=800)return arguments[0]}else i=0;return s.apply(void 0,arguments)}}},51420:(s,o,i)=>{var u=i(80079);s.exports=function stackClear(){this.__data__=new u,this.size=0}},90938:s=>{s.exports=function stackDelete(s){var o=this.__data__,i=o.delete(s);return this.size=o.size,i}},63605:s=>{s.exports=function stackGet(s){return this.__data__.get(s)}},29817:s=>{s.exports=function stackHas(s){return this.__data__.has(s)}},80945:(s,o,i)=>{var u=i(80079),_=i(68223),w=i(53661);s.exports=function stackSet(s,o){var i=this.__data__;if(i instanceof u){var x=i.__data__;if(!_||x.length<199)return x.push([s,o]),this.size=++i.size,this;i=this.__data__=new w(x)}return i.set(s,o),this.size=i.size,this}},76959:s=>{s.exports=function strictIndexOf(s,o,i){for(var u=i-1,_=s.length;++u<_;)if(s[u]===o)return u;return-1}},63912:(s,o,i)=>{var u=i(61074),_=i(49698),w=i(42054);s.exports=function stringToArray(s){return _(s)?w(s):u(s)}},61802:(s,o,i)=>{var u=i(62224),_=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,w=/\\(\\)?/g,x=u((function(s){var o=[];return 46===s.charCodeAt(0)&&o.push(""),s.replace(_,(function(s,i,u,_){o.push(u?_.replace(w,"$1"):i||s)})),o}));s.exports=x},77797:(s,o,i)=>{var u=i(44394);s.exports=function toKey(s){if("string"==typeof s||u(s))return s;var o=s+"";return"0"==o&&1/s==-1/0?"-0":o}},47473:s=>{var o=Function.prototype.toString;s.exports=function toSource(s){if(null!=s){try{return o.call(s)}catch(s){}try{return s+""}catch(s){}}return""}},31800:s=>{var o=/\s/;s.exports=function trimmedEndIndex(s){for(var i=s.length;i--&&o.test(s.charAt(i)););return i}},42054:s=>{var o="\\ud800-\\udfff",i="["+o+"]",u="[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]",_="\\ud83c[\\udffb-\\udfff]",w="[^"+o+"]",x="(?:\\ud83c[\\udde6-\\uddff]){2}",C="[\\ud800-\\udbff][\\udc00-\\udfff]",j="(?:"+u+"|"+_+")"+"?",L="[\\ufe0e\\ufe0f]?",B=L+j+("(?:\\u200d(?:"+[w,x,C].join("|")+")"+L+j+")*"),$="(?:"+[w+u+"?",u,x,C,i].join("|")+")",V=RegExp(_+"(?="+_+")|"+$+B,"g");s.exports=function unicodeToArray(s){return s.match(V)||[]}},22225:s=>{var o="\\ud800-\\udfff",i="\\u2700-\\u27bf",u="a-z\\xdf-\\xf6\\xf8-\\xff",_="A-Z\\xc0-\\xd6\\xd8-\\xde",w="\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",x="["+w+"]",C="\\d+",j="["+i+"]",L="["+u+"]",B="[^"+o+w+C+i+u+_+"]",$="(?:\\ud83c[\\udde6-\\uddff]){2}",V="[\\ud800-\\udbff][\\udc00-\\udfff]",U="["+_+"]",z="(?:"+L+"|"+B+")",Y="(?:"+U+"|"+B+")",Z="(?:['’](?:d|ll|m|re|s|t|ve))?",ee="(?:['’](?:D|LL|M|RE|S|T|VE))?",ie="(?:[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]|\\ud83c[\\udffb-\\udfff])?",ae="[\\ufe0e\\ufe0f]?",le=ae+ie+("(?:\\u200d(?:"+["[^"+o+"]",$,V].join("|")+")"+ae+ie+")*"),ce="(?:"+[j,$,V].join("|")+")"+le,pe=RegExp([U+"?"+L+"+"+Z+"(?="+[x,U,"$"].join("|")+")",Y+"+"+ee+"(?="+[x,U+z,"$"].join("|")+")",U+"?"+z+"+"+Z,U+"+"+ee,"\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])","\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])",C,ce].join("|"),"g");s.exports=function unicodeWords(s){return s.match(pe)||[]}},75948:(s,o,i)=>{var u=i(83729),_=i(15325),w=[["ary",128],["bind",1],["bindKey",2],["curry",8],["curryRight",16],["flip",512],["partial",32],["partialRight",64],["rearg",256]];s.exports=function updateWrapDetails(s,o){return u(w,(function(i){var u="_."+i[0];o&i[1]&&!_(s,u)&&s.push(u)})),s.sort()}},80257:(s,o,i)=>{var u=i(30980),_=i(56017),w=i(23007);s.exports=function wrapperClone(s){if(s instanceof u)return s.clone();var o=new _(s.__wrapped__,s.__chain__);return o.__actions__=w(s.__actions__),o.__index__=s.__index__,o.__values__=s.__values__,o}},64626:(s,o,i)=>{var u=i(66977);s.exports=function ary(s,o,i){return o=i?void 0:o,o=s&&null==o?s.length:o,u(s,128,void 0,void 0,void 0,void 0,o)}},84058:(s,o,i)=>{var u=i(14792),_=i(45539)((function(s,o,i){return o=o.toLowerCase(),s+(i?u(o):o)}));s.exports=_},14792:(s,o,i)=>{var u=i(13222),_=i(55808);s.exports=function capitalize(s){return _(u(s).toLowerCase())}},32629:(s,o,i)=>{var u=i(9999);s.exports=function clone(s){return u(s,4)}},37334:s=>{s.exports=function constant(s){return function(){return s}}},49747:(s,o,i)=>{var u=i(66977);function curry(s,o,i){var _=u(s,8,void 0,void 0,void 0,void 0,void 0,o=i?void 0:o);return _.placeholder=curry.placeholder,_}curry.placeholder={},s.exports=curry},38221:(s,o,i)=>{var u=i(23805),_=i(10124),w=i(99374),x=Math.max,C=Math.min;s.exports=function debounce(s,o,i){var j,L,B,$,V,U,z=0,Y=!1,Z=!1,ee=!0;if("function"!=typeof s)throw new TypeError("Expected a function");function invokeFunc(o){var i=j,u=L;return j=L=void 0,z=o,$=s.apply(u,i)}function shouldInvoke(s){var i=s-U;return void 0===U||i>=o||i<0||Z&&s-z>=B}function timerExpired(){var s=_();if(shouldInvoke(s))return trailingEdge(s);V=setTimeout(timerExpired,function remainingWait(s){var i=o-(s-U);return Z?C(i,B-(s-z)):i}(s))}function trailingEdge(s){return V=void 0,ee&&j?invokeFunc(s):(j=L=void 0,$)}function debounced(){var s=_(),i=shouldInvoke(s);if(j=arguments,L=this,U=s,i){if(void 0===V)return function leadingEdge(s){return z=s,V=setTimeout(timerExpired,o),Y?invokeFunc(s):$}(U);if(Z)return clearTimeout(V),V=setTimeout(timerExpired,o),invokeFunc(U)}return void 0===V&&(V=setTimeout(timerExpired,o)),$}return o=w(o)||0,u(i)&&(Y=!!i.leading,B=(Z="maxWait"in i)?x(w(i.maxWait)||0,o):B,ee="trailing"in i?!!i.trailing:ee),debounced.cancel=function cancel(){void 0!==V&&clearTimeout(V),z=0,j=U=L=V=void 0},debounced.flush=function flush(){return void 0===V?$:trailingEdge(_())},debounced}},50828:(s,o,i)=>{var u=i(24647),_=i(13222),w=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,x=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]","g");s.exports=function deburr(s){return(s=_(s))&&s.replace(w,u).replace(x,"")}},75288:s=>{s.exports=function eq(s,o){return s===o||s!=s&&o!=o}},60680:(s,o,i)=>{var u=i(13222),_=/[\\^$.*+?()[\]{}|]/g,w=RegExp(_.source);s.exports=function escapeRegExp(s){return(s=u(s))&&w.test(s)?s.replace(_,"\\$&"):s}},7309:(s,o,i)=>{var u=i(62006)(i(24713));s.exports=u},24713:(s,o,i)=>{var u=i(2523),_=i(15389),w=i(61489),x=Math.max;s.exports=function findIndex(s,o,i){var C=null==s?0:s.length;if(!C)return-1;var j=null==i?0:w(i);return j<0&&(j=x(C+j,0)),u(s,_(o,3),j)}},35970:(s,o,i)=>{var u=i(83120);s.exports=function flatten(s){return(null==s?0:s.length)?u(s,1):[]}},73424:(s,o,i)=>{var u=i(16962),_=i(2874),w=Array.prototype.push;function baseAry(s,o){return 2==o?function(o,i){return s(o,i)}:function(o){return s(o)}}function cloneArray(s){for(var o=s?s.length:0,i=Array(o);o--;)i[o]=s[o];return i}function wrapImmutable(s,o){return function(){var i=arguments.length;if(i){for(var u=Array(i);i--;)u[i]=arguments[i];var _=u[0]=o.apply(void 0,u);return s.apply(void 0,u),_}}}s.exports=function baseConvert(s,o,i,x){var C="function"==typeof o,j=o===Object(o);if(j&&(x=i,i=o,o=void 0),null==i)throw new TypeError;x||(x={});var L=!("cap"in x)||x.cap,B=!("curry"in x)||x.curry,$=!("fixed"in x)||x.fixed,V=!("immutable"in x)||x.immutable,U=!("rearg"in x)||x.rearg,z=C?i:_,Y="curry"in x&&x.curry,Z="fixed"in x&&x.fixed,ee="rearg"in x&&x.rearg,ie=C?i.runInContext():void 0,ae=C?i:{ary:s.ary,assign:s.assign,clone:s.clone,curry:s.curry,forEach:s.forEach,isArray:s.isArray,isError:s.isError,isFunction:s.isFunction,isWeakMap:s.isWeakMap,iteratee:s.iteratee,keys:s.keys,rearg:s.rearg,toInteger:s.toInteger,toPath:s.toPath},le=ae.ary,ce=ae.assign,pe=ae.clone,de=ae.curry,fe=ae.forEach,ye=ae.isArray,be=ae.isError,_e=ae.isFunction,we=ae.isWeakMap,Se=ae.keys,xe=ae.rearg,Pe=ae.toInteger,Te=ae.toPath,Re=Se(u.aryMethod),qe={castArray:function(s){return function(){var o=arguments[0];return ye(o)?s(cloneArray(o)):s.apply(void 0,arguments)}},iteratee:function(s){return function(){var o=arguments[1],i=s(arguments[0],o),u=i.length;return L&&"number"==typeof o?(o=o>2?o-2:1,u&&u<=o?i:baseAry(i,o)):i}},mixin:function(s){return function(o){var i=this;if(!_e(i))return s(i,Object(o));var u=[];return fe(Se(o),(function(s){_e(o[s])&&u.push([s,i.prototype[s]])})),s(i,Object(o)),fe(u,(function(s){var o=s[1];_e(o)?i.prototype[s[0]]=o:delete i.prototype[s[0]]})),i}},nthArg:function(s){return function(o){var i=o<0?1:Pe(o)+1;return de(s(o),i)}},rearg:function(s){return function(o,i){var u=i?i.length:0;return de(s(o,i),u)}},runInContext:function(o){return function(i){return baseConvert(s,o(i),x)}}};function castCap(s,o){if(L){var i=u.iterateeRearg[s];if(i)return function iterateeRearg(s,o){return overArg(s,(function(s){var i=o.length;return function baseArity(s,o){return 2==o?function(o,i){return s.apply(void 0,arguments)}:function(o){return s.apply(void 0,arguments)}}(xe(baseAry(s,i),o),i)}))}(o,i);var _=!C&&u.iterateeAry[s];if(_)return function iterateeAry(s,o){return overArg(s,(function(s){return"function"==typeof s?baseAry(s,o):s}))}(o,_)}return o}function castFixed(s,o,i){if($&&(Z||!u.skipFixed[s])){var _=u.methodSpread[s],x=_&&_.start;return void 0===x?le(o,i):function flatSpread(s,o){return function(){for(var i=arguments.length,u=i-1,_=Array(i);i--;)_[i]=arguments[i];var x=_[o],C=_.slice(0,o);return x&&w.apply(C,x),o!=u&&w.apply(C,_.slice(o+1)),s.apply(this,C)}}(o,x)}return o}function castRearg(s,o,i){return U&&i>1&&(ee||!u.skipRearg[s])?xe(o,u.methodRearg[s]||u.aryRearg[i]):o}function cloneByPath(s,o){for(var i=-1,u=(o=Te(o)).length,_=u-1,w=pe(Object(s)),x=w;null!=x&&++i1?de(o,i):o}(0,_=castCap(w,_),s),!1}})),!_})),_||(_=x),_==o&&(_=Y?de(_,1):function(){return o.apply(this,arguments)}),_.convert=createConverter(w,o),_.placeholder=o.placeholder=i,_}if(!j)return wrap(o,i,z);var $e=i,ze=[];return fe(Re,(function(s){fe(u.aryMethod[s],(function(s){var o=$e[u.remap[s]||s];o&&ze.push([s,wrap(s,o,$e)])}))})),fe(Se($e),(function(s){var o=$e[s];if("function"==typeof o){for(var i=ze.length;i--;)if(ze[i][0]==s)return;o.convert=createConverter(s,o),ze.push([s,o])}})),fe(ze,(function(s){$e[s[0]]=s[1]})),$e.convert=function convertLib(s){return $e.runInContext.convert(s)(void 0)},$e.placeholder=$e,fe(Se($e),(function(s){fe(u.realToAlias[s]||[],(function(o){$e[o]=$e[s]}))})),$e}},16962:(s,o)=>{o.aliasToReal={each:"forEach",eachRight:"forEachRight",entries:"toPairs",entriesIn:"toPairsIn",extend:"assignIn",extendAll:"assignInAll",extendAllWith:"assignInAllWith",extendWith:"assignInWith",first:"head",conforms:"conformsTo",matches:"isMatch",property:"get",__:"placeholder",F:"stubFalse",T:"stubTrue",all:"every",allPass:"overEvery",always:"constant",any:"some",anyPass:"overSome",apply:"spread",assoc:"set",assocPath:"set",complement:"negate",compose:"flowRight",contains:"includes",dissoc:"unset",dissocPath:"unset",dropLast:"dropRight",dropLastWhile:"dropRightWhile",equals:"isEqual",identical:"eq",indexBy:"keyBy",init:"initial",invertObj:"invert",juxt:"over",omitAll:"omit",nAry:"ary",path:"get",pathEq:"matchesProperty",pathOr:"getOr",paths:"at",pickAll:"pick",pipe:"flow",pluck:"map",prop:"get",propEq:"matchesProperty",propOr:"getOr",props:"at",symmetricDifference:"xor",symmetricDifferenceBy:"xorBy",symmetricDifferenceWith:"xorWith",takeLast:"takeRight",takeLastWhile:"takeRightWhile",unapply:"rest",unnest:"flatten",useWith:"overArgs",where:"conformsTo",whereEq:"isMatch",zipObj:"zipObject"},o.aryMethod={1:["assignAll","assignInAll","attempt","castArray","ceil","create","curry","curryRight","defaultsAll","defaultsDeepAll","floor","flow","flowRight","fromPairs","invert","iteratee","memoize","method","mergeAll","methodOf","mixin","nthArg","over","overEvery","overSome","rest","reverse","round","runInContext","spread","template","trim","trimEnd","trimStart","uniqueId","words","zipAll"],2:["add","after","ary","assign","assignAllWith","assignIn","assignInAllWith","at","before","bind","bindAll","bindKey","chunk","cloneDeepWith","cloneWith","concat","conformsTo","countBy","curryN","curryRightN","debounce","defaults","defaultsDeep","defaultTo","delay","difference","divide","drop","dropRight","dropRightWhile","dropWhile","endsWith","eq","every","filter","find","findIndex","findKey","findLast","findLastIndex","findLastKey","flatMap","flatMapDeep","flattenDepth","forEach","forEachRight","forIn","forInRight","forOwn","forOwnRight","get","groupBy","gt","gte","has","hasIn","includes","indexOf","intersection","invertBy","invoke","invokeMap","isEqual","isMatch","join","keyBy","lastIndexOf","lt","lte","map","mapKeys","mapValues","matchesProperty","maxBy","meanBy","merge","mergeAllWith","minBy","multiply","nth","omit","omitBy","overArgs","pad","padEnd","padStart","parseInt","partial","partialRight","partition","pick","pickBy","propertyOf","pull","pullAll","pullAt","random","range","rangeRight","rearg","reject","remove","repeat","restFrom","result","sampleSize","some","sortBy","sortedIndex","sortedIndexOf","sortedLastIndex","sortedLastIndexOf","sortedUniqBy","split","spreadFrom","startsWith","subtract","sumBy","take","takeRight","takeRightWhile","takeWhile","tap","throttle","thru","times","trimChars","trimCharsEnd","trimCharsStart","truncate","union","uniqBy","uniqWith","unset","unzipWith","without","wrap","xor","zip","zipObject","zipObjectDeep"],3:["assignInWith","assignWith","clamp","differenceBy","differenceWith","findFrom","findIndexFrom","findLastFrom","findLastIndexFrom","getOr","includesFrom","indexOfFrom","inRange","intersectionBy","intersectionWith","invokeArgs","invokeArgsMap","isEqualWith","isMatchWith","flatMapDepth","lastIndexOfFrom","mergeWith","orderBy","padChars","padCharsEnd","padCharsStart","pullAllBy","pullAllWith","rangeStep","rangeStepRight","reduce","reduceRight","replace","set","slice","sortedIndexBy","sortedLastIndexBy","transform","unionBy","unionWith","update","xorBy","xorWith","zipWith"],4:["fill","setWith","updateWith"]},o.aryRearg={2:[1,0],3:[2,0,1],4:[3,2,0,1]},o.iterateeAry={dropRightWhile:1,dropWhile:1,every:1,filter:1,find:1,findFrom:1,findIndex:1,findIndexFrom:1,findKey:1,findLast:1,findLastFrom:1,findLastIndex:1,findLastIndexFrom:1,findLastKey:1,flatMap:1,flatMapDeep:1,flatMapDepth:1,forEach:1,forEachRight:1,forIn:1,forInRight:1,forOwn:1,forOwnRight:1,map:1,mapKeys:1,mapValues:1,partition:1,reduce:2,reduceRight:2,reject:1,remove:1,some:1,takeRightWhile:1,takeWhile:1,times:1,transform:2},o.iterateeRearg={mapKeys:[1],reduceRight:[1,0]},o.methodRearg={assignInAllWith:[1,0],assignInWith:[1,2,0],assignAllWith:[1,0],assignWith:[1,2,0],differenceBy:[1,2,0],differenceWith:[1,2,0],getOr:[2,1,0],intersectionBy:[1,2,0],intersectionWith:[1,2,0],isEqualWith:[1,2,0],isMatchWith:[2,1,0],mergeAllWith:[1,0],mergeWith:[1,2,0],padChars:[2,1,0],padCharsEnd:[2,1,0],padCharsStart:[2,1,0],pullAllBy:[2,1,0],pullAllWith:[2,1,0],rangeStep:[1,2,0],rangeStepRight:[1,2,0],setWith:[3,1,2,0],sortedIndexBy:[2,1,0],sortedLastIndexBy:[2,1,0],unionBy:[1,2,0],unionWith:[1,2,0],updateWith:[3,1,2,0],xorBy:[1,2,0],xorWith:[1,2,0],zipWith:[1,2,0]},o.methodSpread={assignAll:{start:0},assignAllWith:{start:0},assignInAll:{start:0},assignInAllWith:{start:0},defaultsAll:{start:0},defaultsDeepAll:{start:0},invokeArgs:{start:2},invokeArgsMap:{start:2},mergeAll:{start:0},mergeAllWith:{start:0},partial:{start:1},partialRight:{start:1},without:{start:1},zipAll:{start:0}},o.mutate={array:{fill:!0,pull:!0,pullAll:!0,pullAllBy:!0,pullAllWith:!0,pullAt:!0,remove:!0,reverse:!0},object:{assign:!0,assignAll:!0,assignAllWith:!0,assignIn:!0,assignInAll:!0,assignInAllWith:!0,assignInWith:!0,assignWith:!0,defaults:!0,defaultsAll:!0,defaultsDeep:!0,defaultsDeepAll:!0,merge:!0,mergeAll:!0,mergeAllWith:!0,mergeWith:!0},set:{set:!0,setWith:!0,unset:!0,update:!0,updateWith:!0}},o.realToAlias=function(){var s=Object.prototype.hasOwnProperty,i=o.aliasToReal,u={};for(var _ in i){var w=i[_];s.call(u,w)?u[w].push(_):u[w]=[_]}return u}(),o.remap={assignAll:"assign",assignAllWith:"assignWith",assignInAll:"assignIn",assignInAllWith:"assignInWith",curryN:"curry",curryRightN:"curryRight",defaultsAll:"defaults",defaultsDeepAll:"defaultsDeep",findFrom:"find",findIndexFrom:"findIndex",findLastFrom:"findLast",findLastIndexFrom:"findLastIndex",getOr:"get",includesFrom:"includes",indexOfFrom:"indexOf",invokeArgs:"invoke",invokeArgsMap:"invokeMap",lastIndexOfFrom:"lastIndexOf",mergeAll:"merge",mergeAllWith:"mergeWith",padChars:"pad",padCharsEnd:"padEnd",padCharsStart:"padStart",propertyOf:"get",rangeStep:"range",rangeStepRight:"rangeRight",restFrom:"rest",spreadFrom:"spread",trimChars:"trim",trimCharsEnd:"trimEnd",trimCharsStart:"trimStart",zipAll:"zip"},o.skipFixed={castArray:!0,flow:!0,flowRight:!0,iteratee:!0,mixin:!0,rearg:!0,runInContext:!0},o.skipRearg={add:!0,assign:!0,assignIn:!0,bind:!0,bindKey:!0,concat:!0,difference:!0,divide:!0,eq:!0,gt:!0,gte:!0,isEqual:!0,lt:!0,lte:!0,matchesProperty:!0,merge:!0,multiply:!0,overArgs:!0,partial:!0,partialRight:!0,propertyOf:!0,random:!0,range:!0,rangeRight:!0,subtract:!0,zip:!0,zipObject:!0,zipObjectDeep:!0}},47934:(s,o,i)=>{s.exports={ary:i(64626),assign:i(74733),clone:i(32629),curry:i(49747),forEach:i(83729),isArray:i(56449),isError:i(23546),isFunction:i(1882),isWeakMap:i(47886),iteratee:i(33855),keys:i(88984),rearg:i(84195),toInteger:i(61489),toPath:i(42072)}},56367:(s,o,i)=>{s.exports=i(77731)},79920:(s,o,i)=>{var u=i(73424),_=i(47934);s.exports=function convert(s,o,i){return u(_,s,o,i)}},2874:s=>{s.exports={}},77731:(s,o,i)=>{var u=i(79920)("set",i(63560));u.placeholder=i(2874),s.exports=u},58156:(s,o,i)=>{var u=i(47422);s.exports=function get(s,o,i){var _=null==s?void 0:u(s,o);return void 0===_?i:_}},61448:(s,o,i)=>{var u=i(20426),_=i(49326);s.exports=function has(s,o){return null!=s&&_(s,o,u)}},80631:(s,o,i)=>{var u=i(28077),_=i(49326);s.exports=function hasIn(s,o){return null!=s&&_(s,o,u)}},83488:s=>{s.exports=function identity(s){return s}},72428:(s,o,i)=>{var u=i(27534),_=i(40346),w=Object.prototype,x=w.hasOwnProperty,C=w.propertyIsEnumerable,j=u(function(){return arguments}())?u:function(s){return _(s)&&x.call(s,"callee")&&!C.call(s,"callee")};s.exports=j},56449:s=>{var o=Array.isArray;s.exports=o},64894:(s,o,i)=>{var u=i(1882),_=i(30294);s.exports=function isArrayLike(s){return null!=s&&_(s.length)&&!u(s)}},83693:(s,o,i)=>{var u=i(64894),_=i(40346);s.exports=function isArrayLikeObject(s){return _(s)&&u(s)}},53812:(s,o,i)=>{var u=i(72552),_=i(40346);s.exports=function isBoolean(s){return!0===s||!1===s||_(s)&&"[object Boolean]"==u(s)}},3656:(s,o,i)=>{s=i.nmd(s);var u=i(9325),_=i(89935),w=o&&!o.nodeType&&o,x=w&&s&&!s.nodeType&&s,C=x&&x.exports===w?u.Buffer:void 0,j=(C?C.isBuffer:void 0)||_;s.exports=j},62193:(s,o,i)=>{var u=i(88984),_=i(5861),w=i(72428),x=i(56449),C=i(64894),j=i(3656),L=i(55527),B=i(37167),$=Object.prototype.hasOwnProperty;s.exports=function isEmpty(s){if(null==s)return!0;if(C(s)&&(x(s)||"string"==typeof s||"function"==typeof s.splice||j(s)||B(s)||w(s)))return!s.length;var o=_(s);if("[object Map]"==o||"[object Set]"==o)return!s.size;if(L(s))return!u(s).length;for(var i in s)if($.call(s,i))return!1;return!0}},2404:(s,o,i)=>{var u=i(60270);s.exports=function isEqual(s,o){return u(s,o)}},23546:(s,o,i)=>{var u=i(72552),_=i(40346),w=i(11331);s.exports=function isError(s){if(!_(s))return!1;var o=u(s);return"[object Error]"==o||"[object DOMException]"==o||"string"==typeof s.message&&"string"==typeof s.name&&!w(s)}},1882:(s,o,i)=>{var u=i(72552),_=i(23805);s.exports=function isFunction(s){if(!_(s))return!1;var o=u(s);return"[object Function]"==o||"[object GeneratorFunction]"==o||"[object AsyncFunction]"==o||"[object Proxy]"==o}},30294:s=>{s.exports=function isLength(s){return"number"==typeof s&&s>-1&&s%1==0&&s<=9007199254740991}},87730:(s,o,i)=>{var u=i(29172),_=i(27301),w=i(86009),x=w&&w.isMap,C=x?_(x):u;s.exports=C},5187:s=>{s.exports=function isNull(s){return null===s}},98023:(s,o,i)=>{var u=i(72552),_=i(40346);s.exports=function isNumber(s){return"number"==typeof s||_(s)&&"[object Number]"==u(s)}},23805:s=>{s.exports=function isObject(s){var o=typeof s;return null!=s&&("object"==o||"function"==o)}},40346:s=>{s.exports=function isObjectLike(s){return null!=s&&"object"==typeof s}},11331:(s,o,i)=>{var u=i(72552),_=i(28879),w=i(40346),x=Function.prototype,C=Object.prototype,j=x.toString,L=C.hasOwnProperty,B=j.call(Object);s.exports=function isPlainObject(s){if(!w(s)||"[object Object]"!=u(s))return!1;var o=_(s);if(null===o)return!0;var i=L.call(o,"constructor")&&o.constructor;return"function"==typeof i&&i instanceof i&&j.call(i)==B}},38440:(s,o,i)=>{var u=i(16038),_=i(27301),w=i(86009),x=w&&w.isSet,C=x?_(x):u;s.exports=C},85015:(s,o,i)=>{var u=i(72552),_=i(56449),w=i(40346);s.exports=function isString(s){return"string"==typeof s||!_(s)&&w(s)&&"[object String]"==u(s)}},44394:(s,o,i)=>{var u=i(72552),_=i(40346);s.exports=function isSymbol(s){return"symbol"==typeof s||_(s)&&"[object Symbol]"==u(s)}},37167:(s,o,i)=>{var u=i(4901),_=i(27301),w=i(86009),x=w&&w.isTypedArray,C=x?_(x):u;s.exports=C},47886:(s,o,i)=>{var u=i(5861),_=i(40346);s.exports=function isWeakMap(s){return _(s)&&"[object WeakMap]"==u(s)}},33855:(s,o,i)=>{var u=i(9999),_=i(15389);s.exports=function iteratee(s){return _("function"==typeof s?s:u(s,1))}},95950:(s,o,i)=>{var u=i(70695),_=i(88984),w=i(64894);s.exports=function keys(s){return w(s)?u(s):_(s)}},37241:(s,o,i)=>{var u=i(70695),_=i(72903),w=i(64894);s.exports=function keysIn(s){return w(s)?u(s,!0):_(s)}},68090:s=>{s.exports=function last(s){var o=null==s?0:s.length;return o?s[o-1]:void 0}},50104:(s,o,i)=>{var u=i(53661);function memoize(s,o){if("function"!=typeof s||null!=o&&"function"!=typeof o)throw new TypeError("Expected a function");var memoized=function(){var i=arguments,u=o?o.apply(this,i):i[0],_=memoized.cache;if(_.has(u))return _.get(u);var w=s.apply(this,i);return memoized.cache=_.set(u,w)||_,w};return memoized.cache=new(memoize.Cache||u),memoized}memoize.Cache=u,s.exports=memoize},55364:(s,o,i)=>{var u=i(85250),_=i(20999)((function(s,o,i){u(s,o,i)}));s.exports=_},6048:s=>{s.exports=function negate(s){if("function"!=typeof s)throw new TypeError("Expected a function");return function(){var o=arguments;switch(o.length){case 0:return!s.call(this);case 1:return!s.call(this,o[0]);case 2:return!s.call(this,o[0],o[1]);case 3:return!s.call(this,o[0],o[1],o[2])}return!s.apply(this,o)}}},63950:s=>{s.exports=function noop(){}},10124:(s,o,i)=>{var u=i(9325);s.exports=function(){return u.Date.now()}},90179:(s,o,i)=>{var u=i(34932),_=i(9999),w=i(19931),x=i(31769),C=i(21791),j=i(53138),L=i(38816),B=i(83349),$=L((function(s,o){var i={};if(null==s)return i;var L=!1;o=u(o,(function(o){return o=x(o,s),L||(L=o.length>1),o})),C(s,B(s),i),L&&(i=_(i,7,j));for(var $=o.length;$--;)w(i,o[$]);return i}));s.exports=$},50583:(s,o,i)=>{var u=i(47237),_=i(17255),w=i(28586),x=i(77797);s.exports=function property(s){return w(s)?u(x(s)):_(s)}},84195:(s,o,i)=>{var u=i(66977),_=i(38816),w=_((function(s,o){return u(s,256,void 0,void 0,void 0,o)}));s.exports=w},40860:(s,o,i)=>{var u=i(40882),_=i(80909),w=i(15389),x=i(85558),C=i(56449);s.exports=function reduce(s,o,i){var j=C(s)?u:x,L=arguments.length<3;return j(s,w(o,4),i,L,_)}},63560:(s,o,i)=>{var u=i(73170);s.exports=function set(s,o,i){return null==s?s:u(s,o,i)}},42426:(s,o,i)=>{var u=i(14248),_=i(15389),w=i(90916),x=i(56449),C=i(36800);s.exports=function some(s,o,i){var j=x(s)?u:w;return i&&C(s,o,i)&&(o=void 0),j(s,_(o,3))}},63345:s=>{s.exports=function stubArray(){return[]}},89935:s=>{s.exports=function stubFalse(){return!1}},17400:(s,o,i)=>{var u=i(99374),_=1/0;s.exports=function toFinite(s){return s?(s=u(s))===_||s===-1/0?17976931348623157e292*(s<0?-1:1):s==s?s:0:0===s?s:0}},61489:(s,o,i)=>{var u=i(17400);s.exports=function toInteger(s){var o=u(s),i=o%1;return o==o?i?o-i:o:0}},80218:(s,o,i)=>{var u=i(13222);s.exports=function toLower(s){return u(s).toLowerCase()}},99374:(s,o,i)=>{var u=i(54128),_=i(23805),w=i(44394),x=/^[-+]0x[0-9a-f]+$/i,C=/^0b[01]+$/i,j=/^0o[0-7]+$/i,L=parseInt;s.exports=function toNumber(s){if("number"==typeof s)return s;if(w(s))return NaN;if(_(s)){var o="function"==typeof s.valueOf?s.valueOf():s;s=_(o)?o+"":o}if("string"!=typeof s)return 0===s?s:+s;s=u(s);var i=C.test(s);return i||j.test(s)?L(s.slice(2),i?2:8):x.test(s)?NaN:+s}},42072:(s,o,i)=>{var u=i(34932),_=i(23007),w=i(56449),x=i(44394),C=i(61802),j=i(77797),L=i(13222);s.exports=function toPath(s){return w(s)?u(s,j):x(s)?[s]:_(C(L(s)))}},69884:(s,o,i)=>{var u=i(21791),_=i(37241);s.exports=function toPlainObject(s){return u(s,_(s))}},13222:(s,o,i)=>{var u=i(77556);s.exports=function toString(s){return null==s?"":u(s)}},55808:(s,o,i)=>{var u=i(12507)("toUpperCase");s.exports=u},66645:(s,o,i)=>{var u=i(1733),_=i(45434),w=i(13222),x=i(22225);s.exports=function words(s,o,i){return s=w(s),void 0===(o=i?void 0:o)?_(s)?x(s):u(s):s.match(o)||[]}},53758:(s,o,i)=>{var u=i(30980),_=i(56017),w=i(94033),x=i(56449),C=i(40346),j=i(80257),L=Object.prototype.hasOwnProperty;function lodash(s){if(C(s)&&!x(s)&&!(s instanceof u)){if(s instanceof _)return s;if(L.call(s,"__wrapped__"))return j(s)}return new _(s)}lodash.prototype=w.prototype,lodash.prototype.constructor=lodash,s.exports=lodash},47248:(s,o,i)=>{var u=i(16547),_=i(51234);s.exports=function zipObject(s,o){return _(s||[],o||[],u)}},43768:(s,o,i)=>{"use strict";var u=i(45981),_=i(85587);o.highlight=highlight,o.highlightAuto=function highlightAuto(s,o){var i,x,C,j,L=o||{},B=L.subset||u.listLanguages(),$=L.prefix,V=B.length,U=-1;null==$&&($=w);if("string"!=typeof s)throw _("Expected `string` for value, got `%s`",s);x={relevance:0,language:null,value:[]},i={relevance:0,language:null,value:[]};for(;++Ux.relevance&&(x=C),C.relevance>i.relevance&&(x=i,i=C));x.language&&(i.secondBest=x);return i},o.registerLanguage=function registerLanguage(s,o){u.registerLanguage(s,o)},o.listLanguages=function listLanguages(){return u.listLanguages()},o.registerAlias=function registerAlias(s,o){var i,_=s;o&&((_={})[s]=o);for(i in _)u.registerAliases(_[i],{languageName:i})},Emitter.prototype.addText=function text(s){var o,i,u=this.stack;if(""===s)return;o=u[u.length-1],(i=o.children[o.children.length-1])&&"text"===i.type?i.value+=s:o.children.push({type:"text",value:s})},Emitter.prototype.addKeyword=function addKeyword(s,o){this.openNode(o),this.addText(s),this.closeNode()},Emitter.prototype.addSublanguage=function addSublanguage(s,o){var i=this.stack,u=i[i.length-1],_=s.rootNode.children,w=o?{type:"element",tagName:"span",properties:{className:[o]},children:_}:_;u.children=u.children.concat(w)},Emitter.prototype.openNode=function open(s){var o=this.stack,i=this.options.classPrefix+s,u=o[o.length-1],_={type:"element",tagName:"span",properties:{className:[i]},children:[]};u.children.push(_),o.push(_)},Emitter.prototype.closeNode=function close(){this.stack.pop()},Emitter.prototype.closeAllNodes=noop,Emitter.prototype.finalize=noop,Emitter.prototype.toHTML=function toHtmlNoop(){return""};var w="hljs-";function highlight(s,o,i){var x,C=u.configure({}),j=(i||{}).prefix;if("string"!=typeof s)throw _("Expected `string` for name, got `%s`",s);if(!u.getLanguage(s))throw _("Unknown language: `%s` is not registered",s);if("string"!=typeof o)throw _("Expected `string` for value, got `%s`",o);if(null==j&&(j=w),u.configure({__emitter:Emitter,classPrefix:j}),x=u.highlight(o,{language:s,ignoreIllegals:!0}),u.configure(C||{}),x.errorRaised)throw x.errorRaised;return{relevance:x.relevance,language:x.language,value:x.emitter.rootNode.children}}function Emitter(s){this.options=s,this.rootNode={children:[]},this.stack=[this.rootNode]}function noop(){}},92340:(s,o,i)=>{const u=i(6048);function coerceElementMatchingCallback(s){return"string"==typeof s?o=>o.element===s:s.constructor&&s.extend?o=>o instanceof s:s}class ArraySlice{constructor(s){this.elements=s||[]}toValue(){return this.elements.map((s=>s.toValue()))}map(s,o){return this.elements.map(s,o)}flatMap(s,o){return this.map(s,o).reduce(((s,o)=>s.concat(o)),[])}compactMap(s,o){const i=[];return this.forEach((u=>{const _=s.bind(o)(u);_&&i.push(_)})),i}filter(s,o){return s=coerceElementMatchingCallback(s),new ArraySlice(this.elements.filter(s,o))}reject(s,o){return s=coerceElementMatchingCallback(s),new ArraySlice(this.elements.filter(u(s),o))}find(s,o){return s=coerceElementMatchingCallback(s),this.elements.find(s,o)}forEach(s,o){this.elements.forEach(s,o)}reduce(s,o){return this.elements.reduce(s,o)}includes(s){return this.elements.some((o=>o.equals(s)))}shift(){return this.elements.shift()}unshift(s){this.elements.unshift(this.refract(s))}push(s){return this.elements.push(this.refract(s)),this}add(s){this.push(s)}get(s){return this.elements[s]}getValue(s){const o=this.elements[s];if(o)return o.toValue()}get length(){return this.elements.length}get isEmpty(){return 0===this.elements.length}get first(){return this.elements[0]}}"undefined"!=typeof Symbol&&(ArraySlice.prototype[Symbol.iterator]=function symbol(){return this.elements[Symbol.iterator]()}),s.exports=ArraySlice},55973:s=>{class KeyValuePair{constructor(s,o){this.key=s,this.value=o}clone(){const s=new KeyValuePair;return this.key&&(s.key=this.key.clone()),this.value&&(s.value=this.value.clone()),s}}s.exports=KeyValuePair},3110:(s,o,i)=>{const u=i(5187),_=i(85015),w=i(98023),x=i(53812),C=i(23805),j=i(85105),L=i(86804);class Namespace{constructor(s){this.elementMap={},this.elementDetection=[],this.Element=L.Element,this.KeyValuePair=L.KeyValuePair,s&&s.noDefault||this.useDefault(),this._attributeElementKeys=[],this._attributeElementArrayKeys=[]}use(s){return s.namespace&&s.namespace({base:this}),s.load&&s.load({base:this}),this}useDefault(){return this.register("null",L.NullElement).register("string",L.StringElement).register("number",L.NumberElement).register("boolean",L.BooleanElement).register("array",L.ArrayElement).register("object",L.ObjectElement).register("member",L.MemberElement).register("ref",L.RefElement).register("link",L.LinkElement),this.detect(u,L.NullElement,!1).detect(_,L.StringElement,!1).detect(w,L.NumberElement,!1).detect(x,L.BooleanElement,!1).detect(Array.isArray,L.ArrayElement,!1).detect(C,L.ObjectElement,!1),this}register(s,o){return this._elements=void 0,this.elementMap[s]=o,this}unregister(s){return this._elements=void 0,delete this.elementMap[s],this}detect(s,o,i){return void 0===i||i?this.elementDetection.unshift([s,o]):this.elementDetection.push([s,o]),this}toElement(s){if(s instanceof this.Element)return s;let o;for(let i=0;i{const o=s[0].toUpperCase()+s.substr(1);this._elements[o]=this.elementMap[s]}))),this._elements}get serialiser(){return new j(this)}}j.prototype.Namespace=Namespace,s.exports=Namespace},10866:(s,o,i)=>{const u=i(6048),_=i(92340);class ObjectSlice extends _{map(s,o){return this.elements.map((i=>s.bind(o)(i.value,i.key,i)))}filter(s,o){return new ObjectSlice(this.elements.filter((i=>s.bind(o)(i.value,i.key,i))))}reject(s,o){return this.filter(u(s.bind(o)))}forEach(s,o){return this.elements.forEach(((i,u)=>{s.bind(o)(i.value,i.key,i,u)}))}keys(){return this.map(((s,o)=>o.toValue()))}values(){return this.map((s=>s.toValue()))}}s.exports=ObjectSlice},86804:(s,o,i)=>{const u=i(10316),_=i(41067),w=i(71167),x=i(40239),C=i(12242),j=i(6233),L=i(87726),B=i(61045),$=i(86303),V=i(14540),U=i(92340),z=i(10866),Y=i(55973);function refract(s){if(s instanceof u)return s;if("string"==typeof s)return new w(s);if("number"==typeof s)return new x(s);if("boolean"==typeof s)return new C(s);if(null===s)return new _;if(Array.isArray(s))return new j(s.map(refract));if("object"==typeof s){return new B(s)}return s}u.prototype.ObjectElement=B,u.prototype.RefElement=V,u.prototype.MemberElement=L,u.prototype.refract=refract,U.prototype.refract=refract,s.exports={Element:u,NullElement:_,StringElement:w,NumberElement:x,BooleanElement:C,ArrayElement:j,MemberElement:L,ObjectElement:B,LinkElement:$,RefElement:V,refract,ArraySlice:U,ObjectSlice:z,KeyValuePair:Y}},86303:(s,o,i)=>{const u=i(10316);s.exports=class LinkElement extends u{constructor(s,o,i){super(s||[],o,i),this.element="link"}get relation(){return this.attributes.get("relation")}set relation(s){this.attributes.set("relation",s)}get href(){return this.attributes.get("href")}set href(s){this.attributes.set("href",s)}}},14540:(s,o,i)=>{const u=i(10316);s.exports=class RefElement extends u{constructor(s,o,i){super(s||[],o,i),this.element="ref",this.path||(this.path="element")}get path(){return this.attributes.get("path")}set path(s){this.attributes.set("path",s)}}},34035:(s,o,i)=>{const u=i(3110),_=i(86804);o.g$=u,o.KeyValuePair=i(55973),o.G6=_.ArraySlice,o.ot=_.ObjectSlice,o.Hg=_.Element,o.Om=_.StringElement,o.kT=_.NumberElement,o.bd=_.BooleanElement,o.Os=_.NullElement,o.wE=_.ArrayElement,o.Sh=_.ObjectElement,o.Pr=_.MemberElement,o.sI=_.RefElement,o.Ft=_.LinkElement,o.e=_.refract,i(85105),i(75147)},6233:(s,o,i)=>{const u=i(6048),_=i(10316),w=i(92340);class ArrayElement extends _{constructor(s,o,i){super(s||[],o,i),this.element="array"}primitive(){return"array"}get(s){return this.content[s]}getValue(s){const o=this.get(s);if(o)return o.toValue()}getIndex(s){return this.content[s]}set(s,o){return this.content[s]=this.refract(o),this}remove(s){const o=this.content.splice(s,1);return o.length?o[0]:null}map(s,o){return this.content.map(s,o)}flatMap(s,o){return this.map(s,o).reduce(((s,o)=>s.concat(o)),[])}compactMap(s,o){const i=[];return this.forEach((u=>{const _=s.bind(o)(u);_&&i.push(_)})),i}filter(s,o){return new w(this.content.filter(s,o))}reject(s,o){return this.filter(u(s),o)}reduce(s,o){let i,u;void 0!==o?(i=0,u=this.refract(o)):(i=1,u="object"===this.primitive()?this.first.value:this.first);for(let o=i;o{s.bind(o)(i,this.refract(u))}))}shift(){return this.content.shift()}unshift(s){this.content.unshift(this.refract(s))}push(s){return this.content.push(this.refract(s)),this}add(s){this.push(s)}findElements(s,o){const i=o||{},u=!!i.recursive,_=void 0===i.results?[]:i.results;return this.forEach(((o,i,w)=>{u&&void 0!==o.findElements&&o.findElements(s,{results:_,recursive:u}),s(o,i,w)&&_.push(o)})),_}find(s){return new w(this.findElements(s,{recursive:!0}))}findByElement(s){return this.find((o=>o.element===s))}findByClass(s){return this.find((o=>o.classes.includes(s)))}getById(s){return this.find((o=>o.id.toValue()===s)).first}includes(s){return this.content.some((o=>o.equals(s)))}contains(s){return this.includes(s)}empty(){return new this.constructor([])}"fantasy-land/empty"(){return this.empty()}concat(s){return new this.constructor(this.content.concat(s.content))}"fantasy-land/concat"(s){return this.concat(s)}"fantasy-land/map"(s){return new this.constructor(this.map(s))}"fantasy-land/chain"(s){return this.map((o=>s(o)),this).reduce(((s,o)=>s.concat(o)),this.empty())}"fantasy-land/filter"(s){return new this.constructor(this.content.filter(s))}"fantasy-land/reduce"(s,o){return this.content.reduce(s,o)}get length(){return this.content.length}get isEmpty(){return 0===this.content.length}get first(){return this.getIndex(0)}get second(){return this.getIndex(1)}get last(){return this.getIndex(this.length-1)}}ArrayElement.empty=function empty(){return new this},ArrayElement["fantasy-land/empty"]=ArrayElement.empty,"undefined"!=typeof Symbol&&(ArrayElement.prototype[Symbol.iterator]=function symbol(){return this.content[Symbol.iterator]()}),s.exports=ArrayElement},12242:(s,o,i)=>{const u=i(10316);s.exports=class BooleanElement extends u{constructor(s,o,i){super(s,o,i),this.element="boolean"}primitive(){return"boolean"}}},10316:(s,o,i)=>{const u=i(2404),_=i(55973),w=i(92340);class Element{constructor(s,o,i){o&&(this.meta=o),i&&(this.attributes=i),this.content=s}freeze(){Object.isFrozen(this)||(this._meta&&(this.meta.parent=this,this.meta.freeze()),this._attributes&&(this.attributes.parent=this,this.attributes.freeze()),this.children.forEach((s=>{s.parent=this,s.freeze()}),this),this.content&&Array.isArray(this.content)&&Object.freeze(this.content),Object.freeze(this))}primitive(){}clone(){const s=new this.constructor;return s.element=this.element,this.meta.length&&(s._meta=this.meta.clone()),this.attributes.length&&(s._attributes=this.attributes.clone()),this.content?this.content.clone?s.content=this.content.clone():Array.isArray(this.content)?s.content=this.content.map((s=>s.clone())):s.content=this.content:s.content=this.content,s}toValue(){return this.content instanceof Element?this.content.toValue():this.content instanceof _?{key:this.content.key.toValue(),value:this.content.value?this.content.value.toValue():void 0}:this.content&&this.content.map?this.content.map((s=>s.toValue()),this):this.content}toRef(s){if(""===this.id.toValue())throw Error("Cannot create reference to an element that does not contain an ID");const o=new this.RefElement(this.id.toValue());return s&&(o.path=s),o}findRecursive(...s){if(arguments.length>1&&!this.isFrozen)throw new Error("Cannot find recursive with multiple element names without first freezing the element. Call `element.freeze()`");const o=s.pop();let i=new w;const append=(s,o)=>(s.push(o),s),checkElement=(s,i)=>{i.element===o&&s.push(i);const u=i.findRecursive(o);return u&&u.reduce(append,s),i.content instanceof _&&(i.content.key&&checkElement(s,i.content.key),i.content.value&&checkElement(s,i.content.value)),s};return this.content&&(this.content.element&&checkElement(i,this.content),Array.isArray(this.content)&&this.content.reduce(checkElement,i)),s.isEmpty||(i=i.filter((o=>{let i=o.parents.map((s=>s.element));for(const o in s){const u=s[o],_=i.indexOf(u);if(-1===_)return!1;i=i.splice(0,_)}return!0}))),i}set(s){return this.content=s,this}equals(s){return u(this.toValue(),s)}getMetaProperty(s,o){if(!this.meta.hasKey(s)){if(this.isFrozen){const s=this.refract(o);return s.freeze(),s}this.meta.set(s,o)}return this.meta.get(s)}setMetaProperty(s,o){this.meta.set(s,o)}get element(){return this._storedElement||"element"}set element(s){this._storedElement=s}get content(){return this._content}set content(s){if(s instanceof Element)this._content=s;else if(s instanceof w)this.content=s.elements;else if("string"==typeof s||"number"==typeof s||"boolean"==typeof s||"null"===s||null==s)this._content=s;else if(s instanceof _)this._content=s;else if(Array.isArray(s))this._content=s.map(this.refract);else{if("object"!=typeof s)throw new Error("Cannot set content to given value");this._content=Object.keys(s).map((o=>new this.MemberElement(o,s[o])))}}get meta(){if(!this._meta){if(this.isFrozen){const s=new this.ObjectElement;return s.freeze(),s}this._meta=new this.ObjectElement}return this._meta}set meta(s){s instanceof this.ObjectElement?this._meta=s:this.meta.set(s||{})}get attributes(){if(!this._attributes){if(this.isFrozen){const s=new this.ObjectElement;return s.freeze(),s}this._attributes=new this.ObjectElement}return this._attributes}set attributes(s){s instanceof this.ObjectElement?this._attributes=s:this.attributes.set(s||{})}get id(){return this.getMetaProperty("id","")}set id(s){this.setMetaProperty("id",s)}get classes(){return this.getMetaProperty("classes",[])}set classes(s){this.setMetaProperty("classes",s)}get title(){return this.getMetaProperty("title","")}set title(s){this.setMetaProperty("title",s)}get description(){return this.getMetaProperty("description","")}set description(s){this.setMetaProperty("description",s)}get links(){return this.getMetaProperty("links",[])}set links(s){this.setMetaProperty("links",s)}get isFrozen(){return Object.isFrozen(this)}get parents(){let{parent:s}=this;const o=new w;for(;s;)o.push(s),s=s.parent;return o}get children(){if(Array.isArray(this.content))return new w(this.content);if(this.content instanceof _){const s=new w([this.content.key]);return this.content.value&&s.push(this.content.value),s}return this.content instanceof Element?new w([this.content]):new w}get recursiveChildren(){const s=new w;return this.children.forEach((o=>{s.push(o),o.recursiveChildren.forEach((o=>{s.push(o)}))})),s}}s.exports=Element},87726:(s,o,i)=>{const u=i(55973),_=i(10316);s.exports=class MemberElement extends _{constructor(s,o,i,_){super(new u,i,_),this.element="member",this.key=s,this.value=o}get key(){return this.content.key}set key(s){this.content.key=this.refract(s)}get value(){return this.content.value}set value(s){this.content.value=this.refract(s)}}},41067:(s,o,i)=>{const u=i(10316);s.exports=class NullElement extends u{constructor(s,o,i){super(s||null,o,i),this.element="null"}primitive(){return"null"}set(){return new Error("Cannot set the value of null")}}},40239:(s,o,i)=>{const u=i(10316);s.exports=class NumberElement extends u{constructor(s,o,i){super(s,o,i),this.element="number"}primitive(){return"number"}}},61045:(s,o,i)=>{const u=i(6048),_=i(23805),w=i(6233),x=i(87726),C=i(10866);s.exports=class ObjectElement extends w{constructor(s,o,i){super(s||[],o,i),this.element="object"}primitive(){return"object"}toValue(){return this.content.reduce(((s,o)=>(s[o.key.toValue()]=o.value?o.value.toValue():void 0,s)),{})}get(s){const o=this.getMember(s);if(o)return o.value}getMember(s){if(void 0!==s)return this.content.find((o=>o.key.toValue()===s))}remove(s){let o=null;return this.content=this.content.filter((i=>i.key.toValue()!==s||(o=i,!1))),o}getKey(s){const o=this.getMember(s);if(o)return o.key}set(s,o){if(_(s))return Object.keys(s).forEach((o=>{this.set(o,s[o])})),this;const i=s,u=this.getMember(i);return u?u.value=o:this.content.push(new x(i,o)),this}keys(){return this.content.map((s=>s.key.toValue()))}values(){return this.content.map((s=>s.value.toValue()))}hasKey(s){return this.content.some((o=>o.key.equals(s)))}items(){return this.content.map((s=>[s.key.toValue(),s.value.toValue()]))}map(s,o){return this.content.map((i=>s.bind(o)(i.value,i.key,i)))}compactMap(s,o){const i=[];return this.forEach(((u,_,w)=>{const x=s.bind(o)(u,_,w);x&&i.push(x)})),i}filter(s,o){return new C(this.content).filter(s,o)}reject(s,o){return this.filter(u(s),o)}forEach(s,o){return this.content.forEach((i=>s.bind(o)(i.value,i.key,i)))}}},71167:(s,o,i)=>{const u=i(10316);s.exports=class StringElement extends u{constructor(s,o,i){super(s,o,i),this.element="string"}primitive(){return"string"}get length(){return this.content.length}}},75147:(s,o,i)=>{const u=i(85105);s.exports=class JSON06Serialiser extends u{serialise(s){if(!(s instanceof this.namespace.elements.Element))throw new TypeError(`Given element \`${s}\` is not an Element instance`);let o;s._attributes&&s.attributes.get("variable")&&(o=s.attributes.get("variable"));const i={element:s.element};s._meta&&s._meta.length>0&&(i.meta=this.serialiseObject(s.meta));const u="enum"===s.element||-1!==s.attributes.keys().indexOf("enumerations");if(u){const o=this.enumSerialiseAttributes(s);o&&(i.attributes=o)}else if(s._attributes&&s._attributes.length>0){let{attributes:u}=s;u.get("metadata")&&(u=u.clone(),u.set("meta",u.get("metadata")),u.remove("metadata")),"member"===s.element&&o&&(u=u.clone(),u.remove("variable")),u.length>0&&(i.attributes=this.serialiseObject(u))}if(u)i.content=this.enumSerialiseContent(s,i);else if(this[`${s.element}SerialiseContent`])i.content=this[`${s.element}SerialiseContent`](s,i);else if(void 0!==s.content){let u;o&&s.content.key?(u=s.content.clone(),u.key.attributes.set("variable",o),u=this.serialiseContent(u)):u=this.serialiseContent(s.content),this.shouldSerialiseContent(s,u)&&(i.content=u)}else this.shouldSerialiseContent(s,s.content)&&s instanceof this.namespace.elements.Array&&(i.content=[]);return i}shouldSerialiseContent(s,o){return"parseResult"===s.element||"httpRequest"===s.element||"httpResponse"===s.element||"category"===s.element||"link"===s.element||void 0!==o&&(!Array.isArray(o)||0!==o.length)}refSerialiseContent(s,o){return delete o.attributes,{href:s.toValue(),path:s.path.toValue()}}sourceMapSerialiseContent(s){return s.toValue()}dataStructureSerialiseContent(s){return[this.serialiseContent(s.content)]}enumSerialiseAttributes(s){const o=s.attributes.clone(),i=o.remove("enumerations")||new this.namespace.elements.Array([]),u=o.get("default");let _=o.get("samples")||new this.namespace.elements.Array([]);if(u&&u.content&&(u.content.attributes&&u.content.attributes.remove("typeAttributes"),o.set("default",new this.namespace.elements.Array([u.content]))),_.forEach((s=>{s.content&&s.content.element&&s.content.attributes.remove("typeAttributes")})),s.content&&0!==i.length&&_.unshift(s.content),_=_.map((s=>s instanceof this.namespace.elements.Array?[s]:new this.namespace.elements.Array([s.content]))),_.length&&o.set("samples",_),o.length>0)return this.serialiseObject(o)}enumSerialiseContent(s){if(s._attributes){const o=s.attributes.get("enumerations");if(o&&o.length>0)return o.content.map((s=>{const o=s.clone();return o.attributes.remove("typeAttributes"),this.serialise(o)}))}if(s.content){const o=s.content.clone();return o.attributes.remove("typeAttributes"),[this.serialise(o)]}return[]}deserialise(s){if("string"==typeof s)return new this.namespace.elements.String(s);if("number"==typeof s)return new this.namespace.elements.Number(s);if("boolean"==typeof s)return new this.namespace.elements.Boolean(s);if(null===s)return new this.namespace.elements.Null;if(Array.isArray(s))return new this.namespace.elements.Array(s.map(this.deserialise,this));const o=this.namespace.getElementClass(s.element),i=new o;i.element!==s.element&&(i.element=s.element),s.meta&&this.deserialiseObject(s.meta,i.meta),s.attributes&&this.deserialiseObject(s.attributes,i.attributes);const u=this.deserialiseContent(s.content);if(void 0===u&&null!==i.content||(i.content=u),"enum"===i.element){i.content&&i.attributes.set("enumerations",i.content);let s=i.attributes.get("samples");if(i.attributes.remove("samples"),s){const u=s;s=new this.namespace.elements.Array,u.forEach((u=>{u.forEach((u=>{const _=new o(u);_.element=i.element,s.push(_)}))}));const _=s.shift();i.content=_?_.content:void 0,i.attributes.set("samples",s)}else i.content=void 0;let u=i.attributes.get("default");if(u&&u.length>0){u=u.get(0);const s=new o(u);s.element=i.element,i.attributes.set("default",s)}}else if("dataStructure"===i.element&&Array.isArray(i.content))[i.content]=i.content;else if("category"===i.element){const s=i.attributes.get("meta");s&&(i.attributes.set("metadata",s),i.attributes.remove("meta"))}else"member"===i.element&&i.key&&i.key._attributes&&i.key._attributes.getValue("variable")&&(i.attributes.set("variable",i.key.attributes.get("variable")),i.key.attributes.remove("variable"));return i}serialiseContent(s){if(s instanceof this.namespace.elements.Element)return this.serialise(s);if(s instanceof this.namespace.KeyValuePair){const o={key:this.serialise(s.key)};return s.value&&(o.value=this.serialise(s.value)),o}return s&&s.map?s.map(this.serialise,this):s}deserialiseContent(s){if(s){if(s.element)return this.deserialise(s);if(s.key){const o=new this.namespace.KeyValuePair(this.deserialise(s.key));return s.value&&(o.value=this.deserialise(s.value)),o}if(s.map)return s.map(this.deserialise,this)}return s}shouldRefract(s){return!!(s._attributes&&s.attributes.keys().length||s._meta&&s.meta.keys().length)||"enum"!==s.element&&(s.element!==s.primitive()||"member"===s.element)}convertKeyToRefract(s,o){return this.shouldRefract(o)?this.serialise(o):"enum"===o.element?this.serialiseEnum(o):"array"===o.element?o.map((o=>this.shouldRefract(o)||"default"===s?this.serialise(o):"array"===o.element||"object"===o.element||"enum"===o.element?o.children.map((s=>this.serialise(s))):o.toValue())):"object"===o.element?(o.content||[]).map(this.serialise,this):o.toValue()}serialiseEnum(s){return s.children.map((s=>this.serialise(s)))}serialiseObject(s){const o={};return s.forEach(((s,i)=>{if(s){const u=i.toValue();o[u]=this.convertKeyToRefract(u,s)}})),o}deserialiseObject(s,o){Object.keys(s).forEach((i=>{o.set(i,this.deserialise(s[i]))}))}}},85105:s=>{s.exports=class JSONSerialiser{constructor(s){this.namespace=s||new this.Namespace}serialise(s){if(!(s instanceof this.namespace.elements.Element))throw new TypeError(`Given element \`${s}\` is not an Element instance`);const o={element:s.element};s._meta&&s._meta.length>0&&(o.meta=this.serialiseObject(s.meta)),s._attributes&&s._attributes.length>0&&(o.attributes=this.serialiseObject(s.attributes));const i=this.serialiseContent(s.content);return void 0!==i&&(o.content=i),o}deserialise(s){if(!s.element)throw new Error("Given value is not an object containing an element name");const o=new(this.namespace.getElementClass(s.element));o.element!==s.element&&(o.element=s.element),s.meta&&this.deserialiseObject(s.meta,o.meta),s.attributes&&this.deserialiseObject(s.attributes,o.attributes);const i=this.deserialiseContent(s.content);return void 0===i&&null!==o.content||(o.content=i),o}serialiseContent(s){if(s instanceof this.namespace.elements.Element)return this.serialise(s);if(s instanceof this.namespace.KeyValuePair){const o={key:this.serialise(s.key)};return s.value&&(o.value=this.serialise(s.value)),o}if(s&&s.map){if(0===s.length)return;return s.map(this.serialise,this)}return s}deserialiseContent(s){if(s){if(s.element)return this.deserialise(s);if(s.key){const o=new this.namespace.KeyValuePair(this.deserialise(s.key));return s.value&&(o.value=this.deserialise(s.value)),o}if(s.map)return s.map(this.deserialise,this)}return s}serialiseObject(s){const o={};if(s.forEach(((s,i)=>{s&&(o[i.toValue()]=this.serialise(s))})),0!==Object.keys(o).length)return o}deserialiseObject(s,o){Object.keys(s).forEach((i=>{o.set(i,this.deserialise(s[i]))}))}}},65606:s=>{var o,i,u=s.exports={};function defaultSetTimout(){throw new Error("setTimeout has not been defined")}function defaultClearTimeout(){throw new Error("clearTimeout has not been defined")}function runTimeout(s){if(o===setTimeout)return setTimeout(s,0);if((o===defaultSetTimout||!o)&&setTimeout)return o=setTimeout,setTimeout(s,0);try{return o(s,0)}catch(i){try{return o.call(null,s,0)}catch(i){return o.call(this,s,0)}}}!function(){try{o="function"==typeof setTimeout?setTimeout:defaultSetTimout}catch(s){o=defaultSetTimout}try{i="function"==typeof clearTimeout?clearTimeout:defaultClearTimeout}catch(s){i=defaultClearTimeout}}();var _,w=[],x=!1,C=-1;function cleanUpNextTick(){x&&_&&(x=!1,_.length?w=_.concat(w):C=-1,w.length&&drainQueue())}function drainQueue(){if(!x){var s=runTimeout(cleanUpNextTick);x=!0;for(var o=w.length;o;){for(_=w,w=[];++C1)for(var i=1;i{"use strict";var u=i(6925);function emptyFunction(){}function emptyFunctionWithReset(){}emptyFunctionWithReset.resetWarningCache=emptyFunction,s.exports=function(){function shim(s,o,i,_,w,x){if(x!==u){var C=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw C.name="Invariant Violation",C}}function getShim(){return shim}shim.isRequired=shim;var s={array:shim,bigint:shim,bool:shim,func:shim,number:shim,object:shim,string:shim,symbol:shim,any:shim,arrayOf:getShim,element:shim,elementType:shim,instanceOf:getShim,node:shim,objectOf:getShim,oneOf:getShim,oneOfType:getShim,shape:getShim,exact:getShim,checkPropTypes:emptyFunctionWithReset,resetWarningCache:emptyFunction};return s.PropTypes=s,s}},5556:(s,o,i)=>{s.exports=i(2694)()},6925:s=>{"use strict";s.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},73992:(s,o)=>{"use strict";var i=Object.prototype.hasOwnProperty;function decode(s){try{return decodeURIComponent(s.replace(/\+/g," "))}catch(s){return null}}function encode(s){try{return encodeURIComponent(s)}catch(s){return null}}o.stringify=function querystringify(s,o){o=o||"";var u,_,w=[];for(_ in"string"!=typeof o&&(o="?"),s)if(i.call(s,_)){if((u=s[_])||null!=u&&!isNaN(u)||(u=""),_=encode(_),u=encode(u),null===_||null===u)continue;w.push(_+"="+u)}return w.length?o+w.join("&"):""},o.parse=function querystring(s){for(var o,i=/([^=?#&]+)=?([^&]*)/g,u={};o=i.exec(s);){var _=decode(o[1]),w=decode(o[2]);null===_||null===w||_ in u||(u[_]=w)}return u}},41859:(s,o,i)=>{const u=i(27096),_=i(78004),w=u.types;s.exports=class RandExp{constructor(s,o){if(this._setDefaults(s),s instanceof RegExp)this.ignoreCase=s.ignoreCase,this.multiline=s.multiline,s=s.source;else{if("string"!=typeof s)throw new Error("Expected a regexp or string");this.ignoreCase=o&&-1!==o.indexOf("i"),this.multiline=o&&-1!==o.indexOf("m")}this.tokens=u(s)}_setDefaults(s){this.max=null!=s.max?s.max:null!=RandExp.prototype.max?RandExp.prototype.max:100,this.defaultRange=s.defaultRange?s.defaultRange:this.defaultRange.clone(),s.randInt&&(this.randInt=s.randInt)}gen(){return this._gen(this.tokens,[])}_gen(s,o){var i,u,_,x,C;switch(s.type){case w.ROOT:case w.GROUP:if(s.followedBy||s.notFollowedBy)return"";for(s.remember&&void 0===s.groupNumber&&(s.groupNumber=o.push(null)-1),u="",x=0,C=(i=s.options?this._randSelect(s.options):s.stack).length;x{"use strict";var u=i(65606),_=65536,w=4294967295;var x=i(92861).Buffer,C=i.g.crypto||i.g.msCrypto;C&&C.getRandomValues?s.exports=function randomBytes(s,o){if(s>w)throw new RangeError("requested too many random bytes");var i=x.allocUnsafe(s);if(s>0)if(s>_)for(var j=0;j{"use strict";function _typeof(s){return _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(s){return typeof s}:function(s){return s&&"function"==typeof Symbol&&s.constructor===Symbol&&s!==Symbol.prototype?"symbol":typeof s},_typeof(s)}Object.defineProperty(o,"__esModule",{value:!0}),o.CopyToClipboard=void 0;var u=_interopRequireDefault(i(96540)),_=_interopRequireDefault(i(17965)),w=["text","onCopy","options","children"];function _interopRequireDefault(s){return s&&s.__esModule?s:{default:s}}function ownKeys(s,o){var i=Object.keys(s);if(Object.getOwnPropertySymbols){var u=Object.getOwnPropertySymbols(s);o&&(u=u.filter((function(o){return Object.getOwnPropertyDescriptor(s,o).enumerable}))),i.push.apply(i,u)}return i}function _objectSpread(s){for(var o=1;o=0||(_[i]=s[i]);return _}(s,o);if(Object.getOwnPropertySymbols){var w=Object.getOwnPropertySymbols(s);for(u=0;u=0||Object.prototype.propertyIsEnumerable.call(s,i)&&(_[i]=s[i])}return _}function _defineProperties(s,o){for(var i=0;i{"use strict";var u=i(25264).CopyToClipboard;u.CopyToClipboard=u,s.exports=u},81214:(s,o,i)=>{"use strict";function _typeof(s){return _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(s){return typeof s}:function(s){return s&&"function"==typeof Symbol&&s.constructor===Symbol&&s!==Symbol.prototype?"symbol":typeof s},_typeof(s)}Object.defineProperty(o,"__esModule",{value:!0}),o.DebounceInput=void 0;var u=_interopRequireDefault(i(96540)),_=_interopRequireDefault(i(20181)),w=["element","onChange","value","minLength","debounceTimeout","forceNotifyByEnter","forceNotifyOnBlur","onKeyDown","onBlur","inputRef"];function _interopRequireDefault(s){return s&&s.__esModule?s:{default:s}}function _objectWithoutProperties(s,o){if(null==s)return{};var i,u,_=function _objectWithoutPropertiesLoose(s,o){if(null==s)return{};var i,u,_={},w=Object.keys(s);for(u=0;u=0||(_[i]=s[i]);return _}(s,o);if(Object.getOwnPropertySymbols){var w=Object.getOwnPropertySymbols(s);for(u=0;u=0||Object.prototype.propertyIsEnumerable.call(s,i)&&(_[i]=s[i])}return _}function ownKeys(s,o){var i=Object.keys(s);if(Object.getOwnPropertySymbols){var u=Object.getOwnPropertySymbols(s);o&&(u=u.filter((function(o){return Object.getOwnPropertyDescriptor(s,o).enumerable}))),i.push.apply(i,u)}return i}function _objectSpread(s){for(var o=1;o=u?i.notify(s):o.length>_.length&&i.notify(_objectSpread(_objectSpread({},s),{},{target:_objectSpread(_objectSpread({},s.target),{},{value:""})}))}))})),_defineProperty(_assertThisInitialized(i),"onKeyDown",(function(s){"Enter"===s.key&&i.forceNotify(s);var o=i.props.onKeyDown;o&&(s.persist(),o(s))})),_defineProperty(_assertThisInitialized(i),"onBlur",(function(s){i.forceNotify(s);var o=i.props.onBlur;o&&(s.persist(),o(s))})),_defineProperty(_assertThisInitialized(i),"createNotifier",(function(s){if(s<0)i.notify=function(){return null};else if(0===s)i.notify=i.doNotify;else{var o=(0,_.default)((function(s){i.isDebouncing=!1,i.doNotify(s)}),s);i.notify=function(s){i.isDebouncing=!0,o(s)},i.flush=function(){return o.flush()},i.cancel=function(){i.isDebouncing=!1,o.cancel()}}})),_defineProperty(_assertThisInitialized(i),"doNotify",(function(){i.props.onChange.apply(void 0,arguments)})),_defineProperty(_assertThisInitialized(i),"forceNotify",(function(s){var o=i.props.debounceTimeout;if(i.isDebouncing||!(o>0)){i.cancel&&i.cancel();var u=i.state.value,_=i.props.minLength;u.length>=_?i.doNotify(s):i.doNotify(_objectSpread(_objectSpread({},s),{},{target:_objectSpread(_objectSpread({},s.target),{},{value:u})}))}})),i.isDebouncing=!1,i.state={value:void 0===s.value||null===s.value?"":s.value};var u=i.props.debounceTimeout;return i.createNotifier(u),i}return function _createClass(s,o,i){return o&&_defineProperties(s.prototype,o),i&&_defineProperties(s,i),Object.defineProperty(s,"prototype",{writable:!1}),s}(DebounceInput,[{key:"componentDidUpdate",value:function componentDidUpdate(s){if(!this.isDebouncing){var o=this.props,i=o.value,u=o.debounceTimeout,_=s.debounceTimeout,w=s.value,x=this.state.value;void 0!==i&&w!==i&&x!==i&&this.setState({value:i}),u!==_&&this.createNotifier(u)}}},{key:"componentWillUnmount",value:function componentWillUnmount(){this.flush&&this.flush()}},{key:"render",value:function render(){var s,o,i=this.props,_=i.element,x=(i.onChange,i.value,i.minLength,i.debounceTimeout,i.forceNotifyByEnter),C=i.forceNotifyOnBlur,j=i.onKeyDown,L=i.onBlur,B=i.inputRef,$=_objectWithoutProperties(i,w),V=this.state.value;s=x?{onKeyDown:this.onKeyDown}:j?{onKeyDown:j}:{},o=C?{onBlur:this.onBlur}:L?{onBlur:L}:{};var U=B?{ref:B}:{};return u.default.createElement(_,_objectSpread(_objectSpread(_objectSpread(_objectSpread({},$),{},{onChange:this.onChange,value:V},s),o),U))}}]),DebounceInput}(u.default.PureComponent);o.DebounceInput=x,_defineProperty(x,"defaultProps",{element:"input",type:"text",onKeyDown:void 0,onBlur:void 0,value:void 0,minLength:0,debounceTimeout:100,forceNotifyByEnter:!0,forceNotifyOnBlur:!0,inputRef:void 0})},24677:(s,o,i)=>{"use strict";var u=i(81214).DebounceInput;u.DebounceInput=u,s.exports=u},22551:(s,o,i)=>{"use strict";var u=i(96540),_=i(69982);function p(s){for(var o="https://reactjs.org/docs/error-decoder.html?invariant="+s,i=1;i