diff --git a/.claude/commands/audit-docs.md b/.claude/commands/audit-docs.md index d3c8297..2a514a8 100644 --- a/.claude/commands/audit-docs.md +++ b/.claude/commands/audit-docs.md @@ -44,9 +44,12 @@ pre-PR pass. (the highest-confidence drift). Judge each hit — a legitimate "renamed from X" migration note is fine; a live reference to a dead symbol is not. 4. **Check cross-links.** Every relative markdown link and `github.com/.../blob` - link must resolve. Remember `docs/` is served as a GitHub Pages site under - `baseurl: /xray` — links that escape the `docs/` root must use the full blob - URL, not `../`. + link must resolve. `docs/` is built into a static site by VitePress + (`docs/.vitepress/config.mts`, `base: '/xray/'`) and deployed to GitHub Pages by + `.github/workflows/docs.yml`. In-docs `./*.md` links are fine — VitePress + rewrites them and they also resolve on GitHub. Links that escape `docs/` (to + source files or other repo paths) must use the full `github.com/.../blob` URL, + not `../`, since those resolve on GitHub but not on the built site. 5. **Honesty pass** ([`honesty.md`](../rules/honesty.md)). Flag any claim that says *more* than the code guarantees, not just outright-wrong ones. Over-claims are the subtle drift. diff --git a/.claude/rules/docs-freshness.md b/.claude/rules/docs-freshness.md index 17fd5be..6a9f927 100644 --- a/.claude/rules/docs-freshness.md +++ b/.claude/rules/docs-freshness.md @@ -26,6 +26,8 @@ Coarse on purpose (directory / file → doc), so it survives refactors that a li | `src/server/env/env.ts` (`XRAY_*` env vars + defaults) | `docs/sdk-python.md`, `docs/architecture.md`, `README.md`, `CONTRIBUTING.md` | | Control-plane routes (`src/server/**/.router.ts`) | `docs/architecture.md` (control-plane list). The OpenAPI at `/docs` self-syncs from `describeRoute` — only the *narrative* needs a human. | +> **The public docs site.** `docs/*.md` are the source for the site at https://xray-eval.github.io/xray/ — built by VitePress (`docs/.vitepress/config.mts`, `base: '/xray/'`) and deployed by `.github/workflows/docs.yml` on a release tag. They render on GitHub too, so keep them **plain markdown** (no MDX / Vue components) and keep in-docs links relative (`./other-page.md`). + ## 3 · Honesty bar (cross-link) A doc must not claim more than the code guarantees — see [`honesty.md`](./honesty.md). "The SDK synthesizes TTS" when TTS moved server-side, or "X is supported" when the code does less, is an honesty failure, not a typo. The `/audit-docs` method is built on this: every doc claim is verified *against source*, and an unverifiable claim is removed or softened, never left to look authoritative. diff --git a/.env.example b/.env.example index c7ea031..c511708 100644 --- a/.env.example +++ b/.env.example @@ -54,3 +54,8 @@ COMPOSE_PROJECT_NAME=xray # (non-numeric, empty string) produces a confusing Docker error at `up` time # rather than a `.env`-level validation, so be deliberate when editing. # HOST_PORT=8080 + +# Host-side port that compose.dev.yaml maps to the docs (VitePress) dev server's +# :5173. Browse the live docs at http://localhost:${DOCS_PORT}/xray/. Override +# per worktree to run parallel docs servers alongside HOST_PORT. +# DOCS_PORT=5173 diff --git a/.github/workflows/docs-check.yml b/.github/workflows/docs-check.yml new file mode 100644 index 0000000..075fdb0 --- /dev/null +++ b/.github/workflows/docs-check.yml @@ -0,0 +1,72 @@ +# Docs PR build-gate — compile-only check that the VitePress site still builds, +# so a broken docs build can't merge. No deploy and no Pages permissions: the +# deploy half lives in docs.yml (release tag / workflow_dispatch), kept separate +# so the deploy job never shows up as a skipped check on pull requests. +# +# Local equivalent: `pnpm docs:build`. + +name: Docs check + +on: + pull_request: + paths: + - "docs/**" + - ".github/workflows/docs-check.yml" + - "pnpm-lock.yaml" + - "package.json" + +permissions: {} + +concurrency: + group: docs-check-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: vitepress build + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + contents: read + steps: + - name: Harden runner + uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup Node (pinned via .nvmrc) + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version-file: ".nvmrc" + + # Corepack must run AFTER setup-node — current pnpm (11.x) needs Node ≥22.13, + # newer than the runner's pre-installed Node. package.json#packageManager is + # the source of truth for the pnpm version corepack activates. + - name: Activate the pinned pnpm version via corepack + run: | + corepack enable + corepack prepare --activate + pnpm --version + + - name: Resolve pnpm store path + id: pnpm-store + run: echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT" + + - name: Cache pnpm store + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: ${{ steps.pnpm-store.outputs.path }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('pnpm-lock.yaml') }} + restore-keys: ${{ runner.os }}-pnpm-store- + + - name: Install (frozen lockfile) + run: pnpm install --frozen-lockfile + + # Base path (/xray/) is hardcoded in docs/.vitepress/config.mts. + - name: Build docs + run: pnpm docs:build diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..bf4d090 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,103 @@ +# Docs site — build the VitePress docs and deploy to GitHub Pages. +# Replaces the old GitHub-managed Jekyll build (docs/_config.yml, removed). +# +# Deploy happens on a release tag (v*.*.*), mirroring publish.yml — docs are cut +# alongside the Docker image. workflow_dispatch is a deliberate escape hatch — +# re-publish (or first-publish, before the next tag) without cutting a release; +# this is the documented reason for the trigger per .claude/rules/supply-chain.md §2. +# +# The PR build-gate (compile-only, no deploy) lives in docs-check.yml — kept in a +# separate file so this deploy job never shows up as a skipped check on PRs. +# +# One-time setup: repo Settings → Pages → Source = "GitHub Actions". +# Local equivalent: `pnpm docs:build` (and `pnpm docs:dev` for a live preview). + +name: Docs + +on: + push: + tags: + - "v*.*.*" + workflow_dispatch: + +permissions: {} + +concurrency: + group: docs-${{ github.ref }} + cancel-in-progress: false # never cancel an in-flight Pages deploy (mirrors publish.yml) + +jobs: + build: + name: vitepress build + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + contents: read + steps: + - name: Harden runner + uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup Node (pinned via .nvmrc) + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version-file: ".nvmrc" + + # Corepack must run AFTER setup-node — current pnpm (11.x) needs Node ≥22.13, + # newer than the runner's pre-installed Node. package.json#packageManager is + # the source of truth for the pnpm version corepack activates. + - name: Activate the pinned pnpm version via corepack + run: | + corepack enable + corepack prepare --activate + pnpm --version + + - name: Resolve pnpm store path + id: pnpm-store + run: echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT" + + - name: Cache pnpm store + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: ${{ steps.pnpm-store.outputs.path }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('pnpm-lock.yaml') }} + restore-keys: ${{ runner.os }}-pnpm-store- + + - name: Install (frozen lockfile) + run: pnpm install --frozen-lockfile + + # Base path (/xray/) is hardcoded in docs/.vitepress/config.mts. + - name: Build docs + run: pnpm docs:build + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 + with: + path: docs/.vitepress/dist + + deploy: + name: Deploy to GitHub Pages + needs: build + runs-on: ubuntu-24.04 + timeout-minutes: 5 + permissions: + pages: write # deploy to the Pages service + id-token: write # OIDC token the deploy-pages action verifies + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Harden runner + uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3 + with: + egress-policy: audit + + - name: Deploy + id: deployment + uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0 diff --git a/.github/workflows/supply-chain.yml b/.github/workflows/supply-chain.yml index 0fb8c39..851ff90 100644 --- a/.github/workflows/supply-chain.yml +++ b/.github/workflows/supply-chain.yml @@ -104,6 +104,18 @@ jobs: uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 with: fail-on-severity: moderate + # Dev-only docs-toolchain advisories — the same "NOT AFFECTED" + # dispositions documented in pnpm-workspace.yaml's + # `auditConfig.ignoreGhsas`, mirrored here because + # dependency-review-action keeps its OWN allowlist and does not read + # pnpm's auditConfig. All five ride in via vitepress/vite/esbuild/ + # mermaid (devDependencies, dropped by the prod image's `--prod` + # install) and the deployed docs are static HTML, so none has a + # production surface. See pnpm-workspace.yaml for the per-advisory + # rationale. Added 2026-06-21. + allow-ghsas: >- + GHSA-fx2h-pf6j-xcff, GHSA-4w7w-66w2-5vf9, GHSA-v6wh-96g9-6wx3, + GHSA-67mh-4wv8-2f99, GHSA-cmwh-pvxp-8882 # ELv2-redistributable bundle: reject every copyleft family on any # new prod dep. The SPA is built with Bun's HTML bundler, which # inlines JS into single output chunks — so even weak-copyleft @@ -128,4 +140,13 @@ jobs: EUPL-1.0, EUPL-1.1, EUPL-1.2, CC-BY-SA-1.0, CC-BY-SA-2.0, CC-BY-SA-2.5, CC-BY-SA-3.0, CC-BY-SA-4.0 + # dompurify is dual-licensed `MPL-2.0 OR Apache-2.0`; the deny-list + # above matches the MPL-2.0 term, but the OR lets us take it under + # permissive Apache-2.0, so no copyleft obligation attaches. It is also + # a dev-only docs dep (mermaid → dompurify), dropped by the prod image's + # `--prod` install and never inlined into the shipped SPA bundle — so + # the bundle source-availability concern the deny-list guards cannot + # arise here. Excluded from license checking by purl (version-agnostic, + # so the planned 3.4.11 bump doesn't reopen it). Added 2026-06-21. + allow-dependencies-licenses: pkg:npm/dompurify comment-summary-in-pr: on-failure diff --git a/.gitignore b/.gitignore index b2e5967..229a26f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,9 @@ dist/ # Build caches *.tsbuildinfo +# VitePress docs cache (build output under docs/.vitepress/dist/ is covered by dist/ above) +docs/.vitepress/cache/ + # Test & coverage coverage/ .nyc_output/ diff --git a/compose.dev.yaml b/compose.dev.yaml index 2a1e8e7..2717028 100644 --- a/compose.dev.yaml +++ b/compose.dev.yaml @@ -67,6 +67,37 @@ services: retries: 24 start_period: 60s + # Docs site (VitePress) — live preview at http://localhost:${DOCS_PORT:-5173}/xray/ + # while you edit docs/*.md (Vite HMR). Reuses the app's dev image (node+bun+pnpm) + # and the shared node_modules volume; waits for `app` to finish `pnpm install` + # (so it doesn't race on that volume), then serves docs/ over the Vite dev + # server. Dev-only — never built into the shipped image. + docs: + build: + context: . + dockerfile: Dockerfile.dev.server + args: + UID: "${UID:-1000}" + GID: "${GID:-1000}" + container_name: ${COMPOSE_PROJECT_NAME:-xray}-docs + user: "${UID:-1000}:${GID:-1000}" + working_dir: /app + environment: + - HOME=/tmp + - PNPM_HOME=/tmp/pnpm + depends_on: + app: + condition: service_healthy + # `app` owns dependency install; by the time this starts, vitepress and its + # platform esbuild binary are already in the shared volume, so just run the + # server. `--host` exposes it outside the container; base path is /xray/. + command: sh -c "pnpm exec vitepress dev docs --host 0.0.0.0 --port 5173" + volumes: + - ./:/app + - dev_node_modules:/app/node_modules + ports: + - "${DOCS_PORT:-5173}:5173" + volumes: dev_node_modules: dev_data: diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts new file mode 100644 index 0000000..4646c0a --- /dev/null +++ b/docs/.vitepress/config.mts @@ -0,0 +1,70 @@ +import { defineConfig } from "vitepress"; +import { withMermaid } from "vitepress-plugin-mermaid"; + +// Site config for the public docs at https://xray-eval.github.io/xray/. +// Built by `pnpm docs:build`, deployed by .github/workflows/docs.yml on release. +// `withMermaid` registers client-side mermaid rendering for the ```mermaid fences +// (no headless browser at build time); GitHub renders the same fences natively. +export default withMermaid( + defineConfig({ + title: "xray", + description: + "Open-source, self-hosted replay/eval framework for LiveKit voice agents", + base: "/xray/", // GitHub Pages project site + cleanUrls: true, + lastUpdated: true, + appearance: "dark", // dark-first (matches the app palette); the light toggle still works + // localhost URLs (the quickstart's "open http://localhost:8080") are + // intentionally unreachable at build time; everything else stays checked. + ignoreDeadLinks: [/^https?:\/\/localhost(:\d+)?/], + // Mermaid: render at natural (readable) size instead of shrinking to fit the + // prose column — wide diagrams scroll inside their panel (see style.css). The + // plugin still auto-switches to mermaid's dark theme with the site. Defaults + // here replace the plugin's, so keep securityLevel/startOnLoad explicit. + mermaid: { + securityLevel: "loose", + startOnLoad: false, + // Render labels as native SVG , not HTML . The HTML + // labels measure their width against .vp-doc's font, so cloning the SVG into + // the fullscreen viewer (a different CSS context) reflowed and clipped them. + // SVG text bakes geometry into the SVG, so the clone renders identically. + htmlLabels: false, + flowchart: { useMaxWidth: false, htmlLabels: false }, + sequence: { useMaxWidth: false }, + er: { useMaxWidth: false }, + class: { useMaxWidth: false, htmlLabels: false }, + }, + themeConfig: { + search: { provider: "local" }, // built-in MiniSearch — no native binary, no external service + // Reading order runs from "use it" to "understand it": Architecture (the + // deep mental model) sits last, after the practical guides. + nav: [ + { text: "Quick start", link: "/quickstart" }, + { text: "Integrate", link: "/integrate" }, + { text: "Python SDK", link: "/sdk-python" }, + { text: "Wire contract", link: "/wire-contract" }, + { text: "Architecture", link: "/architecture" }, + ], + sidebar: [ + { + text: "Documentation", + items: [ + { text: "Overview", link: "/" }, + { text: "Quick start", link: "/quickstart" }, + { text: "Integrate", link: "/integrate" }, + { text: "Python SDK", link: "/sdk-python" }, + { text: "Wire contract", link: "/wire-contract" }, + { text: "Architecture", link: "/architecture" }, + ], + }, + ], + socialLinks: [ + { icon: "github", link: "https://github.com/xray-eval/xray" }, + ], + editLink: { + pattern: "https://github.com/xray-eval/xray/edit/main/docs/:path", + text: "Edit this page on GitHub", + }, + }, + }), +); diff --git a/docs/.vitepress/theme/Layout.vue b/docs/.vitepress/theme/Layout.vue new file mode 100644 index 0000000..b24178b --- /dev/null +++ b/docs/.vitepress/theme/Layout.vue @@ -0,0 +1,184 @@ + + + diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts new file mode 100644 index 0000000..450872b --- /dev/null +++ b/docs/.vitepress/theme/index.ts @@ -0,0 +1,15 @@ +// Custom VitePress theme — palette + fonts mirror the application, and a custom +// Layout adds a fullscreen zoom/pan viewer for mermaid diagrams (Layout.vue). +// Reuses the app's own variable fonts (bundled, no external font request). +import DefaultTheme from "vitepress/theme"; + +import Layout from "./Layout.vue"; + +import "@fontsource-variable/onest"; +import "@fontsource-variable/jetbrains-mono"; +import "./style.css"; + +export default { + extends: DefaultTheme, + Layout, +}; diff --git a/docs/.vitepress/theme/style.css b/docs/.vitepress/theme/style.css new file mode 100644 index 0000000..a51e8ee --- /dev/null +++ b/docs/.vitepress/theme/style.css @@ -0,0 +1,274 @@ +/* xray docs — the palette mirrors the application itself (src/client/globals.css): + a monochrome, near-black "instrument console" with a white high-contrast primary + and semantic accents reserved for status. Same oklch tokens and variable fonts as + the app, so the docs and the product read as one surface. No invented hue. */ + +/* ── Fonts (identical to the app's @theme) ───────────────────────────────── */ +:root { + --vp-font-family-base: "Onest Variable", ui-sans-serif, system-ui, -apple-system, + "Segoe UI", Helvetica, Arial, sans-serif; + --vp-font-family-mono: "JetBrains Mono Variable", ui-monospace, SFMono-Regular, + "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; +} + +/* ── Light (app :root) ───────────────────────────────────────────────────── */ +:root { + --vp-c-bg: oklch(1 0 0); + --vp-c-bg-alt: oklch(0.985 0 0); + --vp-c-bg-soft: oklch(0.97 0 0); + --vp-c-bg-elv: oklch(1 0 0); + + --vp-c-text-1: oklch(0.145 0 0); + --vp-c-text-2: oklch(0.45 0 0); + --vp-c-text-3: oklch(0.556 0 0); + + --vp-c-divider: oklch(0 0 0 / 10%); + --vp-c-border: oklch(0.922 0 0); + --vp-c-gutter: oklch(0.97 0 0); + + /* app --primary (near-black) is the one high-contrast interactive color */ + --vp-c-brand-1: oklch(0.205 0 0); + --vp-c-brand-2: oklch(0.31 0 0); + --vp-c-brand-3: oklch(0.145 0 0); + --vp-c-brand-soft: oklch(0.145 0 0 / 10%); + + --vp-button-brand-bg: oklch(0.205 0 0); + --vp-button-brand-hover-bg: oklch(0.31 0 0); + --vp-button-brand-active-bg: oklch(0.145 0 0); + --vp-button-brand-text: oklch(0.985 0 0); + --vp-button-brand-hover-text: oklch(0.985 0 0); + --vp-button-brand-active-text: oklch(0.985 0 0); + --vp-button-brand-border: transparent; + --vp-button-brand-hover-border: transparent; + + --vp-home-hero-name-color: oklch(0.205 0 0); +} + +/* ── Dark (app .dark — the primary look) ─────────────────────────────────── */ +.dark { + --vp-c-bg: oklch(0.135 0 0); + --vp-c-bg-alt: oklch(0.185 0 0); + --vp-c-bg-soft: oklch(0.205 0 0); + --vp-c-bg-elv: oklch(0.205 0 0); + + --vp-c-text-1: oklch(0.985 0 0); + --vp-c-text-2: oklch(0.68 0 0); + --vp-c-text-3: oklch(0.5 0 0); + + --vp-c-divider: oklch(1 0 0 / 9%); + --vp-c-border: oklch(1 0 0 / 14%); + --vp-c-gutter: oklch(0.135 0 0); + + /* app --primary dark is near-white; links are high-contrast + underlined */ + --vp-c-brand-1: oklch(0.985 0 0); + --vp-c-brand-2: oklch(0.88 0 0); + --vp-c-brand-3: oklch(0.985 0 0); + --vp-c-brand-soft: oklch(1 0 0 / 12%); + + --vp-button-brand-bg: oklch(0.985 0 0); + --vp-button-brand-hover-bg: oklch(0.9 0 0); + --vp-button-brand-active-bg: oklch(0.82 0 0); + --vp-button-brand-text: oklch(0.205 0 0); + --vp-button-brand-hover-text: oklch(0.205 0 0); + --vp-button-brand-active-text: oklch(0.205 0 0); + + --vp-home-hero-name-color: oklch(0.985 0 0); +} + +/* ── Hero wordmark — flat monochrome, monospace, tight (matches the app mark) */ +.VPHero .name { + font-family: var(--vp-font-family-mono); + letter-spacing: -0.04em; + font-weight: 700; +} +.VPHero .text { + font-weight: 600; + letter-spacing: -0.015em; +} +.VPHero .tagline { + max-width: 58ch; +} + +/* Atmosphere: a faint neutral instrument grid behind the hero — colorless, so it + never competes with the app's flat field. Home page only. */ +.VPHome { + position: relative; + overflow: hidden; +} +.VPHome::before { + content: ""; + position: absolute; + inset: 0 0 auto 0; + height: 680px; + pointer-events: none; + z-index: 0; + background: + radial-gradient(56% 52% at 50% -10%, oklch(1 0 0 / 5%), transparent 72%), + linear-gradient(oklch(1 0 0 / 3.5%) 1px, transparent 1px) 0 0 / 100% 58px, + linear-gradient(90deg, oklch(1 0 0 / 3.5%) 1px, transparent 1px) 0 0 / 58px 100%; + -webkit-mask-image: linear-gradient(to bottom, #000 0%, transparent 100%); + mask-image: linear-gradient(to bottom, #000 0%, transparent 100%); +} +:root:not(.dark) .VPHome::before { + background: + radial-gradient(56% 52% at 50% -10%, oklch(0 0 0 / 4%), transparent 72%), + linear-gradient(oklch(0 0 0 / 4%) 1px, transparent 1px) 0 0 / 100% 58px, + linear-gradient(90deg, oklch(0 0 0 / 4%) 1px, transparent 1px) 0 0 / 58px 100%; +} +.VPHome .VPHero, +.VPHome .VPFeatures { + position: relative; + z-index: 1; +} + +/* ── Feature cards — hairline that brightens to the primary on hover ─────── */ +.VPFeature { + border: 1px solid var(--vp-c-border); + border-radius: 10px; /* app --radius */ + background: var(--vp-c-bg-soft); + transition: + border-color 0.22s ease, + transform 0.22s ease; +} +.VPFeature:hover { + border-color: var(--vp-c-text-2); + transform: translateY(-3px); +} +.VPFeature .title { + font-family: var(--vp-font-family-mono); + letter-spacing: -0.01em; +} + +/* ── Nav wordmark ────────────────────────────────────────────────────────── */ +.VPNavBarTitle .title { + font-family: var(--vp-font-family-mono); + letter-spacing: -0.04em; + font-weight: 700; +} + +/* ── Links — underline is the affordance in a monochrome system ──────────── */ +.vp-doc a { + text-decoration-line: underline; + text-decoration-color: var(--vp-c-brand-soft); + text-underline-offset: 3px; + transition: text-decoration-color 0.2s ease; +} +.vp-doc a:hover { + text-decoration-color: var(--vp-c-brand-1); +} + +/* ── Code — mono + a hairline panel in dark ──────────────────────────────── */ +.vp-doc code { + font-family: var(--vp-font-family-mono); +} +.dark .vp-doc div[class*="language-"] { + border: 1px solid var(--vp-c-border); +} + +/* ── Mermaid — clickable overview inline, fullscreen zoom/pan viewer ─────── */ +/* Inline, the whole diagram fits the column (an overview). Click anywhere in the + panel to open the fullscreen viewer (Layout.vue), which zooms + pans. */ +.vp-doc .mermaid { + position: relative; + margin: 28px 0; + padding: 20px; + border: 1px solid var(--vp-c-divider); + border-radius: 10px; + background: var(--vp-c-bg-alt); + cursor: zoom-in; +} +.vp-doc .mermaid svg { + display: block; + margin: 0 auto; + max-width: 100%; + height: auto; +} +.vp-doc .mermaid::after { + content: "Click to zoom"; + position: absolute; + top: 10px; + right: 12px; + z-index: 1; + padding: 5px 9px; + border: 1px solid var(--vp-c-border); + border-radius: 999px; + background: var(--vp-c-bg); + color: var(--vp-c-text-2); + font: 600 11px/1 var(--vp-font-family-mono); + letter-spacing: 0.02em; + opacity: 0; + pointer-events: none; + transition: opacity 0.2s ease; +} +.vp-doc .mermaid:hover::after { + opacity: 1; +} + +/* Fullscreen viewer (DOM built in Layout.vue). */ +html.mz-open { + overflow: hidden; +} +.mermaid-zoom { + position: fixed; + inset: 0; + z-index: 100; + background: color-mix(in oklch, var(--vp-c-bg) 92%, transparent); + backdrop-filter: blur(3px); +} +.mermaid-zoom[hidden] { + display: none; +} +.mz-stage { + position: absolute; + inset: 0; + overflow: hidden; + cursor: grab; + touch-action: none; +} +.mz-stage.mz-dragging { + cursor: grabbing; +} +.mz-canvas { + position: absolute; + top: 0; + left: 0; + transform-origin: 0 0; + will-change: transform; + background: var(--vp-c-bg-alt); + border-radius: 12px; + box-shadow: 0 24px 80px -24px rgba(0, 0, 0, 0.6); +} +.mz-canvas svg { + display: block; +} +.mz-bar { + position: absolute; + bottom: 24px; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 4px; + padding: 6px; + border: 1px solid var(--vp-c-border); + border-radius: 12px; + background: var(--vp-c-bg-soft); + box-shadow: 0 12px 40px -12px rgba(0, 0, 0, 0.5); +} +.mz-bar button { + min-width: 42px; + height: 36px; + padding: 0 12px; + border: 1px solid transparent; + border-radius: 8px; + background: transparent; + color: var(--vp-c-text-1); + font: 600 15px/1 var(--vp-font-family-mono); + cursor: pointer; + transition: + background 0.15s ease, + border-color 0.15s ease; +} +.mz-bar button:hover { + background: var(--vp-c-bg-alt); + border-color: var(--vp-c-border); +} diff --git a/docs/_config.yml b/docs/_config.yml deleted file mode 100644 index ce7a16a..0000000 --- a/docs/_config.yml +++ /dev/null @@ -1,32 +0,0 @@ -# GitHub Pages config for the docs/ folder. -# -# Hosting: repo Settings → Pages → "Deploy from a branch" → branch `main`, -# folder `/docs`. GitHub's managed Jekyll build renders this folder server-side -# — there is no workflow file to maintain and nothing is added to the repo's -# pnpm lockfile or CI. -# -# Theme: just-the-docs (sidebar nav + search) is pulled in via remote_theme and -# resolved by GitHub's Pages build, not by this repo's dependencies. To drop the -# external gem entirely, replace the `remote_theme:` line with a built-in theme, -# e.g. `theme: jekyll-theme-cayman` (no nav sidebar, but zero external gems). - -title: xray -description: Open-source, self-hosted replay/eval framework for LiveKit voice agents -remote_theme: just-the-docs/just-the-docs - -# Project-page paths assume the repo is published at github.com/xray-eval/xray. -# If you fork/rename, update both to .github.io and /. -url: https://xray-eval.github.io -baseurl: /xray - -color_scheme: dark -search_enabled: true - -# Render the mermaid diagrams in architecture.md / integrate.md (loaded from a -# CDN at view time by just-the-docs; not a build dependency). -mermaid: - version: "10.9.0" - -aux_links: - "GitHub": - - "https://github.com/xray-eval/xray" diff --git a/docs/architecture.md b/docs/architecture.md index d844986..36af69a 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,17 +1,26 @@ --- -layout: default title: Architecture -nav_order: 2 --- # xray architecture -This doc is the map for anyone contributing to xray. It explains the -three processes, the two write paths into storage, the read path that -backs the inspector, and the trust boundary between them. +This doc is the map for anyone contributing to xray. Read it to learn four things: + +- The three processes that make up the system. +- The two write paths that put data into storage. +- The read path that feeds the inspector (the web UI). +- The trust boundary that keeps those paths apart. End-user integration instructions live in [`integrate.md`](./integrate.md). +A few terms show up a lot. Here they are in plain words: + +- **Replay**: one run of one test conversation against a real voice agent. +- **Turn**: one back-and-forth step in a conversation. Either the user speaks or the agent speaks. +- **Span**: one timed event from the agent's code, in OpenTelemetry format. For example, "the LLM call took 800ms." +- **OTLP**: OpenTelemetry Protocol. The wire format agents use to send spans. +- **VAD**: Voice Activity Detection. It scans audio and marks where someone is actually speaking. + --- ## TL;DR @@ -35,7 +44,7 @@ End-user integration instructions live in [`integrate.md`](./integrate.md). [`single-image-distribution.md`](https://github.com/xray-eval/xray/blob/main/.claude/rules/single-image-distribution.md) for why this is non-negotiable. - The **inspector SPA** is served by the same Bun process that owns - the API — one image, one port, one volume. + the API: one image, one port, one volume. --- @@ -43,7 +52,7 @@ End-user integration instructions live in [`integrate.md`](./integrate.md). ```mermaid flowchart LR - subgraph DRV["Driver — test side (Python)"] + subgraph DRV["Driver - test side (Python)"] direction TB D1["xray.run(...)
orchestrator
(POSTs control plane,
installs OTLP pipeline,
attaches replay baggage,
waits via SSE)"] D2["LiveKitRuntime
plays user audio,
captures agent audio + transcripts,
writes wall-clock stereo WAV
(L = user, R = agent)"] @@ -54,14 +63,14 @@ flowchart LR LKR["WebRTC audio
+ live transcripts"] end - subgraph AGT["Agent worker — dev's code (Python)"] + subgraph AGT["Agent worker - dev's code (Python)"] direction TB A1["async with xray.attach(ctx)
reads JWT 'xray' attribute,
sets OTEL baggage,
installs OTLP pipeline"] A2["dev's agent code:
strategy, STT, TTS, tool-calls
emits gen_ai.* + xray.stage.*
(and any other OTEL spans)"] A1 --> A2 end - subgraph XR["xray — single Bun process"] + subgraph XR["xray - single Bun process"] direction TB CTL["Control plane
POST /v1/conversations
POST /v1/replays
POST /v1/replays/:id/audio
POST /v1/replays/:id/analyze
GET /v1/replays/:id/events (SSE)
PATCH /v1/replays/:id
GET /v1/conversations
GET /v1/replays/:id"] OTLP["OTLP receiver
POST /v1/otlp/v1/traces
JSON + protobuf

Vocabulary registry:
xray.* + gen_ai.* + Langfuse
routes by xray.replay.id"] @@ -83,56 +92,67 @@ flowchart LR D1 -- "POST /v1/conversations
POST /v1/replays
POST /audio
POST /analyze
GET /events (SSE)
PATCH /v1/replays/:id" --> CTL D2 -- "publish audio track" --> LKR - D2 -- "xray.turn spans
(raw spans only —
turn boundaries come
from server-side VAD)" --> OTLP + D2 -- "xray.turn spans
(raw spans only -
turn boundaries come
from server-side VAD)" --> OTLP LKR -- "deliver audio +
live transcripts" --> A2 A2 -- "gen_ai.* / xray.stage.* /
any OTEL spans" --> OTLP ``` ### Why three processes -- The **driver** runs in CI or on the dev's laptop. It owns the test - spec, plays the user audio, captures the agent audio, writes the - stereo WAV, uploads it, then waits via SSE for the server to finish - VAD + turn derivation. It is also the only thing that mints LiveKit - JWTs carrying the `xray` attribute (replay_id, conversation_hash, - modality) — that JWT is how the agent side learns which replay - it's inside. -- The **agent worker** is the dev's own LiveKit Agents code, with one - thin xray wrapper: `async with xray.attach(ctx, …)`. It runs the - same way it would in production (because in production, no `xray` - attribute is on the JWT, and `attach` no-ops). Its job from xray's - point of view is *to emit OTEL spans*. -- **xray** is the single Bun image that takes both inputs and renders - the inspector. The analyze-replay job runs in-process via bunqueue - in `embedded` mode — no second container, no Redis, no separate - worker process. +Each process has one job. Here is what each one does. + +The **driver** runs in CI or on the dev's laptop. It owns the test work: + +- It holds the test spec (the conversation you wrote in code). +- It plays the user audio into the room. +- It captures the agent audio coming back. +- It writes the stereo WAV (one audio file, user on the left channel, agent on the right). +- It uploads that WAV, then waits via SSE for the server to finish VAD and turn derivation. + +The driver is also the only thing that mints LiveKit JWTs. +(A JWT is a signed login token. LiveKit is the real-time audio service the agent runs in.) +Each JWT carries the `xray` attribute: `replay_id`, `conversation_hash`, and `modality`. +That JWT is how the agent side learns which replay it is inside. + +The **agent worker** is the dev's own LiveKit Agents code. It has one thin xray wrapper: `async with xray.attach(ctx, …)`. + +- It runs the same way it would in production. In production, no `xray` attribute is on the JWT, so `attach` does nothing. +- Its only job, from xray's point of view, is *to emit OTEL spans*. + +**xray** is the single Bun image. It takes both inputs and renders the inspector. +The analyze-replay job runs in-process via bunqueue in `embedded` mode. +No second container. No Redis. No separate worker process. The driver and the agent worker **never talk to each other directly**. -They share state through (a) the LiveKit room (audio + JWT attribute), -and (b) xray itself (every span lands under the same `xray.replay.id`). +They share state through two channels: + +1. The LiveKit room (audio plus the JWT attribute). +2. xray itself (every span lands under the same `xray.replay.id`). --- ## The two write paths -xray has exactly two write surfaces. Every byte that mutates state in -`/data/xray.db` arrives through one of them. They are coupled by trust: -the OTLP receiver **never** creates Conversation or Replay rows; that -is exclusively the SDK control plane's job. +xray has exactly two write surfaces. +Every byte that changes state in `/data/xray.db` arrives through one of them. + +They are coupled by trust. +The OTLP receiver **never** creates Conversation or Replay rows. +That is exclusively the SDK control plane's job. ```mermaid flowchart TB - subgraph WRITES["Write surfaces — trust boundary lives here"] + subgraph WRITES["Write surfaces - trust boundary lives here"] direction LR - subgraph CP["Control plane — Valibot-validated, idempotent"] + subgraph CP["Control plane - Valibot-validated, idempotent"] CP1["POST /v1/conversations
multipart spec + audio bytes
server hashes canonical turns → conversation_hash
upsert by hash (last-write-wins on name)
"] - CP2["POST /v1/replays
eager row create — lifecycle_state='pending'
returns replay_id
"] + CP2["POST /v1/replays
eager row create - lifecycle_state='pending'
returns replay_id
"] CP3["POST /v1/replays/:id/audio
stereo WAV → XRAY_AUDIO_ROOT
X-Recording-Started-At → replays.recording_started_at
lifecycle_state='recording_uploaded'
"] CP4["POST /v1/replays/:id/analyze
enqueue bunqueue job
lifecycle_state='analyzing'
analysis_step='vad'
"] CP5["PATCH /v1/replays/:id
lifecycle_state / failure_reason / finished_at"] CP6["analyze-chain workers
analyze-replay: VAD + Whisper → speech_segments + replay_turns + turn_transcripts
calculate-metrics: agent_response_ms + interrupted → replay_metrics
evaluate-replay: assertions + judges → assertion_results + judge_results + replay_evaluations
lifecycle_state='completed' on chain success
"] end - subgraph RX["OTLP receiver — filters, not gates"] + subgraph RX["OTLP receiver - filters, not gates"] RX1["POST /v1/otlp/v1/traces
JSON or protobuf

Routes by xray.replay.id resource attr.
Unknown replay_id → silent drop.
Unknown vocabulary → silent drop.

Vocabularies in src/server/otlp/vocabularies/:
• xray.ts (xray.* recognized, raw spans only)
• gen-ai-semconv.ts (gen_ai.* per OTel)
• langfuse.ts (Langfuse-flavoured GenAI)"] end end @@ -171,39 +191,40 @@ flowchart TB `sdk/python/src/xray/orchestrator.py:run(...)` POSTs to these endpoints in order: -1. `POST /v1/conversations` — Valibot-validated upsert keyed by - `hash`. Multipart body: a `spec` JSON part with `name` + `turns` - (+ optional `judges` / `live`), plus one named file part per - `RecordedAudio` turn keyed by the turn's declared `upload_key`. The - server reads each audio part, sha256s the bytes, substitutes the hash - into the canonical turn, then hashes the canonical spec JSON - (`{turns, judges}`) to derive `conversation_hash` — so changing a judge - forks a new Conversation. - Re-POSTing the same hash with a different `name` updates the - row's display label (last-write-wins). The SDK never hashes - anything. -2. `POST /v1/replays` — creates the Replay row **eagerly** at +1. `POST /v1/conversations`. This is a Valibot-validated upsert, keyed by `hash`. + The body is multipart. It has a `spec` JSON part with `name` plus `turns` + (and optional `judges` / `live`). It also has one named file part per + `RecordedAudio` turn, keyed by the turn's declared `upload_key`. Here is what the + server does with it: + - It reads each audio part and sha256s the bytes. + - It substitutes that hash into the canonical turn. + - It then hashes the canonical spec JSON (`{turns, judges}`) to derive `conversation_hash`. + + So changing a judge forks a new Conversation. + Re-POSTing the same hash with a different `name` updates the row's display label (last-write-wins). + The SDK never hashes anything. +2. `POST /v1/replays`. This creates the Replay row **eagerly** at `lifecycle_state='pending'` and returns `replay_id`. This must - happen before the runtime emits its first span; otherwise the OTLP - receiver would drop them as "unknown replay_id." -3. `POST /v1/replays/:id/audio` — uploads the stereo WAV (left = user, + happen before the runtime emits its first span. Otherwise the OTLP + receiver would drop those spans as "unknown replay_id." +3. `POST /v1/replays/:id/audio`. This uploads the stereo WAV (left = user, right = agent, wall-clock-aligned, written under `XRAY_AUDIO_ROOT//replay.`). The server flips `lifecycle_state` to `recording_uploaded`. -4. `POST /v1/replays/:id/analyze` — enqueues the bunqueue +4. `POST /v1/replays/:id/analyze`. This enqueues the bunqueue `analyze-replay` job. The server transitions to - `lifecycle_state='analyzing'` with `analysis_step='vad'`. Returns + `lifecycle_state='analyzing'` with `analysis_step='vad'`. It returns `202 Accepted` with the bunqueue job id. -5. `GET /v1/replays/:id/events` (SSE) — the SDK streams `state`, +5. `GET /v1/replays/:id/events` (SSE). The SDK streams `state`, `progress`, `evaluation_complete`, and `failed` events. The `evaluation_complete` payload carries the full `ReplayResult` (passed/failed verdict + per-assertion + per-judge + per-turn - metrics) so the SDK can return immediately without a follow-up GET. - Heartbeat `:` line every 15s keeps proxies from idling out. SDK + metrics). So the SDK can return immediately without a follow-up GET. + A heartbeat `:` line every 15s keeps proxies from idling out. The SDK closes the stream when `lifecycle_state` hits a terminal value. -6. `GET /v1/replays/:id/result` — same `ReplayResult` payload outside - the SSE stream for late subscribers / inspector hydration. -7. `PATCH /v1/replays/:id` — only used by the SDK for driver-side +6. `GET /v1/replays/:id/result`. This is the same `ReplayResult` payload outside + the SSE stream, for late subscribers and inspector hydration. +7. `PATCH /v1/replays/:id`. The SDK uses this only for driver-side failures (`failure_reason='driver_aborted'` / `audio_missing` / `agent_not_joined`). Lifecycle transitions during the analyze chain are server-owned. @@ -211,41 +232,44 @@ in order: ### OTLP receiver (both sides) `src/server/otlp/otlp.service.ts` accepts both `application/json` and -`application/x-protobuf`, normalises to a JSON-shape that the -Valibot schema validates, then dispatches each span through the +`application/x-protobuf`. It normalises them to a JSON-shape that the +Valibot schema validates. Then it dispatches each span through the vocabulary registry (`src/server/otlp/vocabularies/registry.ts`). -Each registered vocabulary is one file. To add a new one (e.g. a +Each registered vocabulary is one file. To add a new one (for example, a provider-specific semconv), drop a file in `vocabularies/` plus one -line in `registry.ts`. The receiver is a **filter, not a gate**: -- Unknown vocabulary → silently dropped (so an agent worker emitting - noisy framework spans doesn't pollute storage). -- Unknown `xray.replay.id` → silently dropped (so an agent running in - production, where there is no replay context, doesn't write rows). +line in `registry.ts`. + +The receiver is a **filter, not a gate**. Two kinds of input get dropped: -**xray vocabulary** (`src/server/otlp/vocabularies/xray.ts`) — recognized +- Unknown vocabulary is silently dropped. So an agent worker emitting + noisy framework spans doesn't pollute storage. +- An unknown `xray.replay.id` is silently dropped. So an agent running in + production, where there is no replay context, doesn't write rows. + +**xray vocabulary** (`src/server/otlp/vocabularies/xray.ts`). These are the recognized span names: `xray.turn`, `xray.stage.stt`, `xray.stage.tts`. They land in the raw `spans` table for the inspector's timeline but produce no -structured rows — turn boundaries come from server-side VAD, and -assertion + judge outcomes come from the server's evaluate-replay job +structured rows. Turn boundaries come from server-side VAD, and +assertion plus judge outcomes come from the server's evaluate-replay job walking the declared catalog. `xray.assertion` and `xray.judge` are no -longer recognized: the spec-0001 server reads its checks from the +longer recognized. The spec-0001 server reads its checks from the `Assertion` / `Judge` variants declared on the conversation, not from driver-emitted spans. **Tool / model → turn attribution** is timestamp-based, not span-tag -based — and derived, not stored. `tool_calls` / `model_usage` rows carry -only their wall-clock `started_at`; turn membership is computed at -eval/read time by mapping `started_at` onto the audio timeline +based. It is also derived, not stored. `tool_calls` / `model_usage` rows carry +only their wall-clock `started_at`. Turn membership is computed at +eval/read time. The server maps `started_at` onto the audio timeline (`audio_offset_ms = started_at − replays.recording_started_at`, the -anchor the driver sends via the `X-Recording-Started-At` upload header) -and testing the VAD-derived turn window `[turn_start_ms, turn_end_ms)`. +anchor the driver sends via the `X-Recording-Started-At` upload header). +Then it tests that offset against the VAD-derived turn window `[turn_start_ms, turn_end_ms)`. There is no `turn_idx` column on those tables and no backfill stage. The origin is always `replays.recording_started_at` (the audio sample-0 -wall-clock); `replays.started_at` (row-creation time, which precedes the +wall-clock). `replays.started_at` (row-creation time, which precedes the recording by the room-connect + agent-join latency) must never be used. -**gen_ai semconv** (`gen-ai-semconv.ts`) — dispatches on +**gen_ai semconv** (`gen-ai-semconv.ts`). This dispatches on `gen_ai.operation.name`: `execute_tool` → `tool_calls`; `chat` / `text_completion` → `model_usage` (model TTFT lifted from `gen_ai.response.time_to_first_chunk`, seconds → ms). **Langfuse** @@ -302,16 +326,17 @@ sequenceDiagram X-->>D: SSE 'evaluation_complete' event
(full ReplayResult payload) ``` -Two things to notice in this diagram: +Two things to notice in this diagram. + +First, **the audio plane (LiveKit) and the observability plane (OTLP) are +separate.** Audio never goes through xray during the run. xray just +receives the post-hoc stereo WAV at the end. The agent worker's STT is the +dev's STT. xray sees only its emitted OTEL spans. -- **The audio plane (LiveKit) and the observability plane (OTLP) are - separate.** Audio never goes through xray during the run; xray just - receives the post-hoc stereo WAV. The agent worker's STT is the - dev's STT — xray sees only its emitted OTEL spans. -- **The replay row is created _before_ any spans land.** This is what - makes the OTLP receiver's "unknown replay_id → drop" rule safe: by - the time the agent worker emits its first span, the Replay row - already exists, so the receiver routes the span correctly. +Second, **the replay row is created _before_ any spans land.** This is what +makes the OTLP receiver's "unknown replay_id → drop" rule safe. By +the time the agent worker emits its first span, the Replay row +already exists. So the receiver routes the span correctly. --- @@ -334,7 +359,7 @@ erDiagram conversations { text hash PK "SHA-256 of canonical spec JSON {turns (incl. assertions + sha256 of RecordedAudio bytes), judges}" text name "Free-form display label; last-write-wins on re-POST" - text turns_json "canonical JSON of the spec {turns, judges} — the hash input" + text turns_json "canonical JSON of the spec {turns, judges} - the hash input" text created_at text last_run_at "Bumped on every POST /v1/conversations" } @@ -344,7 +369,7 @@ erDiagram text lifecycle_state "pending | running | recording_uploaded | analyzing | completed | failed" text analysis_step "vad | transcribe | metrics | evaluate | null" text failure_reason "14 values: driver_aborted | audio_missing | agent_not_joined | upload_failed | missing_credential | transcription_failed | metrics_failed | evaluation_failed | spec_vad_mismatch | stalled | timeout | explicit_fail | max_attempts_exceeded | worker_lost | null" - text recording_started_at "wall-clock (ISO) of audio sample 0 — sole origin for span→turn attribution; null before audio upload" + text recording_started_at "wall-clock (ISO) of audio sample 0 - sole origin for span→turn attribution; null before audio upload" text started_at text finished_at text audio_path "relative path under XRAY_AUDIO_ROOT to the stereo WAV" @@ -370,24 +395,25 @@ erDiagram ``` `replay_turns` is the join point between the spec -(`conversations.turns_json`) and the observed execution. Rows are -written by the `analyze-replay` worker after running VAD on each -channel of the uploaded stereo WAV. `speech_segments` carries the raw -VAD output (one row per detected voiced chunk per channel); the -inspector renders these alongside the turn boundaries for debugging -overlap / silence / latency. +(`conversations.turns_json`) and the observed execution. The rows are +written by the `analyze-replay` worker after it runs VAD on each +channel of the uploaded stereo WAV. + +`speech_segments` carries the raw +VAD output: one row per detected voiced chunk per channel. The +inspector renders these alongside the turn boundaries. That helps you debug overlap, silence, and latency. `tool_calls`, `model_usage`, and `spans` are written by the OTLP receiver as it ingests `gen_ai.*` / Langfuse / `xray.*` spans. --- -## Read path — what the inspector sees +## Read path: what the inspector sees The inspector (`src/client/inspector/` + slice folders under -`src/client/`) is a React SPA bundled by Bun's HTML bundler and -served by the same Bun process that owns the API. There is **no -client-side build step in CI**; Bun builds it at request time and at +`src/client/`) is a React SPA. Bun's HTML bundler builds it, and the +same Bun process that owns the API serves it. There is **no +client-side build step in CI**. Bun builds it at request time and at container start. ```mermaid @@ -395,10 +421,10 @@ flowchart LR UI["Inspector SPA
(React)"] EP1["GET /v1/conversations
GET /v1/conversations/:hash"] EP2["GET /v1/conversations/:hash/replays
(every replay for this hash)"] - EP3["GET /v1/replays/:id
(buildReplayDetail — the
big join)"] + EP3["GET /v1/replays/:id
(buildReplayDetail - the
big join)"] EP4["POST /v1/replays/compare
(body: 2–8 replay ids)"] EP5["GET /v1/replays/:id/audio
(stereo WAV bytes)"] - EP6["GET /v1/replays/:id/events
(SSE — live progress)"] + EP6["GET /v1/replays/:id/events
(SSE - live progress)"] UI --> EP1 UI --> EP2 @@ -411,7 +437,7 @@ flowchart LR EP5 -. "streams
WAV bytes" .-> AUDIO[("XRAY_AUDIO_ROOT")] ``` -Every read endpoint is in `src/server//.router.ts`. The +Every read endpoint lives in `src/server//.router.ts`. The service layer (`.service.ts`) does the actual SQL via Drizzle on `bun:sqlite`. The slice convention is documented in [`code-layout.md`](https://github.com/xray-eval/xray/blob/main/.claude/rules/code-layout.md). @@ -420,8 +446,8 @@ on `bun:sqlite`. The slice convention is documented in ## Distribution -Shipped artifact: a Docker image published to GHCR -(`ghcr.io/xray-eval/xray`) by CI on tagged releases. Operators run +The shipped artifact is a Docker image. CI publishes it to GHCR +(`ghcr.io/xray-eval/xray`) on tagged releases. Operators run ``` docker run -v ./data:/data ghcr.io/xray-eval/xray @@ -429,20 +455,20 @@ docker run -v ./data:/data ghcr.io/xray-eval/xray (`XRAY_AUDIO_ROOT` defaults to `/audio`, so it needn't be passed.) -and that is the install. The image carries the Bun process, the -pre-built SPA, the SQLite schema (migrated at startup), the bunqueue -worker (embedded, same process), and nothing else. No SaaS. No hosted +That is the whole install. The image carries the Bun process, the +pre-built SPA, the SQLite schema (migrated at startup), and the bunqueue +worker (embedded, same process). Nothing else. No SaaS. No hosted version. No second container. This single-image promise is load-bearing for several other choices -in the codebase (SQLite over Postgres, `bun:sqlite` over a network -driver, embedded reads over a separate query service, embedded -bunqueue worker over a separate queue process). See +in the codebase. SQLite over Postgres. `bun:sqlite` over a network +driver. Embedded reads over a separate query service. Embedded +bunqueue worker over a separate queue process. See [`single-image-distribution.md`](https://github.com/xray-eval/xray/blob/main/.claude/rules/single-image-distribution.md) before proposing any change that would break it. **Two SQLite files in `/data/`.** xray owns `xray.db` (conversations, -replays, etc.). bunqueue owns `bunqueue.db` (jobs, DLQ). Acknowledged -tradeoff vs the "one file" reading of the rule — single volume, two -files, no second process. Operator backs up the whole `/data` volume. -Path is configurable via `BUNQUEUE_DATA_PATH`. +replays, and so on). bunqueue owns `bunqueue.db` (jobs, DLQ). This is an acknowledged +tradeoff against the "one file" reading of the rule: single volume, two +files, no second process. The operator backs up the whole `/data` volume. +The path is configurable via `BUNQUEUE_DATA_PATH`. diff --git a/docs/assets/hero.png b/docs/assets/hero.png deleted file mode 100644 index 6f6b62d..0000000 Binary files a/docs/assets/hero.png and /dev/null differ diff --git a/docs/index.md b/docs/index.md index f381d8d..05dd3e6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,48 +1,53 @@ --- -layout: default -title: Home -nav_order: 1 +layout: home +title: xray + +hero: + name: xray + text: See every turn your agent takes. + tagline: Open-source, self-hosted replay and eval for LiveKit voice agents. Hear what your agent heard. See what it decided. Find where it broke. + actions: + - theme: brand + text: Get started + link: /quickstart + - theme: alt + text: Python SDK + link: /sdk-python + - theme: alt + text: GitHub + link: https://github.com/xray-eval/xray + +features: + - title: Quick start + details: Zero to your first replay in five minutes. Copy, paste, run. + link: /quickstart + linkText: Start here + - title: Integrate + details: Add xray to a LiveKit Agents worker. The full walkthrough. + link: /integrate + linkText: Walkthrough + - title: Python SDK + details: The xray-py reference. Conversation, Turn, Assertion, Judge, run, run_live, and attach. + link: /sdk-python + linkText: API reference + - title: Wire contract + details: The OpenTelemetry spans your agent must emit to be recognized. + link: /wire-contract + linkText: OTLP spec + - title: Architecture + details: How xray works inside. The processes, the data paths, and storage. + link: /architecture + linkText: Read the model --- -# xray docs - -**xray** is an open-source, self-hosted replay/eval framework for LiveKit -voice agents. You author a conversation in Python, run it against your agent, -and inspect every turn — audio, transcripts, tool calls, model usage, timings, -and pass/fail verdicts. One Docker image, one SQLite file, one Python SDK. No -accounts, no telemetry, no external database. - -```bash -docker run -v ./data:/data -p 127.0.0.1:8080:8080 ghcr.io/xray-eval/xray:latest -# open http://localhost:8080 · API reference at /docs -pip install "xray-py[livekit]" -``` - -## Where to go - -| Doc | Read it when | -|---|---| -| [Architecture](./architecture.md) | You want the mental model: the three processes, the two write paths, the analyze chain, and storage. | -| [Integrate](./integrate.md) | You have a LiveKit Agents worker and want xray to record + evaluate it. The end-to-end walkthrough. | -| [Python SDK](./sdk-python.md) | The authoritative `xray-py` reference: `Conversation` / `Turn` / `Assertion` / `Judge`, `run` / `run_live`, `attach`, the runtimes. | -| [Wire contract](./wire-contract.md) | You're checking what your agent's OTLP spans must look like to be recognized. | - -The live API reference (OpenAPI 3.1, rendered by Scalar) is at `/docs` on any -running instance. - ## How it fits together -1. **Author** a `Conversation` — user turns, per-turn `Assertion`s, and an - optional conversation-level `Judge` — in Python. -2. **Run** it with `xray.run(...)`. The SDK joins your LiveKit room as a - user-side participant, plays the user audio, captures the agent's audio + - transcript, and uploads a stereo WAV. -3. Your agent emits **OpenTelemetry spans** during the run; xray's OTLP - receiver routes them by `xray.replay.id` and extracts tool calls + model - usage. -4. The server **analyzes** the recording (VAD → per-turn transcription → - metrics → assertions + judges) and returns a `ReplayResult`. -5. **Inspect** and **compare** runs in the UI. +xray works in five steps. + +1. **Write a test.** You define a `Conversation` in Python. It holds user turns, a per-turn `Assertion` or two, and an optional `Judge` for the whole conversation. +2. **Run it.** You call `xray.run(...)`. The SDK joins your LiveKit room as the user. It plays the user audio, records the agent's audio and transcript, and uploads one stereo WAV file. +3. **Your agent reports spans.** During the run, your agent emits OpenTelemetry spans. These are small records of what it did. xray matches them to the run by the `xray.replay.id` value and reads the tool calls and model usage. +4. **The server analyzes the recording.** It finds each turn, transcribes it, measures the timings, and runs your assertions and judges. You get back a `ReplayResult`. +5. **You inspect the run.** Open the UI to replay the audio, read each turn, and compare runs side by side. -> **Alpha.** The wire and SDK API can break between minor versions. Upgrading -> wipes data — delete `/data/xray.db` before starting a new container. +> **Alpha.** The wire format and the SDK API can change between minor versions. Upgrading wipes your data. Delete `/data/xray.db` before you start a new container. diff --git a/docs/integrate.md b/docs/integrate.md index f9f8bd8..55f3f7a 100644 --- a/docs/integrate.md +++ b/docs/integrate.md @@ -1,33 +1,24 @@ --- -layout: default title: Integrate -nav_order: 3 --- # Integrating xray into an existing LiveKit Agents worker -This is the canonical walkthrough. If you've got a LiveKit Agents -worker today and you want xray to record + replay its conversations, -read top-to-bottom and copy the code blocks. +This is the main walkthrough. LiveKit Agents is a framework for building voice agents (programs you talk to). A "worker" is the process that runs your agent. + +Do you already have a LiveKit Agents worker? Do you want xray to record and replay its conversations? Then read this page top-to-bottom and copy the code blocks. You'll need: -- xray running. Latest image, mounted volume for `/data`. The - reference compose snippet is at the bottom of this doc. -- LiveKit server **≥ v1.7** reachable from both the test driver and - the agent worker (the xray SDK propagates the replay context via - `participant.attributes`, added in 1.7). -- Python **3.10+** for the agent. The xray SDK runs on the same - Python you ship your agent on; no version uplift required. +- xray running. Use the latest image, with a mounted volume for `/data`. The reference compose snippet is at the bottom of this doc. +- LiveKit server **≥ v1.7**. It must be reachable from both the test driver and the agent worker. The xray SDK propagates the replay context through `participant.attributes`, a field added in 1.7. ("Propagate" here means it passes the replay info along so the agent can read it.) +- Python **3.10+** for the agent. The xray SDK runs on the same Python you ship your agent on. You do not need to upgrade your Python version. -The example below is a real-world voice-service worker; the wiring -is identical for any LiveKit Agents codebase. +The example below is a real voice-service worker. The wiring is the same for any LiveKit Agents codebase. ## Request flow at a glance -`xray.run(...)` orchestrates the calls below. You don't write any of -them by hand — but this is the sequence to keep in your head when -something fails: +`xray.run(...)` makes the calls below for you. You don't write any of them by hand. But this is the sequence to keep in your head when something fails: ```mermaid sequenceDiagram @@ -62,13 +53,12 @@ sequenceDiagram Xray-->>SDK: evaluation_complete (full ReplayResult payload) ``` -Two surfaces, one trust boundary: the **control plane** (the first -two POSTs + the audio / analyze / events / PATCH calls) is the only -write path that can create rows. The **OTLP receiver** (the agent's -`POST /v1/otlp/v1/traces`) is a *filter*: spans tagged with an -unknown `xray.replay.id` are dropped silently. That's safe precisely -because the replay row exists before the runtime emits its first -span. +There are two surfaces, with one trust boundary between them. + +- The **control plane** is the first two POSTs, plus the audio, analyze, events, and PATCH calls. It is the only write path that can create rows in the database. +- The **OTLP receiver** is the agent's `POST /v1/otlp/v1/traces`. (OTLP is the OpenTelemetry Protocol, a standard way to send tracing data. A "span" is one timed unit of work in a trace, like a single tool call.) This receiver is a *filter*. If a span is tagged with an `xray.replay.id` the server doesn't know, the span is dropped without an error. + +Why is dropping unknown spans safe? Because the replay row is always created first, before the runtime sends its first span. So a known replay id always has a row waiting for it. --- @@ -78,8 +68,7 @@ span. pip install "xray-py[livekit]" ``` -The `[livekit]` extra pulls in `livekit` + `livekit-api`. Drop it if -you implement your own driver class. +The `[livekit]` extra pulls in `livekit` and `livekit-api`. (An "extra" is an optional set of dependencies you opt into with the square brackets.) Drop the `[livekit]` part if you write your own driver class. Set `XRAY_OTLP_ENDPOINT` on the agent worker: @@ -87,10 +76,12 @@ Set `XRAY_OTLP_ENDPOINT` on the agent worker: export XRAY_OTLP_ENDPOINT=http://xray:8080 ``` -xray's OTLP receiver accepts both `application/x-protobuf` (the stock -OTel HTTP exporter's default) and `application/json`, so existing OTEL -pipelines work too — `xray.attach` ships an OTLP/JSON exporter -configured to point at xray. +xray's OTLP receiver accepts two formats: + +- `application/x-protobuf`, which is the stock OTel HTTP exporter's default. +- `application/json`. + +So your existing OpenTelemetry pipelines work too. `xray.attach` ships an OTLP/JSON exporter already pointed at xray. --- @@ -120,40 +111,26 @@ cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint)) Notes: -- `xray.attach` is an **async context manager**, not a decorator. - Decorator wrappers break LiveKit Agents' multiprocessing - forkserver pickling (the agent runs each job in a fresh - subprocess that picks up the entrypoint by `__main__.entrypoint` - lookup). -- Call `xray.attach` **after** `ctx.connect(...)` — before connect, - `ctx.room.remote_participants` is empty and the bind has nothing - to scan. +- `xray.attach` is an **async context manager**, not a decorator. (A context manager is the `async with` block above. It runs setup code when you enter and cleanup code when you exit.) Decorator wrappers break LiveKit Agents' multiprocessing forkserver pickling. LiveKit runs each job in a fresh subprocess, and that subprocess finds the entrypoint by looking up `__main__.entrypoint`. +- Call `xray.attach` **after** `ctx.connect(...)`. Before connect, `ctx.room.remote_participants` is empty, so the bind has nothing to scan. --- ## 3. Emit OTEL spans -xray's OTLP receiver accepts every span the agent worker emits. -Recognized vocabularies (`xray.*`, OTel GenAI `gen_ai.*`, Langfuse -`langfuse.*`) get persisted as raw spans AND extracted into structured -rows where the vocabulary supports it: +xray's OTLP receiver accepts every span the agent worker emits. ("Emit" means the agent sends the span out.) -- `gen_ai.operation.name = execute_tool` → `tool_calls` row. -- `gen_ai.operation.name = chat` / `text_completion` (and the Langfuse - `generation` observation) → `model_usage` row. -- `xray.turn` / `xray.stage.stt` / `xray.stage.tts` spans land in the raw - `spans` table for the inspector's timeline. They carry no structured - payload — server evaluation runs from the declared `Assertion` / - `Judge` variants, not from driver-emitted spans. +Some vocabularies are recognized: `xray.*`, OTel GenAI `gen_ai.*`, and Langfuse `langfuse.*`. (A "vocabulary" is a known set of attribute names.) Recognized spans are saved as raw spans. They are also pulled into structured database rows when the vocabulary supports it: + +- `gen_ai.operation.name = execute_tool` becomes a `tool_calls` row. +- `gen_ai.operation.name = chat` or `text_completion` (and the Langfuse `generation` observation) becomes a `model_usage` row. +- `xray.turn`, `xray.stage.stt`, and `xray.stage.tts` spans land in the raw `spans` table for the inspector's timeline. They carry no structured payload. Server evaluation runs from the declared `Assertion` and `Judge` variants, not from driver-emitted spans. The full attribute contract is in [`wire-contract.md`](./wire-contract.md). -Spans from unrecognized vocabularies are dropped silently — that's the -"filter, not a gate" design so noisy framework spans don't fill the DB. +Spans from unrecognized vocabularies are dropped without an error. This is the "filter, not a gate" design. It keeps noisy framework spans from filling the database. -Tool calls / model usage from any OTel-instrumented LLM client (the -`opentelemetry-instrumentation-openai-v2` package, Langfuse, etc.) -land in xray automatically. No xray-specific code required. +Tool calls and model usage land in xray automatically. This works for any OTel-instrumented LLM client, such as the `opentelemetry-instrumentation-openai-v2` package or Langfuse. No xray-specific code is required. --- @@ -212,7 +189,7 @@ async def main(): for a in result.assertions: print(f" turn {a.turn_idx} [{a.kind}]: {a.status} {a.message or ''}") for j in result.judges: - print(f" judge {j.judge_idx} [{j.kind}]: {j.status} score={j.score} — {j.reason}") + print(f" judge {j.judge_idx} [{j.kind}]: {j.status} score={j.score} - {j.reason}") asyncio.run(main()) @@ -226,54 +203,26 @@ async def test_booking_happy_path(): assert result.passed, xray.format_failures(result) ``` -`xray.run` is async — wrap in `asyncio.run` for sync test harnesses. -There is no sync `xray.run`; the previous one was a footgun in -already-running loops (pytest-asyncio, Jupyter, LiveKit Agents). +`xray.run` is async. Wrap it in `asyncio.run` for sync test harnesses. There is no sync `xray.run`. The old sync version was a footgun in loops that are already running, such as pytest-asyncio, Jupyter, and LiveKit Agents. What `xray.run` does: -1. POST the Conversation (idempotent upsert; assertions + judges - are part of the canonical spec the server hashes). +1. POST the Conversation. This is an idempotent upsert, meaning calling it twice with the same input is safe. Assertions and judges are part of the canonical spec the server hashes. 2. POST the Replay row eagerly (`lifecycle_state='pending'`). -3. Bind the driver, attach replay baggage, run the driver — playing - user audio + capturing agent audio + transcripts. -4. Assemble a 48 kHz int16 **stereo WAV** (L = user, R = agent, - wall-clock-aligned) and POST it to `/v1/replays/:id/audio`, with the - `X-Recording-Started-At` header set to the wall-clock (ISO-8601 UTC) of - audio sample 0. This anchor is the sole origin for mapping span - timestamps onto the audio timeline — the server derives each tool / - model / span row's per-turn membership from it. **A custom `Runtime` - that produces audio MUST report it**: return - `RuntimeResult.recording_started_at_epoch` (Unix epoch seconds of sample - 0) and `xray.run` sends the header for you. Omit it and span→turn - attribution is skipped — every `tool_called` / `tool_not_called` / - `tool_args_match` / `max_ttft_ms` assertion comes back `errored`. -5. POST `/v1/replays/:id/analyze` — server enqueues the three-stage - analyze chain (`analyze-replay` → `calculate-metrics` → - `evaluate-replay`). -6. Stream SSE on `/v1/replays/:id/events` until `evaluation_complete` - (chain finished) or `failed` (chain stopped). -7. Translate the `evaluation_complete` payload into - `xray.ReplayResult` and return it. - -Failure model: assertion failures don't raise — they're outcomes on -`result.assertions`. Infrastructure failures (transcription provider -down, judge LLM unavailable) raise `xray.ReplayEvaluationError`. +3. Bind the driver, attach replay baggage, and run the driver. This plays the user audio, then captures the agent audio and transcripts. +4. Assemble a 48 kHz int16 **stereo WAV** (L = user, R = agent, wall-clock-aligned) and POST it to `/v1/replays/:id/audio`. Set the `X-Recording-Started-At` header to the wall-clock time (ISO-8601 UTC) of audio sample 0. This anchor is the sole origin for mapping span timestamps onto the audio timeline. The server uses it to work out which turn each tool, model, and span row belongs to. **A custom `Runtime` that produces audio MUST report it.** Return `RuntimeResult.recording_started_at_epoch` (Unix epoch seconds of sample 0), and `xray.run` sends the header for you. If you omit it, span-to-turn attribution is skipped. Then every `tool_called`, `tool_not_called`, `tool_args_match`, and `max_ttft_ms` assertion comes back `errored`. +5. POST `/v1/replays/:id/analyze`. The server enqueues the three-stage analyze chain (`analyze-replay`, then `calculate-metrics`, then `evaluate-replay`). +6. Stream SSE on `/v1/replays/:id/events` until `evaluation_complete` (the chain finished) or `failed` (the chain stopped). (SSE is Server-Sent Events, a one-way stream of updates from the server.) +7. Translate the `evaluation_complete` payload into `xray.ReplayResult` and return it. + +Failure model: assertion failures don't raise. They are outcomes on `result.assertions`. Infrastructure failures do raise `xray.ReplayEvaluationError`. Examples are the transcription provider being down or the judge LLM being unavailable. User-turn audio formats: -- `RecordedAudio(path=...)` — 48 kHz mono int16 WAV on disk, uploaded - to xray with the conversation spec. -- `TtsAudio()` (or no `audio` at all) — synthesized **server-side** - during the conversation upsert, by the provider the xray server is - configured with (`XRAY_TTS_PROVIDER`: OpenAI / Google / Mistral). - The generated audio is stored content-addressed and its sha256 is - part of the conversation hash; the driver pulls the exact bytes back - before joining the room. No TTS key in the SDK's process. +- `RecordedAudio(path=...)`. This is a 48 kHz mono int16 WAV file on disk. It is uploaded to xray with the conversation spec. +- `TtsAudio()` (or no `audio` at all). This is synthesized **server-side** during the conversation upsert. (TTS means text-to-speech: turning written text into spoken audio.) The xray server uses whichever provider it is configured with (`XRAY_TTS_PROVIDER`: OpenAI, Google, or Mistral). The generated audio is stored content-addressed, and its sha256 is part of the conversation hash. The driver pulls the exact bytes back before joining the room. There is no TTS key in the SDK's process. -For Cartesia / 11Labs / Deepgram, synthesize externally and pass the -output as `RecordedAudio` — additional server-side TTS providers are -one file each in `src/server/tts/`. +For Cartesia, 11Labs, or Deepgram, synthesize the audio yourself and pass the output as `RecordedAudio`. Adding more server-side TTS providers is one file each in `src/server/tts/`. --- @@ -281,28 +230,13 @@ one file each in `src/server/tts/`. `xray.run(...)` returns `xray.ReplayResult`: -- `passed: bool` — `True` iff every assertion *and* every judge ran to - a `passed` status. `errored` counts as not-passed. -- `assertions: tuple[AssertionOutcome, ...]` — one entry per declared - assertion, in the order they appear on each turn. Each carries - `turn_idx`, `assertion_idx`, `kind`, `status` (`passed`/`failed`/ - `errored`), and `message` (the reason on non-passed). -- `judges: tuple[JudgeOutcome, ...]` — one entry per declared judge. - Each carries `judge_idx`, `kind`, `status`, the LLM's 0..100 - `score`, and the LLM's natural-language `reason`. -- `metrics: tuple[TurnMetrics, ...]` — per-turn timing computed - server-side: `agent_response_ms`, `interrupted`. (Model TTFT is a - per-call attribute on the replay's `model_usage` rows, populated when - the agent's instrumentation emits - `gen_ai.response.time_to_first_chunk` — not a per-turn metric.) -- `replay_id` + `conversation_hash` — pointers back to the server-side - rows for follow-up inspection. - -If you need the live audio + turn boundaries the server derived (for a -custom UI, ad-hoc analysis), `GET /v1/replays/:id` carries the full -detail; `GET /v1/replays/:id/result` returns the same `ReplayResult` -payload outside of the SSE stream so late subscribers can fetch it -directly. +- `passed: bool`. This is `True` only if every assertion *and* every judge ran to a `passed` status. An `errored` status counts as not-passed. +- `assertions: tuple[AssertionOutcome, ...]`. There is one entry per declared assertion, in the order they appear on each turn. Each one carries `turn_idx`, `assertion_idx`, `kind`, `status` (`passed`, `failed`, or `errored`), and `message` (the reason, set when the status is not passed). +- `judges: tuple[JudgeOutcome, ...]`. There is one entry per declared judge. Each one carries `judge_idx`, `kind`, `status`, the LLM's 0..100 `score`, and the LLM's natural-language `reason`. +- `metrics: tuple[TurnMetrics, ...]`. These are per-turn timings computed server-side: `agent_response_ms` and `interrupted`. (TTFT means time to first token. Model TTFT is a per-call attribute on the replay's `model_usage` rows. It is populated when the agent's instrumentation emits `gen_ai.response.time_to_first_chunk`. It is not a per-turn metric.) +- `replay_id` and `conversation_hash`. These point back to the server-side rows for follow-up inspection. + +Do you need the live audio plus the turn boundaries the server derived? For example, for a custom UI or ad-hoc analysis. Then `GET /v1/replays/:id` carries the full detail. `GET /v1/replays/:id/result` returns the same `ReplayResult` payload outside of the SSE stream, so late subscribers can fetch it directly. --- @@ -328,55 +262,24 @@ volumes: xray-data: ``` -xray ships as a single Docker image. Two SQLite files share the -mounted volume: +xray ships as a single Docker image. Two SQLite files share the mounted volume: -- `/data/xray.db` — conversations, replays, replay_turns, - speech_segments, spans, tool_calls, model_usage, turn_transcripts, - replay_metrics, assertion_results, judge_results, replay_evaluations, - tts_synth_cache (13 tables; see [`architecture.md`](./architecture.md)). -- `/data/bunqueue.db` — bunqueue's job queue + DLQ (the - `analyze-replay` worker runs embedded in the same Bun process). +- `/data/xray.db`. This holds conversations, replays, replay_turns, speech_segments, spans, tool_calls, model_usage, turn_transcripts, replay_metrics, assertion_results, judge_results, replay_evaluations, and tts_synth_cache (13 tables; see [`architecture.md`](./architecture.md)). +- `/data/bunqueue.db`. This holds bunqueue's job queue and DLQ. (DLQ means dead-letter queue, where failed jobs go.) The `analyze-replay` worker runs embedded in the same Bun process. -Inspector UI at `http://localhost:8080`. +The inspector UI is at `http://localhost:8080`. --- ## What changed from earlier alphas -This release moves assertion + judge evaluation onto the server. - -- **Declarative assertions + judges.** Replace per-turn lambdas with - `Assertion.contains(...)`, `Assertion.tool_called(...)`, - `Assertion.max_latency_ms(...)`, etc. Replace per-replay judge - callables with `Judge.text_match(reference, pass_score=...)`. Both - ship on the wire and run server-side — every SDK (Python today, - others tomorrow) speaks the same shape. -- **`xray.run(...)` returns `ReplayResult`** with `passed` + per- - assertion / per-judge / per-turn-metrics. Assertion failures don't - raise — `assert result.passed, format_failures(result)` is the - pytest idiom. Only infrastructure failures (transcription provider - down, judge crashing) raise `ReplayEvaluationError`. -- **Three-stage server chain.** `/analyze` now enqueues - `analyze-replay` (VAD + per-turn Whisper transcription), which - enqueues `calculate-metrics` (agent_response_ms, interrupted), which - enqueues `evaluate-replay` (runs all assertions + judges, emits - `evaluation_complete` SSE). -- **SSE event renamed.** The `completed` event is gone; the chain - emits `evaluation_complete` with the full `ReplayResult` payload. -- **`GET /v1/replays/:id/result`** — fetches the same payload outside - the SSE stream for late subscribers. -- **Server requires a provider key** at runtime (`OPENAI_API_KEY`, - `GOOGLE_API_KEY`, or `MISTRAL_API_KEY`) for TTS + transcription + - judge calls. Per-stage overrides: `XRAY_TTS_PROVIDER` / - `XRAY_TRANSCRIPTION_PROVIDER` / `XRAY_JUDGE_PROVIDER`, plus - `XRAY_TTS_MODEL` / `XRAY_TTS_VOICE` / `XRAY_TRANSCRIPTION_MODEL` / - `XRAY_JUDGE_MODEL`. -- **No more SDK-side enrichment fetch / final PATCH.** The SDK only - PATCHes when the driver itself fails (mixdown error, missing audio - file). -- **Schema reset.** If you're upgrading an existing volume, drop - `xray.db` and `bunqueue.db` before starting the new container — - the column shape of `replays` + the new `turn_transcripts` / - `replay_metrics` / `assertion_results` / `judge_results` / - `replay_evaluations` tables aren't migration-compatible. +This release moves assertion and judge evaluation onto the server. + +- **Declarative assertions + judges.** Replace per-turn lambdas with `Assertion.contains(...)`, `Assertion.tool_called(...)`, `Assertion.max_latency_ms(...)`, and so on. Replace per-replay judge callables with `Judge.text_match(reference, pass_score=...)`. Both ship on the wire and run server-side. So every SDK (Python today, others tomorrow) speaks the same shape. +- **`xray.run(...)` returns `ReplayResult`.** It includes `passed` plus per-assertion, per-judge, and per-turn-metrics data. Assertion failures don't raise. The pytest idiom is `assert result.passed, format_failures(result)`. Only infrastructure failures raise `ReplayEvaluationError`, such as the transcription provider being down or the judge crashing. +- **Three-stage server chain.** `/analyze` now enqueues `analyze-replay` (VAD plus per-turn Whisper transcription). That enqueues `calculate-metrics` (agent_response_ms, interrupted). That enqueues `evaluate-replay`, which runs all assertions and judges, then emits the `evaluation_complete` SSE. (VAD means voice activity detection: finding which parts of the audio contain speech.) +- **SSE event renamed.** The `completed` event is gone. The chain now emits `evaluation_complete` with the full `ReplayResult` payload. +- **`GET /v1/replays/:id/result`.** This fetches the same payload outside the SSE stream, for late subscribers. +- **Server requires a provider key** at runtime (`OPENAI_API_KEY`, `GOOGLE_API_KEY`, or `MISTRAL_API_KEY`) for TTS, transcription, and judge calls. You can override each stage: `XRAY_TTS_PROVIDER`, `XRAY_TRANSCRIPTION_PROVIDER`, and `XRAY_JUDGE_PROVIDER`, plus `XRAY_TTS_MODEL`, `XRAY_TTS_VOICE`, `XRAY_TRANSCRIPTION_MODEL`, and `XRAY_JUDGE_MODEL`. +- **No more SDK-side enrichment fetch / final PATCH.** The SDK only PATCHes when the driver itself fails, such as a mixdown error or a missing audio file. +- **Schema reset.** Are you upgrading an existing volume? Then drop `xray.db` and `bunqueue.db` before starting the new container. The column shape of `replays`, plus the new `turn_transcripts`, `replay_metrics`, `assertion_results`, `judge_results`, and `replay_evaluations` tables, are not migration-compatible. diff --git a/docs/quickstart.md b/docs/quickstart.md new file mode 100644 index 0000000..922aa7c --- /dev/null +++ b/docs/quickstart.md @@ -0,0 +1,121 @@ +--- +title: Quick start +--- + +# Quick start + +Zero to your first replay in about five minutes. Your first test needs **no +changes to your agent's code**. + +You need three things: Docker, Python 3.10 or newer, and a running LiveKit Agents +worker (the voice agent you want to test). + +## 1. Start xray + +```bash +docker run -v ./data:/data -p 127.0.0.1:8080:8080 \ + -e OPENAI_API_KEY=sk-your-key \ + ghcr.io/xray-eval/xray:latest +``` + +Open [http://localhost:8080](http://localhost:8080). That is the inspector. Every +run shows up there. + +xray uses `OPENAI_API_KEY` to transcribe the recorded audio. It is the only +credential the server needs to get you started. + +## 2. Install the SDK + +```bash +pip install "xray-py[livekit]" +``` + +## 3. Write and run your first test + +```python +import asyncio +import xray +from xray import Assertion +from xray.runtime.livekit import LiveKitRuntime + +conv = xray.Conversation( + name="opening-hours", + turns=[ + xray.Turn.user("Hi, are you open today?"), + xray.Turn.agent(assertions=(Assertion.contains("open"),)), + ], +) + +driver = LiveKitRuntime( + url="ws://localhost:7880", + api_key="devkey", + api_secret="devsecret32charsminimumlengthxyz123", + room="quickstart", +) + +async def main(): + result = await xray.run(conversation=conv, runtime=driver) + assert result.passed, xray.format_failures(result) + +asyncio.run(main()) +``` + +That is the whole loop. xray joins your LiveKit room as the user, speaks the user +line, records your agent's reply, transcribes it, and checks the assertion. + +Swap in your own LiveKit URL and keys, and change the assertion to match what your +agent says. + +Your agent did not need any xray code. xray records it from the outside, by +listening in the room. + +The user turn has no audio file, so xray synthesizes the speech for you. To use a +recording instead, pass `audio=RecordedAudio(path="utterance.wav")`. + +## 4. Inspect the run + +Open [http://localhost:8080](http://localhost:8080). Click the run to replay the +audio, read every turn, and see the timings. + +## Go further: check tool calls and model usage + +Want to assert that your agent called a tool, or check its time-to-first-token? +Those facts live in your agent's traces, not its audio. So xray needs your agent +to send them. This takes two small additions. + +First, wrap your agent's entrypoint body in `xray.attach`: + +```python +import xray +from livekit.agents import JobContext + +async def entrypoint(ctx: JobContext) -> None: + await ctx.connect() + async with xray.attach(ctx, service_name="my-agent"): + await your_agent.run(ctx) # your existing agent code, unchanged +``` + +Second, tell the SDK where to send the traces. Set this in the shell where you +start your agent worker: + +```bash +export XRAY_OTLP_ENDPOINT=http://localhost:8080 +``` + +Use the same address you opened in step 1. If your agent runs inside the same +Docker network as xray, use the service name instead, like `http://xray:8080`. + +Now tool and model assertions work: + +```python +xray.Turn.agent(assertions=( + Assertion.tool_called("reserve_table"), + Assertion.max_ttft_ms(800), +)) +``` + +## Next steps + +- [Integrate](./integrate.md): the full walkthrough, with audio files, judges, and CI. +- [Python SDK](./sdk-python.md): every `Conversation`, `Assertion`, and `Judge` option. +- [Wire contract](./wire-contract.md): the spans your agent emits, and how your existing OpenTelemetry setup plugs in. diff --git a/docs/sdk-python.md b/docs/sdk-python.md index d5fd4ee..2c5ffb3 100644 --- a/docs/sdk-python.md +++ b/docs/sdk-python.md @@ -1,33 +1,39 @@ --- -layout: default title: Python SDK -nav_order: 4 --- # Python SDK (`xray-py`) -The Python SDK is how you author conversations, drive them against your -LiveKit agent, and wire the agent so its OpenTelemetry spans land in xray. -This is the authoritative reference; it is generated from — and kept in sync -with — the source under [`sdk/python/`](https://github.com/xray-eval/xray/tree/main/sdk/python). +The Python SDK does three jobs for you: -- Package: **`xray-py`**, import name **`xray`**, Elastic-2.0, ships `py.typed`. +1. You author conversations (the scripts your agent will be tested against). +2. You drive those conversations against your LiveKit agent. LiveKit is the framework your voice agent runs on. +3. You wire the agent so its OpenTelemetry spans land in xray. OpenTelemetry (OTel) is a standard for emitting trace data; a "span" is one timed unit of work. + +This page is the authoritative reference. It is generated from the source code, and kept in sync with it. The source lives under [`sdk/python/`](https://github.com/xray-eval/xray/tree/main/sdk/python). + +Quick facts: + +- Package: **`xray-py`**. Import name: **`xray`**. License: Elastic-2.0. Ships `py.typed` (so type checkers see its types). - Requires **Python ≥ 3.10**. -- Everything in `__all__` is importable directly as `xray.`. +- Everything listed in `__all__` is importable directly as `xray.`. --- ## Install ```bash -pip install xray-py # base — authoring + a custom Runtime +pip install xray-py # base - authoring + a custom Runtime pip install "xray-py[livekit]" # the scripted + live LiveKit runtimes pip install "xray-py[live]" # OS-mic capture for run_live (sounddevice) ``` -Base dependencies: `httpx`, the OpenTelemetry API/SDK + OTLP/HTTP exporter, -`pydantic`, `typing-extensions`. The `[livekit]` extra pulls in `livekit` + -`livekit-api`; `[live]` adds `sounddevice` for microphone capture. +The base install pulls in these dependencies: `httpx`, the OpenTelemetry API/SDK plus the OTLP/HTTP exporter, `pydantic`, and `typing-extensions`. + +The extras add more: + +- The `[livekit]` extra pulls in `livekit` and `livekit-api`. +- The `[live]` extra adds `sounddevice` for microphone capture. --- @@ -53,19 +59,24 @@ Base dependencies: `httpx`, the OpenTelemetry API/SDK + OTLP/HTTP exporter, | `format_failures` | fn | Render non-passed outcomes as a string. | | `XrayError` / `ReplayEvaluationError` | exception | SDK / server-chain errors. | -The runtimes and the low-level OTEL helpers live one import below the top -level: `xray.runtime.livekit.LiveKitRuntime`, `xray.runtime.livekit_live.LiveKitLiveRuntime`, -the `Runtime` ABC + protocols in `xray.runtime.base`, and `install` / -`XraySpanExporter` / `XrayBaggageSpanProcessor` in `xray.otel`. +A few things live one import level below the top, not at `xray.`: + +- The runtimes: `xray.runtime.livekit.LiveKitRuntime` and `xray.runtime.livekit_live.LiveKitLiveRuntime`. +- The `Runtime` ABC plus its protocols, in `xray.runtime.base`. +- The low-level OTEL helpers `install`, `XraySpanExporter`, and `XrayBaggageSpanProcessor`, in `xray.otel`. -> There is **no `LiveKitDriver`** — the v1 LiveKit class is `LiveKitRuntime`. -> There is **no `xray.instrument` decorator** — the wiring entry point is the +Two naming notes, so you don't go looking for the wrong symbol: + +> There is **no `LiveKitDriver`**. The v1 LiveKit class is `LiveKitRuntime`. +> There is **no `xray.instrument` decorator**. The wiring entry point is the > async context manager `attach`. --- ## Authoring a Conversation +Here is a complete example. Read the sections below for what each piece does. + ```python from xray import Assertion, Conversation, Judge, Turn @@ -92,13 +103,17 @@ conv = Conversation( Conversation(name: str, turns: list[Turn], judges: tuple[Judge, ...] = (), live: bool = False) ``` -A `Conversation`'s identity is a SHA-256 content hash over the canonical spec -(turns + per-turn assertions + judges, with any `RecordedAudio` bytes folded -in by their sha256). `name` is a free-form display label — renaming the same -spec re-attaches Replays to the same Conversation row; editing any turn, -assertion, judge, or WAV forks a new Conversation. The server computes the -hash; the SDK never hashes anything. Construction raises `ValueError` on an -empty `name`, or on empty `turns` unless `live=True`. +A `Conversation`'s identity is a SHA-256 content hash. The hash is computed over the canonical spec: the turns, the per-turn assertions, and the judges. Any `RecordedAudio` bytes are folded in by their sha256. + +What this means in practice: + +- `name` is a free-form display label. It is not part of the identity. +- Renaming the same spec re-attaches Replays to the same Conversation row. +- Editing any turn, assertion, judge, or WAV forks a new Conversation. + +The server computes the hash. The SDK never hashes anything. + +Construction raises `ValueError` in two cases: an empty `name`, or empty `turns` (unless `live=True`). ### `Turn` @@ -107,15 +122,15 @@ Turn.user(text: str, *, key=None, audio: AudioRef | None = None, assertions=()) Turn.agent(*, key=None, assertions=()) -> Turn ``` -`Turn.agent` takes **no `text`** — the agent's text is observed at runtime and -transcribed server-side, not declared. A user turn with no `audio` is sent as -a server-side TTS marker (see [Audio references](#audio-references)). +Note that `Turn.agent` takes **no `text`**. You don't declare what the agent says. The agent's text is observed at runtime and transcribed server-side. + +A user turn with no `audio` is sent as a server-side TTS marker. TTS means text-to-speech: the server generates the audio. See [Audio references](#audio-references). ### Assertions -All nine builders are `Assertion` classmethods and validate their arguments at -construction (fail-fast `ValueError`). All run **server-side** during the -`evaluate-replay` stage. +An assertion is a single declarative check on one turn. + +All nine builders are `Assertion` classmethods. They validate their arguments at construction time, so a bad argument raises `ValueError` right away (fail-fast). All of them run **server-side**, during the `evaluate-replay` stage. ```python Assertion.contains(text, *, case_insensitive=True) @@ -139,11 +154,9 @@ Assertion.max_ttft_ms(max_ms) # max_ms >= 1 | `max_latency_ms` | The agent responded within `max_ms` of the user turn ending. | | `max_ttft_ms` | The model's time-to-first-chunk was within `max_ms`. | -The tool/TTFT assertions depend on span→turn attribution from the audio -timeline. If the runtime uploaded no recording anchor, they come back -**`errored`** (not failed) — xray can't place spans on the timeline without -it. `LiveKitRuntime` always reports the anchor; a custom `Runtime` must too -(see [Runtimes](#runtimes)). +The tool assertions and the TTFT assertion need span-to-turn attribution. That is, xray has to map each span onto the audio timeline to know which turn it belongs to. (TTFT means time-to-first-token: how long the model takes to start replying.) + +To do that mapping, xray needs a recording anchor: the wall-clock time of the first audio sample. If the runtime uploaded no anchor, these assertions come back **`errored`**, not failed. Without the anchor xray cannot place spans on the timeline. `LiveKitRuntime` always reports the anchor. A custom `Runtime` must report it too (see [Runtimes](#runtimes)). ### Judges @@ -151,11 +164,13 @@ it. `LiveKitRuntime` always reports the anchor; a custom `Runtime` must too Judge.text_match(reference: str, *, rubric: str | None = None, pass_score: int = 70) -> Judge ``` -A conversation-level LLM judge: the server asks the configured judge model to -score the full transcript against `reference` (optionally guided by `rubric`) -on a 0–100 scale, passing iff `score >= pass_score`. `text_match` is the only -judge kind in v1. Validation raises `ValueError` on an empty `reference`, an -empty `rubric`, or a `pass_score` outside `0..100`. +A judge is a conversation-level LLM evaluator. It scores the whole transcript, not a single turn. + +Here is how `text_match` works. The server asks the configured judge model to score the full transcript against `reference`. You can optionally guide the scoring with `rubric`. The score is on a 0 to 100 scale. The judge passes only when `score >= pass_score`. + +`text_match` is the only judge kind in v1. + +Validation raises `ValueError` for any of these: an empty `reference`, an empty `rubric`, or a `pass_score` outside `0..100`. ### Audio references @@ -164,23 +179,23 @@ RecordedAudio(path: str) # an on-disk WAV: 48 kHz, mono, 16-bit TtsAudio(voice_id: str | None = None) ``` -A user turn's `audio` is one of these (or omitted, which is equivalent to -`TtsAudio()`): +A user turn's `audio` is one of these two references. You can also omit it, which is the same as passing `TtsAudio()`. + +- **`RecordedAudio`** is a real WAV file you already have. Its bytes are uploaded as a multipart file part alongside the conversation, and folded into the hash. +- **`TtsAudio`** is just a marker. It tells the **server** to synthesize the turn for you. + +Here is what the server does with a `TtsAudio` marker: + +1. It synthesizes the audio at conversation-upsert time, using the provider it's configured with (`XRAY_TTS_PROVIDER`). The voice comes from `XRAY_TTS_VOICE`, or from the per-turn `voice_id`. +2. It content-addresses the WAV (keys it by content). +3. It folds the WAV's sha256 into the conversation hash. -- **`RecordedAudio`** — a real WAV you already have. Its bytes are uploaded as - a multipart file part with the conversation and folded into the hash. -- **`TtsAudio`** — a marker that the **server** should synthesize the turn. - The xray server synthesizes at conversation-upsert time using the provider - it's configured with (`XRAY_TTS_PROVIDER`, voice from `XRAY_TTS_VOICE` or the - per-turn `voice_id`), content-addresses the WAV, and folds its sha256 into - the conversation hash. **The SDK does no TTS** — no provider key in the SDK - process. `run(...)` simply fetches the synthesized bytes back over HTTP - before driving the room. +**The SDK does no TTS.** There is no provider key in the SDK process. When you call `run(...)`, it simply fetches the synthesized bytes back over HTTP before driving the room. -Convert a WAV to the required format with -`ffmpeg -i in.wav -ar 48000 -ac 1 -sample_fmt s16 out.wav`. For voices xray -doesn't host (Cartesia, ElevenLabs, Deepgram, …), synthesize externally and -pass the result as `RecordedAudio`. +To convert a WAV to the required format, use: +`ffmpeg -i in.wav -ar 48000 -ac 1 -sample_fmt s16 out.wav`. + +xray does not host every voice. For voices it doesn't host (Cartesia, ElevenLabs, Deepgram, and so on), synthesize the audio externally and pass the result as `RecordedAudio`. --- @@ -191,15 +206,18 @@ RunConfig(model: str | None = None, temperature: float | None = None, extra: dict[str, JsonValue] = {}) ``` -Per-replay configuration carried to the server on `POST /v1/replays`. `extra` -keys are flattened to the top level on the wire so the compare UI diffs them -as first-class columns; `model` / `temperature` are omitted when `None`. +This is per-replay configuration. It is carried to the server on `POST /v1/replays`. + +Two details about how it goes over the wire: + +- `extra` keys are flattened to the top level. This lets the compare UI diff them as first-class columns. +- `model` and `temperature` are omitted entirely when they are `None`. --- ## Running -### `run` — scripted +### `run`: scripted ```python async def run(*, conversation: Conversation, runtime: Runtime, @@ -207,23 +225,17 @@ async def run(*, conversation: Conversation, runtime: Runtime, run_config: RunConfig | None = None) -> ReplayResult ``` -Fully keyword-only and async — wrap in `asyncio.run(...)` for a sync harness. -There is no sync `run`. End to end it: - -1. Checks every `RecordedAudio` file exists locally. -2. POSTs the Conversation to `/v1/conversations` (multipart: a `spec` JSON - part + one file part per `RecordedAudio` turn) and reads back the hash. -3. Prefetches every user turn's audio (the bytes the server synthesized or - stored) — before creating the replay, so a failure leaves no orphan row. -4. POSTs the Replay to `/v1/replays` (`{conversation_hash, run_config?}`) and - reads back its `id`. -5. Drives the runtime: binds the replay context, injects user audio, installs - the OTEL pipeline pointed at `xray_url`, attaches replay baggage, runs the - runtime, then force-flushes spans. -6. Uploads the stereo mixdown WAV to `/v1/replays/:id/audio` with the - `X-Recording-Started-At` header, then POSTs `/v1/replays/:id/analyze`. -7. Streams `/v1/replays/:id/events` until `evaluation_complete` (→ returns - `ReplayResult`) or `failed` (→ raises `ReplayEvaluationError`). +This function is fully keyword-only and async. There is no sync `run`. For a sync test harness, wrap the call in `asyncio.run(...)`. + +End to end, here is what `run` does: + +1. Checks that every `RecordedAudio` file exists locally. +2. POSTs the Conversation to `/v1/conversations` and reads back the hash. The body is multipart: a `spec` JSON part plus one file part per `RecordedAudio` turn. +3. Prefetches every user turn's audio (the bytes the server synthesized or stored). It does this before creating the replay, so a failure leaves no orphan row behind. +4. POSTs the Replay to `/v1/replays` (`{conversation_hash, run_config?}`) and reads back its `id`. +5. Drives the runtime. This step binds the replay context, injects the user audio, installs the OTEL pipeline pointed at `xray_url`, attaches the replay baggage, runs the runtime, then force-flushes the spans. +6. Uploads the stereo mixdown WAV to `/v1/replays/:id/audio` with the `X-Recording-Started-At` header, then POSTs `/v1/replays/:id/analyze`. +7. Streams `/v1/replays/:id/events` until one of two events arrives. On `evaluation_complete` it returns a `ReplayResult`. On `failed` it raises `ReplayEvaluationError`. ```python import asyncio @@ -245,12 +257,11 @@ async def main() -> None: asyncio.run(main()) ``` -Per-assertion and per-judge failures **do not raise** — they're outcomes on -the result. `assert result.passed, xray.format_failures(result)` is the pytest -idiom. Only driver-side faults (`XrayError` subclasses) and server-chain -crashes (`ReplayEvaluationError`) raise. +A failed assertion or judge **does not raise**. Each one is an outcome on the result instead. The pytest idiom is `assert result.passed, xray.format_failures(result)`. + +Only two kinds of fault raise: driver-side faults (`XrayError` subclasses) and server-chain crashes (`ReplayEvaluationError`). -### `run_live` — unscripted (OS mic) +### `run_live`: unscripted (OS mic) ```python async def run_live(*, runtime: Runtime, xray_url: str = "http://localhost:8080", @@ -258,13 +269,13 @@ async def run_live(*, runtime: Runtime, xray_url: str = "http://localhost:8080", run_config: RunConfig | None = None) -> ReplayResult ``` -For talking to the agent yourself instead of replaying a scripted WAV. There's -no authored `Conversation`: `run_live` builds an empty `live=True` Conversation -(named `name` or `live-`; the server salts the hash so each session -is its own row), records your mic + the agent's audio, and analyzes the result -the same way. SIGINT (Ctrl-C) ends the session and uploads it. The returned -`ReplayResult` has empty `assertions` / `judges` and `passed=True`, but -populated `metrics`. +Use this when you want to talk to the agent yourself, instead of replaying a scripted WAV. + +There is no authored `Conversation` here. Instead, `run_live` builds an empty `live=True` Conversation for you. It is named `name`, or `live-` if you pass no name. The server salts the hash so each session is its own row. `run_live` records your mic plus the agent's audio, and analyzes the result the same way `run` does. + +To end the session, press Ctrl-C (SIGINT). That stops the session and uploads it. + +The returned `ReplayResult` has empty `assertions` and `judges`, and `passed=True`. Its `metrics` are populated. ```python from xray.runtime.livekit_live import LiveKitLiveRuntime @@ -286,9 +297,7 @@ async def attach(ctx, *, service_name: str | None = None, bind_timeout_s: float = 10.0) -> AsyncGenerator[XraySession | None, None] ``` -Wrap your LiveKit Agents worker entrypoint with `attach`. It is an **async -context manager, not a decorator** — a decorator wrapper breaks LiveKit -Agents' forkserver pickling. +Wrap your LiveKit Agents worker entrypoint with `attach`. It is an **async context manager, not a decorator**. A decorator wrapper would break LiveKit Agents' forkserver pickling. ```python import xray @@ -303,39 +312,36 @@ async def entrypoint(ctx: JobContext) -> None: cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint)) ``` -- Call it **after** `ctx.connect(...)` — before connect, the room has no - remote participants to scan. -- **Endpoint resolution:** the explicit `endpoint=` argument, else the - `XRAY_OTLP_ENDPOINT` environment variable, else none. With no endpoint, - `attach` still binds baggage in-process but installs no OTLP exporter. (In - production no participant carries the `xray` attribute, so `attach` yields - `None` and is effectively a no-op.) -- On block exit it detaches baggage and force-flushes the tracer provider so - spans land before the worker shuts down. +A few rules for using `attach`: -`ctx` is duck-typed — any object exposing `.room.remote_participants` plus -`.on` / `.off` event hooks works; the SDK does not import `livekit.agents`. +- Call it **after** `ctx.connect(...)`. Before connect, the room has no remote participants to scan. +- Endpoint resolution follows this order: the explicit `endpoint=` argument first, then the `XRAY_OTLP_ENDPOINT` environment variable, then none. With no endpoint, `attach` still binds baggage in-process but installs no OTLP exporter. (In production no participant carries the `xray` attribute. So `attach` yields `None` and is effectively a no-op.) +- On block exit, `attach` detaches the baggage and force-flushes the tracer provider. This makes sure spans land before the worker shuts down. + +`ctx` is duck-typed. Any object works as long as it exposes `.room.remote_participants` plus `.on` / `.off` event hooks. The SDK does not import `livekit.agents`. ### Replay-context propagation -The replay context travels on the joining participant's **JWT `xray` -attribute** (read via `participant.attributes["xray"]`). No room or participant -metadata is set, and no `can_update_own_metadata` grant is required. The JSON -payload is exactly: +The replay context travels on the joining participant's **JWT `xray` attribute**. (A JWT is a signed token; "attribute" here means a field on the participant.) The SDK reads it via `participant.attributes["xray"]`. + +No room or participant metadata is set, and no `can_update_own_metadata` grant is required. + +The JSON payload is exactly this: ```json { "replay_id": "...", "conversation_hash": "...", "modality": "voice" } ``` -`attach` reads it, sets OTEL baggage (`xray.replay.id`, -`xray.conversation.hash`, `xray.modality`), and the bundled span processor -lifts that baggage onto every span at start. The server routes spans by -`xray.replay.id`. +Here is the chain that happens next: + +1. `attach` reads that payload. +2. It sets OTEL baggage (`xray.replay.id`, `xray.conversation.hash`, `xray.modality`). Baggage is OTel's way of carrying key-value context along a trace. +3. The bundled span processor lifts that baggage onto every span at start. +4. The server routes spans by `xray.replay.id`. ### `XraySession` -`attach` yields an `XraySession` (or `None`). It exposes read-only -`replay_id` / `conversation_hash` / `modality`, plus one helper: +`attach` yields an `XraySession` (or `None`). The session exposes three read-only fields: `replay_id`, `conversation_hash`, and `modality`. It also exposes one helper: ```python async with session.turn(idx, key=None): @@ -344,20 +350,19 @@ async with session.turn(idx, key=None): ### Low-level OTEL helpers (`xray.otel`) -If you wire OpenTelemetry manually instead of using `attach`: +Use these only if you wire OpenTelemetry manually instead of using `attach`: -- `install(*, endpoint, tracer_provider=None) -> TracerProvider` — idempotent; - registers the baggage span processor + a batch exporter. -- `XraySpanExporter` — POSTs **OTLP/JSON** to `${endpoint}/v1/otlp/v1/traces`. -- `XrayBaggageSpanProcessor` — lifts the `xray.*` baggage onto every span. +- `install(*, endpoint, tracer_provider=None) -> TracerProvider`. This is idempotent (safe to call more than once). It registers the baggage span processor plus a batch exporter. +- `XraySpanExporter`. This POSTs **OTLP/JSON** to `${endpoint}/v1/otlp/v1/traces`. +- `XrayBaggageSpanProcessor`. This lifts the `xray.*` baggage onto every span. --- ## Runtimes -A `Runtime` drives one Conversation against your agent and produces the audio -recording. `LiveKitRuntime` is the v1 implementation; you can subclass -`Runtime` for any other transport. +A `Runtime` does two things: it drives one Conversation against your agent, and it produces the audio recording. + +`LiveKitRuntime` is the v1 implementation. You can subclass `Runtime` for any other transport. ### The `Runtime` ABC @@ -376,17 +381,15 @@ class RuntimeResult: recording_started_at_epoch: float | None = None # Unix seconds of audio sample 0 ``` -`recording_started_at_epoch` is what `run(...)` turns into the -`X-Recording-Started-At` upload header. **A runtime that produces audio MUST -report it** — omit it and span→turn attribution is skipped, so every -`tool_called` / `tool_not_called` / `tool_args_match` / `max_ttft_ms` -assertion comes back `errored`. +`run(...)` turns `recording_started_at_epoch` into the `X-Recording-Started-At` upload header. -Optional structural protocols the orchestrator probes for (`@runtime_checkable`): +**A runtime that produces audio MUST report `recording_started_at_epoch`.** If you omit it, span-to-turn attribution is skipped. As a result, every `tool_called`, `tool_not_called`, `tool_args_match`, and `max_ttft_ms` assertion comes back `errored`. -- `RuntimeBindable` — `bind(*, replay_id, conversation_hash)`. -- `UserAudioInjectable` — `inject_user_audio(audio: Mapping[int, bytes])`. -- `StoppableRuntime` — `request_stop()` (used by `run_live` on SIGINT). +The orchestrator also probes for some optional structural protocols. (They are `@runtime_checkable`, so the check is a runtime `isinstance` test.) + +- `RuntimeBindable`: `bind(*, replay_id, conversation_hash)`. +- `UserAudioInjectable`: `inject_user_audio(audio: Mapping[int, bytes])`. +- `StoppableRuntime`: `request_stop()` (used by `run_live` on SIGINT). ### `LiveKitRuntime` @@ -402,11 +405,9 @@ LiveKitRuntime( ) ``` -Joins the room as a user-side participant, plays the per-turn user PCM, captures -the agent's audio + transcripts, and writes a wall-clock-aligned stereo WAV -(left = user, right = agent) at 48 kHz / 16-bit. Implements `bind`, -`inject_user_audio`, `run`, and `aclose`; `run` raises `RuntimeBindError` if -called before `bind`. Used with `xray.run`. +This runtime joins the room as a user-side participant. It plays the per-turn user PCM (raw audio), captures the agent's audio and transcripts, and writes a wall-clock-aligned stereo WAV at 48 kHz / 16-bit. In that WAV, the left channel is the user and the right channel is the agent. + +It implements `bind`, `inject_user_audio`, `run`, and `aclose`. Calling `run` before `bind` raises `RuntimeBindError`. Use it with `xray.run`. ### `LiveKitLiveRuntime` @@ -423,11 +424,11 @@ LiveKitLiveRuntime( ) ``` -Powers `run_live`: streams the OS microphone (needs the `[live]` extra), -publishes mic frames, captures and optionally plays the agent's audio, and -writes a live stereo mixdown. Set `play_agent_audio=False` (or -`XRAY_LIVE_NO_PLAYBACK=1` in the example) for a record-only run. It emits no -`xray.turn` spans — turn boundaries come from server-side VAD. +This runtime powers `run_live`. It streams the OS microphone (which needs the `[live]` extra), publishes the mic frames, captures and optionally plays the agent's audio, and writes a live stereo mixdown. + +For a record-only run, set `play_agent_audio=False` (or set `XRAY_LIVE_NO_PLAYBACK=1` in the example). + +It emits no `xray.turn` spans. Turn boundaries come from server-side VAD instead. VAD (voice activity detection) finds where speech starts and stops. ### `SimulatedSipCall` @@ -439,13 +440,13 @@ SimulatedSipCall( ) ``` -Pass to a runtime's `simulated_sip=` to make the driver join as a simulated SIP -participant: the driver mints the JWT with `with_kind("sip")` plus the `sip.*` -attributes (`sip.phoneNumber`, `sip.trunkPhoneNumber`, `sip.callID`, -`sip.callStatus`, …). `call_status` is one of `"active"`, `"automation"`, -`"dialing"`, `"hangup"`, `"ringing"`. An all-empty object raises `ValueError` -(use `simulated_sip=None` for a non-SIP run), as does an `"xray"` key in -`extra_attrs`. +Pass this to a runtime's `simulated_sip=` argument. It makes the driver join as a simulated SIP participant. (SIP is the protocol behind phone calls.) + +When you pass it, the driver mints the JWT with `with_kind("sip")` plus the `sip.*` attributes: `sip.phoneNumber`, `sip.trunkPhoneNumber`, `sip.callID`, `sip.callStatus`, and so on. + +`call_status` must be one of `"active"`, `"automation"`, `"dialing"`, `"hangup"`, or `"ringing"`. + +Two cases raise `ValueError`: an all-empty object (use `simulated_sip=None` for a non-SIP run), and an `"xray"` key in `extra_attrs`. --- @@ -462,8 +463,7 @@ class ReplayResult: metrics: tuple[TurnMetrics, ...] ``` -`passed` is the aggregate: `True` iff every assertion **and** every judge ran -to `"passed"` — `"errored"` counts as not-passed. +`passed` is the aggregate verdict. It is `True` only when every assertion **and** every judge ran to `"passed"`. An `"errored"` outcome counts as not-passed. ```python AssertionOutcome(turn_idx: int, assertion_idx: int, kind: str, @@ -474,20 +474,15 @@ TurnMetrics(turn_idx: int, role: Role, agent_response_ms: int | None, interrupted: bool) ``` -`EvaluationStatus` is `"passed" | "failed" | "errored"`. `format_failures(result)` -renders just the non-passed assertion + judge outcomes (or -`"all assertions and judges passed"`). +`EvaluationStatus` is `"passed" | "failed" | "errored"`. `format_failures(result)` renders just the non-passed assertion and judge outcomes. If there are none, it returns `"all assertions and judges passed"`. -`AgentResponse` / `ToolCall` / `ModelUsage` are informational records the -runtime captured during the run; they are **not** what evaluation reads -(evaluation runs server-side from the declared catalog + the OTLP spans). +`AgentResponse`, `ToolCall`, and `ModelUsage` are informational records that the runtime captured during the run. They are **not** what evaluation reads. Evaluation runs server-side, from the declared catalog plus the OTLP spans. --- ## Errors -`XrayError` is the base SDK exception; every subclass carries a -`failure_reason`. The ones a test might catch: +`XrayError` is the base SDK exception. Every subclass carries a `failure_reason`. These are the ones a test might want to catch: | Class | `failure_reason` | When | |---|---|---| @@ -501,13 +496,12 @@ runtime captured during the run; they are **not** what evaluation reads | `XrayServerError` | `driver_aborted` | An HTTP error from the server before the replay row exists. | | `ReplayEvaluationError` | the failing stage | The server's analyze chain crashed before producing a verdict (`transcription_failed` / `metrics_failed` / `evaluation_failed`, …). Carries `replay_id`. | -Driver-side failures map to a `PATCH /v1/replays/:id` so the replay records why -it stopped. Lifecycle transitions during the analyze chain are server-owned. +Driver-side failures map to a `PATCH /v1/replays/:id`, so the replay records why it stopped. Lifecycle transitions during the analyze chain are owned by the server. --- ## See also -- [`integrate.md`](./integrate.md) — the end-to-end walkthrough. -- [`wire-contract.md`](./wire-contract.md) — what the agent's spans must look like. -- [`architecture.md`](./architecture.md) — how the server turns a run into a verdict. +- [`integrate.md`](./integrate.md): the end-to-end walkthrough. +- [`wire-contract.md`](./wire-contract.md): what the agent's spans must look like. +- [`architecture.md`](./architecture.md): how the server turns a run into a verdict. diff --git a/docs/wire-contract.md b/docs/wire-contract.md index 55c21d6..90248db 100644 --- a/docs/wire-contract.md +++ b/docs/wire-contract.md @@ -1,20 +1,30 @@ --- -layout: default title: Wire contract (OTLP) -nav_order: 5 --- # OTLP wire contract -xray ingests your agent's OpenTelemetry traces through one endpoint and turns -recognized spans into structured `tool_calls` / `model_usage` rows. This is the -contract: what the endpoint accepts, how a span is routed to a Replay, and -which span shapes are recognized. It's derived from +First, some vocabulary. OpenTelemetry (OTel) is a standard for emitting +traces from running software. OTLP is the wire protocol OTel uses to ship +those traces over the network. A trace is made of **spans**. A span is one +timed unit of work (a tool call, an LLM request). Each span carries +**attributes**, which are key/value pairs that describe it. + +xray ingests your agent's OpenTelemetry traces through one endpoint. It turns +recognized spans into structured `tool_calls` / `model_usage` rows. This page +is the contract. It covers three things: + +- what the endpoint accepts, +- how a span is routed to a Replay, and +- which span shapes are recognized. + +It's derived from [`src/server/otlp/`](https://github.com/xray-eval/xray/tree/main/src/server/otlp). -You don't have to emit xray-specific spans. Any agent already instrumented with -the OTel GenAI semantic conventions (`gen_ai.*`) or Langfuse lights up -automatically. The `xray.*` vocabulary is optional and additive. +You don't have to emit xray-specific spans. Maybe your agent is already +instrumented with the OTel GenAI semantic conventions (`gen_ai.*`) or with +Langfuse. If so, it lights up automatically. The `xray.*` vocabulary is +optional and additive. --- @@ -26,8 +36,8 @@ automatically. The `xray.*` vocabulary is optional and additive. | **Content types** | `application/json` and `application/x-protobuf` (both standard OTLP `ExportTraceServiceRequest`). | | **Success** | `200` with `{ "partialSuccess": { "rejectedSpans": N } }`. | -`xray.attach`'s exporter posts OTLP/JSON; the stock OTel HTTP exporter's -protobuf default works too. A `Content-Type` with parameters +`xray.attach`'s exporter posts OTLP/JSON. The stock OTel HTTP exporter defaults +to protobuf, and that works too. A `Content-Type` with parameters (`application/json; charset=utf-8`) is matched correctly. ### Limits @@ -35,85 +45,92 @@ protobuf default works too. A `Content-Type` with parameters | Cap | Value | Behaviour on exceed | |---|---|---| | Body size | 4 MiB | `413 body_too_large`. | -| Spans per request | 512 | `400 too_many_spans_per_request` — the whole request is rejected. | +| Spans per request | 512 | `400 too_many_spans_per_request`. The whole request is rejected. | | Spans per replay | 5,000 | Spans over the cap are counted in `rejectedSpans`; in-cap spans in the same batch still persist. No error. | -Other failures map to `400 invalid_otlp_body` (malformed / schema-invalid body), -`415 unsupported_content_type`, or `500 internal_error`. +Other failures map to one of these: + +- `400 invalid_otlp_body` (malformed or schema-invalid body), +- `415 unsupported_content_type`, or +- `500 internal_error`. ### Idempotency -Spans are de-duplicated on `(replay_id, span_id)`. Re-sending a span already -stored is a no-op — it isn't re-counted against the cap and its extracted rows -aren't re-processed. Safe to retry a batch. +Spans are de-duplicated on `(replay_id, span_id)`. Re-sending a span that is +already stored is a no-op. It isn't re-counted against the cap. Its extracted +rows aren't re-processed. So a batch is safe to retry. --- ## Routing and the trust boundary -Every span is routed to a Replay by the **`xray.replay.id`** attribute. A -**span-level** value takes precedence; the **resource-level** value is the -fallback. (`attach` sets it as baggage, which the span processor lifts onto -every span, so in practice it's present at the span level.) +Every span is routed to a Replay by the **`xray.replay.id`** attribute. The +value can sit in two places. A **span-level** value takes precedence. The +**resource-level** value is the fallback. (`attach` sets it as baggage. The span +processor lifts that baggage onto every span. So in practice the value is +present at the span level.) The receiver is a **filter, not a gate**. A span is silently dropped (counted -in `rejectedSpans`, never an error) when: +in `rejectedSpans`, never an error) in three cases: -1. it carries no `xray.replay.id` (no replay context — e.g. the agent running - in production); +1. it carries no `xray.replay.id` (no replay context, for example the agent + running in production); 2. the `xray.replay.id` names a Replay that doesn't exist; or 3. its vocabulary isn't recognized. This is the trust boundary: **the OTLP receiver never creates Conversation or Replay rows.** It only reads existing ones. Replay rows are created exclusively -by the SDK control plane, *before* the agent emits its first span — which is -what makes "unknown replay id → drop" safe rather than lossy. +by the SDK control plane. That happens *before* the agent emits its first span. +That ordering is what makes "unknown replay id, so drop" safe rather than +lossy. ### Timestamps -`startTimeUnixNano` / `endTimeUnixNano` are converted to ISO-8601 and stored as -each row's `started_at` / `ended_at`. These feed the audio-timeline turn -attribution described below. +`startTimeUnixNano` / `endTimeUnixNano` are converted to ISO-8601. They are +stored as each row's `started_at` / `ended_at`. These feed the audio-timeline +turn attribution described below. --- ## The three vocabularies -Each span is run through an ordered registry; the **first** vocabulary that -recognizes it wins. Order is fixed: +A **vocabulary** is a set of rules for recognizing one family of spans. Each +span is run through an ordered registry of vocabularies. The **first** one that +recognizes the span wins. The order is fixed: 1. `xray` 2. `gen_ai` (OTel GenAI semconv) 3. `langfuse` A vocabulary match can emit a `tool_calls` row, a `model_usage` row, or -neither — but **every** recognized span is also stored raw in the `spans` -table (tagged with the matching vocabulary) for the inspector's timeline. +neither. But **every** recognized span is also stored raw in the `spans` +table, tagged with the matching vocabulary, for the inspector's timeline. ### 1 · `xray` -Recognizes exactly three span names — an exact-match set, **not** a prefix -wildcard: +This vocabulary recognizes exactly three span names. It is an exact-match set, +**not** a prefix wildcard: - `xray.turn` - `xray.stage.stt` - `xray.stage.tts` -These land in the raw `spans` table only; they produce no `tool_calls` / -`model_usage` rows. Turn boundaries come from server-side VAD, and assertion / -judge outcomes come from the declared catalog — not from these spans. Any other -`xray.*` name (e.g. `xray.stage.llm`) is unrecognized and dropped. +These land in the raw `spans` table only. They produce no `tool_calls` / +`model_usage` rows. Turn boundaries come from server-side VAD. Assertion and +judge outcomes come from the declared catalog, not from these spans. Any other +`xray.*` name (for example `xray.stage.llm`) is unrecognized and dropped. > `xray.assertion` and `xray.judge` are **not** recognized. Evaluation runs > server-side from the `Assertion` / `Judge` catalog declared on the -> Conversation, so driver-emitted assertion/judge spans are intentionally +> Conversation. So driver-emitted assertion/judge spans are intentionally > ignored. ### 2 · `gen_ai` (OTel GenAI semantic conventions) -Dispatches on **`gen_ai.operation.name`** (a span also counts as GenAI if any -attribute key starts with `gen_ai.`, or its name starts with `chat`, -`text_completion`, or `execute_tool`). +This vocabulary dispatches on the **`gen_ai.operation.name`** attribute. A span +also counts as GenAI if any of these is true: an attribute key starts with +`gen_ai.`, or the span name starts with `chat`, `text_completion`, or +`execute_tool`. **`execute_tool` → `tool_calls` row:** @@ -132,21 +149,22 @@ attribute key starts with `gen_ai.`, or its name starts with `chat`, | `model` | `gen_ai.response.model` (fallback `gen_ai.request.model`) | | `input_tokens` / `output_tokens` | `gen_ai.usage.input_tokens` / `gen_ai.usage.output_tokens` | | `total_tokens` | sum of the two (null only if both absent) | -| `ttft_ms` | `gen_ai.response.time_to_first_chunk` — interpreted as **seconds**, converted to ms | +| `ttft_ms` | `gen_ai.response.time_to_first_chunk`, interpreted as **seconds**, converted to ms | | `latency_ms` | span `end − start` | -Any other operation (e.g. `embeddings`) is stored as a raw `gen_ai` span with -no extracted row. +Any other operation (for example `embeddings`) is stored as a raw `gen_ai` span +with no extracted row. > Earlier docs referred to `gen_ai.tool` and `gen_ai.client.operation`. Those -> are not what the code matches on — the dispatch key is -> `gen_ai.operation.name` with values `execute_tool` / `chat` / +> are not what the code matches on. The dispatch key is +> `gen_ai.operation.name`, with values `execute_tool` / `chat` / > `text_completion`. ### 3 · `langfuse` -Recognizes any span carrying a `langfuse.`-prefixed attribute. The observation -type is read from `langfuse.observation.type` (fallback `langfuse.type`). +This vocabulary recognizes any span carrying an attribute with a `langfuse.` +prefix. It reads the observation type from `langfuse.observation.type` +(fallback `langfuse.type`). **`generation` → `model_usage` row:** @@ -180,30 +198,31 @@ Other observation types (`event`, `span`, `score`, unset) are stored as raw ### Turn attribution is derived, not stored -`tool_calls` and `model_usage` carry only `replay_id` and (nullable) `span_id` -— there is **no `turn_idx` column** on them. A row's turn membership is -computed at evaluation/read time by mapping its wall-clock `started_at` onto the -audio timeline: +`tool_calls` and `model_usage` carry only `replay_id` and (nullable) `span_id`. +There is **no `turn_idx` column** on them. A row's turn membership is not +stored. It is computed at evaluation/read time. The server maps the row's +wall-clock `started_at` onto the audio timeline: ``` audio_offset_ms = started_at − replays.recording_started_at ``` -and testing it against the turn windows derived from VAD. The +It then tests that offset against the turn windows derived from VAD. The `recording_started_at` origin is set by the driver's audio upload (the -`X-Recording-Started-At` header), never by this OTLP path. With no anchor, the -timeline-dependent assertions (`tool_called`, `tool_not_called`, -`tool_args_match`, `max_ttft_ms`) return `errored`. The origin must be the -audio sample-0 wall-clock (the `X-Recording-Started-At` header), never the -replay row's creation time (which precedes the recording). +`X-Recording-Started-At` header). This OTLP path never sets it. With no anchor, +the timeline-dependent assertions return `errored`. Those assertions are +`tool_called`, `tool_not_called`, `tool_args_match`, and `max_ttft_ms`. The +origin must be the audio sample-0 wall-clock (the `X-Recording-Started-At` +header). It must never be the replay row's creation time, which precedes the +recording. --- ## Adding a vocabulary Each vocabulary is one file in -[`src/server/otlp/vocabularies/`](https://github.com/xray-eval/xray/tree/main/src/server/otlp/vocabularies) -exporting a pure `match(span, resource)` function, plus one line in -`registry.ts`. Test it against synthetic projected spans with the slice's -test-utils — no network. See [`architecture.md`](./architecture.md) and the -contributing guide. +[`src/server/otlp/vocabularies/`](https://github.com/xray-eval/xray/tree/main/src/server/otlp/vocabularies). +The file exports a pure `match(span, resource)` function. You also add one line +in `registry.ts`. Test it against synthetic projected spans with the slice's +test-utils. No network is needed. See [`architecture.md`](./architecture.md) and +the contributing guide. diff --git a/package.json b/package.json index e8b33b6..ee9ff1d 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,9 @@ "dev:down": "docker compose -f compose.dev.yaml down -v", "build": "bun run scripts/build.ts", "start": "bun src/server/main.ts", + "docs:dev": "vitepress dev docs", + "docs:build": "vitepress build docs", + "docs:preview": "vitepress preview docs", "docker:build": "docker build -t xray:local .", "docker:run": "docker run --rm -p 8080:8080 --env-file .env xray:local", "docker:smoke": "bash scripts/docker-smoke.sh", @@ -41,8 +44,11 @@ "drizzle-kit": "0.31.10", "happy-dom": "^20.9.0", "lefthook": "^2.1.6", + "mermaid": "11.15.0", "msw": "2.14.5", - "typescript": "^6.0.3" + "typescript": "^6.0.3", + "vitepress": "1.6.4", + "vitepress-plugin-mermaid": "2.0.17" }, "dependencies": { "@fontsource-variable/jetbrains-mono": "5.2.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bf4e1b8..e828527 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -135,15 +135,103 @@ importers: lefthook: specifier: ^2.1.6 version: 2.1.6 + mermaid: + specifier: 11.15.0 + version: 11.15.0 msw: specifier: 2.14.5 version: 2.14.5(@types/node@25.6.2)(typescript@6.0.3) typescript: specifier: ^6.0.3 version: 6.0.3 + vitepress: + specifier: 1.6.4 + version: 1.6.4(@algolia/client-search@5.54.0)(@types/node@25.6.2)(@types/react@19.2.14)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(search-insights@2.17.3)(typescript@6.0.3) + vitepress-plugin-mermaid: + specifier: 2.0.17 + version: 2.0.17(mermaid@11.15.0)(vitepress@1.6.4(@algolia/client-search@5.54.0)(@types/node@25.6.2)(@types/react@19.2.14)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(search-insights@2.17.3)(typescript@6.0.3)) packages: + '@algolia/abtesting@1.20.0': + resolution: {integrity: sha512-5CqkS592H3+24b6H6CQ2RVpphdmAuIElZzv0Hngqo/ZEZpUZJ+KGLcueBhx33fv2wYBXyuuvskG5aQ7Ti+lR0g==} + engines: {node: '>= 14.0.0'} + + '@algolia/autocomplete-core@1.17.7': + resolution: {integrity: sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==} + + '@algolia/autocomplete-plugin-algolia-insights@1.17.7': + resolution: {integrity: sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==} + peerDependencies: + search-insights: '>= 1 < 3' + + '@algolia/autocomplete-preset-algolia@1.17.7': + resolution: {integrity: sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==} + peerDependencies: + '@algolia/client-search': '>= 4.9.1 < 6' + algoliasearch: '>= 4.9.1 < 6' + + '@algolia/autocomplete-shared@1.17.7': + resolution: {integrity: sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==} + peerDependencies: + '@algolia/client-search': '>= 4.9.1 < 6' + algoliasearch: '>= 4.9.1 < 6' + + '@algolia/client-abtesting@5.54.0': + resolution: {integrity: sha512-IXnH1x3DBsPQA4/FRO0Pvkfy79tKy5Qr+ugAV9jdcGkpzHc476D0WV1MFJ+pZxtbts0xh2JqzUVmYEqA6LkOpA==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-analytics@5.54.0': + resolution: {integrity: sha512-pBpRqm1wpE0GnGy2rNLk9rjDn0Le4iywNRtnAWblLeqfjxpWKg8lWnk7nmSoTShFO31sz2jatXXzxK2lz8ipbA==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-common@5.54.0': + resolution: {integrity: sha512-WbuwRUlFvSOsuxqTDjSSmgusuF5KFt+oFPzobvPDvodra6EWnVwUXjz0elkNSsnsIlZGtcXlX3LhxkO7rF90jw==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-insights@5.54.0': + resolution: {integrity: sha512-/HNLVi3kPI+JhO59WbglLjPM2c4uECU+x4gk1iADseKtE5eYqJ/RJ+FIwM8xzPFKhFJaw+8hVq4lkd0nn8HDDg==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-personalization@5.54.0': + resolution: {integrity: sha512-6TolyyDRumIKeLGBGSFAZsSIJ8hrm6NFCGR1jO7pQTiOtSWgAIxcFE5/JRpZ3g+unG4OkNOFy+I0dUEquIDbig==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-query-suggestions@5.54.0': + resolution: {integrity: sha512-AxW2MLBhjBtGX5kIZrVLO2SP2vRkZxJw5qHGxnQJfLcYhA04mFNP0fWRtHshlcVnDk9M8L9lwfr2lGpf3Er+hw==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-search@5.54.0': + resolution: {integrity: sha512-ngdgVGp05lJzUyA+sUzr0MDZ7AMtANcJpwIzq4ZsfpZL5B3S7A4XYfMcU2sECZc3bx3ysOhYcdbbaTjc3ve0WQ==} + engines: {node: '>= 14.0.0'} + + '@algolia/ingestion@1.54.0': + resolution: {integrity: sha512-hee59Z7FgZ6/13FYL05ANPwRJY1pfvIlrwC8eBZYdiRFXTJVvf94IyTWOxLqRVpbseDP6eQQTW+PT7/DxTZIng==} + engines: {node: '>= 14.0.0'} + + '@algolia/monitoring@1.54.0': + resolution: {integrity: sha512-diCjZVbIO7Pzw98tEKqLWIAgmQBI3Zt1sHsXyAPNGZgn32derpIXTnjjpJbZl+uAhSSznd6SfxFGdC9uYdN1TA==} + engines: {node: '>= 14.0.0'} + + '@algolia/recommend@5.54.0': + resolution: {integrity: sha512-6zeslypRAGWDgVJEJYAPuqmyquHvnw4MQwG+XXdrw5dTNDjXYIcCJdQQcCY06xPF9tUvmzy/1vHiH9QxhgwuOQ==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-browser-xhr@5.54.0': + resolution: {integrity: sha512-iHZax214LPXd7XizQ4BNnTsegl8f3IeKm8JcrmSNZ/5x1rZ5xLkbG/anltAWtLpoRNnfpr4Z80YdYeVVPpx6wQ==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-fetch@5.54.0': + resolution: {integrity: sha512-YKtuG5YwPxZ+kkfd4HmUO7Z9aICPUSMlHslzKTmtMMhxGnetxEqGj9T/v2r/PdcuOUC5oW0CHe8akJk8cpS3gQ==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-node-http@5.54.0': + resolution: {integrity: sha512-zyZDJ4WS5TnjZZ5pqywTBFO9olW7QMtY2kf2dbLnu+UTzfc9ri/HGf27jRN2NTbX9FcRPxSqPqzUhF/BZIx0VA==} + engines: {node: '>= 14.0.0'} + + '@antfu/install-pkg@1.1.0': + resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} + '@babel/code-frame@7.29.0': resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} @@ -152,6 +240,10 @@ packages: resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.28.5': resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} @@ -160,6 +252,11 @@ packages: resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/runtime@7.29.2': resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} engines: {node: '>=6.9.0'} @@ -168,6 +265,10 @@ packages: resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} engines: {node: '>=6.9.0'} + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + '@biomejs/biome@2.4.14': resolution: {integrity: sha512-TmAvxOEgrpLypzVGJ8FulIZnlyA9TxrO1hyqYrCz9r+bwma9xXxuLA5IuYnj55XQneFx460KjRbx6SWGLkg3bQ==} engines: {node: '>=14.21.3'} @@ -225,6 +326,15 @@ packages: cpu: [x64] os: [win32] + '@braintree/sanitize-url@6.0.4': + resolution: {integrity: sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==} + + '@braintree/sanitize-url@7.1.2': + resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==} + + '@chevrotain/types@11.1.2': + resolution: {integrity: sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==} + '@commitlint/cli@21.0.0': resolution: {integrity: sha512-p3y2oC0G2R45zaadMwBxCiSesS8digi5RDplP3Zrfpzm7xIgrgAj0W4fGzONjpHyg8obDVJDU45g5txzeMcblg==} engines: {node: '>=22.12.0'} @@ -306,6 +416,29 @@ packages: conventional-commits-parser: optional: true + '@docsearch/css@3.8.2': + resolution: {integrity: sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==} + + '@docsearch/js@3.8.2': + resolution: {integrity: sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==} + + '@docsearch/react@3.8.2': + resolution: {integrity: sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==} + peerDependencies: + '@types/react': '>= 16.8.0 < 19.0.0' + react: '>= 16.8.0 < 19.0.0' + react-dom: '>= 16.8.0 < 19.0.0' + search-insights: '>= 1 < 3' + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + react-dom: + optional: true + search-insights: + optional: true + '@drizzle-team/brocli@0.10.2': resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} @@ -317,6 +450,12 @@ packages: resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} deprecated: 'Merged into tsx: https://tsx.is' + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.25.12': resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} engines: {node: '>=18'} @@ -329,6 +468,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.25.12': resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} engines: {node: '>=18'} @@ -341,6 +486,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.25.12': resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} engines: {node: '>=18'} @@ -353,6 +504,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.25.12': resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} engines: {node: '>=18'} @@ -365,6 +522,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.25.12': resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} engines: {node: '>=18'} @@ -377,6 +540,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.25.12': resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} engines: {node: '>=18'} @@ -389,6 +558,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.25.12': resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} engines: {node: '>=18'} @@ -401,6 +576,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.12': resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} engines: {node: '>=18'} @@ -413,6 +594,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.25.12': resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} engines: {node: '>=18'} @@ -425,6 +612,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.25.12': resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} engines: {node: '>=18'} @@ -437,6 +630,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.25.12': resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} engines: {node: '>=18'} @@ -449,6 +648,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.25.12': resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} engines: {node: '>=18'} @@ -461,6 +666,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.25.12': resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} engines: {node: '>=18'} @@ -473,6 +684,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.25.12': resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} engines: {node: '>=18'} @@ -485,6 +702,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.25.12': resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} engines: {node: '>=18'} @@ -497,6 +720,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.25.12': resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} engines: {node: '>=18'} @@ -509,6 +738,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.25.12': resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} engines: {node: '>=18'} @@ -533,6 +768,12 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.12': resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} engines: {node: '>=18'} @@ -557,6 +798,12 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.12': resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} engines: {node: '>=18'} @@ -581,6 +828,12 @@ packages: cpu: [arm64] os: [openharmony] + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.25.12': resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} engines: {node: '>=18'} @@ -593,6 +846,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.25.12': resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} engines: {node: '>=18'} @@ -605,6 +864,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.25.12': resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} engines: {node: '>=18'} @@ -617,6 +882,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.25.12': resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} engines: {node: '>=18'} @@ -666,6 +937,15 @@ packages: '@standard-schema/spec': ^1.0.0 hono: '>=3.9.0' + '@iconify-json/simple-icons@1.2.86': + resolution: {integrity: sha512-t3jck5qPQuK1qy+bRn9eCoDQhIB7XSazKz1Fjp8hcan3XOAsTI5Mq/s3F0ekOKSvMQqkVORYK6ns6o6T9f5EMA==} + + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + + '@iconify/utils@3.1.3': + resolution: {integrity: sha512-LPKOXPn/zV+zis1oOfGWogaXVpqUybF3ZS6SCZIsz8vg0ivVp9+fVqyYB7xq0aiST/VhUQYGO1qo6uoYSiEJqw==} + '@inquirer/ansi@2.0.5': resolution: {integrity: sha512-doc2sWgJpbFQ64UflSVd17ibMGDuxO1yKgOgLMwavzESnXjFWJqUeG8saYosqKpHp4kWiM5x1nXvEjbpx90gzw==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} @@ -701,6 +981,15 @@ packages: '@types/node': optional: true + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@mermaid-js/mermaid-mindmap@9.3.0': + resolution: {integrity: sha512-IhtYSVBBRYviH1Ehu8gk69pMDF8DSRqXBRDMWrEfHoaMruHeaP2DXA3PBnuwsMaCdPQhlUUcy/7DBLAEIXvCAw==} + + '@mermaid-js/parser@1.1.1': + resolution: {integrity: sha512-VuHdsYMK1bT6X2JbcAaWAhugTRvRBRyuZgd+c22swUeI9g/ntaxF7CY7dYarhZovofCbUNO0G7JesfmNtjYOCw==} + '@modelcontextprotocol/sdk@1.29.0': resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==} engines: {node: '>=18'} @@ -1507,6 +1796,144 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + '@rollup/rollup-android-arm-eabi@4.61.1': + resolution: {integrity: sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.61.1': + resolution: {integrity: sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.61.1': + resolution: {integrity: sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.61.1': + resolution: {integrity: sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.61.1': + resolution: {integrity: sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.61.1': + resolution: {integrity: sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.61.1': + resolution: {integrity: sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.61.1': + resolution: {integrity: sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.61.1': + resolution: {integrity: sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.61.1': + resolution: {integrity: sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.61.1': + resolution: {integrity: sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.61.1': + resolution: {integrity: sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.61.1': + resolution: {integrity: sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.61.1': + resolution: {integrity: sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.61.1': + resolution: {integrity: sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.61.1': + resolution: {integrity: sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.61.1': + resolution: {integrity: sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.61.1': + resolution: {integrity: sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.61.1': + resolution: {integrity: sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.61.1': + resolution: {integrity: sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.61.1': + resolution: {integrity: sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.61.1': + resolution: {integrity: sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.61.1': + resolution: {integrity: sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.61.1': + resolution: {integrity: sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.61.1': + resolution: {integrity: sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw==} + cpu: [x64] + os: [win32] + '@scalar/client-side-rendering@0.1.7': resolution: {integrity: sha512-IDzjKF93jrOljlvKBsLHXT1FPWgz56jFrMPC+iLihREp1qH8wF92mG8Zpakw8cURkEuw5WijRk0xNBP2moGyuw==} engines: {node: '>=22'} @@ -1525,6 +1952,30 @@ packages: resolution: {integrity: sha512-UaCQQcscFTJdxZREE8KhUdSJgaDlc44TZbmWcZffs4m1hzqOvEI7lEBS13iBpLq7/cxUXFgyJdecywvNqJ0PkA==} engines: {node: '>=22'} + '@shikijs/core@2.5.0': + resolution: {integrity: sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==} + + '@shikijs/engine-javascript@2.5.0': + resolution: {integrity: sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==} + + '@shikijs/engine-oniguruma@2.5.0': + resolution: {integrity: sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==} + + '@shikijs/langs@2.5.0': + resolution: {integrity: sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==} + + '@shikijs/themes@2.5.0': + resolution: {integrity: sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==} + + '@shikijs/transformers@2.5.0': + resolution: {integrity: sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==} + + '@shikijs/types@2.5.0': + resolution: {integrity: sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + '@simple-libs/child-process-utils@1.0.2': resolution: {integrity: sha512-/4R8QKnd/8agJynkNdJmNw2MBxuFTRcNFnE5Sg/G+jkSsV8/UBgULMzhizWWW42p8L5H7flImV2ATi79Ove2Tw==} engines: {node: '>=18'} @@ -1654,37 +2105,261 @@ packages: '@types/bun@1.3.13': resolution: {integrity: sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw==} - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} - '@types/node@25.6.2': - resolution: {integrity: sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw==} + '@types/d3-axis@3.0.6': + resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} - '@types/react-dom@19.2.3': - resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} - peerDependencies: - '@types/react': ^19.2.0 + '@types/d3-brush@3.0.6': + resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} - '@types/react@19.2.14': - resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + '@types/d3-chord@3.0.6': + resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} - '@types/set-cookie-parser@2.4.10': - resolution: {integrity: sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw==} + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} - '@types/statuses@2.0.6': - resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} + '@types/d3-contour@3.0.6': + resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} - '@types/whatwg-mimetype@3.0.2': - resolution: {integrity: sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==} + '@types/d3-delaunay@6.0.4': + resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} - '@types/ws@8.18.1': - resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + '@types/d3-dispatch@3.0.7': + resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==} - '@valibot/to-json-schema@1.7.0': + '@types/d3-drag@3.0.7': + resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + + '@types/d3-dsv@3.0.7': + resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-fetch@3.0.7': + resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} + + '@types/d3-force@3.0.10': + resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} + + '@types/d3-format@3.0.4': + resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} + + '@types/d3-geo@3.1.0': + resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} + + '@types/d3-hierarchy@3.1.7': + resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-polygon@3.0.2': + resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} + + '@types/d3-quadtree@3.0.6': + resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} + + '@types/d3-random@3.0.3': + resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==} + + '@types/d3-scale-chromatic@3.1.0': + resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-selection@3.0.11': + resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} + + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + + '@types/d3-time-format@4.0.3': + resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/d3-transition@3.0.9': + resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} + + '@types/d3-zoom@3.0.8': + resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + + '@types/d3@7.4.3': + resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/linkify-it@5.0.0': + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + + '@types/markdown-it@14.1.2': + resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/mdurl@2.0.0': + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + + '@types/node@25.6.2': + resolution: {integrity: sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.14': + resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + + '@types/set-cookie-parser@2.4.10': + resolution: {integrity: sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw==} + + '@types/statuses@2.0.6': + resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} + + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@types/web-bluetooth@0.0.21': + resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} + + '@types/whatwg-mimetype@3.0.2': + resolution: {integrity: sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==} + + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + + '@ungap/structured-clone@1.3.1': + resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==} + + '@upsetjs/venn.js@2.0.0': + resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==} + + '@valibot/to-json-schema@1.7.0': resolution: {integrity: sha512-Y3pPVibbIOHzohrlxSINvO7w/bvXkoYS3BQHoImV9ynE+bXKf171bdMucPurV2zp7gdmt0L1HCcNAsbo7cFRQw==} peerDependencies: valibot: ^1.4.0 + '@vitejs/plugin-vue@5.2.4': + resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 + vue: ^3.2.25 + + '@vue/compiler-core@3.5.38': + resolution: {integrity: sha512-s99aGxWYig9ErHbct27KXEGhrBYlRI6c4MwAgXErOAbX9xiW37/uMa+XUDO69zLz83dng8UUZ70CTOJrLrYrEQ==} + + '@vue/compiler-dom@3.5.38': + resolution: {integrity: sha512-JTqp25l8aFfJYF7/KmsXZjAxJz7T+SjmTJLoXVjHtc2BrSgSiW2n9Aem/cWq1OPe68A8JL06B3eVdhlP0H4TVw==} + + '@vue/compiler-sfc@3.5.38': + resolution: {integrity: sha512-DuA2GiZawSEW442iw/9+Fkol8hTgb4Ke5KkhmSry65QA7YuyMbIdy8p0XZRMvNwJdgRz307W8g1CSzdvS4nuNg==} + + '@vue/compiler-ssr@3.5.38': + resolution: {integrity: sha512-7s+W5Gc42FGxZMcuwl8H5B29T8BJPMdBT7KHFE+BbAuZ/iTEdTtv7z2XiMjiaUUw4w3ZcCEdHs36RuYJ2VA7bA==} + + '@vue/devtools-api@7.7.9': + resolution: {integrity: sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==} + + '@vue/devtools-kit@7.7.9': + resolution: {integrity: sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==} + + '@vue/devtools-shared@7.7.9': + resolution: {integrity: sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==} + + '@vue/reactivity@3.5.38': + resolution: {integrity: sha512-pG6LV/NDNRbKizcUjFFLAfjaL8mcv4DmR9avNcUw2gDHBzZneuS2TWCmp633ynzxz9YYKNeEPK2I8Wraqy2HUQ==} + + '@vue/runtime-core@3.5.38': + resolution: {integrity: sha512-iyW8WVfF1CpCXxncZY5Ei6rSd6oZr5DgEom//fUjRBRl56AXPD+s9ATvukRt77ZFTuYlnVA1bxY+dJB94tWVYw==} + + '@vue/runtime-dom@3.5.38': + resolution: {integrity: sha512-apX2wt9sdfDshS+a2xueFZLVpt0GkRJZSoPmrW/SA4yzXTznhfcMVW59gr7h4YQeY0vJhdJkk2rsIDwgfFgC5A==} + + '@vue/server-renderer@3.5.38': + resolution: {integrity: sha512-vue8vbf2QlV4quHqzwmJy6dWfmRhP1J8l4wtZg60CL6VoKqcPY2oe7may3+1d9qfpedjK5PRLFqd5k3Isj9mUw==} + peerDependencies: + vue: 3.5.38 + + '@vue/shared@3.5.38': + resolution: {integrity: sha512-FTW0AFZNaK5/mOqvGBwVfUlNLU38TiQn4+DQgIFUnrBBJQ1crMJ82yeGQLV5jyKFsO8yRukpbuP7x+nRbH6aug==} + + '@vueuse/core@12.8.2': + resolution: {integrity: sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==} + + '@vueuse/integrations@12.8.2': + resolution: {integrity: sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==} + peerDependencies: + async-validator: ^4 + axios: ^1 + change-case: ^5 + drauu: ^0.4 + focus-trap: ^7 + fuse.js: ^7 + idb-keyval: ^6 + jwt-decode: ^4 + nprogress: ^0.2 + qrcode: ^1.5 + sortablejs: ^1 + universal-cookie: ^7 + peerDependenciesMeta: + async-validator: + optional: true + axios: + optional: true + change-case: + optional: true + drauu: + optional: true + focus-trap: + optional: true + fuse.js: + optional: true + idb-keyval: + optional: true + jwt-decode: + optional: true + nprogress: + optional: true + qrcode: + optional: true + sortablejs: + optional: true + universal-cookie: + optional: true + + '@vueuse/metadata@12.8.2': + resolution: {integrity: sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==} + + '@vueuse/shared@12.8.2': + resolution: {integrity: sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==} + '@wavesurfer/react@1.0.12': resolution: {integrity: sha512-BNHpz2ryKNVvJdxB47pCPUsNCsjb2pZRysg82M5djIiw0vsiSJwdlt5jaAfDo3vd5IWrcoK9OiPQKO9ZEVNpDQ==} peerDependencies: @@ -1706,6 +2381,10 @@ packages: ajv@8.20.0: resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + algoliasearch@5.54.0: + resolution: {integrity: sha512-APAX4ajIOgsmYoUlGe++oNZkSTBgmXYM4maHC0OxC+Yo7xkaKQElV0ATZYCZA7jzrSJX1OBiqEs7mk+ZxXgYqA==} + engines: {node: '>= 14.0.0'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1739,6 +2418,9 @@ packages: array-ify@1.0.0: resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} + birpc@2.9.0: + resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} + body-parser@2.2.2: resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} engines: {node: '>=18'} @@ -1783,6 +2465,15 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} @@ -1809,6 +2500,17 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + compare-func@2.0.0: resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} @@ -1852,10 +2554,20 @@ packages: resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} + copy-anything@4.0.5: + resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} + engines: {node: '>=18'} + cors@2.8.6: resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} engines: {node: '>= 0.10'} + cose-base@1.0.3: + resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} + + cose-base@2.2.0: + resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} + cosmiconfig-typescript-loader@6.3.0: resolution: {integrity: sha512-Akr82WH1Wfqatyiqpj8HDkO2o2KmJRu1FhKfSNJP3K4IdXwHfEyL7MOb62i1AGQVLtIQM+iCE9CGOtrfhR+mmA==} engines: {node: '>=v18'} @@ -1884,6 +2596,165 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + cytoscape-cose-bilkent@4.1.0: + resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape-fcose@2.2.0: + resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape@3.34.0: + resolution: {integrity: sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg==} + engines: {node: '>=0.10'} + + d3-array@2.12.1: + resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-axis@3.0.0: + resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} + engines: {node: '>=12'} + + d3-brush@3.0.0: + resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} + engines: {node: '>=12'} + + d3-chord@3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-contour@4.0.2: + resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} + engines: {node: '>=12'} + + d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-fetch@3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} + + d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-geo@3.1.1: + resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@1.0.9: + resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-polygon@3.0.1: + resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} + engines: {node: '>=12'} + + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + + d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + + d3-sankey@0.12.3: + resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-shape@1.3.7: + resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + + d3@7.9.0: + resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} + engines: {node: '>=12'} + + dagre-d3-es@7.0.14: + resolution: {integrity: sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==} + + dayjs@1.11.21: + resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -1893,6 +2764,9 @@ packages: supports-color: optional: true + delaunator@5.1.0: + resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==} + depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -1908,9 +2782,15 @@ packages: detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + dompurify@3.4.10: + resolution: {integrity: sha512-0xzNv0e7oYC6yyuOGZIABPM4qtg3QxLFniDNPP4ZP90wR8Yq3zgwpRbrNiT4N3IKqDbbYFEJLV+JWEs19aZ//w==} + dot-prop@5.3.0: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} engines: {node: '>=8'} @@ -2018,6 +2898,9 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + emoji-regex-xs@1.0.0: + resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} + emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} @@ -2054,6 +2937,11 @@ packages: es-toolkit@1.46.1: resolution: {integrity: sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==} + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + esbuild@0.25.12: resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} engines: {node: '>=18'} @@ -2071,6 +2959,9 @@ packages: escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + etag@1.8.1: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} @@ -2112,6 +3003,9 @@ packages: resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} engines: {node: '>= 18.0.0'} + focus-trap@7.8.0: + resolution: {integrity: sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==} + forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -2168,6 +3062,9 @@ packages: resolution: {integrity: sha512-BBvQ/406p+4CZbTpCbVPSxfzrZrbnuWSP1ELYgyS6B+hNeKzgrdB4JczCa5VZUBQrDa9hUngm0KnexY6pJRN5Q==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + hachure-fill@0.5.2: + resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} + happy-dom@20.9.0: resolution: {integrity: sha512-GZZ9mKe8r646NUAf/zemnGbjYh4Bt8/MqASJY+pSm5ZDtc3YQox+4gsLI7yi1hba6o+eCsGxpHn5+iEVn31/FQ==} engines: {node: '>=20.0.0'} @@ -2180,6 +3077,12 @@ packages: resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} engines: {node: '>= 0.4'} + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + headers-polyfill@5.0.1: resolution: {integrity: sha512-1TJ6Fih/b8h5TIcv+1+Hw0PDQWJTKDKzFZzcKOiW1wJza3XoAQlkCuXLbymPYB8+ZQyw8mHvdw560e8zVFIWyA==} @@ -2202,10 +3105,20 @@ packages: resolution: {integrity: sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ==} engines: {node: '>=16.9.0'} + hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + http-errors@2.0.1: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + iconv-lite@0.7.2: resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} @@ -2214,6 +3127,9 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -2221,6 +3137,13 @@ packages: resolution: {integrity: sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==} engines: {node: ^20.17.0 || >=22.9.0} + internmap@1.0.1: + resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + ip-address@10.2.0: resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==} engines: {node: '>= 12'} @@ -2250,6 +3173,10 @@ packages: is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-what@5.5.0: + resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==} + engines: {node: '>=18'} + isbot@5.1.40: resolution: {integrity: sha512-yNeeynhhtIVRBk12tBV4eHNxwB42HzR4Q3Ea7vCOiJhImGaAIdIMrbJtacQlBizGLjUPw+akkFI5Dn9T70XoVQ==} engines: {node: '>=18'} @@ -2280,10 +3207,23 @@ packages: json-schema-typed@8.0.2: resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + katex@0.16.47: + resolution: {integrity: sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==} + hasBin: true + + khroma@2.1.0: + resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} + ky@2.0.2: resolution: {integrity: sha512-/GmXpo9F9W+f8n4Ivr2iH+7h7wL7jLbLKWkMlpflcCRb6kGjBfTlASEXaZ9qUgNTn4VgS0P2pwxxzQ4EM6Ulgg==} engines: {node: '>=22'} + layout-base@1.0.2: + resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} + + layout-base@2.0.1: + resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==} + lefthook-darwin-arm64@2.1.6: resolution: {integrity: sha512-hyB7eeiX78BS66f70byTJacDLC/xV1vgMv9n+idFUsrM7J3Udd/ag9Ag5NP3t0eN0EqQqAtrNnt35EH01lxnRQ==} cpu: [arm64] @@ -2341,6 +3281,9 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} + lucide-react@1.14.0: resolution: {integrity: sha512-+1mdWcfSJVUsaTIjN9zoezmUhfXo5l0vP7ekBMPo3jcS/aIkxHnXqAPsByszMZx/Y8oQBRJxJx5xg+RH3urzxA==} peerDependencies: @@ -2350,10 +3293,24 @@ packages: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + mark.js@8.11.1: + resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==} + + marked@16.4.2: + resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==} + engines: {node: '>= 20'} + hasBin: true + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + media-typer@1.1.0: resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} engines: {node: '>= 0.8'} @@ -2366,6 +3323,24 @@ packages: resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} engines: {node: '>=18'} + mermaid@11.15.0: + resolution: {integrity: sha512-pTMbcf3rWdtLiYGpmoTjHEpeY8seiy6sR+9nD7LOs8KfUbHE4lOUAprTRqRAcWSQ6MQpdX+YEsxShtGsINtPtw==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + mime-db@1.54.0: resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} engines: {node: '>= 0.6'} @@ -2374,6 +3349,12 @@ packages: resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} engines: {node: '>=18'} + minisearch@7.2.0: + resolution: {integrity: sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==} + + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -2398,6 +3379,11 @@ packages: resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} engines: {node: ^20.17.0 || >=22.9.0} + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + nanoid@5.1.11: resolution: {integrity: sha512-v+KEsUv2ps74PaSKv0gHTxTCgMXOIfBEbaqa6w6ISIGC7ZsvHN4N9oJ8d4cmf0n5oTzQz2SLmThbQWhjd/8eKg==} engines: {node: ^18 || >=20} @@ -2411,6 +3397,9 @@ packages: resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} hasBin: true + non-layered-tidy-tree-layout@2.0.2: + resolution: {integrity: sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2426,12 +3415,18 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + oniguruma-to-es@3.1.1: + resolution: {integrity: sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==} + openapi-types@12.1.3: resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} outvariant@1.4.3: resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + package-manager-detector@1.6.0: + resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -2444,6 +3439,9 @@ packages: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} + path-data-parser@0.1.0: + resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -2454,6 +3452,9 @@ packages: path-to-regexp@8.4.2: resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2461,10 +3462,26 @@ packages: resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} engines: {node: '>=16.20.0'} + points-on-curve@0.2.0: + resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} + + points-on-path@0.2.1: + resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==} + + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + engines: {node: ^10 || ^12 || >=14} + + preact@10.29.2: + resolution: {integrity: sha512-7tNmwg/7mzzAoB/8kSg6Hl37JraAZw3Z3A0JSY7VXlZwo82Xn0G7wKbNNs2qoF4ZEEsQGTwDAroNdqKs1ofJxQ==} + pretty-format@27.5.1: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + property-information@7.2.0: + resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -2545,6 +3562,15 @@ packages: resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} engines: {node: '>=0.10.0'} + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@6.1.0: + resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -2567,16 +3593,36 @@ packages: rettime@0.11.11: resolution: {integrity: sha512-ILJRqVWBCTlg9r42fFgwVZx1gnFAcQF8mRoMkbgQfIrjEDf9nbBFDFx00oloOa+Q869FUtaYDXZvEfnecQSCoQ==} + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + robust-predicates@3.0.3: + resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} + + rollup@4.61.1: + resolution: {integrity: sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + roughjs@4.6.6: + resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} + router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + search-insights@2.17.3: + resolution: {integrity: sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==} + semver@7.8.0: resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==} engines: {node: '>=10'} @@ -2614,6 +3660,9 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shiki@2.5.0: + resolution: {integrity: sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==} + side-channel-list@1.0.1: resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} engines: {node: '>= 0.4'} @@ -2634,6 +3683,10 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} @@ -2641,6 +3694,13 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + speakingurl@14.0.1: + resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} + engines: {node: '>=0.10.0'} + statuses@2.0.2: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} @@ -2656,6 +3716,9 @@ packages: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -2664,6 +3727,16 @@ packages: resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} engines: {node: '>=12'} + stylis@4.4.0: + resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==} + + superjson@2.2.6: + resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==} + engines: {node: '>=16'} + + tabbable@6.4.0: + resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==} + tagged-tag@1.0.0: resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} engines: {node: '>=20'} @@ -2693,6 +3766,13 @@ packages: resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} engines: {node: '>=16'} + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + ts-dedent@2.3.0: + resolution: {integrity: sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==} + engines: {node: '>=6.10'} + ts-pattern@5.9.0: resolution: {integrity: sha512-6s5V71mX8qBUmlgbrfL33xDUwO0fq48rxAu2LBE11WBeGdpCPOsXksQbZJHvHwhrd3QjUusd3mAOM5Gg0mFBLg==} @@ -2723,6 +3803,21 @@ packages: undici-types@7.19.2: resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} @@ -2750,22 +3845,89 @@ packages: '@types/react': optional: true - use-sync-external-store@1.6.0: - resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + uuid@14.0.0: + resolution: {integrity: sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==} + hasBin: true + + valibot@1.3.1: + resolution: {integrity: sha512-sfdRir/QFM0JaF22hqTroPc5xy4DimuGQVKFrzF1YfGwaS1nJot3Y8VqMdLO2Lg27fMzat2yD3pY5PbAYO39Gg==} + peerDependencies: + typescript: '>=5' + peerDependenciesMeta: + typescript: + optional: true + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + 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 + + vitepress-plugin-mermaid@2.0.17: + resolution: {integrity: sha512-IUzYpwf61GC6k0XzfmAmNrLvMi9TRrVRMsUyCA8KNXhg/mQ1VqWnO0/tBVPiX5UoKF1mDUwqn5QV4qAJl6JnUg==} peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + mermaid: 10 || 11 + vitepress: ^1.0.0 || ^1.0.0-alpha - valibot@1.3.1: - resolution: {integrity: sha512-sfdRir/QFM0JaF22hqTroPc5xy4DimuGQVKFrzF1YfGwaS1nJot3Y8VqMdLO2Lg27fMzat2yD3pY5PbAYO39Gg==} + vitepress@1.6.4: + resolution: {integrity: sha512-+2ym1/+0VVrbhNyRoFFesVvBvHAVMZMK0rw60E3X/5349M1GuVdKeazuksqopEdvkKwKGs21Q729jX81/bkBJg==} + hasBin: true peerDependencies: - typescript: '>=5' + markdown-it-mathjax3: ^4 + postcss: ^8 peerDependenciesMeta: - typescript: + markdown-it-mathjax3: + optional: true + postcss: optional: true - vary@1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} + vue@3.5.38: + resolution: {integrity: sha512-vAMKHfImQlYSy0C+PBue4s3ERZ2xGKfgZg5GXAsLInq1dyh2H78ILVP5sK0KPFPVW4kv+OGCIvBEondcjpZp7A==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true wavesurfer.js@7.12.7: resolution: {integrity: sha512-TIe7hB6OCZysNOZ2cn2NR8Qpko22POWel6rauNcqOammFoH65NYQUM35unNLLMIlUMVYvjJ6w/TTl/G/m+w0nA==} @@ -2830,8 +3992,128 @@ packages: zod@4.4.3: resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + snapshots: + '@algolia/abtesting@1.20.0': + dependencies: + '@algolia/client-common': 5.54.0 + '@algolia/requester-browser-xhr': 5.54.0 + '@algolia/requester-fetch': 5.54.0 + '@algolia/requester-node-http': 5.54.0 + + '@algolia/autocomplete-core@1.17.7(@algolia/client-search@5.54.0)(algoliasearch@5.54.0)(search-insights@2.17.3)': + dependencies: + '@algolia/autocomplete-plugin-algolia-insights': 1.17.7(@algolia/client-search@5.54.0)(algoliasearch@5.54.0)(search-insights@2.17.3) + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.54.0)(algoliasearch@5.54.0) + transitivePeerDependencies: + - '@algolia/client-search' + - algoliasearch + - search-insights + + '@algolia/autocomplete-plugin-algolia-insights@1.17.7(@algolia/client-search@5.54.0)(algoliasearch@5.54.0)(search-insights@2.17.3)': + dependencies: + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.54.0)(algoliasearch@5.54.0) + search-insights: 2.17.3 + transitivePeerDependencies: + - '@algolia/client-search' + - algoliasearch + + '@algolia/autocomplete-preset-algolia@1.17.7(@algolia/client-search@5.54.0)(algoliasearch@5.54.0)': + dependencies: + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.54.0)(algoliasearch@5.54.0) + '@algolia/client-search': 5.54.0 + algoliasearch: 5.54.0 + + '@algolia/autocomplete-shared@1.17.7(@algolia/client-search@5.54.0)(algoliasearch@5.54.0)': + dependencies: + '@algolia/client-search': 5.54.0 + algoliasearch: 5.54.0 + + '@algolia/client-abtesting@5.54.0': + dependencies: + '@algolia/client-common': 5.54.0 + '@algolia/requester-browser-xhr': 5.54.0 + '@algolia/requester-fetch': 5.54.0 + '@algolia/requester-node-http': 5.54.0 + + '@algolia/client-analytics@5.54.0': + dependencies: + '@algolia/client-common': 5.54.0 + '@algolia/requester-browser-xhr': 5.54.0 + '@algolia/requester-fetch': 5.54.0 + '@algolia/requester-node-http': 5.54.0 + + '@algolia/client-common@5.54.0': {} + + '@algolia/client-insights@5.54.0': + dependencies: + '@algolia/client-common': 5.54.0 + '@algolia/requester-browser-xhr': 5.54.0 + '@algolia/requester-fetch': 5.54.0 + '@algolia/requester-node-http': 5.54.0 + + '@algolia/client-personalization@5.54.0': + dependencies: + '@algolia/client-common': 5.54.0 + '@algolia/requester-browser-xhr': 5.54.0 + '@algolia/requester-fetch': 5.54.0 + '@algolia/requester-node-http': 5.54.0 + + '@algolia/client-query-suggestions@5.54.0': + dependencies: + '@algolia/client-common': 5.54.0 + '@algolia/requester-browser-xhr': 5.54.0 + '@algolia/requester-fetch': 5.54.0 + '@algolia/requester-node-http': 5.54.0 + + '@algolia/client-search@5.54.0': + dependencies: + '@algolia/client-common': 5.54.0 + '@algolia/requester-browser-xhr': 5.54.0 + '@algolia/requester-fetch': 5.54.0 + '@algolia/requester-node-http': 5.54.0 + + '@algolia/ingestion@1.54.0': + dependencies: + '@algolia/client-common': 5.54.0 + '@algolia/requester-browser-xhr': 5.54.0 + '@algolia/requester-fetch': 5.54.0 + '@algolia/requester-node-http': 5.54.0 + + '@algolia/monitoring@1.54.0': + dependencies: + '@algolia/client-common': 5.54.0 + '@algolia/requester-browser-xhr': 5.54.0 + '@algolia/requester-fetch': 5.54.0 + '@algolia/requester-node-http': 5.54.0 + + '@algolia/recommend@5.54.0': + dependencies: + '@algolia/client-common': 5.54.0 + '@algolia/requester-browser-xhr': 5.54.0 + '@algolia/requester-fetch': 5.54.0 + '@algolia/requester-node-http': 5.54.0 + + '@algolia/requester-browser-xhr@5.54.0': + dependencies: + '@algolia/client-common': 5.54.0 + + '@algolia/requester-fetch@5.54.0': + dependencies: + '@algolia/client-common': 5.54.0 + + '@algolia/requester-node-http@5.54.0': + dependencies: + '@algolia/client-common': 5.54.0 + + '@antfu/install-pkg@1.1.0': + dependencies: + package-manager-detector: 1.6.0 + tinyexec: 1.1.2 + '@babel/code-frame@7.29.0': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -2844,14 +4126,25 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 + '@babel/helper-string-parser@7.29.7': {} + '@babel/helper-validator-identifier@7.28.5': {} '@babel/helper-validator-identifier@7.29.7': {} + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + '@babel/runtime@7.29.2': {} '@babel/runtime@7.29.7': {} + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@biomejs/biome@2.4.14': optionalDependencies: '@biomejs/cli-darwin-arm64': 2.4.14 @@ -2887,6 +4180,13 @@ snapshots: '@biomejs/cli-win32-x64@2.4.14': optional: true + '@braintree/sanitize-url@6.0.4': + optional: true + + '@braintree/sanitize-url@7.1.2': {} + + '@chevrotain/types@11.1.2': {} + '@commitlint/cli@21.0.0(@types/node@25.6.2)(conventional-commits-parser@6.4.0)(typescript@6.0.3)': dependencies: '@commitlint/format': 21.0.0 @@ -3003,6 +4303,33 @@ snapshots: optionalDependencies: conventional-commits-parser: 6.4.0 + '@docsearch/css@3.8.2': {} + + '@docsearch/js@3.8.2(@algolia/client-search@5.54.0)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(search-insights@2.17.3)': + dependencies: + '@docsearch/react': 3.8.2(@algolia/client-search@5.54.0)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(search-insights@2.17.3) + preact: 10.29.2 + transitivePeerDependencies: + - '@algolia/client-search' + - '@types/react' + - react + - react-dom + - search-insights + + '@docsearch/react@3.8.2(@algolia/client-search@5.54.0)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(search-insights@2.17.3)': + dependencies: + '@algolia/autocomplete-core': 1.17.7(@algolia/client-search@5.54.0)(algoliasearch@5.54.0)(search-insights@2.17.3) + '@algolia/autocomplete-preset-algolia': 1.17.7(@algolia/client-search@5.54.0)(algoliasearch@5.54.0) + '@docsearch/css': 3.8.2 + algoliasearch: 5.54.0 + optionalDependencies: + '@types/react': 19.2.14 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + search-insights: 2.17.3 + transitivePeerDependencies: + - '@algolia/client-search' + '@drizzle-team/brocli@0.10.2': {} '@esbuild-kit/core-utils@3.3.2': @@ -3015,102 +4342,153 @@ snapshots: '@esbuild-kit/core-utils': 3.3.2 get-tsconfig: 4.14.0 + '@esbuild/aix-ppc64@0.21.5': + optional: true + '@esbuild/aix-ppc64@0.25.12': optional: true '@esbuild/aix-ppc64@0.27.7': optional: true + '@esbuild/android-arm64@0.21.5': + optional: true + '@esbuild/android-arm64@0.25.12': optional: true '@esbuild/android-arm64@0.27.7': optional: true + '@esbuild/android-arm@0.21.5': + optional: true + '@esbuild/android-arm@0.25.12': optional: true '@esbuild/android-arm@0.27.7': optional: true + '@esbuild/android-x64@0.21.5': + optional: true + '@esbuild/android-x64@0.25.12': optional: true '@esbuild/android-x64@0.27.7': optional: true + '@esbuild/darwin-arm64@0.21.5': + optional: true + '@esbuild/darwin-arm64@0.25.12': optional: true '@esbuild/darwin-arm64@0.27.7': optional: true + '@esbuild/darwin-x64@0.21.5': + optional: true + '@esbuild/darwin-x64@0.25.12': optional: true '@esbuild/darwin-x64@0.27.7': optional: true + '@esbuild/freebsd-arm64@0.21.5': + optional: true + '@esbuild/freebsd-arm64@0.25.12': optional: true '@esbuild/freebsd-arm64@0.27.7': optional: true + '@esbuild/freebsd-x64@0.21.5': + optional: true + '@esbuild/freebsd-x64@0.25.12': optional: true '@esbuild/freebsd-x64@0.27.7': optional: true + '@esbuild/linux-arm64@0.21.5': + optional: true + '@esbuild/linux-arm64@0.25.12': optional: true '@esbuild/linux-arm64@0.27.7': optional: true + '@esbuild/linux-arm@0.21.5': + optional: true + '@esbuild/linux-arm@0.25.12': optional: true '@esbuild/linux-arm@0.27.7': optional: true + '@esbuild/linux-ia32@0.21.5': + optional: true + '@esbuild/linux-ia32@0.25.12': optional: true '@esbuild/linux-ia32@0.27.7': optional: true + '@esbuild/linux-loong64@0.21.5': + optional: true + '@esbuild/linux-loong64@0.25.12': optional: true '@esbuild/linux-loong64@0.27.7': optional: true + '@esbuild/linux-mips64el@0.21.5': + optional: true + '@esbuild/linux-mips64el@0.25.12': optional: true '@esbuild/linux-mips64el@0.27.7': optional: true + '@esbuild/linux-ppc64@0.21.5': + optional: true + '@esbuild/linux-ppc64@0.25.12': optional: true '@esbuild/linux-ppc64@0.27.7': optional: true + '@esbuild/linux-riscv64@0.21.5': + optional: true + '@esbuild/linux-riscv64@0.25.12': optional: true '@esbuild/linux-riscv64@0.27.7': optional: true + '@esbuild/linux-s390x@0.21.5': + optional: true + '@esbuild/linux-s390x@0.25.12': optional: true '@esbuild/linux-s390x@0.27.7': optional: true + '@esbuild/linux-x64@0.21.5': + optional: true + '@esbuild/linux-x64@0.25.12': optional: true @@ -3123,6 +4501,9 @@ snapshots: '@esbuild/netbsd-arm64@0.27.7': optional: true + '@esbuild/netbsd-x64@0.21.5': + optional: true + '@esbuild/netbsd-x64@0.25.12': optional: true @@ -3135,6 +4516,9 @@ snapshots: '@esbuild/openbsd-arm64@0.27.7': optional: true + '@esbuild/openbsd-x64@0.21.5': + optional: true + '@esbuild/openbsd-x64@0.25.12': optional: true @@ -3147,24 +4531,36 @@ snapshots: '@esbuild/openharmony-arm64@0.27.7': optional: true + '@esbuild/sunos-x64@0.21.5': + optional: true + '@esbuild/sunos-x64@0.25.12': optional: true '@esbuild/sunos-x64@0.27.7': optional: true + '@esbuild/win32-arm64@0.21.5': + optional: true + '@esbuild/win32-arm64@0.25.12': optional: true '@esbuild/win32-arm64@0.27.7': optional: true + '@esbuild/win32-ia32@0.21.5': + optional: true + '@esbuild/win32-ia32@0.25.12': optional: true '@esbuild/win32-ia32@0.27.7': optional: true + '@esbuild/win32-x64@0.21.5': + optional: true + '@esbuild/win32-x64@0.25.12': optional: true @@ -3209,6 +4605,18 @@ snapshots: '@standard-schema/spec': 1.1.0 hono: 4.12.25 + '@iconify-json/simple-icons@1.2.86': + dependencies: + '@iconify/types': 2.0.0 + + '@iconify/types@2.0.0': {} + + '@iconify/utils@3.1.3': + dependencies: + '@antfu/install-pkg': 1.1.0 + '@iconify/types': 2.0.0 + import-meta-resolve: 4.2.0 + '@inquirer/ansi@2.0.5': {} '@inquirer/confirm@6.0.12(@types/node@25.6.2)': @@ -3236,6 +4644,23 @@ snapshots: optionalDependencies: '@types/node': 25.6.2 + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@mermaid-js/mermaid-mindmap@9.3.0': + dependencies: + '@braintree/sanitize-url': 6.0.4 + cytoscape: 3.34.0 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.34.0) + cytoscape-fcose: 2.2.0(cytoscape@3.34.0) + d3: 7.9.0 + khroma: 2.1.0 + non-layered-tidy-tree-layout: 2.0.2 + optional: true + + '@mermaid-js/parser@1.1.1': + dependencies: + '@chevrotain/types': 11.1.2 + '@modelcontextprotocol/sdk@1.29.0(zod@4.4.3)': dependencies: '@hono/node-server': 1.19.14(hono@4.12.25) @@ -4079,6 +5504,81 @@ snapshots: '@radix-ui/rect@1.1.1': {} + '@rollup/rollup-android-arm-eabi@4.61.1': + optional: true + + '@rollup/rollup-android-arm64@4.61.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.61.1': + optional: true + + '@rollup/rollup-darwin-x64@4.61.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.61.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.61.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.61.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.61.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.61.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.61.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.61.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.61.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.61.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.61.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.61.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.61.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.61.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.61.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.61.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.61.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.61.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.61.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.61.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.61.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.61.1': + optional: true + '@scalar/client-side-rendering@0.1.7': dependencies: '@scalar/types': 0.9.6 @@ -4097,6 +5597,46 @@ snapshots: type-fest: 5.6.0 zod: 4.4.3 + '@shikijs/core@2.5.0': + dependencies: + '@shikijs/engine-javascript': 2.5.0 + '@shikijs/engine-oniguruma': 2.5.0 + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@2.5.0': + dependencies: + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 3.1.1 + + '@shikijs/engine-oniguruma@2.5.0': + dependencies: + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@2.5.0': + dependencies: + '@shikijs/types': 2.5.0 + + '@shikijs/themes@2.5.0': + dependencies: + '@shikijs/types': 2.5.0 + + '@shikijs/transformers@2.5.0': + dependencies: + '@shikijs/core': 2.5.0 + '@shikijs/types': 2.5.0 + + '@shikijs/types@2.5.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + '@simple-libs/child-process-utils@1.0.2': dependencies: '@simple-libs/stream-utils': 1.2.0 @@ -4182,11 +5722,149 @@ snapshots: '@types/aria-query@5.0.4': {} - '@types/bun@1.3.13': + '@types/bun@1.3.13': + dependencies: + bun-types: 1.3.13 + + '@types/d3-array@3.2.2': {} + + '@types/d3-axis@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-brush@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-chord@3.0.6': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-contour@3.0.6': + dependencies: + '@types/d3-array': 3.2.2 + '@types/geojson': 7946.0.16 + + '@types/d3-delaunay@6.0.4': {} + + '@types/d3-dispatch@3.0.7': {} + + '@types/d3-drag@3.0.7': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-dsv@3.0.7': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-fetch@3.0.7': + dependencies: + '@types/d3-dsv': 3.0.7 + + '@types/d3-force@3.0.10': {} + + '@types/d3-format@3.0.4': {} + + '@types/d3-geo@3.1.0': + dependencies: + '@types/geojson': 7946.0.16 + + '@types/d3-hierarchy@3.1.7': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-polygon@3.0.2': {} + + '@types/d3-quadtree@3.0.6': {} + + '@types/d3-random@3.0.3': {} + + '@types/d3-scale-chromatic@3.1.0': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-selection@3.0.11': {} + + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time-format@4.0.3': {} + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/d3-transition@3.0.9': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-zoom@3.0.8': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + + '@types/d3@7.4.3': + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-axis': 3.0.6 + '@types/d3-brush': 3.0.6 + '@types/d3-chord': 3.0.6 + '@types/d3-color': 3.1.3 + '@types/d3-contour': 3.0.6 + '@types/d3-delaunay': 6.0.4 + '@types/d3-dispatch': 3.0.7 + '@types/d3-drag': 3.0.7 + '@types/d3-dsv': 3.0.7 + '@types/d3-ease': 3.0.2 + '@types/d3-fetch': 3.0.7 + '@types/d3-force': 3.0.10 + '@types/d3-format': 3.0.4 + '@types/d3-geo': 3.1.0 + '@types/d3-hierarchy': 3.1.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-path': 3.1.1 + '@types/d3-polygon': 3.0.2 + '@types/d3-quadtree': 3.0.6 + '@types/d3-random': 3.0.3 + '@types/d3-scale': 4.0.9 + '@types/d3-scale-chromatic': 3.1.0 + '@types/d3-selection': 3.0.11 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-time-format': 4.0.3 + '@types/d3-timer': 3.0.2 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + + '@types/estree@1.0.9': {} + + '@types/geojson@7946.0.16': {} + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/json-schema@7.0.15': {} + + '@types/linkify-it@5.0.0': {} + + '@types/markdown-it@14.1.2': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + + '@types/mdast@4.0.4': dependencies: - bun-types: 1.3.13 + '@types/unist': 3.0.3 - '@types/json-schema@7.0.15': {} + '@types/mdurl@2.0.0': {} '@types/node@25.6.2': dependencies: @@ -4206,16 +5884,134 @@ snapshots: '@types/statuses@2.0.6': {} + '@types/trusted-types@2.0.7': + optional: true + + '@types/unist@3.0.3': {} + + '@types/web-bluetooth@0.0.21': {} + '@types/whatwg-mimetype@3.0.2': {} '@types/ws@8.18.1': dependencies: '@types/node': 25.6.2 + '@ungap/structured-clone@1.3.1': {} + + '@upsetjs/venn.js@2.0.0': + optionalDependencies: + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + '@valibot/to-json-schema@1.7.0(valibot@1.3.1(typescript@6.0.3))': dependencies: valibot: 1.3.1(typescript@6.0.3) + '@vitejs/plugin-vue@5.2.4(vite@5.4.21(@types/node@25.6.2))(vue@3.5.38(typescript@6.0.3))': + dependencies: + vite: 5.4.21(@types/node@25.6.2) + vue: 3.5.38(typescript@6.0.3) + + '@vue/compiler-core@3.5.38': + dependencies: + '@babel/parser': 7.29.7 + '@vue/shared': 3.5.38 + entities: 7.0.1 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.38': + dependencies: + '@vue/compiler-core': 3.5.38 + '@vue/shared': 3.5.38 + + '@vue/compiler-sfc@3.5.38': + dependencies: + '@babel/parser': 7.29.7 + '@vue/compiler-core': 3.5.38 + '@vue/compiler-dom': 3.5.38 + '@vue/compiler-ssr': 3.5.38 + '@vue/shared': 3.5.38 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.15 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.38': + dependencies: + '@vue/compiler-dom': 3.5.38 + '@vue/shared': 3.5.38 + + '@vue/devtools-api@7.7.9': + dependencies: + '@vue/devtools-kit': 7.7.9 + + '@vue/devtools-kit@7.7.9': + dependencies: + '@vue/devtools-shared': 7.7.9 + birpc: 2.9.0 + hookable: 5.5.3 + mitt: 3.0.1 + perfect-debounce: 1.0.0 + speakingurl: 14.0.1 + superjson: 2.2.6 + + '@vue/devtools-shared@7.7.9': + dependencies: + rfdc: 1.4.1 + + '@vue/reactivity@3.5.38': + dependencies: + '@vue/shared': 3.5.38 + + '@vue/runtime-core@3.5.38': + dependencies: + '@vue/reactivity': 3.5.38 + '@vue/shared': 3.5.38 + + '@vue/runtime-dom@3.5.38': + dependencies: + '@vue/reactivity': 3.5.38 + '@vue/runtime-core': 3.5.38 + '@vue/shared': 3.5.38 + csstype: 3.2.3 + + '@vue/server-renderer@3.5.38(vue@3.5.38(typescript@6.0.3))': + dependencies: + '@vue/compiler-ssr': 3.5.38 + '@vue/shared': 3.5.38 + vue: 3.5.38(typescript@6.0.3) + + '@vue/shared@3.5.38': {} + + '@vueuse/core@12.8.2(typescript@6.0.3)': + dependencies: + '@types/web-bluetooth': 0.0.21 + '@vueuse/metadata': 12.8.2 + '@vueuse/shared': 12.8.2(typescript@6.0.3) + vue: 3.5.38(typescript@6.0.3) + transitivePeerDependencies: + - typescript + + '@vueuse/integrations@12.8.2(focus-trap@7.8.0)(typescript@6.0.3)': + dependencies: + '@vueuse/core': 12.8.2(typescript@6.0.3) + '@vueuse/shared': 12.8.2(typescript@6.0.3) + vue: 3.5.38(typescript@6.0.3) + optionalDependencies: + focus-trap: 7.8.0 + transitivePeerDependencies: + - typescript + + '@vueuse/metadata@12.8.2': {} + + '@vueuse/shared@12.8.2(typescript@6.0.3)': + dependencies: + vue: 3.5.38(typescript@6.0.3) + transitivePeerDependencies: + - typescript + '@wavesurfer/react@1.0.12(react@19.2.6)(wavesurfer.js@7.12.7)': dependencies: react: 19.2.6 @@ -4237,6 +6033,23 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + algoliasearch@5.54.0: + dependencies: + '@algolia/abtesting': 1.20.0 + '@algolia/client-abtesting': 5.54.0 + '@algolia/client-analytics': 5.54.0 + '@algolia/client-common': 5.54.0 + '@algolia/client-insights': 5.54.0 + '@algolia/client-personalization': 5.54.0 + '@algolia/client-query-suggestions': 5.54.0 + '@algolia/client-search': 5.54.0 + '@algolia/ingestion': 1.54.0 + '@algolia/monitoring': 1.54.0 + '@algolia/recommend': 5.54.0 + '@algolia/requester-browser-xhr': 5.54.0 + '@algolia/requester-fetch': 5.54.0 + '@algolia/requester-node-http': 5.54.0 + ansi-regex@5.0.1: {} ansi-regex@6.2.2: {} @@ -4261,6 +6074,8 @@ snapshots: array-ify@1.0.0: {} + birpc@2.9.0: {} + body-parser@2.2.2: dependencies: bytes: 3.1.2 @@ -4325,6 +6140,12 @@ snapshots: callsites@3.1.0: {} + ccount@2.0.1: {} + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + class-variance-authority@0.7.1: dependencies: clsx: 2.1.1 @@ -4351,6 +6172,12 @@ snapshots: color-name@1.1.4: {} + comma-separated-tokens@2.0.3: {} + + commander@7.2.0: {} + + commander@8.3.0: {} + compare-func@2.0.0: dependencies: array-ify: 1.0.0 @@ -4383,11 +6210,23 @@ snapshots: cookie@1.1.1: {} + copy-anything@4.0.5: + dependencies: + is-what: 5.5.0 + cors@2.8.6: dependencies: object-assign: 4.1.1 vary: 1.1.2 + cose-base@1.0.3: + dependencies: + layout-base: 1.0.2 + + cose-base@2.2.0: + dependencies: + layout-base: 2.0.1 + cosmiconfig-typescript-loader@6.3.0(@types/node@25.6.2)(cosmiconfig@9.0.1(typescript@6.0.3))(typescript@6.0.3): dependencies: '@types/node': 25.6.2 @@ -4414,10 +6253,200 @@ snapshots: csstype@3.2.3: {} + cytoscape-cose-bilkent@4.1.0(cytoscape@3.34.0): + dependencies: + cose-base: 1.0.3 + cytoscape: 3.34.0 + + cytoscape-fcose@2.2.0(cytoscape@3.34.0): + dependencies: + cose-base: 2.2.0 + cytoscape: 3.34.0 + + cytoscape@3.34.0: {} + + d3-array@2.12.1: + dependencies: + internmap: 1.0.1 + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-axis@3.0.0: {} + + d3-brush@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3-chord@3.0.1: + dependencies: + d3-path: 3.1.0 + + d3-color@3.1.0: {} + + d3-contour@4.0.2: + dependencies: + d3-array: 3.2.4 + + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.1.0 + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + + d3-ease@3.0.1: {} + + d3-fetch@3.0.1: + dependencies: + d3-dsv: 3.0.1 + + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + + d3-format@3.1.2: {} + + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@1.0.9: {} + + d3-path@3.1.0: {} + + d3-polygon@3.0.1: {} + + d3-quadtree@3.0.1: {} + + d3-random@3.0.1: {} + + d3-sankey@0.12.3: + dependencies: + d3-array: 2.12.1 + d3-shape: 1.3.7 + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-selection@3.0.0: {} + + d3-shape@1.3.7: + dependencies: + d3-path: 1.0.9 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3@7.9.0: + dependencies: + d3-array: 3.2.4 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.2 + d3-delaunay: 6.0.4 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.2 + d3-geo: 3.1.1 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + d3-selection: 3.0.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1(d3-selection@3.0.0) + d3-zoom: 3.0.0 + + dagre-d3-es@7.0.14: + dependencies: + d3: 7.9.0 + lodash-es: 4.18.1 + + dayjs@1.11.21: {} + debug@4.4.3: dependencies: ms: 2.1.3 + delaunator@5.1.0: + dependencies: + robust-predicates: 3.0.3 + depd@2.0.0: {} dequal@2.0.3: {} @@ -4427,8 +6456,16 @@ snapshots: detect-node-es@1.1.0: {} + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + dom-accessibility-api@0.5.16: {} + dompurify@3.4.10: + optionalDependencies: + '@types/trusted-types': 2.0.7 + dot-prop@5.3.0: dependencies: is-obj: 2.0.0 @@ -4452,6 +6489,8 @@ snapshots: ee-first@1.1.1: {} + emoji-regex-xs@1.0.0: {} + emoji-regex@10.6.0: {} emoji-regex@8.0.0: {} @@ -4476,6 +6515,32 @@ snapshots: es-toolkit@1.46.1: {} + esbuild@0.21.5: + 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 + esbuild@0.25.12: optionalDependencies: '@esbuild/aix-ppc64': 0.25.12 @@ -4538,6 +6603,8 @@ snapshots: escape-html@1.0.3: {} + estree-walker@2.0.2: {} + etag@1.8.1: {} eventsource-parser@3.0.8: {} @@ -4609,6 +6676,10 @@ snapshots: transitivePeerDependencies: - supports-color + focus-trap@7.8.0: + dependencies: + tabbable: 6.4.0 + forwarded@0.2.0: {} fresh@2.0.0: {} @@ -4662,6 +6733,8 @@ snapshots: graphql@16.14.0: {} + hachure-fill@0.5.2: {} + happy-dom@20.9.0: dependencies: '@types/node': 25.6.2 @@ -4680,6 +6753,24 @@ snapshots: dependencies: function-bind: 1.1.2 + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + headers-polyfill@5.0.1: dependencies: '@types/set-cookie-parser': 2.4.10 @@ -4697,6 +6788,10 @@ snapshots: hono@4.12.25: {} + hookable@5.5.3: {} + + html-void-elements@3.0.0: {} + http-errors@2.0.1: dependencies: depd: 2.0.0 @@ -4705,6 +6800,10 @@ snapshots: statuses: 2.0.2 toidentifier: 1.0.1 + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + iconv-lite@0.7.2: dependencies: safer-buffer: 2.1.2 @@ -4714,10 +6813,16 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 + import-meta-resolve@4.2.0: {} + inherits@2.0.4: {} ini@6.0.0: {} + internmap@1.0.1: {} + + internmap@2.0.3: {} + ip-address@10.2.0: {} ipaddr.js@1.9.1: {} @@ -4734,6 +6839,8 @@ snapshots: is-promise@4.0.0: {} + is-what@5.5.0: {} + isbot@5.1.40: {} isexe@2.0.0: {} @@ -4754,8 +6861,18 @@ snapshots: json-schema-typed@8.0.2: {} + katex@0.16.47: + dependencies: + commander: 8.3.0 + + khroma@2.1.0: {} + ky@2.0.2: {} + layout-base@1.0.2: {} + + layout-base@2.0.1: {} + lefthook-darwin-arm64@2.1.6: optional: true @@ -4801,26 +6918,93 @@ snapshots: lines-and-columns@1.2.4: {} + lodash-es@4.18.1: {} + lucide-react@1.14.0(react@19.2.6): dependencies: react: 19.2.6 lz-string@1.5.0: {} + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + mark.js@8.11.1: {} + + marked@16.4.2: {} + math-intrinsics@1.1.0: {} + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.1 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + media-typer@1.1.0: {} meow@13.2.0: {} merge-descriptors@2.0.0: {} + mermaid@11.15.0: + dependencies: + '@braintree/sanitize-url': 7.1.2 + '@iconify/utils': 3.1.3 + '@mermaid-js/parser': 1.1.1 + '@types/d3': 7.4.3 + '@upsetjs/venn.js': 2.0.0 + cytoscape: 3.34.0 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.34.0) + cytoscape-fcose: 2.2.0(cytoscape@3.34.0) + d3: 7.9.0 + d3-sankey: 0.12.3 + dagre-d3-es: 7.0.14 + dayjs: 1.11.21 + dompurify: 3.4.10 + es-toolkit: 1.46.1 + katex: 0.16.47 + khroma: 2.1.0 + marked: 16.4.2 + roughjs: 4.6.6 + stylis: 4.4.0 + ts-dedent: 2.3.0 + uuid: 14.0.0 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-encode@2.0.1: {} + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + mime-db@1.54.0: {} mime-types@3.0.2: dependencies: mime-db: 1.54.0 + minisearch@7.2.0: {} + + mitt@3.0.1: {} + ms@2.1.3: {} msgpackr-extract@3.0.3: @@ -4866,6 +7050,8 @@ snapshots: mute-stream@3.0.0: {} + nanoid@3.3.12: {} + nanoid@5.1.11: {} negotiator@1.0.0: {} @@ -4875,6 +7061,9 @@ snapshots: detect-libc: 2.1.2 optional: true + non-layered-tidy-tree-layout@2.0.2: + optional: true + object-assign@4.1.1: {} object-inspect@1.13.4: {} @@ -4887,10 +7076,18 @@ snapshots: dependencies: wrappy: 1.0.2 + oniguruma-to-es@3.1.1: + dependencies: + emoji-regex-xs: 1.0.0 + regex: 6.1.0 + regex-recursion: 6.0.2 + openapi-types@12.1.3: {} outvariant@1.4.3: {} + package-manager-detector@1.6.0: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -4904,22 +7101,43 @@ snapshots: parseurl@1.3.3: {} + path-data-parser@0.1.0: {} + path-key@3.1.1: {} path-to-regexp@6.3.0: {} path-to-regexp@8.4.2: {} + perfect-debounce@1.0.0: {} + picocolors@1.1.1: {} pkce-challenge@5.0.1: {} + points-on-curve@0.2.0: {} + + points-on-path@0.2.1: + dependencies: + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + + postcss@8.5.15: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + preact@10.29.2: {} + pretty-format@27.5.1: dependencies: ansi-regex: 5.0.1 ansi-styles: 5.2.0 react-is: 17.0.2 + property-information@7.2.0: {} + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -5043,6 +7261,16 @@ snapshots: react@19.2.6: {} + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.1.0: + dependencies: + regex-utilities: 2.3.0 + require-directory@2.1.1: {} require-from-string@2.0.2: {} @@ -5055,6 +7283,48 @@ snapshots: rettime@0.11.11: {} + rfdc@1.4.1: {} + + robust-predicates@3.0.3: {} + + rollup@4.61.1: + dependencies: + '@types/estree': 1.0.9 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.61.1 + '@rollup/rollup-android-arm64': 4.61.1 + '@rollup/rollup-darwin-arm64': 4.61.1 + '@rollup/rollup-darwin-x64': 4.61.1 + '@rollup/rollup-freebsd-arm64': 4.61.1 + '@rollup/rollup-freebsd-x64': 4.61.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.61.1 + '@rollup/rollup-linux-arm-musleabihf': 4.61.1 + '@rollup/rollup-linux-arm64-gnu': 4.61.1 + '@rollup/rollup-linux-arm64-musl': 4.61.1 + '@rollup/rollup-linux-loong64-gnu': 4.61.1 + '@rollup/rollup-linux-loong64-musl': 4.61.1 + '@rollup/rollup-linux-ppc64-gnu': 4.61.1 + '@rollup/rollup-linux-ppc64-musl': 4.61.1 + '@rollup/rollup-linux-riscv64-gnu': 4.61.1 + '@rollup/rollup-linux-riscv64-musl': 4.61.1 + '@rollup/rollup-linux-s390x-gnu': 4.61.1 + '@rollup/rollup-linux-x64-gnu': 4.61.1 + '@rollup/rollup-linux-x64-musl': 4.61.1 + '@rollup/rollup-openbsd-x64': 4.61.1 + '@rollup/rollup-openharmony-arm64': 4.61.1 + '@rollup/rollup-win32-arm64-msvc': 4.61.1 + '@rollup/rollup-win32-ia32-msvc': 4.61.1 + '@rollup/rollup-win32-x64-gnu': 4.61.1 + '@rollup/rollup-win32-x64-msvc': 4.61.1 + fsevents: 2.3.3 + + roughjs@4.6.6: + dependencies: + hachure-fill: 0.5.2 + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + points-on-path: 0.2.1 + router@2.2.0: dependencies: debug: 4.4.3 @@ -5065,10 +7335,14 @@ snapshots: transitivePeerDependencies: - supports-color + rw@1.3.3: {} + safer-buffer@2.1.2: {} scheduler@0.27.0: {} + search-insights@2.17.3: {} + semver@7.8.0: {} send@1.2.1: @@ -5112,6 +7386,17 @@ snapshots: shebang-regex@3.0.0: {} + shiki@2.5.0: + dependencies: + '@shikijs/core': 2.5.0 + '@shikijs/engine-javascript': 2.5.0 + '@shikijs/engine-oniguruma': 2.5.0 + '@shikijs/langs': 2.5.0 + '@shikijs/themes': 2.5.0 + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + side-channel-list@1.0.1: dependencies: es-errors: 1.3.0 @@ -5142,6 +7427,8 @@ snapshots: signal-exit@4.1.0: {} + source-map-js@1.2.1: {} + source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 @@ -5149,6 +7436,10 @@ snapshots: source-map@0.6.1: {} + space-separated-tokens@2.0.2: {} + + speakingurl@14.0.1: {} + statuses@2.0.2: {} strict-event-emitter@0.5.1: {} @@ -5165,6 +7456,11 @@ snapshots: get-east-asian-width: 1.6.0 strip-ansi: 7.2.0 + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -5173,6 +7469,14 @@ snapshots: dependencies: ansi-regex: 6.2.2 + stylis@4.4.0: {} + + superjson@2.2.6: + dependencies: + copy-anything: 4.0.5 + + tabbable@6.4.0: {} + tagged-tag@1.0.0: {} tailwind-merge@3.5.0: {} @@ -5193,6 +7497,10 @@ snapshots: dependencies: tldts: 7.0.30 + trim-lines@3.0.1: {} + + ts-dedent@2.3.0: {} + ts-pattern@5.9.0: {} tslib@2.8.1: {} @@ -5220,6 +7528,29 @@ snapshots: undici-types@7.19.2: {} + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + unpipe@1.0.0: {} until-async@3.0.2: {} @@ -5243,12 +7574,99 @@ snapshots: dependencies: react: 19.2.6 + uuid@14.0.0: {} + valibot@1.3.1(typescript@6.0.3): optionalDependencies: typescript: 6.0.3 vary@1.1.2: {} + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + vite@5.4.21(@types/node@25.6.2): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.15 + rollup: 4.61.1 + optionalDependencies: + '@types/node': 25.6.2 + fsevents: 2.3.3 + + vitepress-plugin-mermaid@2.0.17(mermaid@11.15.0)(vitepress@1.6.4(@algolia/client-search@5.54.0)(@types/node@25.6.2)(@types/react@19.2.14)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(search-insights@2.17.3)(typescript@6.0.3)): + dependencies: + mermaid: 11.15.0 + vitepress: 1.6.4(@algolia/client-search@5.54.0)(@types/node@25.6.2)(@types/react@19.2.14)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(search-insights@2.17.3)(typescript@6.0.3) + optionalDependencies: + '@mermaid-js/mermaid-mindmap': 9.3.0 + + vitepress@1.6.4(@algolia/client-search@5.54.0)(@types/node@25.6.2)(@types/react@19.2.14)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(search-insights@2.17.3)(typescript@6.0.3): + dependencies: + '@docsearch/css': 3.8.2 + '@docsearch/js': 3.8.2(@algolia/client-search@5.54.0)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(search-insights@2.17.3) + '@iconify-json/simple-icons': 1.2.86 + '@shikijs/core': 2.5.0 + '@shikijs/transformers': 2.5.0 + '@shikijs/types': 2.5.0 + '@types/markdown-it': 14.1.2 + '@vitejs/plugin-vue': 5.2.4(vite@5.4.21(@types/node@25.6.2))(vue@3.5.38(typescript@6.0.3)) + '@vue/devtools-api': 7.7.9 + '@vue/shared': 3.5.38 + '@vueuse/core': 12.8.2(typescript@6.0.3) + '@vueuse/integrations': 12.8.2(focus-trap@7.8.0)(typescript@6.0.3) + focus-trap: 7.8.0 + mark.js: 8.11.1 + minisearch: 7.2.0 + shiki: 2.5.0 + vite: 5.4.21(@types/node@25.6.2) + vue: 3.5.38(typescript@6.0.3) + optionalDependencies: + postcss: 8.5.15 + transitivePeerDependencies: + - '@algolia/client-search' + - '@types/node' + - '@types/react' + - async-validator + - axios + - change-case + - drauu + - fuse.js + - idb-keyval + - jwt-decode + - less + - lightningcss + - nprogress + - qrcode + - react + - react-dom + - sass + - sass-embedded + - search-insights + - sortablejs + - stylus + - sugarss + - terser + - typescript + - universal-cookie + + vue@3.5.38(typescript@6.0.3): + dependencies: + '@vue/compiler-dom': 3.5.38 + '@vue/compiler-sfc': 3.5.38 + '@vue/runtime-dom': 3.5.38 + '@vue/server-renderer': 3.5.38(vue@3.5.38(typescript@6.0.3)) + '@vue/shared': 3.5.38 + optionalDependencies: + typescript: 6.0.3 + wavesurfer.js@7.12.7: {} whatwg-mimetype@3.0.0: {} @@ -5303,3 +7721,5 @@ snapshots: zod: 4.4.3 zod@4.4.3: {} + + zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index d49a706..2c1a0e8 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -60,6 +60,25 @@ blockExoticSubdeps: true # Fail install if a newer release has weaker publish-time trust than the prior one. trustPolicy: no-downgrade +# Trust-policy exceptions — same bar as `overrides` / `auditConfig.ignoreGhsas` +# below: each entry needs a date + reason. trustPolicy STAYS no-downgrade; these +# are surgical per-package exemptions, not a relaxation of the policy. +# +# vite — VitePress's build engine. Dev-only: it powers `pnpm docs:build` and the +# `docs` dev server, and is never shipped in the Docker image (the prod stage +# installs `--prod`, which drops devDependencies). Trips no-downgrade because +# earlier vite releases carried npm "trusted publisher" evidence while current +# 5.4.x carry Sigstore "provenance attestation" only; pnpm ranks trusted-publisher +# above provenance and flags the transition as a downgrade. This is a benign +# ecosystem-wide shift in npm's attestation story (provenance is itself a strong +# signal, published by the vitejs org), not a takeover — verified 2026-06-19 that +# vite 5.4.0..5.4.21 all carry provenance attestation. Excluded by bare name (not +# version-pinned) because the shift is structural: every current vite trips it, so +# a version pin would only churn on each patch bump. Re-evaluate if vite ever +# regains trusted-publisher evidence. Added 2026-06-19. +trustPolicyExclude: + - vite + # Re-verify node_modules matches the lockfile before any script runs. verifyDepsBeforeRun: install @@ -156,3 +175,44 @@ overrides: auditConfig: ignoreGhsas: - GHSA-gv7w-rqvm-qjhr + # ---- VitePress docs toolchain — dev-only, static-output VEX -------------- + # The advisories below all live in the docs-site toolchain (added with the + # VitePress docs PR). They share one disposition: NOT AFFECTED. + # • Dev-only — vitepress/vite/esbuild/mermaid are devDependencies. The prod + # image installs `--prod` (Dockerfile stage 1), which drops them; none of + # this code ships in the container. + # • Static output — `pnpm docs:build` emits static HTML to GitHub Pages. The + # deployed site runs no vite/esbuild dev server, so the dev-server classes + # below have zero production surface. + # We do NOT bump to remediate the vite findings: they're patched only in vite + # 6.x, but vitepress@1.6.4 pins vite 5.x (no 5.x backport) — forcing vite 6 + # would break the pinned vitepress. Revisit when vitepress ships a stable + # release on vite 6 (its 2.x line). + # + # GHSA-fx2h-pf6j-xcff (HIGH) — vite `server.fs.deny` bypass via Windows + # alternate paths. Dev-server-only AND Windows-only; we build on macOS/Linux + # and deploy static. Unreachable. Added 2026-06-21. + - GHSA-fx2h-pf6j-xcff + # GHSA-4w7w-66w2-5vf9 (moderate) — vite path traversal in optimized-deps + # `.map` handling. Dev-server-only; absent from the static build. 2026-06-21. + - GHSA-4w7w-66w2-5vf9 + # GHSA-v6wh-96g9-6wx3 (moderate) — launch-editor (vite dev-server "open in + # editor") NTLMv2 hash disclosure via UNC paths on Windows. Dev-server-only, + # Windows-only. Unreachable here. Added 2026-06-21. + - GHSA-v6wh-96g9-6wx3 + # GHSA-67mh-4wv8-2f99 (moderate) — esbuild dev server lets any site read + # responses (permissive CORS). Same advisory the `overrides` above patch for + # the drizzle-kit path; here it rides in via vitepress>vite>esbuild (0.21.5, + # which vite 5.4 pins). esbuild is vite's transform library, never run via its + # `serve` mode, and the docs deploy is static — the dev server never runs. + # Bumping vite's esbuild to 0.25 risks breaking vite 5.4's build, so ignore + # rather than override. Added 2026-06-21. + - GHSA-67mh-4wv8-2f99 + # GHSA-cmwh-pvxp-8882 (moderate) — DOMPurify ALLOWED_ATTR pollution via + # setConfig(). Rides in via mermaid>dompurify; mermaid sanitizes the diagrams + # WE author at build time, never untrusted input, and we never call setConfig + # with attacker-controlled config — unreachable. The fix (dompurify 3.4.11, + # published 2026-06-17) is still inside the 7-day cooldown and the finding is + # not urgent, so we don't bypass it. Convert to `overrides: mermaid>dompurify: + # ^3.4.11` once 3.4.11 clears cooldown (~2026-06-24). Added 2026-06-21. + - GHSA-cmwh-pvxp-8882