From 2113207bafb51b75f725654570ab711dca00437f Mon Sep 17 00:00:00 2001 From: "D.Leap" Date: Mon, 14 Sep 2026 17:15:02 +0530 Subject: [PATCH] Build durable extraction backend with recovery, OAuth and MCP --- .env.example | 35 +- .github/workflows/ci.yml | 39 + README.md | 151 +- app/api/auth/google/callback/route.ts | 12 + app/api/auth/google/route.ts | 10 + app/api/auth/logout/route.ts | 10 + app/api/auth/me/route.ts | 4 + app/api/auth/tokens/route.ts | 10 + app/api/contracts/[id]/data/route.ts | 4 + app/api/contracts/[id]/events/route.ts | 98 +- app/api/contracts/[id]/route.ts | 33 +- app/api/contracts/[id]/run/route.ts | 28 +- app/api/contracts/route.ts | 41 +- app/api/dev/break/route.ts | 36 +- app/api/health/route.ts | 6 + app/api/mcp/route.ts | 30 +- app/api/runs/[id]/reconcile/route.ts | 8 + app/api/runs/[id]/review/route.ts | 8 + app/api/runs/[id]/route.ts | 4 + app/contracts/[id]/loading.tsx | 6 - app/contracts/[id]/page.tsx | 94 - app/dashboard/loading.tsx | 12 - app/dashboard/page.tsx | 25 - app/globals.css | 84 - app/layout.tsx | 41 - app/page.tsx | 5 - app/route.ts | 3 + bun.lock | 1293 ------------- components/AppHeader.tsx | 14 - components/SiteFooter.tsx | 23 - components/dashboard/ContractList.tsx | 57 - components/dashboard/EventFeed.tsx | 98 - components/dashboard/LineageGraph.tsx | 63 - components/dashboard/RunButton.tsx | 53 - components/dashboard/Skeletons.tsx | 53 - components/dashboard/StatusBadge.tsx | 16 - components/landing/LandingExperience.tsx | 209 -- compose.yaml | 18 + docs/architecture.md | 86 + eslint.config.mjs | 8 + lib/auth/service.ts | 71 + lib/brightdata/client.ts | 197 +- lib/brightdata/types.ts | 27 - lib/contracts/schema.ts | 71 +- lib/contracts/service.ts | 204 +- lib/contracts/validator.test.ts | 6 +- lib/env.ts | 55 +- lib/errors.ts | 4 + lib/events/bus.ts | 53 +- lib/events/types.ts | 58 +- lib/fixtures/scraper.ts | 42 + lib/http.ts | 16 + lib/jobs/worker.ts | 177 ++ lib/parallel/client.ts | 90 - lib/selfheal/orchestrator.ts | 140 +- lib/semantic/diff.ts | 25 + mcp/server.ts | 79 +- package-lock.json | 1696 +++++++---------- package.json | 22 +- postcss.config.mjs | 6 - .../202609140001_baseline/migration.sql | 54 + .../migration.sql | 131 ++ prisma/migrations/migration_lock.toml | 1 + prisma/schema.prisma | 121 +- scripts/demo.ts | 34 + scripts/worker.ts | 16 + tailwind.config.ts | 20 - tests/brightdata.test.ts | 34 + tests/integration/backend.test.ts | 143 ++ tests/semantic.test.ts | 34 + tests/setup.ts | 6 + vitest.config.ts | 3 + 72 files changed, 2230 insertions(+), 4234 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 app/api/auth/google/callback/route.ts create mode 100644 app/api/auth/google/route.ts create mode 100644 app/api/auth/logout/route.ts create mode 100644 app/api/auth/me/route.ts create mode 100644 app/api/auth/tokens/route.ts create mode 100644 app/api/contracts/[id]/data/route.ts create mode 100644 app/api/health/route.ts create mode 100644 app/api/runs/[id]/reconcile/route.ts create mode 100644 app/api/runs/[id]/review/route.ts create mode 100644 app/api/runs/[id]/route.ts delete mode 100644 app/contracts/[id]/loading.tsx delete mode 100644 app/contracts/[id]/page.tsx delete mode 100644 app/dashboard/loading.tsx delete mode 100644 app/dashboard/page.tsx delete mode 100644 app/globals.css delete mode 100644 app/layout.tsx delete mode 100644 app/page.tsx create mode 100644 app/route.ts delete mode 100644 bun.lock delete mode 100644 components/AppHeader.tsx delete mode 100644 components/SiteFooter.tsx delete mode 100644 components/dashboard/ContractList.tsx delete mode 100644 components/dashboard/EventFeed.tsx delete mode 100644 components/dashboard/LineageGraph.tsx delete mode 100644 components/dashboard/RunButton.tsx delete mode 100644 components/dashboard/Skeletons.tsx delete mode 100644 components/dashboard/StatusBadge.tsx delete mode 100644 components/landing/LandingExperience.tsx create mode 100644 compose.yaml create mode 100644 docs/architecture.md create mode 100644 eslint.config.mjs create mode 100644 lib/auth/service.ts delete mode 100644 lib/brightdata/types.ts create mode 100644 lib/fixtures/scraper.ts create mode 100644 lib/http.ts create mode 100644 lib/jobs/worker.ts delete mode 100644 lib/parallel/client.ts create mode 100644 lib/semantic/diff.ts delete mode 100644 postcss.config.mjs create mode 100644 prisma/migrations/202609140001_baseline/migration.sql create mode 100644 prisma/migrations/202609140002_durable_backend/migration.sql create mode 100644 prisma/migrations/migration_lock.toml create mode 100644 scripts/demo.ts create mode 100644 scripts/worker.ts delete mode 100644 tailwind.config.ts create mode 100644 tests/brightdata.test.ts create mode 100644 tests/integration/backend.test.ts create mode 100644 tests/semantic.test.ts create mode 100644 tests/setup.ts diff --git a/.env.example b/.env.example index 93313a9..57217b5 100644 --- a/.env.example +++ b/.env.example @@ -1,11 +1,32 @@ -# Required — Postgres connection string (Neon, Supabase, local, whatever). -DATABASE_URL="postgresql://user:password@host:5432/dbname" +# Copy this file to .env. Node 22+ is required. +# Local Docker setup: docker compose up -d db +DATABASE_URL="postgresql://lippy:lippy@localhost:5432/lippy?schema=public" +APP_URL="http://localhost:3000" -# Optional — omit either to run that client in mock mode. -BRIGHTDATA_API_KEY= +# google = sign in + session/API-token authentication. +# development = explicit local auth bypass for curl/demo; NEVER works in production. +AUTH_MODE="google" +GOOGLE_CLIENT_ID="" +GOOGLE_CLIENT_SECRET="" +# Register this exact authorized redirect URI in Google Cloud: +# http://localhost:3000/api/auth/google/callback +# For deployment, set APP_URL to the HTTPS origin and register its callback URI. + +# fixture = deterministic HTML extraction + selector repair, no paid API calls. +# brightdata = real Scraper Studio API calls; no silent fallback to fixture mode. +EXTRACTION_PROVIDER="fixture" +BRIGHTDATA_API_KEY="" BRIGHTDATA_API_BASE="https://api.brightdata.com" +# Required ONLY when creating new Bright Data collectors; provider emails results here. +# Leave empty if every contract supplies an existing, dedicated collectorId. +BRIGHTDATA_DELIVERY_EMAIL="" -PARALLEL_API_KEY= -PARALLEL_API_BASE="https://api.parallel.ai" +# Worker / provider polling and bounded recovery +WORKER_POLL_MS=1000 +PROVIDER_POLL_MS=5000 +MAX_HEAL_ATTEMPTS=3 +RUN_TIMEOUT_MS=1800000 -WEBHOOK_SIGNING_SECRET="dev-secret-change-me" +# Optional: API token used by npm run demo when AUTH_MODE=google. +# Issue one using POST /api/auth/tokens after Google sign-in (see README). +LIPPY_API_TOKEN="" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..724769f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,39 @@ +name: Backend CI +on: + pull_request: + push: + branches: [main] +jobs: + verify: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:16 + env: + POSTGRES_USER: lippy + POSTGRES_PASSWORD: lippy + POSTGRES_DB: lippy_test + ports: ["5432:5432"] + options: >- + --health-cmd "pg_isready -U lippy" + --health-interval 5s --health-timeout 5s --health-retries 10 + env: + DATABASE_URL: postgresql://lippy:lippy@localhost:5432/lippy_test + AUTH_MODE: google + EXTRACTION_PROVIDER: fixture + NEXT_TELEMETRY_DISABLED: "1" + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "22" + cache: npm + - run: npm ci + - run: npm run db:generate + - run: npm run db:deploy + - run: npm run lint + - run: npm run typecheck + - run: npm test + env: + RUN_DB_TESTS: "1" + - run: npm run build diff --git a/README.md b/README.md index 9213b42..c6df9cd 100644 --- a/README.md +++ b/README.md @@ -1,67 +1,114 @@ # Lippy Agent +Backend infrastructure for reliable structured web extraction. Define a URL and typed contract; a separate worker extracts data, validates it, repairs broken selectors, verifies changes and publishes durable events. REST and MCP share the same authenticated service layer. -Websites change. Your API shouldn't. See [`SPEC.md`](./SPEC.md) for the full -architecture, judging-criteria mapping, and demo script. +## Run locally -## Setup +Requires Node.js 22+ and PostgreSQL. Docker is optional if you already have PostgreSQL. -```bash -npm install -cp .env.example .env # works out of the box in mock mode, no keys needed +```sh +npm ci +cp .env.example .env +docker compose up -d db npm run db:generate -npm run db:migrate +npm run db:deploy npm run dev +# In another terminal: +npm run worker ``` -Runs fully in **mock mode** with zero API keys — `BrightDataClient` and -`ParallelClient` both fall back to deterministic mock responses so the -create → run → violate → heal → verify → semantic-diff loop is runnable -end to end out of the box. Set `BRIGHTDATA_API_KEY` / `PARALLEL_API_KEY` in -`.env` to hit the real APIs. +Fill `.env` using the explanations in `.env.example`. For a zero-API-key local demonstration, set `AUTH_MODE=development` and `EXTRACTION_PROVIDER=fixture`, then run `npm run demo` with the API and worker running. Development authentication never works with `NODE_ENV=production`. The demo creates a disabled contract, verifies a baseline, changes its HTML, proves selector recovery, and quarantines a suspicious price change while retaining accepted data. Fixture data is isolated per contract and stored in PostgreSQL, not in process-global flags. -## Project layout +For real extraction set `EXTRACTION_PROVIDER=brightdata` and `BRIGHTDATA_API_KEY`. Supply an existing **dedicated batch-mode** Scraper Studio `collectorId` per contract, or set `BRIGHTDATA_DELIVERY_EMAIL` to let the worker create and generate a collector. Newly created collectors also deliver results to that email. Field names/types must match an existing collector's output. Contracts currently represent one page and exactly one output record. +## Google OAuth and API tokens + +1. Create a Google OAuth web application and copy its client ID/secret into `.env`. +2. Register `http://localhost:3000/api/auth/google/callback` as an authorized redirect URI. In deployment use the HTTPS `APP_URL` and its corresponding callback. +3. With `AUTH_MODE=google`, open `/api/auth/google` in your browser. A successful login redirects to `/api/auth/me` and sets an HttpOnly session cookie. +4. From that same origin, request an API token: + +```js +await fetch('/api/auth/tokens', { method: 'POST' }).then(r => r.json()) ``` -src/ - app/ Next.js App Router — routes are thin adapters only - api/contracts/ REST surface (create, get, run, live event stream) - api/mcp/ MCP server over Streamable HTTP - lib/ - contracts/ schema (Zod), validator, service layer - brightdata/ typed client (create/run/heal/approve), mock-capable - parallel/ semantic-diff client, mock-capable - selfheal/ the orchestrator state machine - events/ typed event union + pub/sub bus - env.ts errors.ts logger.ts db.ts - mcp/server.ts tool definitions (create_contract, watch_contract, get_data) - tests/ vitest unit tests -prisma/schema.prisma Contract / Run / ContractEvent models + +The returned token is shown once; store it securely. REST and MCP clients send `Authorization: Bearer TOKEN`. API tokens expire after 30 days, sessions after 7 days. Delete a token with `DELETE /api/auth/tokens` and JSON `{ "id": "TOKEN_ID" }`. Log out with `POST /api/auth/logout`. Cookie-authenticated writes require the matching `Origin`; bearer clients do not. Cross-origin requests are rejected. Google login uses one-time state, PKCE, nonce and signature/issuer/audience verification. Only token hashes are stored. + +## REST example + +In development-auth mode omit the Authorization header. Set TOKEN in your shell for Google mode. + +```sh +curl -X POST http://localhost:3000/api/contracts \ + -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ + -d '{"url":"https://example.com/product","enabled":false,"fields":[ + {"key":"product","type":"string","expectedValue":"Example product"}, + {"key":"price","type":"number","minimum":0,"maxRelativeChange":0.5} + ]}' + +curl -X POST http://localhost:3000/api/contracts/CONTRACT_ID/run \ + -H "Authorization: Bearer $TOKEN" -H 'Idempotency-Key: first-run' +# 202 + {runId, state}; Location points to /api/runs/RUN_ID. + +curl http://localhost:3000/api/runs/RUN_ID -H "Authorization: Bearer $TOKEN" +curl http://localhost:3000/api/contracts/CONTRACT_ID/data -H "Authorization: Bearer $TOKEN" +curl -N http://localhost:3000/api/contracts/CONTRACT_ID/events \ + -H "Authorization: Bearer $TOKEN" -H 'Accept: text/event-stream' ``` -## Commands - -| Command | What it does | -|---|---| -| `npm run dev` | Start the dev server | -| `npm test` | Run the vitest suite | -| `npm run typecheck` | `tsc --noEmit` | -| `npm run db:studio` | Browse the SQLite DB visually | - -## Try it - -```bash -# create a contract -curl -X POST localhost:3000/api/contracts -H 'content-type: application/json' -d '{ - "url": "https://example.com/product", - "fields": [ - { "key": "product", "type": "string", "required": true }, - { "key": "price", "type": "number", "required": true }, - { "key": "stock", "type": "boolean", "required": false } - ] -}' - -# trigger a run (mock mode breaks the extraction every 3rd call, so re-run -# a few times to see contract.violated -> contract.healing -> contract.healed) -curl -X POST localhost:3000/api/contracts//run +| Endpoint | Behavior | +| --- | --- | +| `GET/POST /api/contracts` | List/create owned contracts | +| `GET /api/contracts/:id` | Contract, recent runs and initial event history | +| `PATCH /api/contracts/:id` | Pause/resume scheduling using `{"enabled":false/true}` | +| `POST /api/contracts/:id/run` | Enqueue; optional `Idempotency-Key` deduplicates retries | +| `GET /api/runs/:id` | State, raw/validated output, violations, provider job ID and repair history | +| `GET /api/contracts/:id/data` | Last accepted data and its run/timestamp; includes current health | +| `GET /api/contracts/:id/events?after=SEQUENCE` | Durable event pages; SSE with `Accept: text/event-stream` | +| `POST /api/runs/:id/review` | `{"decision":"accept"}` or `{"decision":"reject"}` for quarantined results | +| `POST /api/runs/:id/reconcile` | Record manual reconciliation of an indeterminate provider submission | +| `POST /api/dev/break` | Non-production fixture mutation: `{contractId, scenario:"layout"}`; also `value` with `field`/`value`, or `unrepairable` | +| `GET /api/health` | Database readiness | + +Review and reconciliation leave scheduling paused. Explicitly resume it after resolving the problem. The reconciliation request requires a descriptive `note`; it can attach `collectorId` after you identify a scraper created during an interrupted request. Verify/cancel outstanding provider work in Scraper Studio before reconciling. + +## MCP + +Connect a Streamable HTTP MCP client to `/api/mcp` with an API bearer token. Available tools: `create_contract`, `list_contracts`, `run_contract`, `get_run`, `get_data`, `watch_contract`. `watch_contract` now reads durable events after a cursor; it does not trigger extraction. Tool calls are stateless JSON responses. Event streaming uses the REST SSE endpoint, with `Last-Event-ID` for reconnect replay. API tokens are configured explicitly; this server does not implement MCP OAuth discovery/dynamic client registration. + +## Verification and guarantees + +- PostgreSQL stores queued work, state transitions, raw output, repair attempts and events. Multiple workers claim jobs with `FOR UPDATE SKIP LOCKED`, expiring leases and fencing tokens. One active run per contract is serialized with a database row lock. +- Each bounded step releases its lease. Polling states survive restarts and read failures use bounded backoff. All runs have a deadline and repair budget. Repeated or unrecoverable failures pause scheduling. +- State and event writes share a transaction. Publishers serialize per contract before allocating event sequences, so a cursor cannot skip a later-committing earlier event. Each SSE/MCP subscriber owns its cursor. This is PostgreSQL-backed pub/sub; it does not depend on Redis or process-local fanout. +- A crash between a non-idempotent provider request and storing its response is **indeterminate**, not silently retried. Exactly-once external side effects are not claimed. +- Schema verification supports required fields, types, numeric bounds and expected identity/value. Semantic difference verification uses explicit normalization (Unicode/whitespace, optional case folding, URL canonicalization) and numeric relative-change limits. It reports initialization, meaningful changes and suspicious changes under those rules. It is not general-purpose AI truth verification. `confidence: 1` means the rule matched deterministically, not that a model proved source accuracy. +- Bright Data repair is approved and saved upstream, then the extraction is run again. Only verified data is promoted locally. The integration does not offer isolated candidate scrapers or automatic upstream rollback. Fields without identity/bounds constraints can still contain semantically wrong but type-valid data. +- No live Bright Data or Google login is claimed as tested without real credentials. Provider protocol tests use documented response fixtures. Removed the previous undocumented Parallel AI diff endpoint and silent "no change" fallback. + +See [architecture and failure walkthrough](docs/architecture.md). + +## Tests and deployment + +```sh +npm run lint +npm run typecheck +npm test +# Explicitly use a dedicated migrated TEST database: +DATABASE_URL=postgresql://user:password@localhost:5432/lippy_test RUN_DB_TESTS=1 npm test +npm run build +npm start +# Separate long-running worker process, with the same .env: +npm run worker ``` + +The CI workflow provisions PostgreSQL, applies migrations, runs unit/integration tests and builds the API. Deploy the API and at least one worker against the same database. Use HTTPS, `AUTH_MODE=google`, and configure secrets on both processes. Polling subscriptions need a hosting environment that allows streaming connections; long extraction jobs execute only in workers. + +For an existing database already matching the old schema, back it up and mark the baseline migration applied before deploying the second migration: + +```sh +npx prisma migrate resolve --applied 202609140001_baseline +npm run db:deploy +``` + +Legacy history is preserved, legacy interrupted runs are closed, and unowned contracts are paused and inaccessible until deliberately assigned to a user. Legacy mock collector IDs are cleared. Duplicate real collector IDs must be resolved before migration because each contract now requires a dedicated collector. diff --git a/app/api/auth/google/callback/route.ts b/app/api/auth/google/callback/route.ts new file mode 100644 index 0000000..412564b --- /dev/null +++ b/app/api/auth/google/callback/route.ts @@ -0,0 +1,12 @@ +import { NextResponse } from "next/server"; +import { completeGoogleLogin, cookie, cookieOptions, STATE_COOKIE, SESSION_COOKIE } from "@/lib/auth/service"; +import { endpoint } from "@/lib/http"; +import { env } from "@/lib/env"; +export async function GET(req: Request) { return endpoint(async () => { + const url = new URL(req.url); + const session = await completeGoogleLogin(url.searchParams.get("code") ?? "", url.searchParams.get("state") ?? "", cookie(req, STATE_COOKIE)); + const response = NextResponse.redirect(`${env.APP_URL}/api/auth/me`); + response.cookies.set(SESSION_COOKIE, session.token, { ...cookieOptions, expires: session.expiresAt }); + response.cookies.set(STATE_COOKIE, "", { ...cookieOptions, maxAge: 0 }); + return response; +}); } diff --git a/app/api/auth/google/route.ts b/app/api/auth/google/route.ts new file mode 100644 index 0000000..cd4c8b5 --- /dev/null +++ b/app/api/auth/google/route.ts @@ -0,0 +1,10 @@ +import { NextResponse } from "next/server"; +import { beginGoogleLogin, STATE_COOKIE, cookieOptions } from "@/lib/auth/service"; +import { endpoint } from "@/lib/http"; +export const dynamic = "force-dynamic"; +export async function GET() { return endpoint(async () => { + const login = await beginGoogleLogin(); + const response = NextResponse.redirect(login.url); + response.cookies.set(STATE_COOKIE, login.state, { ...cookieOptions, maxAge: 600 }); + return response; +}); } diff --git a/app/api/auth/logout/route.ts b/app/api/auth/logout/route.ts new file mode 100644 index 0000000..4d77612 --- /dev/null +++ b/app/api/auth/logout/route.ts @@ -0,0 +1,10 @@ +import { NextResponse } from "next/server"; +import { requireUser, cookie, SESSION_COOKIE, hashToken, cookieOptions } from "@/lib/auth/service"; +import { db } from "@/lib/db"; +import { endpoint } from "@/lib/http"; +export async function POST(req: Request) { return endpoint(async () => { + const userId = await requireUser(req), token = cookie(req, SESSION_COOKIE); + if (token) await db.session.deleteMany({ where: { userId, tokenHash: hashToken(token) } }); + const response = NextResponse.json({ signedOut: true }); + response.cookies.set(SESSION_COOKIE, "", { ...cookieOptions, maxAge: 0 }); return response; +}); } diff --git a/app/api/auth/me/route.ts b/app/api/auth/me/route.ts new file mode 100644 index 0000000..ac56785 --- /dev/null +++ b/app/api/auth/me/route.ts @@ -0,0 +1,4 @@ +import { requireUser } from "@/lib/auth/service"; +import { db } from "@/lib/db"; +import { endpoint } from "@/lib/http"; +export async function GET(req: Request) { return endpoint(async () => Response.json({ user: await db.user.findUnique({ where: { id: await requireUser(req) }, select: { id: true, email: true } }) }, { headers: { "Cache-Control": "no-store" } })); } diff --git a/app/api/auth/tokens/route.ts b/app/api/auth/tokens/route.ts new file mode 100644 index 0000000..44b6142 --- /dev/null +++ b/app/api/auth/tokens/route.ts @@ -0,0 +1,10 @@ +import { z } from "zod"; +import { requireUser, issueToken } from "@/lib/auth/service"; +import { db } from "@/lib/db"; +import { endpoint, body } from "@/lib/http"; +export async function POST(req: Request) { return endpoint(async () => Response.json(await issueToken(await requireUser(req), "api"), { status: 201, headers: { "Cache-Control": "no-store" } })); } +export async function DELETE(req: Request) { return endpoint(async () => { + const owner = await requireUser(req), { id } = z.object({ id: z.string() }).parse(await body(req)); + await db.session.deleteMany({ where: { id, userId: owner, kind: "api" } }); + return new Response(null, { status: 204 }); +}); } diff --git a/app/api/contracts/[id]/data/route.ts b/app/api/contracts/[id]/data/route.ts new file mode 100644 index 0000000..793f306 --- /dev/null +++ b/app/api/contracts/[id]/data/route.ts @@ -0,0 +1,4 @@ +import { requireUser } from "@/lib/auth/service"; +import { getData } from "@/lib/contracts/service"; +import { endpoint } from "@/lib/http"; +export async function GET(req: Request, ctx: { params: Promise<{ id: string }> }) { return endpoint(async () => Response.json(await getData((await ctx.params).id, await requireUser(req)))); } diff --git a/app/api/contracts/[id]/events/route.ts b/app/api/contracts/[id]/events/route.ts index 9318b80..c7f3323 100644 --- a/app/api/contracts/[id]/events/route.ts +++ b/app/api/contracts/[id]/events/route.ts @@ -1,69 +1,39 @@ -import { NextRequest } from "next/server"; -import { eventBus } from "@/lib/events/bus"; - -/** - * GET /api/contracts/:id/events -> Server-Sent Events stream filtered to - * one contract. Subscribes to the in-process event bus on connect, - * forwards each event as a `data: ...\n\n` SSE frame, and emits a - * comment heartbeat every 15s so intermediaries don't drop the connection. - * - * The detail page opens this with EventSource to render the live event - * timeline without polling. - */ -export async function GET(req: NextRequest, ctx: { params: Promise<{ id: string }> }) { - const { id: contractId } = await ctx.params; - +import { requireUser } from "@/lib/auth/service"; +import { getContract } from "@/lib/contracts/service"; +import { readEvents } from "@/lib/events/bus"; +import { cursor, endpoint } from "@/lib/http"; +export const dynamic = "force-dynamic"; +export async function GET(req: Request, ctx: { params: Promise<{ id: string }> }) { return endpoint(async () => { + const owner = await requireUser(req), { id } = await ctx.params; + await getContract(id, owner); + let after = cursor(req.headers.get("last-event-id") ?? new URL(req.url).searchParams.get("after")); + if (!req.headers.get("accept")?.includes("text/event-stream")) return Response.json({ events: await readEvents(id, after) }); const encoder = new TextEncoder(); - let unsubscribe: (() => void) | null = null; - let heartbeat: ReturnType | null = null; - + let cancelled = false; const stream = new ReadableStream({ - start(controller) { - const write = (chunk: string) => { - try { - controller.enqueue(encoder.encode(chunk)); - } catch { - // Stream already closed; cleanup will run via cancel(). + async start(controller) { + const abort = () => { cancelled = true; }; + req.signal.addEventListener("abort", abort, { once: true }); + try { + controller.enqueue(encoder.encode(": subscribed\n\n")); + while (!cancelled && !req.signal.aborted) { + // Re-check authentication so expired/revoked tokens cannot keep reading indefinitely. + await requireUser(req); + const events = await readEvents(id, after); + if (cancelled || req.signal.aborted) break; + for (const event of events) { + controller.enqueue(encoder.encode(`id: ${event.sequence}\nevent: ${event.type}\ndata: ${JSON.stringify(event.data)}\n\n`)); + after = BigInt(event.sequence); + } + if (events.length === 100) continue; + controller.enqueue(encoder.encode(": heartbeat\n\n")); + await new Promise(resolve => setTimeout(resolve, 1000)); } - }; - - // Initial comment + a synthetic hello so the client confirms the - // subscription is live before the first real event. - write(`: subscribed to ${contractId}\n\n`); - write( - `event: hello\ndata: ${JSON.stringify({ contractId, ts: new Date().toISOString() })}\n\n`, - ); - - unsubscribe = eventBus.subscribe((event) => { - if (event.contractId !== contractId) return; - write(`event: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`); - }); - - heartbeat = setInterval(() => write(`: heartbeat ${Date.now()}\n\n`), 15000); - - // If the client disconnects, Next.js calls cancel(); clean up. - req.signal.addEventListener("abort", () => { - if (unsubscribe) unsubscribe(); - if (heartbeat) clearInterval(heartbeat); - try { - controller.close(); - } catch { - // Already closed. - } - }); - }, - cancel() { - if (unsubscribe) unsubscribe(); - if (heartbeat) clearInterval(heartbeat); - }, - }); - - return new Response(stream, { - headers: { - "Content-Type": "text/event-stream; charset=utf-8", - "Cache-Control": "no-cache, no-transform", - Connection: "keep-alive", - "X-Accel-Buffering": "no", + if (!cancelled) controller.close(); + } catch (error) { if (!cancelled) controller.error(error); } + finally { req.signal.removeEventListener("abort", abort); } }, + cancel() { cancelled = true; }, }); -} + return new Response(stream, { headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache, no-transform", "X-Accel-Buffering": "no" } }); +}); } diff --git a/app/api/contracts/[id]/route.ts b/app/api/contracts/[id]/route.ts index 68e9af8..e02e8b3 100644 --- a/app/api/contracts/[id]/route.ts +++ b/app/api/contracts/[id]/route.ts @@ -1,19 +1,14 @@ -import { NextResponse } from "next/server"; -import { getContract, getContractHistory } from "@/lib/contracts/service"; -import { toErrorResponse } from "@/lib/errors"; - -/** - * GET /api/contracts/:id -> contract metadata + recent runs + recent events. - * Bundles history with the contract so the detail page can render in one - * fetch instead of three. - */ -export async function GET(_req: Request, ctx: { params: Promise<{ id: string }> }) { - try { - const { id } = await ctx.params; - const [contract, history] = await Promise.all([getContract(id), getContractHistory(id)]); - return NextResponse.json({ contract, ...history }); - } catch (err) { - const { status, body } = toErrorResponse(err); - return NextResponse.json(body, { status }); - } -} +import { z } from "zod"; +import { requireUser } from "@/lib/auth/service"; +import { getContract, getContractHistory, setEnabled } from "@/lib/contracts/service"; +import { endpoint, body } from "@/lib/http"; +type Context = { params: Promise<{ id: string }> }; +export async function GET(req: Request, ctx: Context) { return endpoint(async () => { + const owner = await requireUser(req), { id } = await ctx.params; + return Response.json({ contract: await getContract(id, owner), ...await getContractHistory(id, owner) }); +}); } +export async function PATCH(req: Request, ctx: Context) { return endpoint(async () => { + const owner = await requireUser(req), { id } = await ctx.params; + const { enabled } = z.object({ enabled: z.boolean() }).parse(await body(req)); + return Response.json({ contract: await setEnabled(id, owner, enabled) }); +}); } diff --git a/app/api/contracts/[id]/run/route.ts b/app/api/contracts/[id]/run/route.ts index 61b9714..3352fa9 100644 --- a/app/api/contracts/[id]/run/route.ts +++ b/app/api/contracts/[id]/run/route.ts @@ -1,22 +1,8 @@ -import { NextResponse } from "next/server"; +import { requireUser } from "@/lib/auth/service"; import { triggerRun } from "@/lib/contracts/service"; -import { toErrorResponse } from "@/lib/errors"; - -/** - * POST /api/contracts/:id/run -> kicks off the orchestrator loop - * (extract -> validate -> heal if needed -> semantic-diff -> emit). - * - * Returns 202 Accepted with a small status payload. The full event stream - * is available at GET /api/contracts/:id/events for clients that want to - * follow the run live. - */ -export async function POST(_req: Request, ctx: { params: Promise<{ id: string }> }) { - try { - const { id } = await ctx.params; - const result = await triggerRun(id); - return NextResponse.json({ status: "ok", result }, { status: 202 }); - } catch (err) { - const { status, body } = toErrorResponse(err); - return NextResponse.json(body, { status }); - } -} +import { endpoint } from "@/lib/http"; +export async function POST(req: Request, ctx: { params: Promise<{ id: string }> }) { return endpoint(async () => { + const owner = await requireUser(req), { id } = await ctx.params; + const run = await triggerRun(id, owner, req.headers.get("idempotency-key") ?? undefined); + return Response.json({ runId: run!.id, state: run!.state }, { status: 202, headers: { Location: `/api/runs/${run!.id}` } }); +}); } diff --git a/app/api/contracts/route.ts b/app/api/contracts/route.ts index c3e45d5..6022b26 100644 --- a/app/api/contracts/route.ts +++ b/app/api/contracts/route.ts @@ -1,34 +1,9 @@ -import { NextRequest, NextResponse } from "next/server"; +import { requireUser } from "@/lib/auth/service"; import { createContract, listContracts } from "@/lib/contracts/service"; -import { toErrorResponse } from "@/lib/errors"; - -/** - * GET /api/contracts -> list all contracts (dashboard feed) - * POST /api/contracts -> create a new contract from { url, fields, pollIntervalMs? } - * - * Both handlers are intentionally thin: parse input, call a service - * function, shape the response. All validation and side effects live in - * `lib/contracts/service.ts` so the MCP `create_contract` tool and the - * HTTP POST route can never drift. - */ - -export async function GET() { - try { - const contracts = await listContracts(); - return NextResponse.json({ contracts }); - } catch (err) { - const { status, body } = toErrorResponse(err); - return NextResponse.json(body, { status }); - } -} - -export async function POST(req: NextRequest) { - try { - const body = await req.json(); - const contract = await createContract(body); - return NextResponse.json({ contract }, { status: 201 }); - } catch (err) { - const { status, body } = toErrorResponse(err); - return NextResponse.json(body, { status }); - } -} +import { endpoint, body } from "@/lib/http"; +export const dynamic = "force-dynamic"; +export async function GET(req: Request) { return endpoint(async () => Response.json({ contracts: await listContracts(await requireUser(req)) })); } +export async function POST(req: Request) { return endpoint(async () => { + const owner = await requireUser(req); + return Response.json({ contract: await createContract(await body(req), owner) }, { status: 201 }); +}); } diff --git a/app/api/dev/break/route.ts b/app/api/dev/break/route.ts index 15337fc..98d71e3 100644 --- a/app/api/dev/break/route.ts +++ b/app/api/dev/break/route.ts @@ -1,13 +1,23 @@ -import { NextResponse } from "next/server"; -import { breakMockForDemo } from "@/lib/brightdata/client"; - -/** - * Demo-only endpoint: forces the next run to violate so the heal cycle - * fires on cue. No-op when real API keys are configured (mock mode - * requires no keys, real mode doesn't need this knob). Not part of the - * MCP surface — purely a live-demo convenience. - */ -export async function POST() { - const applied = breakMockForDemo(); - return NextResponse.json({ applied }); -} +import { z } from "zod"; +import { requireUser } from "@/lib/auth/service"; +import { getContract } from "@/lib/contracts/service"; +import { mutateFixture, type Fixture } from "@/lib/fixtures/scraper"; +import { db } from "@/lib/db"; +import { env } from "@/lib/env"; +import { HttpError } from "@/lib/errors"; +import { endpoint, body } from "@/lib/http"; +export async function POST(req: Request) { return endpoint(async () => { + if (env.NODE_ENV === "production") throw new HttpError(404, "NOT_FOUND", "Not found"); + const owner = await requireUser(req); + const input = z.object({ contractId: z.string(), scenario: z.enum(["layout", "value", "unrepairable"]).default("layout"), field: z.string().regex(/^[a-zA-Z_][a-zA-Z0-9_]*$/).optional(), value: z.string().optional() }).parse(await body(req)); + await getContract(input.contractId, owner); + await db.$transaction(async tx => { + await tx.$queryRaw`SELECT id FROM "Contract" WHERE id = ${input.contractId} FOR UPDATE`; + const c = await tx.contract.findUniqueOrThrow({ where: { id: input.contractId } }); + if (c.provider !== "fixture" || !c.fixture) throw new HttpError(400, "NOT_FIXTURE", "This contract does not use fixtures"); + if (c.activeRunId) throw new HttpError(409, "RUN_IN_PROGRESS", "Wait for the current run before editing the fixture"); + const fixture = mutateFixture(JSON.parse(c.fixture) as Fixture, input.scenario, input.field, input.value); + await tx.contract.update({ where: { id: c.id }, data: { fixture: JSON.stringify(fixture) } }); + }); + return Response.json({ applied: true }); +}); } diff --git a/app/api/health/route.ts b/app/api/health/route.ts new file mode 100644 index 0000000..79df54c --- /dev/null +++ b/app/api/health/route.ts @@ -0,0 +1,6 @@ +import { db } from "@/lib/db"; +export const dynamic = "force-dynamic"; +export async function GET() { + try { await db.$queryRaw`SELECT 1`; return Response.json({ status: "ok" }); } + catch { return Response.json({ status: "unavailable" }, { status: 503 }); } +} diff --git a/app/api/mcp/route.ts b/app/api/mcp/route.ts index fdce1c1..59b3a39 100644 --- a/app/api/mcp/route.ts +++ b/app/api/mcp/route.ts @@ -1,22 +1,14 @@ import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js"; import { createMcpServer } from "@/mcp/server"; - -/** - * Exposes the MCP server over Streamable HTTP at /api/mcp so any MCP client - * (Claude, Cursor, a custom agent) can connect with just this URL — no - * separate process to run. - * - * Uses the Web-Standard variant of the transport so we can hand it the - * Next.js Request directly (no Node IncomingMessage adapter needed) and - * receive a Response back — keeps the handler Web-Request-shaped end to - * end, which is the shape Next.js's edge/node runtime speaks. - * - * A fresh transport per request keeps this stateless and safe for - * serverless deployment. - */ -export async function POST(req: Request) { - const server = createMcpServer(); - const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: undefined }); +import { requireUser } from "@/lib/auth/service"; +import { endpoint } from "@/lib/http"; +export async function POST(req: Request) { return endpoint(async () => { + const owner = await requireUser(req); + const server = createMcpServer(owner); + const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: undefined, enableJsonResponse: true }); await server.connect(transport); - return transport.handleRequest(req); -} + try { return await transport.handleRequest(req); } + finally { await server.close(); } +}); } +export function GET() { return new Response(null, { status: 405, headers: { Allow: "POST" } }); } +export const DELETE = GET; diff --git a/app/api/runs/[id]/reconcile/route.ts b/app/api/runs/[id]/reconcile/route.ts new file mode 100644 index 0000000..4c4e3c5 --- /dev/null +++ b/app/api/runs/[id]/reconcile/route.ts @@ -0,0 +1,8 @@ +import { z } from "zod"; +import { requireUser } from "@/lib/auth/service"; +import { reconcileRun } from "@/lib/contracts/service"; +import { endpoint, body } from "@/lib/http"; +export async function POST(req: Request, ctx: { params: Promise<{ id: string }> }) { return endpoint(async () => { + const owner = await requireUser(req), input = z.object({ note: z.string().min(10).max(2000), collectorId: z.string().optional() }).parse(await body(req)); + return Response.json(await reconcileRun((await ctx.params).id, owner, input.note, input.collectorId)); +}); } diff --git a/app/api/runs/[id]/review/route.ts b/app/api/runs/[id]/review/route.ts new file mode 100644 index 0000000..1248d3f --- /dev/null +++ b/app/api/runs/[id]/review/route.ts @@ -0,0 +1,8 @@ +import { z } from "zod"; +import { requireUser } from "@/lib/auth/service"; +import { reviewRun } from "@/lib/contracts/service"; +import { endpoint, body } from "@/lib/http"; +export async function POST(req: Request, ctx: { params: Promise<{ id: string }> }) { return endpoint(async () => { + const owner = await requireUser(req), { decision } = z.object({ decision: z.enum(["accept", "reject"]) }).parse(await body(req)); + return Response.json({ run: await reviewRun((await ctx.params).id, owner, decision) }); +}); } diff --git a/app/api/runs/[id]/route.ts b/app/api/runs/[id]/route.ts new file mode 100644 index 0000000..4c1e4db --- /dev/null +++ b/app/api/runs/[id]/route.ts @@ -0,0 +1,4 @@ +import { requireUser } from "@/lib/auth/service"; +import { getRun } from "@/lib/contracts/service"; +import { endpoint } from "@/lib/http"; +export async function GET(req: Request, ctx: { params: Promise<{ id: string }> }) { return endpoint(async () => Response.json({ run: await getRun((await ctx.params).id, await requireUser(req)) })); } diff --git a/app/contracts/[id]/loading.tsx b/app/contracts/[id]/loading.tsx deleted file mode 100644 index 059527f..0000000 --- a/app/contracts/[id]/loading.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { ContractDetailSkeleton } from "@/components/dashboard/Skeletons"; - -/** Instant navigation feedback for /contracts/[id] while the DB resolves. */ -export default function ContractDetailLoading() { - return ; -} diff --git a/app/contracts/[id]/page.tsx b/app/contracts/[id]/page.tsx deleted file mode 100644 index b789204..0000000 --- a/app/contracts/[id]/page.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { notFound } from "next/navigation"; -import Link from "next/link"; -import { getContractWithHistory } from "@/lib/contracts/service"; -import type { ContractField } from "@/lib/contracts/schema"; -import { StatusBadge } from "@/components/dashboard/StatusBadge"; -import { RunButton } from "@/components/dashboard/RunButton"; -import { EventFeed } from "@/components/dashboard/EventFeed"; -import { LineageGraph } from "@/components/dashboard/LineageGraph"; -import { AppHeader } from "@/components/AppHeader"; -import { SiteFooter } from "@/components/SiteFooter"; - -/** - * /contracts/:id — the per-contract detail page. - * - * Server-rendered so the initial paint already shows current state (no - * skeleton flash); the EventFeed client component then takes over the - * live updates via SSE. One service call = one DB round trip, which - * matters a lot when the database is remote (Neon) and every query is - * network latency. - */ -export default async function ContractDetailPage({ params }: { params: Promise<{ id: string }> }) { - const { id } = await params; - - let data; - try { - data = await getContractWithHistory(id); - } catch { - notFound(); - } - const { contract, runs, events } = data; - - const fields: ContractField[] = JSON.parse(contract.schema); - const lastValidated: Record | null = runs[0]?.validated - ? JSON.parse(runs[0].validated) - : null; - - return ( -
-
- - ← All contracts - -
-
-

CONTRACT / LIVE SURFACE

-

{contract.url}

-

- id {contract.id} - {" · "} - collector {contract.collectorId ?? "—"} - {" · "} - poll every {Math.round(contract.pollIntervalMs / 1000)}s -

-
-
- - -
-
- -
-
-

Schema

CONTRACT.JSON
-
    - {fields.map((f) => ( -
  • -
    - {f.key} - {f.type} - {f.required && ( - required - )} -
    - {lastValidated && ( - - {JSON.stringify(lastValidated[f.key])} - - )} -
  • - ))} -
-
- - -
- -
- -
-
- -
- ); -} diff --git a/app/dashboard/loading.tsx b/app/dashboard/loading.tsx deleted file mode 100644 index c4b43b1..0000000 --- a/app/dashboard/loading.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { ContractListSkeleton } from "@/components/dashboard/Skeletons"; - -/** - * Instant navigation feedback for /dashboard. Without this, clicking a - * Link to a dynamic route shows the old page frozen until the server - * component resolves — with a remote DB that reads as "multiple seconds - * of nothing." A loading.tsx sibling lets the App Router swap the shell - * in immediately and stream the real content in behind it. - */ -export default function DashboardLoading() { - return ; -} diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx deleted file mode 100644 index bf25147..0000000 --- a/app/dashboard/page.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { ContractList } from "@/components/dashboard/ContractList"; -import { AppHeader } from "@/components/AppHeader"; -import { SiteFooter } from "@/components/SiteFooter"; - -export default function DashboardPage() { - return ( -
-
- -
-
-

CONTROL ROOM / 01

-

Contracts

-

Every contract you've defined, with its current health and a link into the detail view.

-
- ← Home -
-
- -
-
- -
- ); -} diff --git a/app/globals.css b/app/globals.css deleted file mode 100644 index f2dcfb1..0000000 --- a/app/globals.css +++ /dev/null @@ -1,84 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - -:root { color-scheme: light; --bg:#fafaf7; --surface:#fff; --border:#e7e4dd; --ink:#111113; --ink-dim:#6b6b70; --amber:#e8791a; --teal:#1c9c7c; --coral:#d9483d; --band-bg:#0f0f10; --band-ink:#f5f3ee; } -* { box-sizing: border-box; } -html { scroll-behavior: smooth; } -body { margin: 0; background: #fafaf7; color: #111113; font-family: var(--font-plex-sans), "IBM Plex Sans", ui-sans-serif, system-ui, sans-serif; } -::selection { background: #f6b26b; color: #111113; } -a:focus-visible,button:focus-visible { outline:2px solid #e8791a; outline-offset:2px; } - -.new-landing { --bg:#fafaf7; --surface:#fff; --border:#e7e4dd; --ink:#111113; --ink-dim:#6b6b70; --amber:#e8791a; --teal:#1c9c7c; --coral:#d9483d; --band-bg:#0f0f10; --band-ink:#f5f3ee; position:relative; overflow:hidden; background:var(--bg); color:var(--ink); } -.grain { position:fixed; z-index:20; pointer-events:none; inset:0; opacity:.035; background-image:radial-gradient(rgba(17,17,19,.35) .65px,transparent .65px); background-size:5px 5px; } -.wrap { position:relative; z-index:1; width:min(1180px,calc(100% - 80px)); margin:auto; } -.landing-screen { position:relative; isolation:isolate; min-height:100svh; overflow:hidden; border-bottom:1px solid var(--border); } -.landing-screen::before,.landing-screen::after { position:absolute; z-index:0; pointer-events:none; content:""; } -.light-screen { background:var(--bg); } -.screen-inner { min-height:100svh; padding:132px 0 90px; } -.scroll-trace { position:absolute; z-index:0; inset:0; pointer-events:none; overflow:hidden; }.scroll-trace svg { width:100%; height:100%; overflow:visible; }.scroll-trace-path { fill:none; stroke-width:3; stroke-linecap:round; stroke-linejoin:round; vector-effect:non-scaling-stroke; } - -.new-nav { position:absolute; z-index:10; top:0; right:0; left:0; width:min(1180px,calc(100% - 80px)); height:88px; margin:auto; display:flex; align-items:center; justify-content:space-between; border-bottom:1px solid var(--border); color:var(--ink); font-family:var(--font-plex-mono),"IBM Plex Mono",ui-monospace,monospace; font-size:10px; font-weight:600; letter-spacing:.08em; text-transform:uppercase; } -.new-brand,.new-brand-mark,.new-nav>div,.new-nav a { display:flex; align-items:center; } -.new-brand { gap:11px; color:inherit; text-decoration:none; } -.new-brand-mark { width:30px; height:30px; justify-content:center; color:#fff; background:var(--ink); font:500 18px/1 Georgia,serif; letter-spacing:0; } -.new-nav>div { align-self:stretch; gap:0; }.new-nav>div>a { padding:0 18px; color:inherit; text-decoration:none; transition:background .2s,color .2s; }.new-nav>div>a:hover { color:var(--ink); background:#f2eee7; }.new-nav .nav-cta { padding:0 0 0 18px; color:var(--ink); }.new-nav .nav-cta:hover { color:var(--amber); background:transparent; }.nav-cta span { margin-left:10px; color:var(--amber); font-size:16px; font-weight:400; } - -.hero-screen { background:radial-gradient(ellipse 48% 55% at 80% 45%,rgba(255,211,167,.55),transparent 72%),radial-gradient(ellipse 42% 50% at 18% 90%,rgba(218,235,225,.65),transparent 72%),var(--bg); } -.hero-screen::before { top:-260px; right:-180px; width:700px; height:700px; border:1px solid rgba(232,121,26,.18); border-radius:50%; transform:rotate(-22deg) scaleY(.52); } -.hero-screen::after { right:18%; bottom:-430px; width:700px; height:700px; border-radius:50%; background:conic-gradient(from 130deg,rgba(232,121,26,.13),rgba(28,156,124,.13),transparent 54%); transform:translate3d(0,0,0); animation:ambient-drift 22s ease-in-out infinite alternate; will-change:transform; } -.hero-orbit { position:absolute; z-index:0; top:17%; right:7%; width:440px; height:440px; border:1px solid rgba(17,17,19,.1); border-radius:50%; transform:rotate(24deg) scaleY(.5); pointer-events:none; } -.hero-glow { position:absolute; z-index:0; top:18%; right:12%; width:360px; height:360px; pointer-events:none; }.glow-ring { position:absolute; inset:0; border:1px solid rgba(232,121,26,.2); border-radius:50%; }.glow-ring-a { transform:rotate(-18deg) scaleY(.65); }.glow-ring-b { inset:10%; transform:rotate(32deg) scaleY(.42); }.glow-orb { position:absolute; top:35%; right:22%; width:18px; height:18px; border-radius:50%; background:var(--amber); box-shadow:0 0 0 12px rgba(232,121,26,.1),0 0 50px 16px rgba(232,121,26,.24); animation:orb-breathe 5s ease-in-out infinite; } -.hero-inner { display:grid; grid-template-columns:minmax(0,1fr) minmax(390px,.82fr); grid-template-rows:1fr auto; align-items:center; gap:42px 80px; } -.hero-copy-block { max-width:650px; } -.eyebrow,.mono-label { font-family:var(--font-plex-mono),"IBM Plex Mono",ui-monospace,monospace; font-size:10px; font-weight:600; letter-spacing:.12em; text-transform:uppercase; } -.eyebrow { display:flex; align-items:center; gap:10px; margin:0; color:var(--ink-dim); } -.eyebrow i { display:inline-block; width:7px; height:7px; border-radius:50%; background:var(--amber); box-shadow:0 0 0 4px rgba(232,121,26,.13); } -.hero-heading,.screen-inner h2 { margin:26px 0 0; font-family:var(--font-space-grotesk),"Space Grotesk",ui-sans-serif,sans-serif; font-weight:500; letter-spacing:-.07em; line-height:.98; } -.hero-heading { max-width:700px; font-size:clamp(2.75rem,5vw,4.5rem); } -.hero-heading em,.screen-inner h2 em { color:var(--amber); font-style:normal; } -.hero-text { max-width:505px; margin:28px 0 0; color:var(--ink-dim); font-size:1.0625rem; line-height:1.6; } -.hero-actions-new { display:flex; align-items:center; gap:25px; margin-top:32px; } -.primary-cta { display:inline-flex; align-items:center; gap:22px; padding:15px 18px; color:#fff; background:var(--ink); font-size:11px; font-weight:700; letter-spacing:.03em; text-decoration:none; transition:transform .2s,background .2s,box-shadow .2s; }.primary-cta:hover { background:#2a2a2d; box-shadow:0 12px 25px rgba(17,17,19,.16); transform:translateY(-3px); }.primary-cta span { color:#f6b26b; font-size:17px; font-weight:400; } -.quiet-link { color:var(--ink-dim); font-size:11px; font-weight:650; text-decoration:none; }.quiet-link:hover { color:var(--amber); }.quiet-link span { margin-left:9px; color:var(--amber); font-size:15px; } -.hero-panel { align-self:center; justify-self:end; width:min(100%,480px); } -.healing-diff { position:relative; overflow:hidden; border:1px solid #d5d0c7; background:rgba(255,255,255,.88); box-shadow:0 24px 60px rgba(17,17,19,.1); } -.diff-top,.diff-foot { display:flex; align-items:center; justify-content:space-between; padding:16px 18px; border-bottom:1px solid var(--border); }.diff-top { color:var(--ink); }.diff-contract,.diff-foot { color:var(--ink-dim); font:10px var(--font-plex-mono),"IBM Plex Mono",ui-monospace,monospace; }.diff-status-row { display:flex; align-items:center; gap:9px; padding:18px; border-bottom:1px solid var(--border); color:var(--ink-dim); font:11px var(--font-plex-mono),"IBM Plex Mono",ui-monospace,monospace; }.diff-status-row strong { color:var(--ink); font-size:11px; letter-spacing:.08em; }.status-mark { width:7px; height:7px; border-radius:50%; background:var(--teal); box-shadow:0 0 0 4px rgba(28,156,124,.14); }.diff-broken .status-mark { background:var(--coral); box-shadow:0 0 0 4px rgba(217,72,61,.13); }.diff-healing .status-mark { background:var(--amber); box-shadow:0 0 0 4px rgba(232,121,26,.14); }.diff-code { display:flex; flex-direction:column; gap:9px; padding:25px 24px 22px; color:#303035; font:14px/1.6 var(--font-plex-mono),"IBM Plex Mono",ui-monospace,monospace; }.diff-code b { color:#8c4e21; font-weight:500; }.code-muted { color:#aaa6a0; }.code-alert { color:var(--coral); text-decoration:line-through; text-decoration-thickness:1px; }.diff-repair { display:flex; align-items:center; gap:11px; min-height:48px; margin:0 18px 18px; padding:0 13px; border:1px solid var(--border); background:#faf8f3; color:var(--ink-dim); font:10px var(--font-plex-mono),"IBM Plex Mono",ui-monospace,monospace; }.repair-line { width:7px; height:7px; border-radius:50%; background:var(--teal); }.diff-healing .diff-repair { border-color:rgba(232,121,26,.55); background:#fff6e9; color:#8c4e21; }.diff-healing .repair-line { background:var(--amber); animation:repair-pulse 1s ease-in-out infinite alternate; }.repair-time { margin-left:auto; color:var(--ink); }.diff-foot { border-top:1px solid var(--border); border-bottom:0; font-size:10px; }.diff-broken .diff-foot span:last-child { color:var(--coral); }.diff-healing .diff-foot span:last-child { color:var(--amber); }.diff-restored .diff-foot span:last-child { color:var(--teal); } -.hero-bottom { grid-column:1 / -1; display:flex; align-items:center; gap:12px; color:var(--ink-dim); font:10px var(--font-plex-mono),"IBM Plex Mono",ui-monospace,monospace; letter-spacing:.1em; }.hero-bottom span:last-child { margin-left:auto; }.scroll-stroke { width:46px; height:1px; background:var(--amber); } - -.failure-screen { background:radial-gradient(ellipse 45% 55% at 95% 15%,rgba(232,121,26,.14),transparent 70%),var(--bg); }.failure-screen::before { top:10%; right:-160px; width:480px; height:480px; border:1px solid rgba(17,17,19,.08); border-radius:50%; transform:rotate(18deg) scaleY(.55); }.section-top { display:flex; align-items:flex-start; justify-content:space-between; padding-bottom:28px; border-bottom:1px solid var(--border); }.section-note { margin:0; color:var(--ink-dim); font-size:13px; line-height:1.45; text-align:right; }.section-heading-row,.pipeline-heading,.foundation-heading { display:grid; grid-template-columns:minmax(0,1.3fr) minmax(220px,.7fr); gap:70px; align-items:end; }.screen-inner h2 { font-size:clamp(2rem,4vw,3.5rem); }.section-heading-row>div>p,.pipeline-heading>div>p,.foundation-heading>div>p { max-width:350px; margin:0 0 4px; color:var(--ink-dim); font-size:1.0625rem; line-height:1.6; } -.failure-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:20px; margin-top:78px; }.failure-card { position:relative; min-height:310px; padding:24px; border:1px solid var(--border); background:rgba(255,255,255,.72); transition:background .2s,transform .2s,border-color .2s; }.failure-card:hover { border-color:#d8a36d; background:#fff; transform:translateY(-5px); }.card-kicker { display:flex; align-items:center; justify-content:space-between; color:var(--amber); font:12px var(--font-plex-mono),"IBM Plex Mono",ui-monospace,monospace; }.mono-label { color:var(--ink-dim); }.failure-card h3,.integration-card h3,.foundation-card h3 { margin:58px 0 0; font-family:var(--font-space-grotesk),"Space Grotesk",sans-serif; font-size:clamp(1.65rem,3vw,2.2rem); font-weight:500; letter-spacing:-.05em; }.failure-card>p { max-width:360px; margin:13px 0 0; color:var(--ink-dim); font-size:15px; line-height:1.55; }.failure-card pre { margin:26px 0 0; color:#69656a; font:12px/1.65 var(--font-plex-mono),"IBM Plex Mono",ui-monospace,monospace; white-space:pre-wrap; }.card-arrow { position:absolute; right:24px; bottom:20px; color:var(--amber); font-size:20px; } - -.pipeline-screen { color:var(--band-ink); background:var(--band-bg); }.pipeline-screen::before { top:-280px; right:-150px; width:650px; height:650px; border-radius:50%; background:radial-gradient(circle at 40% 40%,rgba(232,121,26,.3),transparent 68%); }.pipeline-screen::after { bottom:-320px; left:-180px; width:620px; height:620px; border-radius:50%; background:conic-gradient(from 120deg,rgba(28,156,124,.18),rgba(232,121,26,.12),transparent 54%); transform:translate3d(0,0,0); animation:ambient-drift-reverse 24s ease-in-out infinite alternate; will-change:transform; }.pipeline-screen .section-top { border-color:rgba(245,243,238,.2); }.pipeline-screen .eyebrow,.pipeline-screen .section-note { color:#aaa7a0; }.pipeline-screen .eyebrow i { background:var(--amber); }.pipeline-screen h2 { color:var(--band-ink); }.pipeline-screen h2 em { color:#f3a04d; }.pipeline-heading { margin-top:30px; }.pipeline-heading>div>p { color:#aaa7a0; }.pipeline-list { margin-top:70px; border-top:1px solid rgba(245,243,238,.2); }.pipeline-row { display:grid; grid-template-columns:105px 1fr 1.1fr 20px; gap:18px; align-items:center; min-height:84px; border-bottom:1px solid rgba(245,243,238,.2); }.pipeline-row>span { color:#f3a04d; font:12px var(--font-plex-mono),"IBM Plex Mono",ui-monospace,monospace; }.pipeline-row h3 { margin:0; font-family:var(--font-space-grotesk),"Space Grotesk",sans-serif; font-size:26px; font-weight:500; letter-spacing:-.04em; }.pipeline-row p { max-width:300px; margin:0; color:#aaa7a0; font-size:14px; line-height:1.5; }.pipeline-row>b { color:#f3a04d; font-size:19px; font-weight:400; } - -.integrations-screen { background:radial-gradient(ellipse 48% 65% at 7% 100%,rgba(28,156,124,.13),transparent 70%),radial-gradient(ellipse 45% 55% at 92% 0%,rgba(232,121,26,.16),transparent 70%),var(--bg); }.integrations-screen::before { top:15%; left:42%; width:300px; height:300px; border:1px solid rgba(28,156,124,.14); border-radius:50%; }.integration-grid { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:20px; margin-top:78px; }.integration-card { position:relative; min-height:355px; padding:24px; border:1px solid var(--border); background:rgba(255,255,255,.76); transition:background .2s,transform .2s,border-color .2s; }.integration-card:hover { border-color:#d8a36d; background:#fff; transform:translateY(-5px); }.integration-card h3 { margin-top:55px; font-family:var(--font-plex-mono),"IBM Plex Mono",ui-monospace,monospace; font-size:16px; letter-spacing:-.02em; }.integration-card pre { margin:28px 0 0; color:#66636a; font:12px/1.7 var(--font-plex-mono),"IBM Plex Mono",ui-monospace,monospace; white-space:pre-wrap; }.integration-card>a { position:absolute; right:24px; bottom:21px; color:var(--ink); font:11px var(--font-plex-mono),"IBM Plex Mono",ui-monospace,monospace; text-decoration:none; }.integration-card>a:hover { color:var(--amber); }.integration-card>a span { margin-left:8px; color:var(--amber); font-size:16px; } - -.foundation-screen { background:radial-gradient(ellipse 55% 65% at 87% 15%,rgba(232,121,26,.16),transparent 70%),radial-gradient(ellipse 50% 50% at 4% 90%,rgba(28,156,124,.13),transparent 70%),var(--bg); }.foundation-screen::before { top:-230px; right:35%; width:500px; height:500px; border:1px solid rgba(17,17,19,.08); border-radius:50%; transform:rotate(-24deg) scaleY(.52); }.foundation-heading { margin-top:30px; }.foundation-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:20px; margin-top:64px; }.foundation-card { min-height:180px; padding:24px; border-top:2px solid var(--ink); }.foundation-card h3 { margin:25px 0 0; font-size:28px; }.foundation-card p { max-width:380px; margin:10px 0 0; color:var(--ink-dim); font-size:15px; line-height:1.55; }.foundation-cta { display:flex; align-items:center; gap:28px; margin-top:58px; }.foundation-cta p { max-width:340px; margin:0; color:var(--ink-dim); font-size:13px; line-height:1.5; }.site-footer { position:absolute; right:0; bottom:30px; left:0; display:flex; justify-content:space-between; color:var(--ink-dim); font:10px var(--font-plex-mono),"IBM Plex Mono",ui-monospace,monospace; letter-spacing:.08em; } - -.global-footer { position:relative; z-index:1; width:min(1180px,calc(100% - 80px)); margin:0 auto; display:grid; grid-template-columns:1fr 2fr 1fr; align-items:center; gap:24px; padding:25px 0 28px; border-top:1px solid var(--border); color:var(--ink-dim); font:10px var(--font-plex-mono),"IBM Plex Mono",ui-monospace,monospace; letter-spacing:.06em; text-transform:uppercase; }.global-footer-absolute { position:absolute; right:0; bottom:28px; left:0; }.footer-brand,.footer-mark,.footer-links,.footer-meta { display:flex; align-items:center; }.footer-brand { gap:10px; color:var(--ink); font-weight:600; letter-spacing:.12em; }.footer-mark { width:24px; height:24px; justify-content:center; color:#fff; background:var(--ink); font:500 15px/1 Georgia,serif; letter-spacing:0; }.footer-links { justify-content:center; gap:21px; }.footer-links a,.footer-meta a { color:inherit; text-decoration:none; transition:color .2s; }.footer-links a:hover,.footer-meta a:hover { color:var(--amber); }.footer-meta { justify-content:flex-end; gap:18px; }.footer-meta span { color:var(--amber); font-size:15px; text-transform:none; } -.app-shell { min-height:100svh; background:radial-gradient(ellipse 55% 50% at 90% 0%,rgba(232,121,26,.14),transparent 70%),radial-gradient(ellipse 48% 50% at 0% 100%,rgba(28,156,124,.11),transparent 72%),var(--bg); color:var(--ink); }.app-frame { width:min(980px,calc(100% - 80px)); min-height:calc(100svh - 110px); margin:auto; padding-bottom:80px; }.app-header { height:88px; display:flex; align-items:center; justify-content:space-between; border-bottom:1px solid var(--border); }.app-brand,.app-brand-mark,.app-nav-links,.app-nav-links a { display:flex; align-items:center; }.app-brand { gap:10px; color:var(--ink); font:600 10px var(--font-plex-mono),"IBM Plex Mono",ui-monospace,monospace; letter-spacing:.12em; text-decoration:none; }.app-brand-mark { width:30px; height:30px; justify-content:center; color:#fff; background:var(--ink); font:500 18px/1 Georgia,serif; letter-spacing:0; }.app-nav-links { height:100%; gap:24px; font:10px var(--font-plex-mono),"IBM Plex Mono",ui-monospace,monospace; text-transform:uppercase; letter-spacing:.08em; }.app-nav-links a { height:100%; color:var(--ink-dim); text-decoration:none; transition:color .2s; }.app-nav-links a:hover,.app-nav-links .app-nav-active { color:var(--amber); }.app-nav-active span { margin-left:8px; color:var(--amber); font-size:15px; }.app-page-heading { display:flex; align-items:flex-end; justify-content:space-between; gap:24px; padding:88px 0 38px; border-bottom:1px solid var(--border); }.app-eyebrow { display:flex; align-items:center; gap:9px; margin:0; color:var(--ink-dim); font:10px var(--font-plex-mono),"IBM Plex Mono",ui-monospace,monospace; letter-spacing:.12em; text-transform:uppercase; }.app-eyebrow i { width:7px; height:7px; border-radius:50%; background:var(--amber); box-shadow:0 0 0 4px rgba(232,121,26,.13); }.app-page-heading h1 { margin:20px 0 0; font:500 clamp(2.2rem,5vw,3.5rem)/.98 var(--font-space-grotesk),"Space Grotesk",sans-serif; letter-spacing:-.07em; }.app-description { max-width:500px; margin:14px 0 0; color:var(--ink-dim); font-size:15px; line-height:1.6; }.app-back-link { color:var(--ink-dim); font:11px var(--font-plex-mono),"IBM Plex Mono",ui-monospace,monospace; text-decoration:none; }.app-back-link:hover { color:var(--amber); }.app-panel-list { margin-top:34px; }.app-loading { color:var(--ink-dim); font-size:14px; }.app-empty-state { padding:42px 24px; border:1px dashed #d4cec3; text-align:center; }.app-empty-state p { margin:0; color:var(--ink-dim); font-size:14px; }.app-empty-state p + p { margin-top:6px; font-size:13px; }.contract-list { overflow:hidden; margin:0; padding:0; list-style:none; border:1px solid var(--border); background:rgba(255,255,255,.72); }.contract-list li + li { border-top:1px solid var(--border); }.contract-row { display:flex; align-items:center; justify-content:space-between; gap:20px; padding:18px 20px; color:inherit; text-decoration:none; transition:background .2s,padding .2s; }.contract-row:hover { padding-left:24px; background:#fff7ed; }.contract-url { margin:0; color:var(--ink); font-size:15px; font-weight:600; }.contract-id { margin:5px 0 0; color:var(--ink-dim); font:11px var(--font-plex-mono),"IBM Plex Mono",ui-monospace,monospace; } -.status-badge { display:inline-flex; align-items:center; gap:7px; padding:5px 9px; border:1px solid; border-radius:999px; font:600 10px var(--font-plex-mono),"IBM Plex Mono",ui-monospace,monospace; text-transform:uppercase; letter-spacing:.05em; }.status-dot { width:6px; height:6px; border-radius:50%; background:currentColor; }.status-healthy { color:var(--teal); border-color:rgba(28,156,124,.3); background:rgba(28,156,124,.08); }.status-degraded { color:#a86418; border-color:rgba(232,121,26,.35); background:rgba(232,121,26,.1); }.status-healing { color:#b76b17; border-color:rgba(232,121,26,.45); background:#fff3df; }.status-broken { color:var(--coral); border-color:rgba(217,72,61,.3); background:rgba(217,72,61,.08); } -@keyframes ambient-drift { from { transform:translate3d(-2%,1%,0) rotate(0deg); } to { transform:translate3d(3%,-2%,0) rotate(8deg); } } -.app-detail-frame { padding-bottom:72px; }.app-detail-frame>.app-back-link { display:inline-block; margin-top:42px; }.detail-header { display:flex; align-items:flex-end; justify-content:space-between; gap:28px; padding:27px 0 36px; border-bottom:1px solid var(--border); }.detail-header h1 { max-width:680px; margin:18px 0 0; overflow-wrap:anywhere; font:500 clamp(1.6rem,4vw,2.7rem)/1.04 var(--font-space-grotesk),"Space Grotesk",sans-serif; letter-spacing:-.06em; }.detail-meta { margin:12px 0 0; color:var(--ink-dim); font:11px/1.7 var(--font-plex-mono),"IBM Plex Mono",ui-monospace,monospace; }.detail-meta code { padding:2px 5px; color:var(--ink); background:#f0ece5; }.detail-actions { display:flex; flex-direction:column; align-items:flex-end; gap:11px; }.detail-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:20px; margin-top:34px; }.data-panel { overflow:hidden; border:1px solid var(--border); background:rgba(255,255,255,.76); }.data-panel-heading { display:flex; align-items:center; justify-content:space-between; padding:15px 17px; border-bottom:1px solid var(--border); }.data-panel-heading h3 { margin:0; font:600 12px var(--font-plex-sans),"IBM Plex Sans",sans-serif; }.data-panel-heading .mono-label { font-size:9px; }.data-list,.event-list,.history-list { margin:0; padding:0; list-style:none; }.data-list>li { display:flex; align-items:center; justify-content:space-between; gap:15px; padding:13px 17px; font-size:13px; }.data-list>li + li,.event-row + .event-row,.history-row + .history-row { border-top:1px solid #efebe4; }.data-key { color:var(--ink); font:600 13px var(--font-plex-mono),"IBM Plex Mono",ui-monospace,monospace; }.data-type,.data-required { margin-left:9px; color:var(--ink-dim); font:10px var(--font-plex-mono),"IBM Plex Mono",ui-monospace,monospace; }.data-required { color:var(--coral); text-transform:uppercase; }.data-value { color:var(--ink); font:11px var(--font-plex-mono),"IBM Plex Mono",ui-monospace,monospace; }.event-panel { min-height:100%; }.event-connection { display:flex; align-items:center; gap:7px; color:var(--ink-dim); font:10px var(--font-plex-mono),"IBM Plex Mono",ui-monospace,monospace; }.event-connection .status-dot { background:#a7a39b; }.event-connection.connected { color:var(--teal); }.event-list { max-height:320px; overflow-y:auto; }.event-empty,.event-row { padding:14px 17px; font-size:12px; }.event-empty { color:var(--ink-dim); }.event-empty strong { color:var(--ink); }.event-row-top { display:flex; align-items:center; gap:8px; }.event-type { padding:4px 6px; border:1px solid; font:600 9px var(--font-plex-mono),"IBM Plex Mono",ui-monospace,monospace; letter-spacing:.04em; }.event-violated,.event-escalated { color:var(--coral); border-color:rgba(217,72,61,.3); background:rgba(217,72,61,.08); }.event-healing,.event-changed { color:#a86418; border-color:rgba(232,121,26,.35); background:rgba(232,121,26,.1); }.event-healed { color:var(--teal); border-color:rgba(28,156,124,.3); background:rgba(28,156,124,.08); }.event-default { color:var(--ink-dim); border-color:var(--border); background:#f5f2ec; }.event-attempt,.event-time { color:var(--ink-dim); font:10px var(--font-plex-mono),"IBM Plex Mono",ui-monospace,monospace; }.event-time { margin-left:auto; color:#9c9890; }.event-summary,.event-reason { margin:7px 0 0; color:#504e50; font-size:12px; line-height:1.45; }.event-reason { color:var(--coral); }.event-confidence { margin-left:8px; color:var(--ink-dim); }.detail-history { margin-top:20px; }.history-row { display:flex; align-items:center; justify-content:space-between; gap:15px; padding:15px 17px; }.history-main { display:flex; align-items:center; gap:12px; }.history-date,.history-events { color:var(--ink-dim); font:10px var(--font-plex-mono),"IBM Plex Mono",ui-monospace,monospace; }.history-events { color:#9c9890; }.history-empty { padding:15px 17px; color:var(--ink-dim); font-size:13px; }.run-control { display:flex; align-items:center; gap:12px; }.run-button { padding:9px 13px; border:0; border-radius:3px; color:#fff; background:var(--ink); font:600 11px var(--font-plex-sans),"IBM Plex Sans",sans-serif; cursor:pointer; transition:background .2s,transform .2s; }.run-button:hover { background:#2a2a2d; transform:translateY(-2px); }.run-button:disabled { cursor:wait; opacity:.55; }.run-message { color:var(--ink-dim); font:11px var(--font-plex-mono),"IBM Plex Mono",ui-monospace,monospace; }.run-error { color:var(--coral); } -@keyframes ambient-drift-reverse { from { transform:translate3d(2%,-1%,0) rotate(0deg); } to { transform:translate3d(-3%,2%,0) rotate(-8deg); } } -@keyframes orb-breathe { 0%,100% { transform:scale(1); } 50% { transform:scale(1.18); } } -@keyframes skeleton-shift { from { background-position:200% 0; } to { background-position:-200% 0; } } -@keyframes repair-pulse { from { box-shadow:0 0 0 0 rgba(232,121,26,.3); } to { box-shadow:0 0 0 6px rgba(232,121,26,0); } } - -.skeleton { background:linear-gradient(90deg,#ece8e0 25%,#f8f5ef 50%,#ece8e0 75%); background-size:200% 100%; animation:skeleton-shift 1.5s ease-in-out infinite; }.app-loading-frame { padding-top:40px; }.skeleton-title { width:160px; height:30px; }.skeleton-description { width:290px; height:14px; margin-top:12px; }.skeleton-list { margin:34px 0 0; padding:0; list-style:none; border:1px solid var(--border); }.skeleton-list li { display:flex; align-items:center; justify-content:space-between; padding:18px 20px; }.skeleton-list li + li { border-top:1px solid var(--border); }.skeleton-row-title { width:220px; height:14px; }.skeleton-row-id { width:125px; height:10px; margin-top:9px; }.skeleton-badge { width:75px; height:22px; border-radius:999px; }.skeleton-back { width:100px; height:11px; }.skeleton-detail-header { display:flex; justify-content:space-between; margin-top:28px; }.skeleton-detail-title { width:370px; height:30px; }.skeleton-detail-meta { width:260px; height:11px; margin-top:12px; }.skeleton-detail-header>div:last-child { display:flex; flex-direction:column; align-items:flex-end; gap:10px; }.skeleton-run-button { width:90px; height:31px; border-radius:3px; }.skeleton-detail-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:20px; margin-top:34px; }.skeleton-panel { padding:20px 17px; border:1px solid var(--border); }.skeleton-line { display:flex; justify-content:space-between; }.skeleton-line + .skeleton-line { margin-top:17px; }.skeleton-field { width:110px; height:13px; }.skeleton-value { width:58px; height:13px; }.skeleton-event-panel,.skeleton-history-panel { border:1px solid var(--border); }.skeleton-event-panel { min-height:260px; }.skeleton-history-panel { height:150px; margin-top:20px; } -@media (max-width:760px) { - .wrap,.new-nav { width:calc(100% - 40px); } - .new-nav { height:72px; }.new-nav>div>a:not(.nav-cta) { display:none; }.new-nav .nav-cta { padding-left:12px; }.new-brand span:last-child { font-size:9px; } - .landing-screen { min-height:100svh; }.screen-inner { min-height:100svh; padding:105px 0 72px; } - .hero-inner { display:flex; flex-direction:column; align-items:stretch; justify-content:center; gap:42px; }.hero-copy-block { max-width:none; }.hero-heading { font-size:clamp(2.75rem,14vw,4rem); }.hero-text { font-size:15px; }.hero-actions-new { align-items:flex-start; flex-direction:column; gap:18px; }.hero-panel { width:100%; }.hero-bottom { display:none; } - .section-top { padding-bottom:22px; }.section-note { font-size:10px; }.section-heading-row,.pipeline-heading,.foundation-heading { display:block; }.section-heading-row>div>p,.pipeline-heading>div>p,.foundation-heading>div>p { margin-top:23px; font-size:15px; }.screen-inner h2 { font-size:clamp(2rem,10vw,3rem); } - .failure-grid,.integration-grid,.foundation-grid { grid-template-columns:1fr; margin-top:48px; }.failure-card { min-height:290px; }.integration-card { min-height:330px; }.failure-card h3 { margin-top:50px; } - .pipeline-list { margin-top:48px; }.pipeline-row { grid-template-columns:42px 1fr 16px; gap:10px; min-height:82px; }.pipeline-row h3 { font-size:22px; }.pipeline-row p { display:none; } - .foundation-cta { align-items:flex-start; flex-direction:column; gap:18px; margin-top:36px; }.site-footer { position:static; gap:12px; margin-top:72px; font-size:8px; }.site-footer span:nth-child(2) { display:none; } - .global-footer { width:calc(100% - 40px); grid-template-columns:1fr; gap:18px; padding:22px 0; }.global-footer-absolute { position:static; margin-top:72px; }.footer-links { justify-content:flex-start; flex-wrap:wrap; gap:12px 18px; }.footer-meta { justify-content:space-between; }.app-frame { width:calc(100% - 40px); padding-bottom:45px; }.app-header { height:72px; }.app-nav-links { gap:12px; font-size:9px; }.app-nav-links a:not(.app-nav-active) { display:none; }.app-page-heading { align-items:flex-start; flex-direction:column; padding:62px 0 30px; }.detail-header { align-items:flex-start; flex-direction:column; padding-bottom:28px; }.detail-actions { align-items:flex-start; }.detail-grid,.skeleton-detail-grid { grid-template-columns:1fr; }.app-detail-frame>.app-back-link { margin-top:30px; }.data-list>li { align-items:flex-start; flex-direction:column; gap:7px; }.history-row { align-items:flex-start; flex-direction:column; gap:9px; }.skeleton-detail-header { align-items:flex-start; flex-direction:column; gap:18px; }.skeleton-detail-header>div:last-child { align-items:flex-start; } -} - -@media (prefers-reduced-motion:reduce) { - html { scroll-behavior:auto; } - *,*::before,*::after { animation-duration:.01ms!important; animation-iteration-count:1!important; transition-duration:.01ms!important; } -} diff --git a/app/layout.tsx b/app/layout.tsx deleted file mode 100644 index 997f2cd..0000000 --- a/app/layout.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import type { Metadata } from "next"; -import { IBM_Plex_Mono, IBM_Plex_Sans, Space_Grotesk } from "next/font/google"; -import "./globals.css"; - -const spaceGrotesk = Space_Grotesk({ - subsets: ["latin"], - display: "swap", - variable: "--font-space-grotesk", -}); - -const plexSans = IBM_Plex_Sans({ - weight: ["400", "500", "600", "700"], - subsets: ["latin"], - display: "swap", - variable: "--font-plex-sans", -}); - -const plexMono = IBM_Plex_Mono({ - weight: ["400", "500", "600"], - subsets: ["latin"], - display: "swap", - variable: "--font-plex-mono", -}); - -export const metadata: Metadata = { - title: "Lippy", - description: "Websites change. Your API shouldn't.", -}; - -/** - * Root layout. Intentionally minimal — the dashboard and contract detail - * pages own their own visual structure. This exists so the App Router has - * a single root for `html`/`body` and a single import of globals.css. - */ -export default function RootLayout({ children }: { children: React.ReactNode }) { - return ( - - {children} - - ); -} diff --git a/app/page.tsx b/app/page.tsx deleted file mode 100644 index 81fa590..0000000 --- a/app/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { LandingExperience } from "@/components/landing/LandingExperience"; - -export default function LandingPage() { - return ; -} diff --git a/app/route.ts b/app/route.ts new file mode 100644 index 0000000..612cc61 --- /dev/null +++ b/app/route.ts @@ -0,0 +1,3 @@ +export function GET() { + return Response.json({ name: "Lippy Agent", service: "Self-healing extraction backend", api: "/api/contracts", mcp: "/api/mcp", login: "/api/auth/google" }); +} diff --git a/bun.lock b/bun.lock deleted file mode 100644 index c938c81..0000000 --- a/bun.lock +++ /dev/null @@ -1,1293 +0,0 @@ -{ - "lockfileVersion": 1, - "configVersion": 0, - "workspaces": { - "": { - "name": "web-contracts", - "dependencies": { - "@modelcontextprotocol/sdk": "^1.12.0", - "@prisma/client": "^6.5.0", - "next": "15.2.0", - "react": "19.0.0", - "react-dom": "19.0.0", - "zod": "^3.24.0", - }, - "devDependencies": { - "@types/node": "^22.10.0", - "@types/react": "^19.0.0", - "@types/react-dom": "^19.0.0", - "@typescript-eslint/eslint-plugin": "^8.20.0", - "@typescript-eslint/parser": "^8.20.0", - "autoprefixer": "^10.4.20", - "eslint": "^9.18.0", - "eslint-config-next": "15.2.0", - "postcss": "^8.4.49", - "prisma": "^6.5.0", - "tailwindcss": "^3.4.17", - "typescript": "^5.7.3", - "vitest": "^3.0.0", - }, - }, - }, - "packages": { - "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], - - "@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], - - "@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="], - - "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], - - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ=="], - - "@esbuild/android-arm": ["@esbuild/android-arm@0.28.2", "", { "os": "android", "cpu": "arm" }, "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg=="], - - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.2", "", { "os": "android", "cpu": "arm64" }, "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A=="], - - "@esbuild/android-x64": ["@esbuild/android-x64@0.28.2", "", { "os": "android", "cpu": "x64" }, "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q=="], - - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw=="], - - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw=="], - - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw=="], - - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg=="], - - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.2", "", { "os": "linux", "cpu": "arm" }, "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w=="], - - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug=="], - - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ=="], - - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ=="], - - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA=="], - - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ=="], - - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA=="], - - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg=="], - - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.2", "", { "os": "linux", "cpu": "x64" }, "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ=="], - - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw=="], - - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.2", "", { "os": "none", "cpu": "x64" }, "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw=="], - - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ=="], - - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw=="], - - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q=="], - - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g=="], - - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ=="], - - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA=="], - - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g=="], - - "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.10.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg=="], - - "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], - - "@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="], - - "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], - - "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], - - "@eslint/eslintrc": ["@eslint/eslintrc@3.3.6", "", { "dependencies": { "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.3.0", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" } }, "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA=="], - - "@eslint/js": ["@eslint/js@9.39.5", "", {}, "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A=="], - - "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], - - "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], - - "@hono/node-server": ["@hono/node-server@2.1.1", "", { "peerDependencies": { "hono": "^4" } }, "sha512-ELuehkj5VCBdgEw9zs+ivkKwyzzUCSQuE96YmiPvn1ECBoZCczbFXJLeEGMTYjphP6gydh4pHMqEYPVMYUVgQg=="], - - "@humanfs/core": ["@humanfs/core@0.19.2", "", { "dependencies": { "@humanfs/types": "^0.15.0" } }, "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA=="], - - "@humanfs/node": ["@humanfs/node@0.16.8", "", { "dependencies": { "@humanfs/core": "^0.19.2", "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ=="], - - "@humanfs/types": ["@humanfs/types@0.15.0", "", {}, "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q=="], - - "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], - - "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], - - "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="], - - "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="], - - "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg=="], - - "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ=="], - - "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.0.5", "", { "os": "linux", "cpu": "arm" }, "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g=="], - - "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA=="], - - "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.0.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA=="], - - "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw=="], - - "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA=="], - - "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw=="], - - "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.0.5" }, "os": "linux", "cpu": "arm" }, "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ=="], - - "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA=="], - - "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.0.4" }, "os": "linux", "cpu": "s390x" }, "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q=="], - - "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA=="], - - "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g=="], - - "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw=="], - - "@img/sharp-wasm32": ["@img/sharp-wasm32@0.33.5", "", { "dependencies": { "@emnapi/runtime": "^1.2.0" }, "cpu": "none" }, "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg=="], - - "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.33.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ=="], - - "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="], - - "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], - - "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], - - "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], - - "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - - "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.30.0", "", { "dependencies": { "@hono/node-server": "^1.19.9 || ^2.0.5", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA=="], - - "@napi-rs/lzma-linux-x64-gnu": ["@napi-rs/lzma-linux-x64-gnu@1.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ=="], - - "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.2.3", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, "sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q=="], - - "@next/env": ["@next/env@15.2.0", "", {}, "sha512-eMgJu1RBXxxqqnuRJQh5RozhskoNUDHBFybvi+Z+yK9qzKeG7dadhv/Vp1YooSZmCnegf7JxWuapV77necLZNA=="], - - "@next/eslint-plugin-next": ["@next/eslint-plugin-next@15.2.0", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-jHFUG2OwmAuOASqq253RAEG/5BYcPHn27p1NoWZDCf4OdvdK0yRYWX92YKkL+Mk2s+GyJrmd/GATlL5b2IySpw=="], - - "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.2.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-rlp22GZwNJjFCyL7h5wz9vtpBVuCt3ZYjFWpEPBGzG712/uL1bbSkS675rVAUCRZ4hjoTJ26Q7IKhr5DfJrHDA=="], - - "@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.2.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-DiU85EqSHogCz80+sgsx90/ecygfCSGl5P3b4XDRVZpgujBm5lp4ts7YaHru7eVTyZMjHInzKr+w0/7+qDrvMA=="], - - "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.2.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-VnpoMaGukiNWVxeqKHwi8MN47yKGyki5q+7ql/7p/3ifuU2341i/gDwGK1rivk0pVYbdv5D8z63uu9yMw0QhpQ=="], - - "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.2.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-ka97/ssYE5nPH4Qs+8bd8RlYeNeUVBhcnsNUmFM6VWEob4jfN9FTr0NBhXVi1XEJpj3cMfgSRW+LdE3SUZbPrw=="], - - "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.2.0", "", { "os": "linux", "cpu": "x64" }, "sha512-zY1JduE4B3q0k2ZCE+DAF/1efjTXUsKP+VXRtrt/rJCTgDlUyyryx7aOgYXNc1d8gobys/Lof9P9ze8IyRDn7Q=="], - - "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.2.0", "", { "os": "linux", "cpu": "x64" }, "sha512-QqvLZpurBD46RhaVaVBepkVQzh8xtlUN00RlG4Iq1sBheNugamUNPuZEH1r9X1YGQo1KqAe1iiShF0acva3jHQ=="], - - "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.2.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-ODZ0r9WMyylTHAN6pLtvUtQlGXBL9voljv6ujSlcsjOxhtXPI1Ag6AhZK0SE8hEpR1374WZZ5w33ChpJd5fsjw=="], - - "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.2.0", "", { "os": "win32", "cpu": "x64" }, "sha512-8+4Z3Z7xa13NdUuUAcpVNA6o76lNPniBd9Xbo02bwXQXnZgFvEopwY2at5+z7yHl47X9qbZpvwatZ2BRo3EdZw=="], - - "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], - - "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], - - "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], - - "@nolyfill/is-core-module": ["@nolyfill/is-core-module@1.0.39", "", {}, "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA=="], - - "@prisma/client": ["@prisma/client@6.19.3", "", { "peerDependencies": { "prisma": "*", "typescript": ">=5.1.0" } }, "sha512-mKq3jQFhjvko5LTJFHGilsuQs+W+T3Gm451NzuTDGQxwCzwXHYnIu2zGkRoW+Exq3Rob7yp2MfzSrdIiZVhrBg=="], - - "@prisma/config": ["@prisma/config@6.19.3", "", { "dependencies": { "c12": "3.1.0", "deepmerge-ts": "7.1.5", "effect": "3.21.0", "empathic": "2.0.0" } }, "sha512-CBPT44BjlQxEt8kiMEauji2WHTDoVBOKl7UlewXmUgBPnr/oPRZC3psci5chJnYmH0ivEIog2OU9PGWoki3DLQ=="], - - "@prisma/debug": ["@prisma/debug@6.19.3", "", {}, "sha512-ljkJ+SgpXNktLG0Q/n4JGYCkKf0f8oYLyjImS2I8e2q2WCfdRRtWER062ZV/ixaNP2M2VKlWXVJiGzZaUgbKZw=="], - - "@prisma/engines": ["@prisma/engines@6.19.3", "", { "dependencies": { "@prisma/debug": "6.19.3", "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", "@prisma/fetch-engine": "6.19.3", "@prisma/get-platform": "6.19.3" } }, "sha512-RSYxtlYFl5pJ8ZePgMv0lZ9IzVCOdTPOegrs2qcbAEFrBI1G33h6wyC9kjQvo0DnYEhEVY0X4LsuFHXLKQk88g=="], - - "@prisma/engines-version": ["@prisma/engines-version@7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", "", {}, "sha512-03bgb1VD5gvuumNf+7fVGBzfpJPjmqV423l/WxsWk2cNQ42JD0/SsFBPhN6z8iAvdHs07/7ei77SKu7aZfq8bA=="], - - "@prisma/fetch-engine": ["@prisma/fetch-engine@6.19.3", "", { "dependencies": { "@prisma/debug": "6.19.3", "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", "@prisma/get-platform": "6.19.3" } }, "sha512-tKtl/qco9Nt7LU5iKhpultD8O4vMCZcU2CHjNTnRrL1QvSUr5W/GcyFPjNL87GtRrwBc7ubXXD9xy4EvLvt8JA=="], - - "@prisma/get-platform": ["@prisma/get-platform@6.19.3", "", { "dependencies": { "@prisma/debug": "6.19.3" } }, "sha512-xFj1VcJ1N3MKooOQAGO0W5tsd0W2QzIvW7DD7c/8H14Zmp4jseeWAITm+w2LLoLrlhoHdPPh0NMZ8mfL6puoHA=="], - - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.62.5", "", { "os": "android", "cpu": "arm" }, "sha512-jfkGfTwhQpsiSckPF8r9bU3pn3vyd72NlWaO+TgEO6WPSDnUhXzrNYCHBMOYj0ACaUgjm6eERLF+XV9a6RstoA=="], - - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.62.5", "", { "os": "android", "cpu": "arm64" }, "sha512-oGVqyQlxnrz9/ty89oHpU857VUHEl5/Xu4R2lS+aivCTrNnSsbiENzTnNaBsjxH0CNWGPhzHArOLFwo+oKXveA=="], - - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.62.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bW7B8xMEq8n99Q3ieEcPRGuphurdZAaFzQc9Efyyw3FL6DZO6pMy9xhdN+kBoD7Sy05xNXSr4OyPPnpkYriS/A=="], - - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.62.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-YSwBS86QeHOGlrxJ1PSOIZSkzRL/JmKeunhc+lV6M1a6En8QuVCD/T/qIA0J4Gd2Y86RIOBYrLcOUtqGh9+/1w=="], - - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.62.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-2fST8lILgl7cKbme/1KDdPCmbXbG+gqoV3bHp19L0ypX/3akYMBVdOunPleRCwonoLnXOZ/0F+Mt/v8POFmfcQ=="], - - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.62.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-cpIxQCP9J+EVad0a6LO1kY3ZGODlk80VlI+2I96B8xMcdHZ4pLVhfQ49JFpYqjPF91FFkQWftf57YlDcTiw9yQ=="], - - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.62.5", "", { "os": "linux", "cpu": "arm" }, "sha512-r9fGh3eFs3e/udWh5ZjXQtxiYK/xoFxQaYR/cELxac/Udkl5Th+IsFm0CX3Kl9hmUH/we7EoMpjJgeQNnE0+IA=="], - - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.62.5", "", { "os": "linux", "cpu": "arm" }, "sha512-xdvFdp7OM6KLJviJT2g/YuRSUjnZgGHk4RNgwIbN7X6cPugOucV60DdHXWzsBVCUdrGb6qSXnJQrrAKMmQuj3Q=="], - - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.62.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-rRqILAndyzHzP7T9NFQrq+4HFWNhqkqkKur7eiBpfLmz01PO0JKx5Vchu3YllE4YXI/Ftgq/szrDWg5GJ0mI8g=="], - - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.62.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-Gf4X3qVMucayUvux6aXXPgXovocSFUC0rrffDuPI/S2nHhNMhjcZxsrAFYCOF350PRreW1XwzFj3CT/3bKsWCw=="], - - "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.62.5", "", { "os": "linux", "cpu": "none" }, "sha512-+s5qA0TNM0qm8PK/a5gt/1Hpx+NV08uSuCncvhziIlQzT6AEV2fnUQo7eBtFTFO0nA9scauvoR2HusfXmQnO4w=="], - - "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.62.5", "", { "os": "linux", "cpu": "none" }, "sha512-ybb6QvWwWJCbBWqERpc8K3pYVGIrXlG8MEQ8IIuJY6Y9KdHQxoFoNyfkAOtKn1VHu3KuLidXvwrvGR1mEjeWCw=="], - - "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.62.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-nZb1DtnOyhCmYvsC8A2CwOkopVg+IS1+fPUa7rMOAXtNw5+lLCLLPqd6XAiNrGtoQKsbvIBOwsHnBH/3wnb4HQ=="], - - "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.62.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-yMbj63Sp89ryrXLWyz+sy+fYD2HpOnMCLGbe4Oa1smclFSUukdtD/BgdiHaAetJNb74URD8U4hM+qG5KVzMEkg=="], - - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.62.5", "", { "os": "linux", "cpu": "none" }, "sha512-mhoan3OJw2kYV/e1jtIdmvUZgyBFeA6zGWsOswmR0Tg19TQbowZuR+JMLID6spbbBN7Zee2ejrgmy3+FxGrIdA=="], - - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.62.5", "", { "os": "linux", "cpu": "none" }, "sha512-5ZTLmjWbb1VZdjuyhe83K/8QO0/h11midQCBP+X5OYn32ra7eOBoM0ZqtaY4nkgNsYgmdVhMYPoyVPTjUpHf3w=="], - - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.62.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-m53kG+br6PGxOTmgBEM2DHSDs9RVjsyEbUwjJPJGTFm1grWOG8EKJggDCTb60unD4Tjby8fi7/m9XfkEWasVWg=="], - - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.62.5", "", { "os": "linux", "cpu": "x64" }, "sha512-6RHPJR1g/uvdYU8uXBnfq3nlqyZCP82Fr6NHgfGoaIeSh0YEqnX/x6uA9MmJJbnSH7swqX4F+CkGdUF+6doiQA=="], - - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.62.5", "", { "os": "linux", "cpu": "x64" }, "sha512-xs+OXQtEXgpXT0DmA5+U3qnRZHdCST/5HRQxS8wSPZTUZN/EMWeHuSIod32LQklTBZBV9DyfncKBQ8n5V3eFdw=="], - - "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.62.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e7hD+sl3s+mcLQDZ8pbudBVsdG6r5yN4w3LqG2TJ8sQHDpblWj5lrJs/3m01Cvlxbt4x13zu5thLjgypgtkYzw=="], - - "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.62.5", "", { "os": "none", "cpu": "arm64" }, "sha512-GiyJaCf+WpMub/17aPcKk27QMl5W6f+KhdPTjlFOn5akH5Wa/DCM9Stdx5cDfmasyKB08MqpVQ1uJE2RkkpbXg=="], - - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.62.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-+OQ8U2DdoEfXl8T4Fb18AjmEwbXMerKDKCL8yCPAYhKCEEKoul7rkbeGCBFCbAlaGaa7pmtRTpkAJM2LE/i5FA=="], - - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.62.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-KanvAZrPKbDBFwrgiU9yEVpQoox9QPV1WZOXX7HudJQY+eSlu82CtWxDU8WtuRRvtN5EGkLczkd6Y6DTcvm9wA=="], - - "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.62.5", "", { "os": "win32", "cpu": "x64" }, "sha512-1aC3UEWTtRl3RK3VpDJ/Tqk1XI4SLTmXIthAq6wRWo8XiSXJNd+VprJM4/1P4+i6HIaFEFlVi9sTTziniD2tOQ=="], - - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.62.5", "", { "os": "win32", "cpu": "x64" }, "sha512-/gDJaRs4gl0NPIwqCz+6PkpmhhjRAD2j6P4rSNHBzUkO3naEx2mIU0pRle1vUNRQ7mE/+8OOeXLTv/J56FKiQg=="], - - "@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="], - - "@rushstack/eslint-patch": ["@rushstack/eslint-patch@1.16.1", "", {}, "sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag=="], - - "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - - "@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="], - - "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], - - "@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="], - - "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], - - "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], - - "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], - - "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], - - "@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="], - - "@types/node": ["@types/node@22.20.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q=="], - - "@types/react": ["@types/react@19.2.18", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w=="], - - "@types/react-dom": ["@types/react-dom@19.2.4", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw=="], - - "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.67.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.67.0", "@typescript-eslint/type-utils": "8.67.0", "@typescript-eslint/utils": "8.67.0", "@typescript-eslint/visitor-keys": "8.67.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.67.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-Un7Heoyj65NREbKAyIrFxeM143NZpExWmy1Nep4DLeQOeLlTeumPjoNKnBrU5D5moWXbPJgRa5Uwcdu0faVNGQ=="], - - "@typescript-eslint/parser": ["@typescript-eslint/parser@8.67.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.67.0", "@typescript-eslint/types": "8.67.0", "@typescript-eslint/typescript-estree": "8.67.0", "@typescript-eslint/visitor-keys": "8.67.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w=="], - - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.67.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.67.0", "@typescript-eslint/types": "^8.67.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw=="], - - "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.67.0", "", { "dependencies": { "@typescript-eslint/types": "8.67.0", "@typescript-eslint/visitor-keys": "8.67.0" } }, "sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg=="], - - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.67.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg=="], - - "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.67.0", "", { "dependencies": { "@typescript-eslint/types": "8.67.0", "@typescript-eslint/typescript-estree": "8.67.0", "@typescript-eslint/utils": "8.67.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-aVWDXbRmdXO9siTfX4ditQI1T9+zVcNazT48EJCD0v40/9RIFoUgZ05CmGEq9H2gixRpjUn/iplwvlcvutJW/Q=="], - - "@typescript-eslint/types": ["@typescript-eslint/types@8.67.0", "", {}, "sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww=="], - - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.67.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.67.0", "@typescript-eslint/tsconfig-utils": "8.67.0", "@typescript-eslint/types": "8.67.0", "@typescript-eslint/visitor-keys": "8.67.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw=="], - - "@typescript-eslint/utils": ["@typescript-eslint/utils@8.67.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.67.0", "@typescript-eslint/types": "8.67.0", "@typescript-eslint/typescript-estree": "8.67.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A=="], - - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.67.0", "", { "dependencies": { "@typescript-eslint/types": "8.67.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA=="], - - "@unrs/resolver-binding-android-arm-eabi": ["@unrs/resolver-binding-android-arm-eabi@1.12.2", "", { "os": "android", "cpu": "arm" }, "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w=="], - - "@unrs/resolver-binding-android-arm64": ["@unrs/resolver-binding-android-arm64@1.12.2", "", { "os": "android", "cpu": "arm64" }, "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ=="], - - "@unrs/resolver-binding-darwin-arm64": ["@unrs/resolver-binding-darwin-arm64@1.12.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w=="], - - "@unrs/resolver-binding-darwin-x64": ["@unrs/resolver-binding-darwin-x64@1.12.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA=="], - - "@unrs/resolver-binding-freebsd-x64": ["@unrs/resolver-binding-freebsd-x64@1.12.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg=="], - - "@unrs/resolver-binding-linux-arm-gnueabihf": ["@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2", "", { "os": "linux", "cpu": "arm" }, "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A=="], - - "@unrs/resolver-binding-linux-arm-musleabihf": ["@unrs/resolver-binding-linux-arm-musleabihf@1.12.2", "", { "os": "linux", "cpu": "arm" }, "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g=="], - - "@unrs/resolver-binding-linux-arm64-gnu": ["@unrs/resolver-binding-linux-arm64-gnu@1.12.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg=="], - - "@unrs/resolver-binding-linux-arm64-musl": ["@unrs/resolver-binding-linux-arm64-musl@1.12.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA=="], - - "@unrs/resolver-binding-linux-loong64-gnu": ["@unrs/resolver-binding-linux-loong64-gnu@1.12.2", "", { "os": "linux", "cpu": "none" }, "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q=="], - - "@unrs/resolver-binding-linux-loong64-musl": ["@unrs/resolver-binding-linux-loong64-musl@1.12.2", "", { "os": "linux", "cpu": "none" }, "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew=="], - - "@unrs/resolver-binding-linux-ppc64-gnu": ["@unrs/resolver-binding-linux-ppc64-gnu@1.12.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg=="], - - "@unrs/resolver-binding-linux-riscv64-gnu": ["@unrs/resolver-binding-linux-riscv64-gnu@1.12.2", "", { "os": "linux", "cpu": "none" }, "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A=="], - - "@unrs/resolver-binding-linux-riscv64-musl": ["@unrs/resolver-binding-linux-riscv64-musl@1.12.2", "", { "os": "linux", "cpu": "none" }, "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w=="], - - "@unrs/resolver-binding-linux-s390x-gnu": ["@unrs/resolver-binding-linux-s390x-gnu@1.12.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw=="], - - "@unrs/resolver-binding-linux-x64-gnu": ["@unrs/resolver-binding-linux-x64-gnu@1.12.2", "", { "os": "linux", "cpu": "x64" }, "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ=="], - - "@unrs/resolver-binding-linux-x64-musl": ["@unrs/resolver-binding-linux-x64-musl@1.12.2", "", { "os": "linux", "cpu": "x64" }, "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A=="], - - "@unrs/resolver-binding-openharmony-arm64": ["@unrs/resolver-binding-openharmony-arm64@1.12.2", "", { "os": "none", "cpu": "arm64" }, "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ=="], - - "@unrs/resolver-binding-wasm32-wasi": ["@unrs/resolver-binding-wasm32-wasi@1.12.2", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A=="], - - "@unrs/resolver-binding-win32-arm64-msvc": ["@unrs/resolver-binding-win32-arm64-msvc@1.12.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g=="], - - "@unrs/resolver-binding-win32-ia32-msvc": ["@unrs/resolver-binding-win32-ia32-msvc@1.12.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g=="], - - "@unrs/resolver-binding-win32-x64-msvc": ["@unrs/resolver-binding-win32-x64-msvc@1.12.2", "", { "os": "win32", "cpu": "x64" }, "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA=="], - - "@vitest/expect": ["@vitest/expect@3.2.7", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.7", "@vitest/utils": "3.2.7", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w=="], - - "@vitest/mocker": ["@vitest/mocker@3.2.7", "", { "dependencies": { "@vitest/spy": "3.2.7", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw"] }, "sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA=="], - - "@vitest/pretty-format": ["@vitest/pretty-format@3.2.7", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA=="], - - "@vitest/runner": ["@vitest/runner@3.2.7", "", { "dependencies": { "@vitest/utils": "3.2.7", "pathe": "^2.0.3", "strip-literal": "^3.0.0" } }, "sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA=="], - - "@vitest/snapshot": ["@vitest/snapshot@3.2.7", "", { "dependencies": { "@vitest/pretty-format": "3.2.7", "magic-string": "^0.30.17", "pathe": "^2.0.3" } }, "sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g=="], - - "@vitest/spy": ["@vitest/spy@3.2.7", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ=="], - - "@vitest/utils": ["@vitest/utils@3.2.7", "", { "dependencies": { "@vitest/pretty-format": "3.2.7", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw=="], - - "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], - - "acorn": ["acorn@8.18.0", "", { "bin": "bin/acorn" }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="], - - "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], - - "ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], - - "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" }, "peerDependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], - - "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - - "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], - - "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], - - "arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="], - - "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], - - "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], - - "array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="], - - "array-includes": ["array-includes@3.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.0", "es-object-atoms": "^1.1.1", "get-intrinsic": "^1.3.0", "is-string": "^1.1.1", "math-intrinsics": "^1.1.0" } }, "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ=="], - - "array.prototype.findlast": ["array.prototype.findlast@1.2.5", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ=="], - - "array.prototype.findlastindex": ["array.prototype.findlastindex@1.2.6", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-shim-unscopables": "^1.1.0" } }, "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ=="], - - "array.prototype.flat": ["array.prototype.flat@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg=="], - - "array.prototype.flatmap": ["array.prototype.flatmap@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg=="], - - "array.prototype.tosorted": ["array.prototype.tosorted@1.1.4", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.3", "es-errors": "^1.3.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA=="], - - "arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="], - - "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], - - "ast-types-flow": ["ast-types-flow@0.0.8", "", {}, "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ=="], - - "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="], - - "autoprefixer": ["autoprefixer@10.5.4", "", { "dependencies": { "browserslist": "^4.28.6", "caniuse-lite": "^1.0.30001806", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": "bin/autoprefixer" }, "sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA=="], - - "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], - - "axe-core": ["axe-core@4.13.0", "", {}, "sha512-UzGt8zg7Ny8djbYMhxl2zuEevVa7r2gJjYY5Lwr1xM7+XU2nd6CkIWFTVcCIbAP63vSz71NaVyyuSk9lHKcy0A=="], - - "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], - - "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - - "baseline-browser-mapping": ["baseline-browser-mapping@2.11.16", "", { "bin": "dist/cli.cjs" }, "sha512-H/bNPUFHewJHyCTdjn1n3Pit5+2GmWT6mmeHImPX+8MA9NA6b67jO4gYmi4jTbCJb2otq34KMZnovndDPqJwhQ=="], - - "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], - - "body-parser": ["body-parser@2.3.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^2.0.0", "debug": "^4.4.3", "http-errors": "^2.0.1", "iconv-lite": "^0.7.2", "on-finished": "^2.4.1", "qs": "^6.15.2", "raw-body": "^3.0.2", "type-is": "^2.1.0" } }, "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw=="], - - "brace-expansion": ["brace-expansion@1.1.18", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw=="], - - "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], - - "browserslist": ["browserslist@4.28.8", "", { "dependencies": { "baseline-browser-mapping": "^2.11.12", "caniuse-lite": "^1.0.30001809", "electron-to-chromium": "^1.5.402", "node-releases": "^2.0.53", "update-browserslist-db": "^1.3.0" }, "bin": "cli.js" }, "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA=="], - - "busboy": ["busboy@1.6.0", "", { "dependencies": { "streamsearch": "^1.1.0" } }, "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA=="], - - "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], - - "c12": ["c12@3.1.0", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^16.6.1", "exsolve": "^1.0.7", "giget": "^2.0.0", "jiti": "^2.4.2", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^1.0.0", "pkg-types": "^2.2.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw=="], - - "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], - - "call-bind": ["call-bind@1.0.9", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" } }, "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ=="], - - "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], - - "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], - - "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], - - "camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="], - - "caniuse-lite": ["caniuse-lite@1.0.30001809", "", {}, "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ=="], - - "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], - - "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - - "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="], - - "chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], - - "citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="], - - "client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="], - - "color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="], - - "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], - - "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], - - "color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="], - - "commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], - - "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], - - "confbox": ["confbox@0.2.4", "", {}, "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ=="], - - "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], - - "content-disposition": ["content-disposition@1.1.0", "", {}, "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g=="], - - "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], - - "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], - - "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], - - "cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="], - - "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - - "cssesc": ["cssesc@3.0.0", "", { "bin": "bin/cssesc" }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], - - "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], - - "damerau-levenshtein": ["damerau-levenshtein@1.0.8", "", {}, "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA=="], - - "data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="], - - "data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="], - - "data-view-byte-offset": ["data-view-byte-offset@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } }, "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ=="], - - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - - "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], - - "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], - - "deepmerge-ts": ["deepmerge-ts@7.1.5", "", {}, "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw=="], - - "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], - - "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="], - - "defu": ["defu@6.1.7", "", {}, "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ=="], - - "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], - - "destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="], - - "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - - "didyoumean": ["didyoumean@1.2.2", "", {}, "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="], - - "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="], - - "doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="], - - "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], - - "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], - - "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], - - "effect": ["effect@3.21.0", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-PPN80qRokCd1f015IANNhrwOnLO7GrrMQfk4/lnZRE/8j7UPWrNNjPV0uBrZutI/nHzernbW+J0hdqQysHiSnQ=="], - - "electron-to-chromium": ["electron-to-chromium@1.5.412", "", {}, "sha512-z4rMe3esBzlzovKHj4gxJnsCGZRK5l4baUvm+gCGJBPE+gsyUMKsuU9tnEUtI1dOebXz1ytAPGjvXhmQ7rIPwA=="], - - "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], - - "empathic": ["empathic@2.0.0", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="], - - "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], - - "es-abstract": ["es-abstract@1.24.2", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg=="], - - "es-abstract-get": ["es-abstract-get@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "es-object-atoms": "^1.1.2", "is-callable": "^1.2.7", "object-inspect": "^1.13.4" } }, "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg=="], - - "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], - - "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], - - "es-iterator-helpers": ["es-iterator-helpers@1.4.0", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.2", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.3.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", "math-intrinsics": "^1.1.0" } }, "sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q=="], - - "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], - - "es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="], - - "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], - - "es-shim-unscopables": ["es-shim-unscopables@1.1.0", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw=="], - - "es-to-primitive": ["es-to-primitive@1.3.4", "", { "dependencies": { "es-abstract-get": "^1.0.0", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "is-callable": "^1.2.7", "is-date-object": "^1.1.0", "is-symbol": "^1.1.1" } }, "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw=="], - - "esbuild": ["esbuild@0.28.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.2", "@esbuild/android-arm": "0.28.2", "@esbuild/android-arm64": "0.28.2", "@esbuild/android-x64": "0.28.2", "@esbuild/darwin-arm64": "0.28.2", "@esbuild/darwin-x64": "0.28.2", "@esbuild/freebsd-arm64": "0.28.2", "@esbuild/freebsd-x64": "0.28.2", "@esbuild/linux-arm": "0.28.2", "@esbuild/linux-arm64": "0.28.2", "@esbuild/linux-ia32": "0.28.2", "@esbuild/linux-loong64": "0.28.2", "@esbuild/linux-mips64el": "0.28.2", "@esbuild/linux-ppc64": "0.28.2", "@esbuild/linux-riscv64": "0.28.2", "@esbuild/linux-s390x": "0.28.2", "@esbuild/linux-x64": "0.28.2", "@esbuild/netbsd-arm64": "0.28.2", "@esbuild/netbsd-x64": "0.28.2", "@esbuild/openbsd-arm64": "0.28.2", "@esbuild/openbsd-x64": "0.28.2", "@esbuild/openharmony-arm64": "0.28.2", "@esbuild/sunos-x64": "0.28.2", "@esbuild/win32-arm64": "0.28.2", "@esbuild/win32-ia32": "0.28.2", "@esbuild/win32-x64": "0.28.2" }, "bin": "bin/esbuild" }, "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA=="], - - "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], - - "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], - - "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - - "eslint": ["eslint@9.39.5", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.6", "@eslint/js": "9.39.5", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "bin": "bin/eslint.js" }, "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw=="], - - "eslint-config-next": ["eslint-config-next@15.2.0", "", { "dependencies": { "@next/eslint-plugin-next": "15.2.0", "@rushstack/eslint-patch": "^1.10.3", "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", "eslint-plugin-react-hooks": "^5.0.0" }, "peerDependencies": { "eslint": "^7.23.0 || ^8.0.0 || ^9.0.0", "typescript": ">=3.3.1" } }, "sha512-LkG0KKpinAoNPk2HXSx0fImFb/hQ6RnhSxTkpJFTkQ0SmnzsbRsjjN95WC/mDY34nKOenpptYEVvfkCR/h+VjA=="], - - "eslint-import-resolver-node": ["eslint-import-resolver-node@0.3.10", "", { "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.16.1", "resolve": "^2.0.0-next.6" } }, "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ=="], - - "eslint-import-resolver-typescript": ["eslint-import-resolver-typescript@3.10.1", "", { "dependencies": { "@nolyfill/is-core-module": "1.0.39", "debug": "^4.4.0", "get-tsconfig": "^4.10.0", "is-bun-module": "^2.0.0", "stable-hash": "^0.0.5", "tinyglobby": "^0.2.13", "unrs-resolver": "^1.6.2" }, "peerDependencies": { "eslint": "*", "eslint-plugin-import": "*", "eslint-plugin-import-x": "*" }, "optionalPeers": ["eslint-plugin-import-x"] }, "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ=="], - - "eslint-module-utils": ["eslint-module-utils@2.14.0", "", { "dependencies": { "debug": "^3.2.7" } }, "sha512-W2WCRZ9Dqntd+2u8jJcVMV2PKulc6RdLgUUoh/yQr3uB6lo/ZOeGx11sv60/8S4QFFKNslAlWhr9u0Ef7ZW6Ig=="], - - "eslint-plugin-import": ["eslint-plugin-import@2.32.0", "", { "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", "array.prototype.findlastindex": "^1.2.6", "array.prototype.flat": "^1.3.3", "array.prototype.flatmap": "^1.3.3", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", "eslint-module-utils": "^2.12.1", "hasown": "^2.0.2", "is-core-module": "^2.16.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "object.groupby": "^1.0.3", "object.values": "^1.2.1", "semver": "^6.3.1", "string.prototype.trimend": "^1.0.9", "tsconfig-paths": "^3.15.0" }, "peerDependencies": { "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA=="], - - "eslint-plugin-jsx-a11y": ["eslint-plugin-jsx-a11y@6.10.2", "", { "dependencies": { "aria-query": "^5.3.2", "array-includes": "^3.1.8", "array.prototype.flatmap": "^1.3.2", "ast-types-flow": "^0.0.8", "axe-core": "^4.10.0", "axobject-query": "^4.1.0", "damerau-levenshtein": "^1.0.8", "emoji-regex": "^9.2.2", "hasown": "^2.0.2", "jsx-ast-utils": "^3.3.5", "language-tags": "^1.0.9", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "safe-regex-test": "^1.0.3", "string.prototype.includes": "^2.0.1" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" } }, "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q=="], - - "eslint-plugin-react": ["eslint-plugin-react@7.37.5", "", { "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", "array.prototype.flatmap": "^1.3.3", "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", "es-iterator-helpers": "^1.2.1", "estraverse": "^5.3.0", "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", "object.entries": "^1.1.9", "object.fromentries": "^2.0.8", "object.values": "^1.2.1", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", "string.prototype.matchall": "^4.0.12", "string.prototype.repeat": "^1.0.0" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA=="], - - "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@5.2.0", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg=="], - - "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], - - "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], - - "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], - - "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="], - - "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], - - "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], - - "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], - - "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], - - "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], - - "eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="], - - "eventsource-parser": ["eventsource-parser@3.1.1", "", {}, "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ=="], - - "expect-type": ["expect-type@1.4.0", "", {}, "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA=="], - - "express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], - - "express-rate-limit": ["express-rate-limit@8.6.2", "", { "dependencies": { "debug": "^4.4.3", "ip-address": "^10.2.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-YH4ru+eOJxQABscKFfRCy9R7x9QFGdezclVMwwgFFndzS2Xnm0uo6B0ABZsLhcpeptGv2qvuJVWlQr9gQZoC3A=="], - - "exsolve": ["exsolve@1.1.1", "", {}, "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g=="], - - "fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="], - - "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], - - "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], - - "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], - - "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], - - "fast-uri": ["fast-uri@3.1.5", "", {}, "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw=="], - - "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], - - "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" } }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], - - "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], - - "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], - - "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], - - "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], - - "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], - - "flatted": ["flatted@3.4.4", "", {}, "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q=="], - - "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], - - "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], - - "fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="], - - "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], - - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], - - "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], - - "function.prototype.name": ["function.prototype.name@1.2.0", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2", "hasown": "^2.0.4", "is-callable": "^1.2.7", "is-document.all": "^1.0.0" } }, "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew=="], - - "functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="], - - "generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="], - - "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], - - "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], - - "get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="], - - "get-tsconfig": ["get-tsconfig@4.14.3", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA=="], - - "giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": "dist/cli.mjs" }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="], - - "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], - - "globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], - - "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], - - "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], - - "has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="], - - "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], - - "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], - - "has-proto": ["has-proto@1.2.0", "", { "dependencies": { "dunder-proto": "^1.0.0" } }, "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ=="], - - "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], - - "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], - - "hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="], - - "hono": ["hono@4.13.3", "", {}, "sha512-r8AO2mYHoLxSHkgafNeC/BXyb2vWRxD3jem4Ts+ptav8oTG5FIRifAjuJEmZI4bSvvc2ns0GxmIYiZnHqN3mMw=="], - - "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], - - "iconv-lite": ["iconv-lite@0.7.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ=="], - - "ignore": ["ignore@7.0.6", "", {}, "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw=="], - - "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], - - "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], - - "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], - - "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], - - "ip-address": ["ip-address@10.5.0", "", {}, "sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g=="], - - "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], - - "is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="], - - "is-arrayish": ["is-arrayish@0.3.4", "", {}, "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA=="], - - "is-async-function": ["is-async-function@2.1.1", "", { "dependencies": { "async-function": "^1.0.0", "call-bound": "^1.0.3", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ=="], - - "is-bigint": ["is-bigint@1.1.0", "", { "dependencies": { "has-bigints": "^1.0.2" } }, "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ=="], - - "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="], - - "is-boolean-object": ["is-boolean-object@1.2.2", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A=="], - - "is-bun-module": ["is-bun-module@2.0.0", "", { "dependencies": { "semver": "^7.7.1" } }, "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ=="], - - "is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="], - - "is-core-module": ["is-core-module@2.16.2", "", { "dependencies": { "hasown": "^2.0.3" } }, "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA=="], - - "is-data-view": ["is-data-view@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" } }, "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw=="], - - "is-date-object": ["is-date-object@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg=="], - - "is-document.all": ["is-document.all@1.0.0", "", { "dependencies": { "call-bound": "^1.0.4" } }, "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g=="], - - "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], - - "is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="], - - "is-generator-function": ["is-generator-function@1.1.2", "", { "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="], - - "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], - - "is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="], - - "is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="], - - "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], - - "is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="], - - "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], - - "is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="], - - "is-set": ["is-set@2.0.3", "", {}, "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg=="], - - "is-shared-array-buffer": ["is-shared-array-buffer@1.0.4", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A=="], - - "is-string": ["is-string@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA=="], - - "is-symbol": ["is-symbol@1.1.1", "", { "dependencies": { "call-bound": "^1.0.2", "has-symbols": "^1.1.0", "safe-regex-test": "^1.1.0" } }, "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w=="], - - "is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="], - - "is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="], - - "is-weakref": ["is-weakref@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew=="], - - "is-weakset": ["is-weakset@2.0.4", "", { "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ=="], - - "isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], - - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - - "iterator.prototype": ["iterator.prototype@1.1.5", "", { "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "get-proto": "^1.0.0", "has-symbols": "^1.1.0", "set-function-name": "^2.0.2" } }, "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g=="], - - "jiti": ["jiti@2.7.0", "", { "bin": "lib/jiti-cli.mjs" }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], - - "jose": ["jose@6.2.9", "", {}, "sha512-XrchZOFZUl/T3vTwRe8XK+cJrGtMF4th1ARnDfwbBXFKThGhlsxEE4Zu03AD/bjJSt/9jT/mxrOCkJWOg77aPA=="], - - "js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="], - - "js-yaml": ["js-yaml@4.3.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": "bin/js-yaml.js" }, "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ=="], - - "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], - - "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], - - "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="], - - "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], - - "json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": "lib/cli.js" }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="], - - "jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="], - - "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], - - "language-subtag-registry": ["language-subtag-registry@0.3.23", "", {}, "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ=="], - - "language-tags": ["language-tags@1.0.9", "", { "dependencies": { "language-subtag-registry": "^0.3.20" } }, "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA=="], - - "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], - - "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], - - "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], - - "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], - - "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], - - "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": "cli.js" }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], - - "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="], - - "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - - "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], - - "media-typer": ["media-typer@1.1.1", "", {}, "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ=="], - - "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], - - "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], - - "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], - - "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], - - "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], - - "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - - "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], - - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], - - "nanoid": ["nanoid@3.3.18", "", { "bin": "bin/nanoid.cjs" }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="], - - "napi-postinstall": ["napi-postinstall@0.3.4", "", { "bin": "lib/cli.js" }, "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ=="], - - "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], - - "negotiator": ["negotiator@1.1.0", "", { "dependencies": { "content-type": "^2.1.0" } }, "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg=="], - - "next": ["next@15.2.0", "", { "dependencies": { "@next/env": "15.2.0", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.2.0", "@next/swc-darwin-x64": "15.2.0", "@next/swc-linux-arm64-gnu": "15.2.0", "@next/swc-linux-arm64-musl": "15.2.0", "@next/swc-linux-x64-gnu": "15.2.0", "@next/swc-linux-x64-musl": "15.2.0", "@next/swc-win32-arm64-msvc": "15.2.0", "@next/swc-win32-x64-msvc": "15.2.0", "sharp": "^0.33.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": "dist/bin/next" }, "sha512-VaiM7sZYX8KIAHBrRGSFytKknkrexNfGb8GlG6e93JqueCspuGte8i4ybn8z4ww1x3f2uzY4YpTaBEW4/hvsoQ=="], - - "node-exports-info": ["node-exports-info@1.6.2", "", { "dependencies": { "array.prototype.flatmap": "^1.3.3", "es-errors": "^1.3.0", "object.entries": "^1.1.9", "semver": "^6.3.1" } }, "sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag=="], - - "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], - - "node-releases": ["node-releases@2.0.53", "", {}, "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ=="], - - "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], - - "nypm": ["nypm@0.6.9", "", { "dependencies": { "citty": "^0.2.2", "pathe": "^2.0.3", "tinyexec": "^1.2.4" }, "bin": "dist/cli.mjs" }, "sha512-zxlE2yvSWZWmHcNdT3+5zV2lrCogeE9YOklHrR3dFjqutq5wO7GFDYLFDRXLsYnJzwvy/im9fYoxePvS0VTW0w=="], - - "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], - - "object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="], - - "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], - - "object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="], - - "object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="], - - "object.entries": ["object.entries@1.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-object-atoms": "^1.1.1" } }, "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw=="], - - "object.fromentries": ["object.fromentries@2.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-object-atoms": "^1.0.0" } }, "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ=="], - - "object.groupby": ["object.groupby@1.0.3", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2" } }, "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ=="], - - "object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="], - - "ohash": ["ohash@2.0.12", "", {}, "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw=="], - - "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], - - "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], - - "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], - - "own-keys": ["own-keys@1.0.2", "", { "dependencies": { "call-bound": "^1.0.4", "get-intrinsic": "^1.3.0", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg=="], - - "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], - - "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], - - "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], - - "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], - - "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], - - "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], - - "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], - - "path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="], - - "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], - - "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="], - - "perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="], - - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - - "picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], - - "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], - - "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="], - - "pkg-types": ["pkg-types@2.3.1", "", { "dependencies": { "confbox": "^0.2.4", "exsolve": "^1.0.8", "pathe": "^2.0.3" } }, "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg=="], - - "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], - - "postcss": ["postcss@8.5.26", "", { "dependencies": { "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ=="], - - "postcss-import": ["postcss-import@15.1.0", "", { "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", "resolve": "^1.1.7" }, "peerDependencies": { "postcss": "^8.0.0" } }, "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew=="], - - "postcss-js": ["postcss-js@4.1.0", "", { "dependencies": { "camelcase-css": "^2.0.1" }, "peerDependencies": { "postcss": "^8.4.21" } }, "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw=="], - - "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], - - "postcss-nested": ["postcss-nested@6.2.0", "", { "dependencies": { "postcss-selector-parser": "^6.1.1" }, "peerDependencies": { "postcss": "^8.2.14" } }, "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ=="], - - "postcss-selector-parser": ["postcss-selector-parser@6.1.4", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ=="], - - "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], - - "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], - - "prisma": ["prisma@6.19.3", "", { "dependencies": { "@prisma/config": "6.19.3", "@prisma/engines": "6.19.3" }, "peerDependencies": { "typescript": ">=5.1.0" }, "bin": "build/index.js" }, "sha512-++ZJ0ijLrDJF6hNB4t4uxg2br3fC4H9Yc9tcbjr2fcNFP3rh/SBNrAgjhsqBU4Ght8JPrVofG/ZkXfnSfnYsFg=="], - - "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], - - "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], - - "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - - "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], - - "qs": ["qs@6.15.3", "", { "dependencies": { "es-define-property": "^1.0.1", "side-channel": "^1.1.1" } }, "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A=="], - - "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], - - "range-parser": ["range-parser@1.3.0", "", {}, "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw=="], - - "raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="], - - "rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="], - - "react": ["react@19.0.0", "", {}, "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ=="], - - "react-dom": ["react-dom@19.0.0", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ=="], - - "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], - - "read-cache": ["read-cache@1.0.2", "", {}, "sha512-/peqiBB/n07gQGLsWaHho3WfvUyRscw0gYTsEFMhrIe/nWLkYaf5SbKYjGYqtRV3aPwykJgF2VEMo1ac4bnsGA=="], - - "readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], - - "reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="], - - "regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="], - - "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], - - "resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": "bin/resolve" }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="], - - "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], - - "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], - - "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], - - "rollup": ["rollup@4.62.5", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@napi-rs/lzma-linux-x64-gnu": "1.5.1", "@rollup/rollup-android-arm-eabi": "4.62.5", "@rollup/rollup-android-arm64": "4.62.5", "@rollup/rollup-darwin-arm64": "4.62.5", "@rollup/rollup-darwin-x64": "4.62.5", "@rollup/rollup-freebsd-arm64": "4.62.5", "@rollup/rollup-freebsd-x64": "4.62.5", "@rollup/rollup-linux-arm-gnueabihf": "4.62.5", "@rollup/rollup-linux-arm-musleabihf": "4.62.5", "@rollup/rollup-linux-arm64-gnu": "4.62.5", "@rollup/rollup-linux-arm64-musl": "4.62.5", "@rollup/rollup-linux-loong64-gnu": "4.62.5", "@rollup/rollup-linux-loong64-musl": "4.62.5", "@rollup/rollup-linux-ppc64-gnu": "4.62.5", "@rollup/rollup-linux-ppc64-musl": "4.62.5", "@rollup/rollup-linux-riscv64-gnu": "4.62.5", "@rollup/rollup-linux-riscv64-musl": "4.62.5", "@rollup/rollup-linux-s390x-gnu": "4.62.5", "@rollup/rollup-linux-x64-gnu": "4.62.5", "@rollup/rollup-linux-x64-musl": "4.62.5", "@rollup/rollup-openbsd-x64": "4.62.5", "@rollup/rollup-openharmony-arm64": "4.62.5", "@rollup/rollup-win32-arm64-msvc": "4.62.5", "@rollup/rollup-win32-ia32-msvc": "4.62.5", "@rollup/rollup-win32-x64-gnu": "4.62.5", "@rollup/rollup-win32-x64-msvc": "4.62.5", "fsevents": "~2.3.2" }, "bin": "dist/bin/rollup" }, "sha512-/tqMfgP7GPA3PHhCmuiS4vIjrSVhHLgY++i+dhbG462euyAj7FpM4D9uq1X3BgjlqRdpcOrYhcQtfiQLNc8tqw=="], - - "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], - - "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], - - "safe-array-concat": ["safe-array-concat@1.1.4", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "get-intrinsic": "^1.3.0", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg=="], - - "safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="], - - "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="], - - "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], - - "scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="], - - "semver": ["semver@7.8.5", "", { "bin": "bin/semver.js" }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], - - "send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], - - "serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="], - - "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], - - "set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="], - - "set-proto": ["set-proto@1.0.0", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="], - - "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], - - "sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="], - - "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], - - "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], - - "side-channel": ["side-channel@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4", "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ=="], - - "side-channel-list": ["side-channel-list@1.0.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" } }, "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w=="], - - "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], - - "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], - - "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], - - "simple-swizzle": ["simple-swizzle@0.2.4", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw=="], - - "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], - - "stable-hash": ["stable-hash@0.0.5", "", {}, "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA=="], - - "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], - - "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], - - "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], - - "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], - - "streamsearch": ["streamsearch@1.1.0", "", {}, "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="], - - "string.prototype.includes": ["string.prototype.includes@2.0.1", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.3" } }, "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg=="], - - "string.prototype.matchall": ["string.prototype.matchall@4.0.12", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "regexp.prototype.flags": "^1.5.3", "set-function-name": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA=="], - - "string.prototype.repeat": ["string.prototype.repeat@1.0.0", "", { "dependencies": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" } }, "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w=="], - - "string.prototype.trim": ["string.prototype.trim@1.2.11", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.2", "es-object-atoms": "^1.1.2", "has-property-descriptors": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w=="], - - "string.prototype.trimend": ["string.prototype.trimend@1.0.10", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-object-atoms": "^1.1.2" } }, "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw=="], - - "string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="], - - "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="], - - "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], - - "strip-literal": ["strip-literal@3.1.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="], - - "styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="], - - "sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], - - "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - - "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], - - "tailwindcss": ["tailwindcss@3.4.19", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.7", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ=="], - - "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], - - "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], - - "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], - - "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], - - "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], - - "tinypool": ["tinypool@1.1.1", "", {}, "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg=="], - - "tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], - - "tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="], - - "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], - - "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], - - "ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], - - "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], - - "tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="], - - "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], - - "type-is": ["type-is@2.1.0", "", { "dependencies": { "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA=="], - - "typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="], - - "typed-array-byte-length": ["typed-array-byte-length@1.0.3", "", { "dependencies": { "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.14" } }, "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg=="], - - "typed-array-byte-offset": ["typed-array-byte-offset@1.0.4", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.15", "reflect.getprototypeof": "^1.0.9" } }, "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ=="], - - "typed-array-length": ["typed-array-length@1.0.8", "", { "dependencies": { "call-bind": "^1.0.9", "for-each": "^0.3.5", "gopd": "^1.2.0", "is-typed-array": "^1.1.15", "possible-typed-array-names": "^1.1.0", "reflect.getprototypeof": "^1.0.10" } }, "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g=="], - - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - - "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="], - - "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - - "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], - - "unrs-resolver": ["unrs-resolver@1.12.2", "", { "dependencies": { "napi-postinstall": "^0.3.4" }, "optionalDependencies": { "@unrs/resolver-binding-android-arm-eabi": "1.12.2", "@unrs/resolver-binding-android-arm64": "1.12.2", "@unrs/resolver-binding-darwin-arm64": "1.12.2", "@unrs/resolver-binding-darwin-x64": "1.12.2", "@unrs/resolver-binding-freebsd-x64": "1.12.2", "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2", "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2", "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2", "@unrs/resolver-binding-linux-arm64-musl": "1.12.2", "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2", "@unrs/resolver-binding-linux-loong64-musl": "1.12.2", "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2", "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2", "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2", "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2", "@unrs/resolver-binding-linux-x64-gnu": "1.12.2", "@unrs/resolver-binding-linux-x64-musl": "1.12.2", "@unrs/resolver-binding-openharmony-arm64": "1.12.2", "@unrs/resolver-binding-wasm32-wasi": "1.12.2", "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2", "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2", "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" } }, "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ=="], - - "update-browserslist-db": ["update-browserslist-db@1.3.1", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": "cli.js" }, "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ=="], - - "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], - - "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], - - "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], - - "vite": ["vite@7.3.6", "", { "dependencies": { "esbuild": "^0.27.0 || ^0.28.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": "bin/vite.js" }, "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg=="], - - "vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": "vite-node.mjs" }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="], - - "vitest": ["vitest@3.2.7", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.7", "@vitest/mocker": "3.2.7", "@vitest/pretty-format": "^3.2.7", "@vitest/runner": "3.2.7", "@vitest/snapshot": "3.2.7", "@vitest/spy": "3.2.7", "@vitest/utils": "3.2.7", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "picomatch": "^4.0.2", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.14", "tinypool": "^1.1.1", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.2.7", "@vitest/ui": "3.2.7", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": "vitest.mjs" }, "sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg=="], - - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - - "which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="], - - "which-builtin-type": ["which-builtin-type@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "function.prototype.name": "^1.1.6", "has-tostringtag": "^1.0.2", "is-async-function": "^2.0.0", "is-date-object": "^1.1.0", "is-finalizationregistry": "^1.1.0", "is-generator-function": "^1.0.10", "is-regex": "^1.2.1", "is-weakref": "^1.0.2", "isarray": "^2.0.5", "which-boxed-primitive": "^1.1.0", "which-collection": "^1.0.2", "which-typed-array": "^1.1.16" } }, "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q=="], - - "which-collection": ["which-collection@1.0.2", "", { "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", "is-weakmap": "^2.0.2", "is-weakset": "^2.0.3" } }, "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw=="], - - "which-typed-array": ["which-typed-array@1.1.22", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.9", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw=="], - - "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": "cli.js" }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], - - "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], - - "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], - - "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - - "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - - "zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], - - "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - - "@eslint/config-array/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - - "@eslint/eslintrc/ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], - - "@eslint/eslintrc/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - - "@eslint/eslintrc/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - - "@modelcontextprotocol/sdk/ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], - - "@next/eslint-plugin-next/fast-glob": ["fast-glob@3.3.1", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg=="], - - "@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="], - - "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], - - "@unrs/resolver-binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], - - "ajv-formats/ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], - - "anymatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], - - "body-parser/content-type": ["content-type@2.1.0", "", {}, "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag=="], - - "c12/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], - - "chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - - "eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - - "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], - - "eslint-import-resolver-node/resolve": ["resolve@2.0.0-next.7", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.2", "node-exports-info": "^1.6.0", "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": "bin/resolve" }, "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ=="], - - "eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], - - "eslint-plugin-import/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], - - "eslint-plugin-import/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - - "eslint-plugin-import/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "eslint-plugin-jsx-a11y/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - - "eslint-plugin-react/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - - "eslint-plugin-react/resolve": ["resolve@2.0.0-next.7", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.2", "node-exports-info": "^1.6.0", "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": "bin/resolve" }, "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ=="], - - "eslint-plugin-react/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "espree/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], - - "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - - "loose-envify/js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], - - "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], - - "negotiator/content-type": ["content-type@2.1.0", "", {}, "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag=="], - - "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], - - "node-exports-info/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "nypm/citty": ["citty@0.2.2", "", {}, "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w=="], - - "nypm/tinyexec": ["tinyexec@1.3.0", "", {}, "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ=="], - - "postcss-import/resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": "bin/resolve" }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="], - - "readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], - - "tailwindcss/jiti": ["jiti@1.21.7", "", { "bin": "bin/jiti.js" }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="], - - "tinyglobby/picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], - - "type-is/content-type": ["content-type@2.1.0", "", {}, "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag=="], - - "vite/fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" } }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], - - "vite/picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], - - "@eslint/config-array/minimatch/brace-expansion": ["brace-expansion@1.1.18", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw=="], - - "@eslint/eslintrc/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], - - "@eslint/eslintrc/minimatch/brace-expansion": ["brace-expansion@1.1.18", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw=="], - - "@modelcontextprotocol/sdk/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], - - "@next/eslint-plugin-next/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - - "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.9", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg=="], - - "ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], - - "c12/chokidar/readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], - - "eslint-plugin-import/minimatch/brace-expansion": ["brace-expansion@1.1.18", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw=="], - - "eslint-plugin-jsx-a11y/minimatch/brace-expansion": ["brace-expansion@1.1.18", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw=="], - - "eslint-plugin-react/minimatch/brace-expansion": ["brace-expansion@1.1.18", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw=="], - - "@eslint/config-array/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - - "@eslint/eslintrc/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - - "@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - - "eslint-plugin-import/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - - "eslint-plugin-jsx-a11y/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - - "eslint-plugin-react/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - } -} diff --git a/components/AppHeader.tsx b/components/AppHeader.tsx deleted file mode 100644 index 6286b50..0000000 --- a/components/AppHeader.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import Link from "next/link"; - -export function AppHeader() { - return ( -
- LLIPPY - -
- ); -} diff --git a/components/SiteFooter.tsx b/components/SiteFooter.tsx deleted file mode 100644 index eb9a8a8..0000000 --- a/components/SiteFooter.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import Link from "next/link"; - -const sections = [ - ["Overview", "#hero"], - ["Drift", "#failure-modes"], - ["Pipeline", "#pipeline"], - ["Interfaces", "#integrations"], - ["Foundation", "#foundation"], -]; - -export function SiteFooter({ className = "", absolute = false }: { className?: string; absolute?: boolean }) { - const prefix = absolute ? "" : "/"; - - return ( -
-
LLIPPY
- -
Control room LIPPY © 2026
-
- ); -} diff --git a/components/dashboard/ContractList.tsx b/components/dashboard/ContractList.tsx deleted file mode 100644 index 4bef48d..0000000 --- a/components/dashboard/ContractList.tsx +++ /dev/null @@ -1,57 +0,0 @@ -"use client"; - -import { useEffect, useState } from "react"; -import Link from "next/link"; -import { StatusBadge } from "./StatusBadge"; - -type Contract = { - id: string; - url: string; - status: string; - createdAt: string; -}; - -export function ContractList() { - const [contracts, setContracts] = useState(null); - - useEffect(() => { - let cancelled = false; - fetch("/api/contracts") - .then((res) => res.json()) - .then((json) => { - if (!cancelled) setContracts(json.contracts); - }); - return () => { - cancelled = true; - }; - }, []); - - if (contracts === null) { - return

Loading contracts...

; - } - - if (contracts.length === 0) { - return ( -
-

No contracts yet.

-

Define a URL and a schema to start one.

-
- ); - } - - return ( -
    - {contracts.map((c) => ( -
  • - -
    -

    {c.url}

    -

    {c.id}

    -
    - - -
  • - ))} -
- ); -} diff --git a/components/dashboard/EventFeed.tsx b/components/dashboard/EventFeed.tsx deleted file mode 100644 index 3e0fa86..0000000 --- a/components/dashboard/EventFeed.tsx +++ /dev/null @@ -1,98 +0,0 @@ -"use client"; - -import { useEffect, useState } from "react"; - -type Event = { - type: string; - contractId: string; - runId?: string; - field?: string; - previousValue?: unknown; - currentValue?: unknown; - confidence?: number; - summary?: string; - reason?: string; - attempts?: number; - attempt?: number; - ts?: string; -}; - -const TYPE_STYLES: Record = { - "contract.violated": "event-violated", - "contract.healing": "event-healing", - "contract.healed": "event-healed", - "contract.escalated": "event-escalated", - "field.changed": "event-changed", -}; - -/** - * Live event feed for a single contract. Opens an EventSource to the SSE - * route and appends each frame to a rolling log. The orchestrator pushes - * typed events; we render a one-line summary per event type. - */ -export function EventFeed({ contractId }: { contractId: string }) { - const [events, setEvents] = useState([]); - const [connected, setConnected] = useState(false); - - useEffect(() => { - const source = new EventSource(`/api/contracts/${contractId}/events`); - - source.addEventListener("open", () => setConnected(true)); - source.addEventListener("error", () => setConnected(false)); - - const handler = (event: MessageEvent) => { - try { - const parsed = JSON.parse(event.data) as Event; - setEvents((prev) => [{ ...parsed, ts: new Date().toISOString() }, ...prev].slice(0, 50)); - } catch { - // Ignore malformed frames. - } - }; - - source.onmessage = handler; - return () => source.close(); - }, [contractId]); - - return ( -
-

Live events

- - - {connected ? "connected" : "disconnected"} - -
-
    - {events.length === 0 && ( -
  • - Waiting for events. Click Run now to trigger a cycle. -
  • - )} - {events.map((event, i) => ( -
  • -
    - - {event.type} - - {event.attempt !== undefined && ( - attempt {event.attempt} - )} - {event.ts?.slice(11, 19)} -
    - {event.summary &&

    {event.summary}

    } - {event.reason &&

    {event.reason}

    } - {event.type === "field.changed" && ( -

    - {event.field}: {String(event.previousValue)} → {String(event.currentValue)} - {event.confidence !== undefined && ( - confidence {event.confidence.toFixed(2)} - )} -

    - )} -
  • - ))} -
-
- ); -} diff --git a/components/dashboard/LineageGraph.tsx b/components/dashboard/LineageGraph.tsx deleted file mode 100644 index 5e1fb2d..0000000 --- a/components/dashboard/LineageGraph.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { StatusBadge } from "@/components/dashboard/StatusBadge"; - -type Run = { - id: string; - startedAt: string | Date; - finishedAt: string | Date | null; - outcome: string | null; -}; - -type Event = { - id: string; - type: string; - createdAt: string | Date; -}; - -/** - * Lineage view: most-recent-first vertical timeline of every run with its - * outcome, plus the events that fired during each run. Static in the - * sense that it's read-only (no interactivity) but driven by real data - * from the DB. This is the "is my contract still alive?" surface for a - * judge clicking through. - */ -export function LineageGraph({ runs, events }: { runs: Run[]; events: Event[] }) { - const eventsByRunId = new Map(); - for (const e of events) { - // Events don't currently carry runId in the persisted payload column, - // so we fall back to a coarse time-window match per run. - const evTime = new Date(e.createdAt).getTime(); - const run = runs.find((r) => { - const start = new Date(r.startedAt).getTime(); - const end = r.finishedAt ? new Date(r.finishedAt).getTime() : Date.now(); - return evTime >= start && evTime <= end + 5_000; - }); - if (!run) continue; - const bucket = eventsByRunId.get(run.id) ?? []; - bucket.push(e); - eventsByRunId.set(run.id, bucket); - } - - return ( -
-

Run history

LINEAGE
-
    - {runs.length === 0 && ( -
  1. No runs yet.
  2. - )} - {runs.map((run) => ( -
  3. -
    -
    - {new Date(run.startedAt).toISOString().slice(0, 19).replace("T", " ")} -
    - {run.outcome && } -
    - - {(eventsByRunId.get(run.id) ?? []).length} event(s) - -
  4. - ))} -
-
- ); -} diff --git a/components/dashboard/RunButton.tsx b/components/dashboard/RunButton.tsx deleted file mode 100644 index c0ca92c..0000000 --- a/components/dashboard/RunButton.tsx +++ /dev/null @@ -1,53 +0,0 @@ -"use client"; - -import { useState } from "react"; - -/** - * Posts to /api/contracts/:id/run and reflects the orchestrator's outcome - * on the button. The actual live updates (violations, heals, field - * changes) come through the EventFeed via SSE — this button is just the - * trigger. - */ -export function RunButton({ contractId }: { contractId: string }) { - const [state, setState] = useState<"idle" | "running" | "error">("idle"); - const [message, setMessage] = useState(null); - - async function onClick() { - setState("running"); - setMessage(null); - try { - const res = await fetch(`/api/contracts/${contractId}/run`, { method: "POST" }); - const json = await res.json(); - if (!res.ok) { - setState("error"); - setMessage(json?.error?.message ?? "Run failed"); - return; - } - setState("idle"); - setMessage( - `Outcome: ${json.result?.outcome ?? "unknown"}${json.result?.fieldChanges?.length ? `, ${json.result.fieldChanges.length} field change(s)` : ""}`, - ); - } catch (err) { - setState("error"); - setMessage(err instanceof Error ? err.message : "Network error"); - } - } - - return ( -
- - {message && ( - - {message} - - )} -
- ); -} diff --git a/components/dashboard/Skeletons.tsx b/components/dashboard/Skeletons.tsx deleted file mode 100644 index 966f48b..0000000 --- a/components/dashboard/Skeletons.tsx +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Skeletons used by the loading.tsx route files. Kept in one component - * file so the shimmer styling lives in exactly one place. - */ -export function ContractListSkeleton() { - return ( -
-
-
-
    - {[0, 1, 2].map((i) => ( -
  • -
    -
    -
    -
    -
    -
  • - ))} -
-
- ); -} - -export function ContractDetailSkeleton() { - return ( -
-
-
-
-
-
-
-
-
-
-
-
-
-
- {[0, 1, 2].map((i) => ( -
-
-
-
- ))} -
-
-
-
-
- ); -} diff --git a/components/dashboard/StatusBadge.tsx b/components/dashboard/StatusBadge.tsx deleted file mode 100644 index 61d8723..0000000 --- a/components/dashboard/StatusBadge.tsx +++ /dev/null @@ -1,16 +0,0 @@ -const STATUS_STYLES: Record = { - healthy: { label: "Healthy", className: "status-healthy" }, - degraded: { label: "Degraded", className: "status-degraded" }, - healing: { label: "Healing", className: "status-healing" }, - broken: { label: "Broken", className: "status-broken" }, -}; - -export function StatusBadge({ status }: { status: string }) { - const style = STATUS_STYLES[status] ?? STATUS_STYLES.degraded!; - return ( - - - {style.label} - - ); -} diff --git a/components/landing/LandingExperience.tsx b/components/landing/LandingExperience.tsx deleted file mode 100644 index c6a9412..0000000 --- a/components/landing/LandingExperience.tsx +++ /dev/null @@ -1,209 +0,0 @@ -"use client"; - -import Link from "next/link"; -import { motion, useReducedMotion, useScroll, useTransform } from "motion/react"; -import { useEffect, useRef, useState, type ReactNode, type RefObject } from "react"; -import { SiteFooter } from "@/components/SiteFooter"; - -const failureModes = [ - { - number: "01", - title: "Structural drift", - body: "The page still responds, but the shape your extractor depends on has moved.", - code: "selector: .product-price\nexpected: number\nreceived: undefined", - signal: "shape changed", - }, - { - number: "02", - title: "Semantic drift", - body: "The fields are present and valid, but the meaning underneath them has changed.", - code: "price: 189.00\nconfidence: 0.94\nmeaning: uncertain", - signal: "meaning changed", - }, -]; - -const pipeline = [ - ["01", "Extract", "Capture the live surface."], - ["02", "Validate", "Compare it to the contract."], - ["03", "Heal", "Propose a tested repair."], - ["04", "Verify", "Check meaning, not just shape."], - ["05", "Emit", "Return a dependable response."], -]; - -const integrations = [ - ["REST", "POST /v1/contracts/run", "curl -X POST /v1/contracts/run\n -H 'x-contract: WC-0048'\n -d '{\"url\": \"…\"}'"], - ["WEBHOOKS", "contract.healed", "{\n \"event\": \"contract.healed\",\n \"contract\": \"WC-0048\",\n \"confidence\": 0.99\n}"], - ["MCP", "get_data(contract_id)", "tool: get_data\ncontract: WC-0048\nreturn: validated\nhealing: remembered"], -]; - -type DiffPhase = "valid" | "broken" | "healing" | "restored"; - -const diffStates: Record = { - valid: { label: "VALID", caption: "contract matches live data" }, - broken: { label: "VIOLATED", caption: "price field returned null" }, - healing: { label: "HEALING", caption: "selector diff proposed" }, - restored: { label: "RESTORED", caption: "contract verified in 14ms" }, -}; - -const TRACE_PATHS = [ - "M 860 -40 C 1030 120 930 280 730 350 C 540 420 430 300 500 180 C 565 70 760 125 770 300 C 785 515 520 570 260 680 C 40 775 90 1015 340 1035", - "M 120 -50 C -20 160 100 290 300 260 C 520 225 590 390 460 500 C 315 625 70 535 65 740 C 60 910 260 930 440 820 C 650 690 850 790 910 1040", - "M 920 -50 C 720 110 735 265 895 330 C 1040 390 920 540 720 495 C 500 445 340 585 465 720 C 590 850 805 755 875 900 C 920 990 830 1030 740 1050", - "M 60 -50 C 240 100 190 255 45 350 C -90 440 30 585 230 520 C 445 450 610 585 535 730 C 470 855 255 810 190 940 C 155 1010 245 1040 350 1050", - "M 850 -50 C 1030 150 850 260 680 220 C 500 175 405 320 550 445 C 700 575 920 480 930 690 C 940 865 750 875 625 790 C 470 685 295 780 330 935 C 345 1000 420 1035 500 1050", -]; - -export function LandingExperience() { - const reducedMotion = useReducedMotion(); - const hero = useRef(null); - const failure = useRef(null); - const pipelineSection = useRef(null); - const integrationsSection = useRef(null); - const foundation = useRef(null); - const { scrollYProgress } = useScroll({ target: hero, offset: ["start start", "end start"] }); - const orbY = useTransform(scrollYProgress, [0, 1], [0, reducedMotion ? 0 : 160]); - const copyY = useTransform(scrollYProgress, [0, 1], [0, reducedMotion ? 0 : -42]); - - return ( -
-
- ); -} - -function Nav() { - return LLIPPY
ProductDocsDashboard
; -} - -function ScrollTrace({ target, variant }: { target: RefObject; variant: number }) { - const reducedMotion = useReducedMotion(); - const { scrollYProgress } = useScroll({ target, offset: ["start end", "end start"] }); - const draw = useTransform(scrollYProgress, [0, .74], [0, 1]); - const opacity = useTransform(scrollYProgress, [0, .08, .55, .86, 1], [0, .78, .58, .16, .025]); - const gradientId = `trace-blue-${variant}`; - - return ( - - ); -} - -function HealingDiff({ reducedMotion }: { reducedMotion: boolean | null }) { - const [phase, setPhase] = useState(reducedMotion ? "restored" : "valid"); - - useEffect(() => { - if (reducedMotion === null) return; - if (reducedMotion) { - setPhase("restored"); - return; - } - const phases: DiffPhase[] = ["valid", "broken", "healing", "restored"]; - const timer = window.setInterval(() => { - setPhase((current) => phases[(phases.indexOf(current) + 1) % phases.length]!); - }, 2400); - return () => window.clearInterval(timer); - }, [reducedMotion]); - - const state = diffStates[phase]; - const price = phase === "broken" ? "null" : "189.00"; - - return ( -
-
HEALING DIFFWC-0048
-
{state.label}{state.caption}
-
- { - "product": "apple-airpods", - "price": {price}, - "stock": "in_stock", - "confidence": {phase === "healing" ? "0.97" : "0.94"} - } -
-
{phase === "healing" ? "selector .price -> .product-price" : "schema: product.v1"}{phase === "restored" ? "14ms" : "live"}
-
contract response{phase === "restored" ? "verified" : "watching"}
-
- ); -} - -function Fade({ children, className = "", delay = 0 }: { children: ReactNode; className?: string; delay?: number }) { - const reducedMotion = useReducedMotion(); - return {children}; -} diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..47108a7 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,18 @@ +services: + db: + image: postgres:16 + environment: + POSTGRES_USER: lippy + POSTGRES_PASSWORD: lippy + POSTGRES_DB: lippy + ports: + - "127.0.0.1:5432:5432" + volumes: + - lippy-postgres:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U lippy"] + interval: 5s + timeout: 5s + retries: 10 +volumes: + lippy-postgres: diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..defdb6b --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,86 @@ +# Architecture and failure walkthrough + +```mermaid +flowchart LR + API[REST / MCP + Google identity] --> Service[Owned contract service] + Service --> DB[(PostgreSQL: contracts, runs, event log)] + DB --> Worker[Worker: claim one persisted step] + Worker --> Bright[Bright Data async scraper / repair] + Bright --> Worker + Worker --> Validate[Schema and semantic rules] + Validate --> DB + DB --> SSE[Replayable SSE subscribers] + DB --> MCP[MCP event readers] +``` + +## Why two processes? + +An HTTP request's lifetime should not determine whether an extraction finishes. The API only validates input, checks ownership and commits a queued run. The worker claims a run, executes one state transition and releases the lease. Provider polling sleeps in PostgreSQL via `availableAt`, not in a long-running request. + +## Run state machine + +```mermaid +stateDiagram-v2 + queued --> provisioning: new live collector + provisioning --> generating + generating --> generation_poll + generation_poll --> extracting: ready + queued --> extracting: fixture / existing collector + extracting --> extraction_poll: live provider + extraction_poll --> validating: result ready + extracting --> validating: fixture + validating --> healing: contract violation + healing --> heal_poll: live provider + heal_poll --> heal_approve: proposed diff + heal_approve --> heal_poll: approved upstream + heal_poll --> extracting: repair complete + healing --> extracting: fixture selector repair + validating --> diffing: schema valid + diffing --> accepting + accepting --> valid: accepted without repair + accepting --> healed: accepted after repair + accepting --> review_required: change limit exceeded + review_required --> valid: explicit review accepts + review_required --> rejected: explicit review rejects +``` + +Any state can fail on timeout or an unrecoverable error. Repeated schema violations exhaust the repair budget and escalate. Side-effect states first write a `*_submitting` marker; recovery from this marker becomes `indeterminate` because the provider might already have processed the request. Human reconciliation prevents accidental duplicate jobs and charges. Known provider job IDs survive polling retries. + +## Ownership, transactions and delivery + +Every external REST/MCP operation resolves a principal and passes its user ID to the same service layer. Worker entry points are internal. Credentials are hashed at rest. Old unowned contracts are never implicitly claimed by the first person to log in. + +A contract row lock serializes enqueue, mutation and state changes. A lease token fences stale workers; expiry is checked at commit, and every provider request has a shorter timeout than the lease. Scheduler replicas recheck due time while holding the contract lock. Idempotency keys are scoped to contracts and remain valid after a run completes. + +The event table doubles as a transactional outbox and durable pub/sub log. A state transition and its events commit atomically. Per-contract publication is serialized before sequence allocation, preserving commit order for cursor readers. Subscribers read independently and reconnect using a sequence. Delivery is replayable and can be repeated: consumers deduplicate by event sequence. There is no separate broker acknowledgment or background webhook sender. + +## What proves healing? + +The fixture adapter stores actual HTML and selector mappings per contract. The layout mutation changes element IDs while retaining semantic `data-field` attributes. The old selectors stop resolving, validation produces field violations, and repair discovers stable semantic selectors. The worker then runs extraction again. Removed source values cannot be repaired by inventing a replacement, so that scenario exhausts the repair budget. + +The live adapter asks Bright Data to refactor its scraper and waits for completion after approval. An upstream approval is not local acceptance: schema and semantic checks still run before the latest data pointer can change. This version does not clone upstream scrapers or restore a previous upstream template. + +A price change from 100 to 110 is meaningful but acceptable under a 50% limit; 100 to 900 needs review. A formatting-only title change can normalize to equality. An `expectedValue` protects a known product identity; a numeric bound rejects an impossible negative price. These are explicit domain rules, not a claim to understand arbitrary text or prove every scraped value. + +## Failure exercises + +1. Start two workers and enqueue with the same idempotency key twice: one run is created. +2. Kill a worker during an ordinary persisted state: after the two-minute lease expires, another worker continues. The old token cannot commit. +3. Interrupt an external submission: the recovered run pauses as indeterminate, requiring provider reconciliation. +4. Break a fixture layout: the event log records violation, healing, re-extraction and acceptance using the same database run ID. +5. Remove the required source elements: three repair attempts fail, scheduling pauses, and last accepted data remains available. +6. Change a numeric value beyond its limit: candidate data is quarantined. Accept/reject it through the review endpoint, then explicitly resume scheduling if appropriate. +7. Reconnect an SSE client with `Last-Event-ID`: events resume after that cursor even from another API process. +8. Revoke an API token: future requests and the next SSE authentication check reject it. + +## Operating limits + +This implementation targets one entity per URL, bounded extraction runs and small-to-moderate subscription counts. One worker processes one short step at a time; run additional workers for concurrency. Persistent event history currently needs an operator-defined retention policy. SSE readers poll PostgreSQL every second. General document semantics, multi-record datasets, isolated upstream repair versions and outbound webhook delivery are future extensions. + +## Provider references + +- [Bright Data AI flow](https://docs.brightdata.com/api-reference/scraper-studio-api/ai-flow/overview) +- [Batch trigger](https://docs.brightdata.com/api-reference/scraper-studio-api/Trigger_a_scraper_for_batch_collection_method) +- [Batch results](https://docs.brightdata.com/api-reference/scraper-studio-api/Receive_batch_data) +- [Repair approval](https://docs.brightdata.com/api-reference/scraper-studio-api/ai-flow/resume-self-healing-job) +- [Google OpenID Connect](https://developers.google.com/identity/openid-connect/openid-connect) diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..68d65c8 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,8 @@ +import parser from "@typescript-eslint/parser"; +import nextPlugin from "@next/eslint-plugin-next"; +import plugin from "@typescript-eslint/eslint-plugin"; +export default [ + { ignores: ["node_modules/**", ".next/**", "next-env.d.ts", "coverage/**"] }, + { files: ["**/*.ts", "**/*.tsx"], languageOptions: { parser, parserOptions: { ecmaVersion: "latest", sourceType: "module" } }, plugins: { "@typescript-eslint": plugin, "@next/next": nextPlugin }, + rules: { "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }], "@typescript-eslint/no-floating-promises": "off" } }, +]; diff --git a/lib/auth/service.ts b/lib/auth/service.ts new file mode 100644 index 0000000..084be82 --- /dev/null +++ b/lib/auth/service.ts @@ -0,0 +1,71 @@ +import { createHash, randomBytes } from "node:crypto"; +import { createRemoteJWKSet, jwtVerify } from "jose"; +import { z } from "zod"; +import { db } from "../db"; +import { env } from "../env"; +import { HttpError } from "../errors"; +export const SESSION_COOKIE = "lippy_session"; +export const STATE_COOKIE = "lippy_oauth_state"; +export const hashToken = (token: string) => createHash("sha256").update(token).digest("hex"); +const randomToken = () => randomBytes(32).toString("base64url"); +export const cookieOptions = { httpOnly: true, sameSite: "lax" as const, secure: new URL(env.APP_URL).protocol === "https:", path: "/" }; +const jwks = createRemoteJWKSet(new URL("https://www.googleapis.com/oauth2/v3/certs")); +export function cookie(req: Request, name: string) { + return req.headers.get("cookie")?.split(";").map(x => x.trim()).find(x => x.startsWith(`${name}=`))?.slice(name.length + 1); +} +export async function requireUser(req: Request): Promise { + const origin = req.headers.get("origin"); + if (origin && origin !== new URL(env.APP_URL).origin) throw new HttpError(403, "ORIGIN_DENIED", "Cross-origin requests are not allowed"); + const authorization = req.headers.get("authorization"); + const token = authorization ? /^Bearer ([A-Za-z0-9_-]+)$/.exec(authorization)?.[1] : cookie(req, SESSION_COOKIE); + // Explicit, isolated local mode. Never permit a production auth bypass. + if (!authorization && !token && env.AUTH_MODE === "development" && env.NODE_ENV !== "production") { + const user = await db.user.upsert({ where: { id: "local-developer" }, create: { id: "local-developer", email: "developer@localhost" }, update: {} }); + return user.id; + } + if (!token) throw new HttpError(401, "UNAUTHENTICATED", "Sign in with Google or supply a valid API token"); + if (!authorization && !["GET", "HEAD", "OPTIONS"].includes(req.method) && origin !== new URL(env.APP_URL).origin) + throw new HttpError(403, "ORIGIN_REQUIRED", "Cookie-authenticated writes require the application Origin header"); + const session = await db.session.findUnique({ where: { tokenHash: hashToken(token) } }); + if (!session || session.expiresAt <= new Date() || (authorization && session.kind !== "api")) + throw new HttpError(401, "UNAUTHENTICATED", "Invalid or expired credentials"); + return session.userId; +} +export async function issueToken(userId: string, kind: "api" | "session") { + const token = randomToken(); + const expiresAt = new Date(Date.now() + (kind === "api" ? 30 : 7) * 86400000); + const session = await db.session.create({ data: { userId, kind, tokenHash: hashToken(token), expiresAt } }); + return { token, expiresAt, id: session.id }; +} +function requireGoogle() { + if (!env.GOOGLE_CLIENT_ID || !env.GOOGLE_CLIENT_SECRET) throw new HttpError(503, "AUTH_NOT_CONFIGURED", "Configure GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET"); + if (env.NODE_ENV === "production" && new URL(env.APP_URL).protocol !== "https:") throw new HttpError(503, "AUTH_NOT_CONFIGURED", "Production APP_URL must use HTTPS"); + return { clientId: env.GOOGLE_CLIENT_ID, clientSecret: env.GOOGLE_CLIENT_SECRET }; +} +export async function beginGoogleLogin() { + const { clientId } = requireGoogle(); + const state = randomToken(), verifier = randomToken(), nonce = randomToken(); + await db.oAuthAttempt.deleteMany({ where: { expiresAt: { lt: new Date() } } }); + await db.oAuthAttempt.create({ data: { stateHash: hashToken(state), verifier, nonce, expiresAt: new Date(Date.now() + 600000) } }); + const url = new URL("https://accounts.google.com/o/oauth2/v2/auth"); + url.search = new URLSearchParams({ client_id: clientId, redirect_uri: `${env.APP_URL}/api/auth/google/callback`, response_type: "code", scope: "openid email", state, nonce, + code_challenge: createHash("sha256").update(verifier).digest("base64url"), code_challenge_method: "S256" }).toString(); + return { state, url: url.toString() }; +} +export async function completeGoogleLogin(code: string, state: string, cookieState: string | undefined) { + const { clientId, clientSecret } = requireGoogle(); + if (!state || state !== cookieState) throw new HttpError(400, "INVALID_STATE", "OAuth state mismatch"); + const attempt = await db.oAuthAttempt.findUnique({ where: { stateHash: hashToken(state) } }); + if (!attempt || attempt.expiresAt <= new Date()) throw new HttpError(400, "INVALID_STATE", "OAuth login expired"); + const consumed = await db.oAuthAttempt.deleteMany({ where: { stateHash: attempt.stateHash } }); + if (!consumed.count) throw new HttpError(400, "INVALID_STATE", "OAuth login already used"); + const response = await fetch("https://oauth2.googleapis.com/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: new URLSearchParams({ code, client_id: clientId, client_secret: clientSecret, redirect_uri: `${env.APP_URL}/api/auth/google/callback`, grant_type: "authorization_code", code_verifier: attempt.verifier }), signal: AbortSignal.timeout(15000) }); + if (!response.ok) throw new HttpError(401, "GOOGLE_LOGIN_FAILED", "Google rejected the authorization code"); + const tokens = z.object({ id_token: z.string() }).parse(await response.json()); + const { payload } = await jwtVerify(tokens.id_token, jwks, { issuer: ["https://accounts.google.com", "accounts.google.com"], audience: clientId, algorithms: ["RS256"], requiredClaims: ["sub", "exp", "iat", "nonce"] }); + if ((payload.azp !== undefined && payload.azp !== clientId) || payload.nonce !== attempt.nonce || payload.email_verified !== true || !payload.sub || typeof payload.email !== "string") + throw new HttpError(401, "GOOGLE_LOGIN_FAILED", "Invalid Google identity"); + const user = await db.user.upsert({ where: { googleSub: payload.sub }, create: { googleSub: payload.sub, email: payload.email }, update: { email: payload.email } }); + return issueToken(user.id, "session"); +} diff --git a/lib/brightdata/client.ts b/lib/brightdata/client.ts index 40f4ad0..e8a113d 100644 --- a/lib/brightdata/client.ts +++ b/lib/brightdata/client.ts @@ -1,157 +1,58 @@ -import { env, isMockBrightData } from "../env"; -import { createLogger } from "../logger"; -import { ExtractionFailedError } from "../errors"; -import type { - CreateScraperInput, - CreateScraperResult, - CollectorRunResult, - RefactorTemplateInput, - RefactorProgress, -} from "./types"; - -const log = createLogger("brightdata-client"); - -/** - * Thin, typed wrapper over Bright Data Scraper Studio's AI Flow API. - * - * Two design choices worth calling out: - * - * 1. Every method mirrors a real Scraper Studio endpoint 1:1 - * (create -> AI Flow, run -> /dca/trigger, heal -> /refactor_template, - * approve -> /resume_automation_job) so swapping the mock implementation - * for live HTTP calls is a matter of filling in `fetch` calls in this - * one file — nothing upstream changes. - * - * 2. `resumeAutomationJob` exists as a first-class method because - * auto-approving the AI's proposed diff (rather than requiring a human - * to click "approve" in the Scraper Studio UI) is what makes the - * self-heal loop actually autonomous. Without it, "self-healing" stops - * at "self-diagnosing." - */ +import { z } from "zod"; +import { env } from "../env"; +const progressSchema = z.object({ status: z.enum(["in_progress", "pending_answer", "done", "failed", "queued", "running"]), step: z.string().optional() }).passthrough(); +export class ProviderError extends Error { + constructor(message: string, readonly retryable = false) { super(message); } +} +/** Each call performs one bounded HTTP operation. The worker persists between polls. */ export class BrightDataClient { - private readonly mock = isMockBrightData; - - async createScraper(input: CreateScraperInput): Promise { - if (this.mock) { - log.info("mock: createScraper", { url: input.url }); - return { collectorId: `c_mock_${hashString(input.url)}` }; - } - return this.request("/scraper/create", input); - } - - async runCollector(collectorId: string): Promise { - if (this.mock) { - log.info("mock: runCollector", { collectorId }); - return mockRun(collectorId); - } + constructor(private fetcher: typeof fetch = fetch) {} + private async request(path: string, body?: unknown): Promise<{ status: number; data: unknown }> { + if (!env.BRIGHTDATA_API_KEY) throw new ProviderError("BRIGHTDATA_API_KEY is required in brightdata mode"); + let response: Response; try { - return await this.request(`/dca/trigger`, { collectorId }); - } catch (err) { - throw new ExtractionFailedError(collectorId, err); - } + response = await this.fetcher(`${env.BRIGHTDATA_API_BASE}${path}`, { + method: body === undefined ? "GET" : "POST", redirect: "error", + headers: { Authorization: `Bearer ${env.BRIGHTDATA_API_KEY}`, "Content-Type": "application/json" }, + ...(body === undefined ? {} : { body: JSON.stringify(body) }), signal: AbortSignal.timeout(20000), + }); + } catch { throw new ProviderError("Bright Data request timed out or failed to connect", true); } + if (!response.ok) throw new ProviderError(`Bright Data returned HTTP ${response.status}`, response.status === 429 || response.status >= 500); + const text = await response.text(); + if (!text) return { status: response.status, data: null }; + try { return { status: response.status, data: JSON.parse(text) }; } + catch { throw new ProviderError("Bright Data returned malformed JSON"); } } - - /** Triggers Scraper Studio's AI self-healing for a broken field. */ - async refactorTemplate(input: RefactorTemplateInput): Promise<{ jobId: string }> { - if (this.mock) { - log.info("mock: refactorTemplate", input); - return { jobId: `job_mock_${hashString(input.prompt)}` }; - } - return this.request<{ jobId: string }>("/refactor_template", input); + async createScraper(name: string) { + if (!env.BRIGHTDATA_DELIVERY_EMAIL) throw new ProviderError("Set BRIGHTDATA_DELIVERY_EMAIL for scraper creation, or supply an existing collectorId"); + const { data } = await this.request("/dca/collector", { name, deliver: { type: "email", address: env.BRIGHTDATA_DELIVERY_EMAIL, filename: { template: "lippy", extension: "json" } } }); + return z.object({ id: z.string().min(1) }).parse(data).id; } - - async pollRefactorProgress(jobId: string): Promise { - if (this.mock) { - // Simulate: first poll finds a diff and asks for approval. - return { - status: "pending_answer", - diff: [{ field: "price", oldSelector: ".price", newSelector: "[data-testid='selling-price']" }], - }; - } - return this.request(`/refactor_template/progress?jobId=${jobId}`); + async generate(collectorId: string, url: string, description: string) { + await this.request(`/dca/collectors/${encodeURIComponent(collectorId)}/automate_template`, { urls: [url], description: description.slice(0, 500) }); } - - /** - * The auto-approve unlock: accepts the AI's proposed diff programmatically - * instead of requiring a human click in the Scraper Studio UI. - */ - async resumeAutomationJob(jobId: string): Promise<{ accepted: boolean }> { - if (this.mock) { - log.info("mock: resumeAutomationJob (auto-approve)", { jobId }); - // In mock mode a successful auto-approve clears the broken flag, so - // the next extraction returns the correct value. Real API would - // persist the updated selector config server-side. - isBroken = false; - return { accepted: true }; - } - return this.request<{ accepted: boolean }>("/resume_automation_job", { - jobId, - message: true, - auto_save: true, - }); + async progress(collectorId: string, healing: boolean) { + const { data } = await this.request(`/dca/collectors/${encodeURIComponent(collectorId)}/${healing ? "refactor_template" : "automate_template"}/progress`); + return progressSchema.parse(data); } - - private async request(path: string, body?: unknown): Promise { - const hasBody = body !== undefined; - const res = await fetch(`${env.BRIGHTDATA_API_BASE}${path}`, { - method: hasBody ? "POST" : "GET", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${env.BRIGHTDATA_API_KEY}`, - }, - ...(hasBody ? { body: JSON.stringify(body) } : {}), - }); - if (!res.ok) { - throw new Error(`Bright Data API ${path} responded ${res.status}: ${await res.text()}`); - } - return res.json() as Promise; + async trigger(collectorId: string, url: string, runId: string) { + const query = new URLSearchParams({ collector: collectorId, queue_next: "1", name: runId }); + const { data } = await this.request(`/dca/trigger?${query}`, [{ url }]); + return z.object({ collection_id: z.string().min(1) }).parse(data).collection_id; } -} - -export const brightData = new BrightDataClient(); - -/** - * Demo helper, mock-mode only. Re-breaks the mock so the next run - * violates and triggers the heal cycle again. Real API would not have - * this — you'd edit the page for real. Exposed via POST /api/dev/break - * for live demos. - */ -export function breakMockForDemo(): boolean { - if (!isMockBrightData) return false; - isBroken = true; - return true; -} - -// --- mock helpers ----------------------------------------------------- - -/** - * System-wide broken flag. The earlier shared-counter version could land - * on multiple broken positions in a row, escalating instead of healing - * — bad for a demo. A single flag that gets cleared by - * `resumeAutomationJob` (the auto-approve that "succeeded") gives the - * deterministic broken → heal → fixed cycle the SPEC's demo script - * actually shows. Defaults to `true` so the first run always violates - * and exercises the heal path. - */ -let isBroken = true; - -function mockRun(_collectorId: string): CollectorRunResult { - return { - collectorId: _collectorId, - fetchedAt: new Date().toISOString(), - data: { - product: "MacBook Air", - price: isBroken ? null : 99990, - stock: true, - }, - }; -} - -function hashString(input: string): string { - let hash = 0; - for (let i = 0; i < input.length; i++) { - hash = (hash << 5) - hash + input.charCodeAt(i); - hash |= 0; + async results(jobId: string): Promise | null> { + const { status, data } = await this.request(`/dca/dataset?id=${encodeURIComponent(jobId)}`); + if (status === 202) return null; + // A contract represents one page/entity. Never silently accept an arbitrary row. + const rows = z.array(z.record(z.unknown())).length(1).parse(data); + return rows[0]!; + } + async heal(collectorId: string, url: string, prompt: string) { + await this.request(`/dca/collectors/${encodeURIComponent(collectorId)}/refactor_template`, { prompt: prompt.slice(0, 1000), custom_input: [{ url }] }); + } + async approve(collectorId: string) { + // This endpoint legitimately returns 200 with no body. + await this.request(`/dca/collectors/${encodeURIComponent(collectorId)}/resume_automation_job`, { message: true, auto_save: true }); } - return Math.abs(hash).toString(36); } +export const brightData = new BrightDataClient(); diff --git a/lib/brightdata/types.ts b/lib/brightdata/types.ts deleted file mode 100644 index 49a342b..0000000 --- a/lib/brightdata/types.ts +++ /dev/null @@ -1,27 +0,0 @@ -export type CreateScraperInput = { - url: string; - description: string; -}; - -export type CreateScraperResult = { - collectorId: string; // c_* -}; - -export type CollectorRunResult = { - collectorId: string; - data: Record; - fetchedAt: string; -}; - -export type RefactorTemplateInput = { - collectorId: string; - /** Plain-language instruction, generated from the field(s) that failed validation. */ - prompt: string; -}; - -export type RefactorProgressStatus = "in_progress" | "pending_answer" | "done" | "failed"; - -export type RefactorProgress = { - status: RefactorProgressStatus; - diff?: { field: string; oldSelector?: string; newSelector: string }[]; -}; diff --git a/lib/contracts/schema.ts b/lib/contracts/schema.ts index b880d83..a0ee18e 100644 --- a/lib/contracts/schema.ts +++ b/lib/contracts/schema.ts @@ -1,51 +1,50 @@ import { z } from "zod"; - -/** - * A ContractField is user-authored (via the dashboard or MCP `create_contract` - * tool) and describes what "valid data from this source" means. It is the - * single source of truth: the same field list is used to (a) build the - * self-heal prompt Bright Data receives, (b) build the runtime Zod - * validator, and (c) render the contract in the dashboard — so there is no - * risk of the UI, the validator, and the heal prompt drifting apart. - */ export const contractFieldTypeSchema = z.enum(["string", "number", "boolean", "url"]); export type ContractFieldType = z.infer; - export const contractFieldSchema = z.object({ - key: z - .string() - .min(1) - .regex(/^[a-zA-Z_][a-zA-Z0-9_]*$/, "Field key must be a valid identifier."), + key: z.string().min(1).max(64).regex(/^[a-zA-Z_][a-zA-Z0-9_]*$/) + .refine(v => !["__proto__", "constructor", "prototype"].includes(v), "Reserved field name"), type: contractFieldTypeSchema, required: z.boolean().default(true), - /** Last known-good selector hint, fed to the heal prompt when this field breaks. */ - selectorHint: z.string().optional(), + description: z.string().max(1000).optional(), + selectorHint: z.string().max(500).optional(), + minimum: z.number().finite().optional(), + maximum: z.number().finite().optional(), + expectedValue: z.union([z.string(), z.number().finite(), z.boolean()]).optional(), + maxRelativeChange: z.number().nonnegative().optional(), + ignoreCase: z.boolean().default(false), +}).superRefine((f, ctx) => { + if (f.minimum !== undefined && f.maximum !== undefined && f.minimum > f.maximum) + ctx.addIssue({ code: "custom", message: "minimum exceeds maximum" }); + if (f.type !== "number" && [f.minimum, f.maximum, f.maxRelativeChange].some(v => v !== undefined)) + ctx.addIssue({ code: "custom", message: "Numeric constraints require a number field" }); + if (f.expectedValue !== undefined && typeof f.expectedValue !== (f.type === "url" ? "string" : f.type)) + ctx.addIssue({ code: "custom", message: "expectedValue must match the field type" }); }); export type ContractField = z.infer; - export const contractDefinitionSchema = z.object({ - url: z.string().url(), - fields: z.array(contractFieldSchema).min(1, "A contract needs at least one field."), - pollIntervalMs: z.number().int().positive().default(5 * 60 * 1000), + url: z.string().url().max(2048).refine(v => { + const u = new URL(v); return ["http:", "https:"].includes(u.protocol) && !u.username && !u.password; + }, "Use an HTTP(S) URL without credentials"), + fields: z.array(contractFieldSchema).min(1).max(50).refine(f => new Set(f.map(x => x.key)).size === f.length, "Duplicate field keys"), + pollIntervalMs: z.number().int().min(10000).max(2147483647).default(300000), + collectorId: z.string().regex(/^c_[a-zA-Z0-9_]+$/).optional(), + enabled: z.boolean().default(true), }); export type ContractDefinition = z.infer; - -const ZOD_BY_FIELD_TYPE: Record = { - string: z.string().min(1), - number: z.number().finite(), - boolean: z.boolean(), - url: z.string().url(), -}; - -/** - * Builds a Zod object schema from a contract's field list at runtime. This - * is what "the contract" actually compiles down to when we validate an - * extraction result — every run is checked against exactly this. - */ -export function buildRuntimeValidator(fields: ContractField[]): z.ZodObject { - const shape: z.ZodRawShape = {}; +export function buildRuntimeValidator(fields: ContractField[]) { + const shape: z.ZodRawShape = Object.create(null); for (const field of fields) { - const base = ZOD_BY_FIELD_TYPE[field.type]; + let base: z.ZodTypeAny; + if (field.type === "number") { + let n = z.number().finite(); + if (field.minimum !== undefined) n = n.min(field.minimum); + if (field.maximum !== undefined) n = n.max(field.maximum); + base = n; + } else if (field.type === "boolean") base = z.boolean(); + else if (field.type === "url") base = z.string().url(); + else base = z.string().trim().min(1); + if (field.expectedValue !== undefined) base = base.refine(v => v === field.expectedValue, "Value does not match the expected identity/value"); shape[field.key] = field.required ? base : base.nullable().optional(); } return z.object(shape); diff --git a/lib/contracts/service.ts b/lib/contracts/service.ts index 8d6219a..c6a8d3b 100644 --- a/lib/contracts/service.ts +++ b/lib/contracts/service.ts @@ -1,130 +1,100 @@ +import { randomUUID } from "node:crypto"; +import { z } from "zod"; import { db } from "../db"; -import { brightData } from "../brightdata/client"; -import { runContract } from "../selfheal/orchestrator"; -import { contractDefinitionSchema, type ContractDefinition, type ContractField } from "./schema"; +import { env } from "../env"; +import { publish, readEvents } from "../events/bus"; +import { contractDefinitionSchema } from "./schema"; import { ContractNotFoundError, RunAlreadyInFlightError } from "../errors"; - -/** - * All contract business logic lives here, not in API route handlers or MCP - * tool handlers — both of those are thin adapters that parse a request, - * call into this service, and shape a response. This means the MCP - * `create_contract` tool and the `POST /api/contracts` route can never - * silently diverge in behavior, because they call the exact same function. - */ - -const inFlightRuns = new Set(); - -export async function createContract(input: unknown) { - const definition: ContractDefinition = contractDefinitionSchema.parse(input); - - const { collectorId } = await brightData.createScraper({ - url: definition.url, - description: describeContractForScraperStudio(definition.fields), - }); - - const contract = await db.contract.create({ - data: { - url: definition.url, - schema: JSON.stringify(definition.fields), - pollIntervalMs: definition.pollIntervalMs, - collectorId, - status: "healthy", - }, - }); - - return contract; +import { createFixture } from "../fixtures/scraper"; +export async function createContract(input: unknown, ownerId: string) { + const d = contractDefinitionSchema.parse(input); + if (env.EXTRACTION_PROVIDER === "brightdata" && !env.BRIGHTDATA_API_KEY) throw new Error("BRIGHTDATA_API_KEY is missing"); + if (env.EXTRACTION_PROVIDER === "brightdata" && !d.collectorId && !env.BRIGHTDATA_DELIVERY_EMAIL) throw new Error("Set BRIGHTDATA_DELIVERY_EMAIL or provide collectorId"); + return db.contract.create({ data: { + ownerId, url: d.url, schema: JSON.stringify(d.fields), pollIntervalMs: d.pollIntervalMs, + enabled: d.enabled, collectorId: d.collectorId, provider: env.EXTRACTION_PROVIDER, + fixture: env.EXTRACTION_PROVIDER === "fixture" ? JSON.stringify(createFixture(d.fields)) : null, + } }); } - -export async function getContract(contractId: string) { - const contract = await db.contract.findUnique({ where: { id: contractId } }); - if (!contract) throw new ContractNotFoundError(contractId); - return contract; +export async function getContract(id: string, ownerId: string) { + const c = await db.contract.findFirst({ where: { id, ownerId } }); + if (!c) throw new ContractNotFoundError(id); + return c; } - -export async function listContracts() { - return db.contract.findMany({ orderBy: { createdAt: "desc" } }); +export async function listContracts(ownerId: string) { + return db.contract.findMany({ where: { ownerId }, orderBy: { createdAt: "desc" }, take: 100 }); } - -export async function triggerRun(contractId: string) { - if (inFlightRuns.has(contractId)) { - throw new RunAlreadyInFlightError(contractId); - } - inFlightRuns.add(contractId); - - try { - const contract = await getContract(contractId); - if (!contract.collectorId) { - throw new Error(`Contract "${contractId}" has no associated collector.`); +export async function triggerRun(contractId: string, ownerId: string, key?: string) { + await getContract(contractId, ownerId); + if (key !== undefined) z.string().min(1).max(128).parse(key); + return enqueueRun(contractId, key); +} +/** Internal scheduler entry point; callers outside this module must enforce ownership. */ +export async function enqueueRun(contractId: string, key?: string, scheduled = false) { + return db.$transaction(async tx => { + await tx.$queryRaw`SELECT id FROM "Contract" WHERE id = ${contractId} FOR UPDATE`; + const c = await tx.contract.findUniqueOrThrow({ where: { id: contractId } }); + if (key) { + const existing = await tx.run.findUnique({ where: { contractId_idempotencyKey: { contractId, idempotencyKey: key } } }); + if (existing) return existing; } - - const fields: ContractField[] = JSON.parse(contract.schema); - const lastRun = await db.run.findFirst({ - where: { contractId, outcome: { in: ["valid", "healed"] } }, - orderBy: { startedAt: "desc" }, - }); - const previousValues: Record = lastRun?.validated ? JSON.parse(lastRun.validated) : {}; - - const run = await db.run.create({ data: { contractId } }); - - try { - const result = await runContract({ - contractId, - collectorId: contract.collectorId, - fields, - previousValues, - }); - - await db.run.update({ - where: { id: run.id }, - data: { - finishedAt: new Date(), - outcome: result.outcome, - validated: JSON.stringify(result.data), - }, - }); - await db.contract.update({ where: { id: contractId }, data: { status: "healthy" } }); - - return result; - } catch (err) { - await db.run.update({ - where: { id: run.id }, - data: { finishedAt: new Date(), outcome: "escalated" }, - }); - await db.contract.update({ where: { id: contractId }, data: { status: "broken" } }); - throw err; + if (scheduled && (!c.enabled || c.nextRunAt > new Date())) return null; + if (c.activeRunId) { + if (scheduled) return null; + throw new RunAlreadyInFlightError(contractId); } - } finally { - inFlightRuns.delete(contractId); - } + if (c.status === "review_required" || c.status === "indeterminate") throw new RunAlreadyInFlightError(contractId); + const run = await tx.run.create({ data: { id: randomUUID(), contractId, idempotencyKey: key, deadlineAt: new Date(Date.now() + env.RUN_TIMEOUT_MS) } }); + await tx.contract.update({ where: { id: contractId }, data: { activeRunId: run.id, nextRunAt: new Date(Date.now() + c.pollIntervalMs) } }); + await publish(tx, { type: "run.queued", contractId, runId: run.id }); + return run; + }); } - -export async function getContractHistory(contractId: string) { - await getContract(contractId); // 404s if missing - const [runs, events] = await Promise.all([ - db.run.findMany({ where: { contractId }, orderBy: { startedAt: "desc" }, take: 50 }), - db.contractEvent.findMany({ where: { contractId }, orderBy: { createdAt: "desc" }, take: 100 }), - ]); +export async function getContractHistory(contractId: string, ownerId: string) { + await getContract(contractId, ownerId); + const runs = await db.run.findMany({ where: { contractId }, orderBy: { startedAt: "desc" }, take: 50, include: { repairs: true } }); + const events = await readEvents(contractId); return { runs, events }; } - -/** - * Single round-trip variant for the detail page: one contract fetch plus - * runs/events in parallel. The old page called getContract AND - * getContractHistory (which internally re-fetched the contract), tripling - * DB latency on every navigation — this exists so the page does exactly - * one await against a possibly-remote Postgres. - */ -export async function getContractWithHistory(contractId: string) { - const contract = await db.contract.findUnique({ where: { id: contractId } }); - if (!contract) throw new ContractNotFoundError(contractId); - const [runs, events] = await Promise.all([ - db.run.findMany({ where: { contractId }, orderBy: { startedAt: "desc" }, take: 50 }), - db.contractEvent.findMany({ where: { contractId }, orderBy: { createdAt: "desc" }, take: 100 }), - ]); - return { contract, runs, events }; +export async function getData(contractId: string, ownerId: string) { + const contract = await getContract(contractId, ownerId); + const run = await db.run.findFirst({ where: { contractId, outcome: { in: ["valid", "healed"] } }, orderBy: { finishedAt: "desc" } }); + return { contractId, status: contract.status, runId: run?.id ?? null, acceptedAt: run?.finishedAt ?? null, data: run?.validated ? JSON.parse(run.validated) : null }; +} +export async function getRun(runId: string, ownerId: string) { + const run = await db.run.findFirst({ where: { id: runId, contract: { ownerId } }, include: { repairs: true } }); + if (!run) throw new ContractNotFoundError(runId); + return run; +} +export async function setEnabled(contractId: string, ownerId: string, enabled: boolean) { + await getContract(contractId, ownerId); + return db.contract.update({ where: { id: contractId }, data: { enabled, nextRunAt: new Date() } }); } -function describeContractForScraperStudio(fields: ContractField[]): string { - const fieldList = fields.map((f) => `${f.key} (${f.type}${f.required ? ", required" : ""})`).join(", "); - return `Extract the following fields: ${fieldList}.`; +export async function reviewRun(runId: string, ownerId: string, decision: "accept" | "reject") { + const run = await getRun(runId, ownerId); + return db.$transaction(async tx => { + await tx.$queryRaw`SELECT id FROM "Contract" WHERE id = ${run.contractId} FOR UPDATE`; + const current = await tx.run.findUniqueOrThrow({ where: { id: runId } }); + if (current.outcome !== "review_required") throw new RunAlreadyInFlightError(run.contractId); + const outcome = decision === "accept" ? (current.healAttempts ? "healed" : "valid") : "rejected"; + const updated = await tx.run.update({ where: { id: runId }, data: { state: outcome, outcome, finishedAt: new Date() } }); + await tx.contract.update({ where: { id: run.contractId }, data: { status: decision === "accept" ? "healthy" : "broken", enabled: false } }); + await publish(tx, { type: `run.review_${decision}`, contractId: run.contractId, runId, reviewedBy: ownerId }); + return updated; + }); +} +export async function reconcileRun(runId: string, ownerId: string, note: string, collectorId?: string) { + const run = await getRun(runId, ownerId); + z.string().min(10).max(2000).parse(note); + if (collectorId) z.string().regex(/^c_[a-zA-Z0-9_]+$/).parse(collectorId); + return db.$transaction(async tx => { + await tx.$queryRaw`SELECT id FROM "Contract" WHERE id = ${run.contractId} FOR UPDATE`; + const current = await tx.run.findUniqueOrThrow({ where: { id: runId } }); + if (current.outcome !== "indeterminate") throw new RunAlreadyInFlightError(run.contractId); + await tx.run.update({ where: { id: runId }, data: { state: "reconciled", outcome: "reconciled" } }); + await tx.contract.update({ where: { id: run.contractId }, data: { status: "broken", enabled: false, ...(collectorId ? { collectorId } : {}) } }); + await publish(tx, { type: "run.reconciled", contractId: run.contractId, runId, note, reconciledBy: ownerId }); + return { runId, state: "reconciled" }; + }); } diff --git a/lib/contracts/validator.test.ts b/lib/contracts/validator.test.ts index 35ecfc4..25dec74 100644 --- a/lib/contracts/validator.test.ts +++ b/lib/contracts/validator.test.ts @@ -3,9 +3,9 @@ import { validateAgainstContract } from "@/lib/contracts/validator"; import type { ContractField } from "@/lib/contracts/schema"; const fields: ContractField[] = [ - { key: "product", type: "string", required: true }, - { key: "price", type: "number", required: true }, - { key: "stock", type: "boolean", required: false }, + { key: "product", type: "string", required: true, ignoreCase: false }, + { key: "price", type: "number", required: true, ignoreCase: false }, + { key: "stock", type: "boolean", required: false, ignoreCase: false }, ]; describe("validateAgainstContract", () => { diff --git a/lib/env.ts b/lib/env.ts index e25f6a7..e5c54c4 100644 --- a/lib/env.ts +++ b/lib/env.ts @@ -1,46 +1,19 @@ import { z } from "zod"; - -/** - * All environment access in the app goes through this module. Nowhere else - * should call `process.env.X` directly — that keeps the "is this var - * required, and did we validate it" question answered in exactly one place. - * - * API keys are intentionally optional: their absence flips the relevant - * client into mock mode (see lib/brightdata/client.ts and - * lib/parallel/client.ts) rather than crashing the app. This is what lets a - * judge clone the repo and run the full self-heal loop with zero setup. - */ +const optional = z.preprocess(v => v === "" ? undefined : v, z.string().optional()); const envSchema = z.object({ - // Required, no default: a missing DB URL is a boot-time error, never a - // silent fallback. The value itself lives only in .env. DATABASE_URL: z.string().min(1), - - BRIGHTDATA_API_KEY: z.string().optional(), + APP_URL: z.string().url().default("http://localhost:3000"), + AUTH_MODE: z.enum(["google", "development"]).default("google"), + GOOGLE_CLIENT_ID: optional, + GOOGLE_CLIENT_SECRET: optional, + EXTRACTION_PROVIDER: z.enum(["fixture", "brightdata"]).default("fixture"), + BRIGHTDATA_API_KEY: optional, BRIGHTDATA_API_BASE: z.string().url().default("https://api.brightdata.com"), - - PARALLEL_API_KEY: z.string().optional(), - PARALLEL_API_BASE: z.string().url().default("https://api.parallel.ai"), - - WEBHOOK_SIGNING_SECRET: z.string().default("dev-secret-change-me"), - - NODE_ENV: z - .enum(["development", "test", "production"]) - .default("development"), + BRIGHTDATA_DELIVERY_EMAIL: z.preprocess(v => v === "" ? undefined : v, z.string().email().optional()), + WORKER_POLL_MS: z.coerce.number().int().min(100).default(1000), + PROVIDER_POLL_MS: z.coerce.number().int().min(100).default(5000), + MAX_HEAL_ATTEMPTS: z.coerce.number().int().min(1).max(10).default(3), + RUN_TIMEOUT_MS: z.coerce.number().int().min(1000).max(86400000).default(1800000), + NODE_ENV: z.enum(["development", "test", "production"]).default("development"), }); - -export type Env = z.infer; - -function loadEnv(): Env { - const parsed = envSchema.safeParse(process.env); - if (!parsed.success) { - // Fail loudly at boot rather than surfacing a cryptic error mid-request. - console.error("Invalid environment configuration:", parsed.error.flatten().fieldErrors); - throw new Error("Invalid environment configuration — see logged field errors above."); - } - return parsed.data; -} - -export const env = loadEnv(); - -export const isMockBrightData = !env.BRIGHTDATA_API_KEY; -export const isMockParallel = !env.PARALLEL_API_KEY; +export const env = envSchema.parse(process.env); diff --git a/lib/errors.ts b/lib/errors.ts index af3a960..0142fb1 100644 --- a/lib/errors.ts +++ b/lib/errors.ts @@ -77,3 +77,7 @@ export function toErrorResponse(err: unknown): { status: number; body: { error: body: { error: { code: "INTERNAL_ERROR", message: "Something went wrong." } }, }; } + +export class HttpError extends AppError { + constructor(readonly status: number, readonly code: string, message: string) { super(message); } +} diff --git a/lib/events/bus.ts b/lib/events/bus.ts index 36d1c25..5aad9a4 100644 --- a/lib/events/bus.ts +++ b/lib/events/bus.ts @@ -1,45 +1,16 @@ +import type { Prisma } from "@prisma/client"; import { db } from "../db"; -import { createLogger } from "../logger"; import type { ContractEvent } from "./types"; - -const log = createLogger("event-bus"); - -type Subscriber = (event: ContractEvent) => void; - -/** - * Minimal in-process pub/sub. For a single-instance hackathon deployment - * this is the right amount of infrastructure — it's trivially swappable for - * Redis pub/sub or a queue later because every publisher/consumer only ever - * talks to `publish`/`subscribe`, never to a transport detail. +/** The durable event log is the pub/sub transport. Each subscriber owns its cursor. + * Publishers use the state-change transaction: an event cannot exist without its state. */ -class EventBus { - private subscribers = new Set(); - - subscribe(fn: Subscriber): () => void { - this.subscribers.add(fn); - return () => this.subscribers.delete(fn); - } - - async publish(event: ContractEvent): Promise { - log.info("event", { type: event.type, contractId: event.contractId }); - - // Persist first so the event is durable even if a subscriber throws. - await db.contractEvent.create({ - data: { - contractId: event.contractId, - type: event.type, - payload: JSON.stringify(event), - }, - }); - - for (const subscriber of this.subscribers) { - try { - subscriber(event); - } catch (err) { - log.error("subscriber threw", { err: String(err) }); - } - } - } +export async function publish(tx: Prisma.TransactionClient, event: ContractEvent) { + await tx.$queryRaw`SELECT id FROM "Contract" WHERE id = ${event.contractId} FOR UPDATE`; + return tx.contractEvent.create({ data: { + contractId: event.contractId, runId: event.runId, type: event.type, payload: JSON.stringify(event), + } }); +} +export async function readEvents(contractId: string, after = 0n, take = 100) { + const events = await db.contractEvent.findMany({ where: { contractId, sequence: { gt: after } }, orderBy: { sequence: "asc" }, take }); + return events.map(({ sequence, ...event }) => ({ ...event, sequence: sequence.toString(), data: JSON.parse(event.payload) })); } - -export const eventBus = new EventBus(); diff --git a/lib/events/types.ts b/lib/events/types.ts index 1dd0d87..e5924dc 100644 --- a/lib/events/types.ts +++ b/lib/events/types.ts @@ -1,54 +1,8 @@ -import type { FieldViolation } from "@/lib/contracts/validator"; - -/** - * One discriminated union for "what happened" — emitted by the orchestrator, - * persisted by the bus, fanned out to SSE subscribers and webhook - * deliveries. The shape is the source of truth, so every consumer parses - * the same way. The `type` literal acts as the discriminator. - */ - -export type SelectorDiff = { - field: string; - oldSelector?: string; - newSelector: string; +export type SelectorDiff = { field: string; oldSelector?: string; newSelector: string }; +export type ContractEvent = { + type: string; + contractId: string; + runId?: string; + [key: string]: unknown; }; - -export type ContractEvent = - | { - type: "contract.violated"; - contractId: string; - runId: string; - violations: FieldViolation[]; - } - | { - type: "contract.healing"; - contractId: string; - runId: string; - attempt: number; - } - | { - type: "contract.healed"; - contractId: string; - runId: string; - attempt: number; - selectorDiff: SelectorDiff[] | undefined; - } - | { - type: "contract.escalated"; - contractId: string; - runId: string; - attempts: number; - reason: string; - } - | { - type: "field.changed"; - contractId: string; - runId: string; - field: string; - previousValue: unknown; - currentValue: unknown; - confidence: number; - summary: string; - }; - export type ContractEventType = ContractEvent["type"]; diff --git a/lib/fixtures/scraper.ts b/lib/fixtures/scraper.ts new file mode 100644 index 0000000..9294c88 --- /dev/null +++ b/lib/fixtures/scraper.ts @@ -0,0 +1,42 @@ +import { load } from "cheerio"; +import type { ContractField } from "../contracts/schema"; +export type Fixture = { html: string; selectors: Record }; +const escape = (value: unknown) => String(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll('"', """); +export function createFixture(fields: ContractField[]): Fixture { + const selectors: Record = {}; + const html = fields.map(field => { + selectors[field.key] = `#old_${field.key}`; + const value = field.expectedValue ?? (field.type === "number" ? Math.min(field.maximum ?? Number.MAX_VALUE, Math.max(field.minimum ?? -Number.MAX_VALUE, 100)) : field.type === "boolean" ? true : field.type === "url" ? "https://example.com/item" : field.key === "product" ? "Example product" : `Example ${field.key}`); + return `${escape(value)}`; + }).join(""); + return { html, selectors }; +} +export function extractFixture(fixture: Fixture, fields: ContractField[]): Record { + const $ = load(fixture.html); + return Object.fromEntries(fields.map(field => { + const matches = $(fixture.selectors[field.key] ?? `#missing_${field.key}`); + if (matches.length !== 1) return [field.key, null]; + const text = matches.text().trim(); + const value = field.type === "number" ? (text !== "" && Number.isFinite(Number(text)) ? Number(text) : text) + : field.type === "boolean" ? (text === "true" ? true : text === "false" ? false : text) : text; + return [field.key, value]; + })); +} +export function repairFixture(fixture: Fixture, fields: ContractField[]) { + const $ = load(fixture.html); + const selectors = { ...fixture.selectors }; + const diff = fields.flatMap(field => { + const next = `[data-field="${field.key}"]`; + if ($(next).length !== 1 || selectors[field.key] === next) return []; + const oldSelector = selectors[field.key]; selectors[field.key] = next; + return [{ field: field.key, oldSelector, newSelector: next }]; + }); + return { fixture: { ...fixture, selectors }, diff }; +} +export function mutateFixture(fixture: Fixture, scenario: "layout" | "value" | "unrepairable", field?: string, value?: string): Fixture { + const $ = load(fixture.html); + if (scenario === "layout") $("[data-field]").each((_, el) => { $(el).attr("id", `new_${$(el).attr("data-field")}`); }); + if (scenario === "unrepairable") $("[data-field]").remove(); + if (scenario === "value" && field) $(`[data-field="${field}"]`).text(value ?? ""); + return { html: $.html(), selectors: fixture.selectors }; +} diff --git a/lib/http.ts b/lib/http.ts new file mode 100644 index 0000000..4bfa942 --- /dev/null +++ b/lib/http.ts @@ -0,0 +1,16 @@ +import { NextResponse } from "next/server"; +import { toErrorResponse, HttpError } from "./errors"; +export async function endpoint(action: () => Promise) { + try { return await action(); } + catch (error) { const { status, body } = toErrorResponse(error); return NextResponse.json(body, { status }); } +} +export async function body(req: Request): Promise { + const text = await req.text(); + if (text.length > 65536) throw new HttpError(413, "BODY_TOO_LARGE", "Maximum request body is 64 KiB"); + try { return JSON.parse(text); } catch { throw new HttpError(400, "INVALID_JSON", "Malformed JSON body"); } +} +export function cursor(value: string | null) { + if (value === null || value === "") return 0n; + if (!/^\d{1,18}$/.test(value)) throw new HttpError(400, "INVALID_CURSOR", "Use the event sequence as the cursor"); + return BigInt(value); +} diff --git a/lib/jobs/worker.ts b/lib/jobs/worker.ts new file mode 100644 index 0000000..4c2cdbd --- /dev/null +++ b/lib/jobs/worker.ts @@ -0,0 +1,177 @@ +import { randomUUID } from "node:crypto"; +import type { Prisma, Run } from "@prisma/client"; +import { db } from "../db"; +import { env } from "../env"; +import { publish } from "../events/bus"; +import { enqueueRun } from "../contracts/service"; +import { contractFieldSchema } from "../contracts/schema"; +import { validateAgainstContract } from "../contracts/validator"; +import { diffFields } from "../semantic/diff"; +import { extractFixture, repairFixture, type Fixture } from "../fixtures/scraper"; +import { brightData, ProviderError } from "../brightdata/client"; +const LEASE_MS = 120000; +export class LeaseLostError extends Error {} +export async function claimRun(): Promise { + const token = randomUUID(); + const rows = await db.$queryRaw` + UPDATE "Run" SET "leaseToken" = ${token}, "leaseUntil" = NOW() + INTERVAL '120 seconds' + WHERE id = (SELECT id FROM "Run" WHERE "finishedAt" IS NULL AND "availableAt" <= NOW() + AND ("leaseUntil" IS NULL OR "leaseUntil" < NOW()) ORDER BY "availableAt", "startedAt" + FOR UPDATE SKIP LOCKED LIMIT 1) RETURNING *`; + return rows[0] ?? null; +} +/** A fencing token and lease deadline prevent a stale worker from committing results. */ +async function transition(run: Run, state: string, data: Prisma.RunUpdateManyMutationInput = {}, extra?: (tx: Prisma.TransactionClient) => Promise, release = true) { + const result = await db.$transaction(async tx => { + await tx.$queryRaw`SELECT id FROM "Contract" WHERE id = ${run.contractId} FOR UPDATE`; + const result = await tx.run.updateMany({ where: { id: run.id, leaseToken: run.leaseToken, leaseUntil: { gt: new Date() }, finishedAt: null }, data: { + state, ...(state !== run.state ? { retryCount: 0, error: null } : {}), ...data, ...(release ? { leaseToken: null, leaseUntil: null } : {}), + } }); + if (!result.count) throw new LeaseLostError("Worker lease expired or was replaced"); + if (extra) await extra(tx); + if (state !== run.state) await publish(tx, { type: `run.${state}`, contractId: run.contractId, runId: run.id, previousState: run.state }); + return tx.run.findUniqueOrThrow({ where: { id: run.id } }); + }); + Object.assign(run, result); +} +async function finish(run: Run, outcome: string, error?: string) { + await transition(run, outcome, { outcome, finishedAt: new Date(), error: error ?? null }, async tx => { + const c = await tx.contract.findUniqueOrThrow({ where: { id: run.contractId } }); + await tx.contract.update({ where: { id: c.id }, data: { + activeRunId: null, status: outcome === "valid" || outcome === "healed" ? "healthy" : outcome === "review_required" || outcome === "indeterminate" ? outcome : "broken", + enabled: ["review_required", "indeterminate", "escalated", "failed"].includes(outcome) ? false : c.enabled, + nextRunAt: new Date(Date.now() + c.pollIntervalMs), + } }); + if (run.healAttempts > 0) await tx.repairAttempt.updateMany({ where: { runId: run.id, attempt: run.healAttempts }, data: { status: outcome } }); + await publish(tx, { type: `contract.${outcome === "valid" ? "healthy" : outcome}`, contractId: run.contractId, runId: run.id, reason: error }); + }); +} +async function waitForPoll(run: Run) { + await transition(run, run.state, { availableAt: new Date(Date.now() + env.PROVIDER_POLL_MS), retryCount: 0, error: null }); +} +/** Persist intent before a non-idempotent provider call. A crash in this window requires + * reconciliation, not blindly replaying a potentially billable external operation. */ +async function submit(run: Run, state: string, action: () => Promise) { + await transition(run, `${state}_submitting`, {}, undefined, false); + await action(); +} +export async function processRun(run: Run): Promise { + try { + if (run.state.endsWith("_submitting")) { await finish(run, "indeterminate", "Provider submission interrupted; reconcile the provider job before starting another run"); return; } + if (run.deadlineAt <= new Date()) { await finish(run, "failed", "Run deadline exceeded"); return; } + const c = await db.contract.findUniqueOrThrow({ where: { id: run.contractId } }); + const fields = contractFieldSchema.array().parse(JSON.parse(c.schema)); + const fixture = c.fixture ? JSON.parse(c.fixture) as Fixture : null; + const collector = c.collectorId; + switch (run.state) { + case "queued": + await transition(run, c.provider === "fixture" || collector ? "extracting" : "provisioning"); return; + case "provisioning": + await submit(run, "provisioning", async () => { + const id = await brightData.createScraper(`Lippy ${c.id}`); + await transition(run, "generating", {}, async tx => { await tx.contract.update({ where: { id: c.id }, data: { collectorId: id } }); }); + }); return; + case "generating": + await submit(run, "generating", async () => { + await brightData.generate(collector!, c.url, `Extract one record using these exact keys and types: ${fields.map(f => `${f.key}:${f.type}${f.description ? ` (${f.description})` : ""}`).join(", ")}`); + await transition(run, "generation_poll"); + }); return; + case "generation_poll": { + const progress = await brightData.progress(collector!, false); + if (progress.status === "done") await transition(run, "extracting"); + else if (progress.status === "failed" || progress.status === "pending_answer") await finish(run, "failed", "Scraper generation failed or needs input in Bright Data Studio"); + else await waitForPoll(run); + return; + } + case "extracting": + if (fixture) { await transition(run, "validating", { rawOutput: JSON.stringify(extractFixture(fixture, fields)) }); return; } + await submit(run, "extracting", async () => { + const providerJobId = await brightData.trigger(collector!, c.url, run.id); + await transition(run, "extraction_poll", { providerJobId }); + }); return; + case "extraction_poll": { + const data = await brightData.results(run.providerJobId!); + if (data) await transition(run, "validating", { rawOutput: JSON.stringify(data), retryCount: 0 }); + else await waitForPoll(run); + return; + } + case "validating": { + const validation = validateAgainstContract(fields, JSON.parse(run.rawOutput!)); + if (validation.ok) { await transition(run, "diffing", { validated: JSON.stringify(validation.data) }); return; } + const violations = JSON.stringify(validation.violations); + if (run.healAttempts >= env.MAX_HEAL_ATTEMPTS) { await finish(run, "escalated", "Repair attempts exhausted"); return; } + const attempt = run.healAttempts + 1; + await transition(run, "healing", { violations, healAttempts: attempt }, async tx => { + await tx.contract.update({ where: { id: c.id }, data: { status: "healing" } }); + if (attempt > 1) await tx.repairAttempt.updateMany({ where: { runId: run.id, attempt: attempt - 1 }, data: { status: "rejected" } }); + await tx.repairAttempt.create({ data: { runId: run.id, attempt, status: "requested", violations } }); + await publish(tx, { type: "contract.violated", contractId: c.id, runId: run.id, violations: validation.violations }); + await publish(tx, { type: "contract.healing", contractId: c.id, runId: run.id, attempt }); + }); return; + } + case "healing": + if (fixture) { + const repaired = repairFixture(fixture, fields); + await transition(run, "extracting", {}, async tx => { + await tx.contract.update({ where: { id: c.id }, data: { fixture: JSON.stringify(repaired.fixture) } }); + await tx.repairAttempt.update({ where: { runId_attempt: { runId: run.id, attempt: run.healAttempts } }, data: { status: "verifying", proposal: JSON.stringify(repaired.diff) } }); + }); return; + } + await submit(run, "healing", async () => { + await brightData.heal(collector!, c.url, `Repair extraction selectors without changing field meaning or output schema. Field definitions: ${JSON.stringify(fields)}. Violations: ${run.violations}`); + await transition(run, "heal_poll"); + }); return; + case "heal_poll": { + const progress = await brightData.progress(collector!, true); + if (progress.status === "failed") { await finish(run, "escalated", "Bright Data repair failed"); return; } + if (progress.status === "done") { await transition(run, "extracting"); return; } + if (progress.status === "pending_answer") { + if (progress.step !== "user_approval") { await finish(run, "escalated", "Repair requires an answer beyond diff approval"); return; } + const attempt = await db.repairAttempt.findUniqueOrThrow({ where: { runId_attempt: { runId: run.id, attempt: run.healAttempts } } }); + if (attempt.status === "approved") { await waitForPoll(run); return; } + await transition(run, "heal_approve", {}, async tx => { + await tx.repairAttempt.update({ where: { runId_attempt: { runId: run.id, attempt: run.healAttempts } }, data: { status: "proposed", proposal: JSON.stringify(progress) } }); + }); return; + } + await waitForPoll(run); return; + } + case "heal_approve": + await submit(run, "heal_approve", async () => { + await brightData.approve(collector!); + await transition(run, "heal_poll", { availableAt: new Date(Date.now() + env.PROVIDER_POLL_MS) }, async tx => { + await tx.repairAttempt.update({ where: { runId_attempt: { runId: run.id, attempt: run.healAttempts } }, data: { status: "approved" } }); + }); + }); return; + case "diffing": { + const previous = await db.run.findFirst({ where: { contractId: c.id, outcome: { in: ["valid", "healed"] } }, orderBy: { finishedAt: "desc" } }); + const changes = diffFields(fields, previous?.validated ? JSON.parse(previous.validated) : null, JSON.parse(run.validated!)); + await transition(run, "accepting", { fieldChanges: JSON.stringify(changes) }, async tx => { + for (const change of changes) await publish(tx, { type: change.reviewRequired ? "field.suspicious" : "field.changed", contractId: c.id, runId: run.id, ...change }); + }); return; + } + case "accepting": { + const changes = JSON.parse(run.fieldChanges ?? "[]") as { reviewRequired: boolean }[]; + await finish(run, changes.some(c => c.reviewRequired) ? "review_required" : run.healAttempts > 0 ? "healed" : "valid"); return; + } + default: await finish(run, "failed", `Unknown persisted state: ${run.state}`); + } + } catch (error) { + if (error instanceof LeaseLostError) return; + const message = error instanceof Error ? error.message : "Unknown worker error"; + if (run.state.endsWith("_submitting")) { await finish(run, "indeterminate", message); return; } + if (error instanceof ProviderError && error.retryable && run.retryCount < 5) { + await transition(run, run.state, { retryCount: run.retryCount + 1, error: message, availableAt: new Date(Date.now() + Math.min(60000, 1000 * 2 ** run.retryCount)) }); + } else await finish(run, "failed", message); + } +} +export async function scheduleDue() { + const due = await db.contract.findMany({ where: { enabled: true, activeRunId: null, nextRunAt: { lte: new Date() }, status: { notIn: ["review_required", "indeterminate"] } }, take: 100, orderBy: { nextRunAt: "asc" } }); + for (const c of due) await enqueueRun(c.id, undefined, true); +} +export async function tick() { + await scheduleDue(); + const run = await claimRun(); + if (run) await processRun(run); + return Boolean(run); +} +export { LEASE_MS }; diff --git a/lib/parallel/client.ts b/lib/parallel/client.ts deleted file mode 100644 index f050c51..0000000 --- a/lib/parallel/client.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { env, isMockParallel } from "@/lib/env"; -import { createLogger } from "@/lib/logger"; -import type { ContractField } from "@/lib/contracts/schema"; - -const log = createLogger("parallel-client"); - -/** - * Semantic-diff client. Parallel AI's job is to answer "did this field's - * value change in a *meaningful* way?" — not just "are the strings - * different?". Two prices that differ only in formatting ("$1,299" vs - * "1299.00") are not a semantic change; a price that dropped 20% is. - * - * Mirrors `BrightDataClient`'s shape so swapping mock for live HTTP is a - * single-file change. In mock mode we approximate with a strict-equality - * check, which is enough to exercise the wiring during the demo. - */ -export type FieldDiffInput = { - field: string; - type: ContractField["type"]; - previousValue: unknown; - currentValue: unknown; -}; - -export type FieldDiffResult = { - changed: boolean; - confidence: number; - summary: string; -}; - -export class ParallelClient { - private readonly mock = isMockParallel; - - async diffField(input: FieldDiffInput): Promise { - if (this.mock) { - return mockDiffField(input); - } - return this.request("/v1/diff/field", input); - } - - private async request(path: string, body: unknown): Promise { - const res = await fetch(`${env.PARALLEL_API_BASE}${path}`, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${env.PARALLEL_API_KEY}`, - }, - body: JSON.stringify(body), - }); - if (!res.ok) { - log.warn("parallel request failed, returning no-change", { path, status: res.status }); - return { changed: false, confidence: 0, summary: "" } as T; - } - return res.json() as Promise; - } -} - -export const parallel = new ParallelClient(); - -function mockDiffField(input: FieldDiffInput): FieldDiffResult { - const { field, previousValue, currentValue, type } = input; - - // No previous value (first run) -> never a semantic change, just initialization. - if (previousValue === undefined || previousValue === null) { - return { changed: false, confidence: 1, summary: "" }; - } - - const samePrimitive = previousValue === currentValue; - if (samePrimitive) { - return { changed: false, confidence: 1, summary: "" }; - } - - // For numeric fields, surface the magnitude of the change as the summary. - // This is the bit the demo script leans on: "price dropped by 12%". - if (type === "number" && typeof previousValue === "number" && typeof currentValue === "number") { - const delta = currentValue - previousValue; - const pct = previousValue === 0 ? 0 : (delta / previousValue) * 100; - const direction = delta < 0 ? "decreased" : "increased"; - return { - changed: true, - confidence: 0.9, - summary: `${field} ${direction} by ${Math.abs(pct).toFixed(1)}% (${previousValue} -> ${currentValue})`, - }; - } - - return { - changed: true, - confidence: 0.85, - summary: `${field} changed from ${JSON.stringify(previousValue)} to ${JSON.stringify(currentValue)}`, - }; -} diff --git a/lib/selfheal/orchestrator.ts b/lib/selfheal/orchestrator.ts index b20fa9e..ef7db70 100644 --- a/lib/selfheal/orchestrator.ts +++ b/lib/selfheal/orchestrator.ts @@ -1,138 +1,2 @@ -import { brightData } from "../brightdata/client"; -import { parallel } from "../parallel/client"; -import { validateAgainstContract, type FieldViolation } from "../contracts/validator"; -import type { ContractField } from "../contracts/schema"; -import { eventBus } from "../events/bus"; -import { HealExhaustedError } from "../errors"; -import { createLogger } from "../logger"; - -const log = createLogger("self-heal-orchestrator"); - -const MAX_HEAL_ATTEMPTS = 3; - -export type OrchestratorResult = { - outcome: "valid" | "healed" | "escalated"; - data: Record | null; - fieldChanges: { field: string; previousValue: unknown; currentValue: unknown; confidence: number }[]; -}; - -/** - * The core loop. Modeled as an explicit sequence of named states rather than - * nested if/else so it reads as a state machine and is unit-testable step - * by step: - * - * extracting -> validating -> (healthy: diffing -> done) - * -> (violated: healing -> verifying -> done) - * -> (still broken, retry up to MAX_HEAL_ATTEMPTS) - * -> (exhausted: escalating -> done) - */ -export async function runContract(args: { - contractId: string; - collectorId: string; - fields: ContractField[]; - previousValues: Record; -}): Promise { - const { contractId, collectorId, fields, previousValues } = args; - const runId = `run_${Date.now()}`; - - // 1. extracting - let extraction = await brightData.runCollector(collectorId); - - // 2. validating - let validation = validateAgainstContract(fields, extraction.data); - - // 3. heal loop, only entered if validation failed - let attempt = 0; - while (!validation.ok && attempt < MAX_HEAL_ATTEMPTS) { - attempt += 1; - await eventBus.publish({ - type: "contract.violated", - contractId, - runId, - violations: validation.violations, - }); - await eventBus.publish({ type: "contract.healing", contractId, runId, attempt }); - - const healed = await attemptHeal(collectorId, validation.violations); - - extraction = await brightData.runCollector(collectorId); - validation = validateAgainstContract(fields, extraction.data); - - if (validation.ok) { - await eventBus.publish({ - type: "contract.healed", - contractId, - runId, - attempt, - selectorDiff: healed.diff, - }); - } - } - - // 4. escalate if still broken after MAX_HEAL_ATTEMPTS - if (!validation.ok) { - await eventBus.publish({ - type: "contract.escalated", - contractId, - runId, - attempts: attempt, - reason: describeViolations(validation.violations), - }); - log.error("heal exhausted, escalating", { contractId, attempts: attempt }); - throw new HealExhaustedError(contractId, attempt); - } - - // 5. semantic diff against previous known-good values, field by field - const fieldChanges: OrchestratorResult["fieldChanges"] = []; - for (const field of fields) { - const previousValue = previousValues[field.key]; - const currentValue = validation.data[field.key]; - const diff = await parallel.diffField({ field: field.key, type: field.type, previousValue, currentValue }); - - if (diff.changed) { - fieldChanges.push({ field: field.key, previousValue, currentValue, confidence: diff.confidence }); - await eventBus.publish({ - type: "field.changed", - contractId, - runId, - field: field.key, - previousValue, - currentValue, - confidence: diff.confidence, - summary: diff.summary, - }); - } - } - - return { - outcome: attempt > 0 ? "healed" : "valid", - data: validation.data, - fieldChanges, - }; -} - -async function attemptHeal(collectorId: string, violations: FieldViolation[]) { - const { jobId } = await brightData.refactorTemplate({ - collectorId, - prompt: buildHealPrompt(violations), - }); - - const progress = await brightData.pollRefactorProgress(jobId); - - if (progress.status === "pending_answer") { - // The auto-approve unlock: accept the AI's proposed diff without a - // human clicking through the Scraper Studio UI. - await brightData.resumeAutomationJob(jobId); - } - - return progress; -} - -function buildHealPrompt(violations: FieldViolation[]): string { - const fieldList = violations.map((v) => `"${v.key}" (expected ${v.expectedType}, got ${JSON.stringify(v.received)})`); - return `The following fields are no longer extracting correctly: ${fieldList.join(", ")}. Update the selectors to correctly extract these fields.`; -} - -function describeViolations(violations: FieldViolation[]): string { - return violations.map((v) => `${v.key}: expected ${v.expectedType}, got ${JSON.stringify(v.received)}`).join("; "); -} +// The orchestrator now advances one persisted state per worker claim. +export { processRun, claimRun, tick } from "../jobs/worker"; diff --git a/lib/semantic/diff.ts b/lib/semantic/diff.ts new file mode 100644 index 0000000..abe90a6 --- /dev/null +++ b/lib/semantic/diff.ts @@ -0,0 +1,25 @@ +import type { ContractField } from "../contracts/schema"; +export type FieldChange = { field: string; previousValue: unknown; currentValue: unknown; confidence: number; summary: string; reviewRequired: boolean }; +function canonical(value: unknown, field: ContractField): unknown { + if (value == null) return null; + if (typeof value !== "string") return value; + let result = value.normalize("NFKC").trim().replace(/\s+/g, " "); + if (field.type === "url") { const url = new URL(result); url.hash = ""; url.searchParams.sort(); return url.toString(); } + if (field.ignoreCase) result = result.toLowerCase(); + return result; +} +/** Deterministic meaning under the contract's explicit normalization and change policy. */ +export function diffFields(fields: ContractField[], previous: Record | null, current: Record): FieldChange[] { + if (!previous) return []; + return fields.flatMap(field => { + const before = previous[field.key], after = current[field.key]; + if (canonical(before, field) === canonical(after, field)) return []; + let reviewRequired = false; + if (field.maxRelativeChange !== undefined && typeof before === "number" && typeof after === "number") { + reviewRequired = before === 0 ? after !== 0 : Math.abs((after - before) / before) > field.maxRelativeChange; + } + return [{ field: field.key, previousValue: before ?? null, currentValue: after ?? null, confidence: 1, + summary: `${field.key}: ${JSON.stringify(before ?? null)} → ${JSON.stringify(after ?? null)}`, + reviewRequired }]; + }); +} diff --git a/mcp/server.ts b/mcp/server.ts index 7bdb58b..5e73e32 100644 --- a/mcp/server.ts +++ b/mcp/server.ts @@ -1,64 +1,23 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; -import { createContract, getContract, getContractHistory, triggerRun } from "@/lib/contracts/service"; -import { contractFieldSchema } from "@/lib/contracts/schema"; - -/** - * The MCP surface is intentionally a thin wrapper over the exact same - * `lib/contracts/service.ts` functions the HTTP API routes call. An agent - * calling `create_contract` and a person using the dashboard's "New - * Contract" form go through identical validation and identical Bright Data - * calls — there's exactly one code path for "create a contract," not two - * that could drift. - */ -export function createMcpServer() { - const server = new McpServer({ name: "web-contracts", version: "0.1.0" }); - - server.registerTool( - "create_contract", - { - description: - "Define a durable data contract for a website: a URL plus a typed schema of fields to extract. Returns a contract id.", - inputSchema: { - url: z.string().url(), - fields: z.array(contractFieldSchema), - pollIntervalMs: z.number().int().positive().optional(), - }, - }, - async ({ url, fields, pollIntervalMs }) => { - const contract = await createContract({ url, fields, pollIntervalMs }); - return { - content: [{ type: "text", text: JSON.stringify({ contractId: contract.id, status: contract.status }) }], - }; - }, - ); - - server.registerTool( - "get_data", - { - description: "Get the current validated data for a contract, plus recent run history.", - inputSchema: { contractId: z.string() }, - }, - async ({ contractId }) => { - const [contract, history] = await Promise.all([getContract(contractId), getContractHistory(contractId)]); - return { - content: [{ type: "text", text: JSON.stringify({ contract, ...history }) }], - }; - }, - ); - - server.registerTool( - "watch_contract", - { - description: - "Trigger an immediate run of a contract (extract, validate, self-heal if needed, semantic-diff against the last known-good value). Events fire on the contract's SSE/webhook channel as they happen.", - inputSchema: { contractId: z.string() }, - }, - async ({ contractId }) => { - const result = await triggerRun(contractId); - return { content: [{ type: "text", text: JSON.stringify(result) }] }; - }, - ); - +import { createContract, listContracts, getContract, triggerRun, getData, getRun } from "@/lib/contracts/service"; +import { contractDefinitionSchema } from "@/lib/contracts/schema"; +import { readEvents } from "@/lib/events/bus"; +import { cursor } from "@/lib/http"; +import { toErrorResponse } from "@/lib/errors"; +export function createMcpServer(ownerId: string) { + const server = new McpServer({ name: "lippy-agent", version: "1.0.0" }); + async function result(action: () => Promise) { + try { return { content: [{ type: "text" as const, text: JSON.stringify(await action()) }] }; } + catch (error) { return { isError: true, content: [{ type: "text" as const, text: JSON.stringify(toErrorResponse(error).body) }] }; } + } + server.registerTool("create_contract", { description: "Create an owned extraction contract; the worker provisions its scraper asynchronously.", inputSchema: contractDefinitionSchema.shape }, input => result(() => createContract(input, ownerId))); + server.registerTool("list_contracts", { description: "List your extraction contracts", inputSchema: {} }, () => result(() => listContracts(ownerId))); + server.registerTool("run_contract", { description: "Enqueue a run and return its persisted ID immediately", inputSchema: { contractId: z.string(), idempotencyKey: z.string().min(1).max(128).optional() } }, ({ contractId, idempotencyKey }) => result(() => triggerRun(contractId, ownerId, idempotencyKey))); + server.registerTool("get_run", { description: "Read persisted execution state, errors and repair attempts", inputSchema: { runId: z.string() } }, ({ runId }) => result(() => getRun(runId, ownerId))); + server.registerTool("get_data", { description: "Read last accepted data; unverified and suspicious results are excluded", inputSchema: { contractId: z.string() } }, ({ contractId }) => result(() => getData(contractId, ownerId))); + server.registerTool("watch_contract", { description: "Read durable events after a sequence cursor; call again with the last sequence to follow progress", inputSchema: { contractId: z.string(), after: z.string().optional() } }, ({ contractId, after }) => result(async () => { + await getContract(contractId, ownerId); return readEvents(contractId, cursor(after ?? null)); + })); return server; } diff --git a/package-lock.json b/package-lock.json index 1240a60..9ee9939 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,18 @@ { - "name": "web-contracts", + "name": "lippy-agent", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "web-contracts", + "name": "lippy-agent", "version": "0.1.0", "dependencies": { "@modelcontextprotocol/sdk": "^1.12.0", "@prisma/client": "^6.5.0", - "motion": "^13.1.1", - "next": "15.2.0", + "cheerio": "^1.2.0", + "jose": "^6.2.12", + "next": "^15.5.25", "react": "19.0.0", "react-dom": "19.0.0", "zod": "^3.24.0" @@ -22,27 +23,15 @@ "@types/react-dom": "^19.0.0", "@typescript-eslint/eslint-plugin": "^8.20.0", "@typescript-eslint/parser": "^8.20.0", - "autoprefixer": "^10.4.20", "eslint": "^9.18.0", - "eslint-config-next": "15.2.0", - "postcss": "^8.4.49", + "eslint-config-next": "^15.5.25", "prisma": "^6.5.0", - "tailwindcss": "^3.4.17", + "tsx": "^4.23.13", "typescript": "^5.7.3", "vitest": "^3.0.0" - } - }, - "node_modules/@alloc/quick-lru": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", - "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=22.0.0" } }, "node_modules/@emnapi/core": { @@ -825,10 +814,20 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, "node_modules/@img/sharp-darwin-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", - "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.4.tgz", + "integrity": "sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==", "cpu": [ "arm64" ], @@ -838,19 +837,19 @@ "darwin" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.0.4" + "@img/sharp-libvips-darwin-arm64": "1.3.3" } }, "node_modules/@img/sharp-darwin-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", - "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.4.tgz", + "integrity": "sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==", "cpu": [ "x64" ], @@ -860,19 +859,38 @@ "darwin" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.0.4" + "@img/sharp-libvips-darwin-x64": "1.3.3" + } + }, + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.4.tgz", + "integrity": "sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==", + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "dependencies": { + "@img/sharp-wasm32": "0.35.4" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", - "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.3.tgz", + "integrity": "sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==", "cpu": [ "arm64" ], @@ -886,9 +904,9 @@ } }, "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", - "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.3.tgz", + "integrity": "sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==", "cpu": [ "x64" ], @@ -902,9 +920,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", - "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.3.tgz", + "integrity": "sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==", "cpu": [ "arm" ], @@ -921,9 +939,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", - "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.3.tgz", + "integrity": "sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==", "cpu": [ "arm64" ], @@ -939,10 +957,48 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.3.tgz", + "integrity": "sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.3.tgz", + "integrity": "sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", - "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.3.tgz", + "integrity": "sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==", "cpu": [ "s390x" ], @@ -959,9 +1015,9 @@ } }, "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", - "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.3.tgz", + "integrity": "sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==", "cpu": [ "x64" ], @@ -978,9 +1034,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", - "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.3.tgz", + "integrity": "sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==", "cpu": [ "arm64" ], @@ -997,9 +1053,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", - "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.3.tgz", + "integrity": "sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==", "cpu": [ "x64" ], @@ -1016,9 +1072,9 @@ } }, "node_modules/@img/sharp-linux-arm": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", - "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.4.tgz", + "integrity": "sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==", "cpu": [ "arm" ], @@ -1031,19 +1087,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.0.5" + "@img/sharp-libvips-linux-arm": "1.3.3" } }, "node_modules/@img/sharp-linux-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", - "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.4.tgz", + "integrity": "sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==", "cpu": [ "arm64" ], @@ -1056,19 +1112,69 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.3.3" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.4.tgz", + "integrity": "sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.3.3" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.4.tgz", + "integrity": "sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.0.4" + "@img/sharp-libvips-linux-riscv64": "1.3.3" } }, "node_modules/@img/sharp-linux-s390x": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", - "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.4.tgz", + "integrity": "sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==", "cpu": [ "s390x" ], @@ -1081,19 +1187,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.0.4" + "@img/sharp-libvips-linux-s390x": "1.3.3" } }, "node_modules/@img/sharp-linux-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", - "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.4.tgz", + "integrity": "sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==", "cpu": [ "x64" ], @@ -1106,19 +1212,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.0.4" + "@img/sharp-libvips-linux-x64": "1.3.3" } }, "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", - "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.4.tgz", + "integrity": "sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==", "cpu": [ "arm64" ], @@ -1131,19 +1237,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + "@img/sharp-libvips-linuxmusl-arm64": "1.3.3" } }, "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", - "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.4.tgz", + "integrity": "sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==", "cpu": [ "x64" ], @@ -1156,38 +1262,73 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + "@img/sharp-libvips-linuxmusl-x64": "1.3.3" } }, "node_modules/@img/sharp-wasm32": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", - "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.4.tgz", + "integrity": "sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==", + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.4.tgz", + "integrity": "sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==", "cpu": [ "wasm32" ], - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "license": "Apache-2.0", "optional": true, "dependencies": { - "@emnapi/runtime": "^1.2.0" + "@img/sharp-wasm32": "0.35.4" }, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.4.tgz", + "integrity": "sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-ia32": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", - "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.4.tgz", + "integrity": "sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==", "cpu": [ "ia32" ], @@ -1197,16 +1338,16 @@ "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": "^20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", - "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.4.tgz", + "integrity": "sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==", "cpu": [ "x64" ], @@ -1216,33 +1357,12 @@ "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -1250,17 +1370,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, "node_modules/@modelcontextprotocol/sdk": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz", @@ -1344,15 +1453,15 @@ } }, "node_modules/@next/env": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.2.0.tgz", - "integrity": "sha512-eMgJu1RBXxxqqnuRJQh5RozhskoNUDHBFybvi+Z+yK9qzKeG7dadhv/Vp1YooSZmCnegf7JxWuapV77necLZNA==", + "version": "15.5.25", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.25.tgz", + "integrity": "sha512-42h1lLr07vl4gawALP1hsgRZjHB1xYa58JfUfHwr0f7jG/zhPakh5GHkADHXOC9ZxUvlQFOPIrp7s6qX4DezPQ==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.2.0.tgz", - "integrity": "sha512-jHFUG2OwmAuOASqq253RAEG/5BYcPHn27p1NoWZDCf4OdvdK0yRYWX92YKkL+Mk2s+GyJrmd/GATlL5b2IySpw==", + "version": "15.5.25", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.5.25.tgz", + "integrity": "sha512-dAzOqZQCAOgIq5yQpsuMBZcwxK0AtzGqHMQpxNWYLQlvf79rsP3SDwdGPNQC4ySaMSihOQXMO/VXubQ3JWW+3A==", "dev": true, "license": "MIT", "dependencies": { @@ -1360,9 +1469,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.2.0.tgz", - "integrity": "sha512-rlp22GZwNJjFCyL7h5wz9vtpBVuCt3ZYjFWpEPBGzG712/uL1bbSkS675rVAUCRZ4hjoTJ26Q7IKhr5DfJrHDA==", + "version": "15.5.25", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.25.tgz", + "integrity": "sha512-w+RR0v/QuApnWEjRGm1z6gcObKwGMb5YPA7V3bzBEVSBpMFUXprer0tS27UxjUcEnqbhL7Zuzohej79B6rYmBg==", "cpu": [ "arm64" ], @@ -1376,9 +1485,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.2.0.tgz", - "integrity": "sha512-DiU85EqSHogCz80+sgsx90/ecygfCSGl5P3b4XDRVZpgujBm5lp4ts7YaHru7eVTyZMjHInzKr+w0/7+qDrvMA==", + "version": "15.5.25", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.25.tgz", + "integrity": "sha512-QiGGBUSakt8S1H4Lt9Ehsh6Ja87axiBnQQgysOObvCbI7iUfJnRGntF1P64S4/ijuHFnSB8KLsEddkY3nN26uw==", "cpu": [ "x64" ], @@ -1392,9 +1501,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.2.0.tgz", - "integrity": "sha512-VnpoMaGukiNWVxeqKHwi8MN47yKGyki5q+7ql/7p/3ifuU2341i/gDwGK1rivk0pVYbdv5D8z63uu9yMw0QhpQ==", + "version": "15.5.25", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.25.tgz", + "integrity": "sha512-ehLos/66zo0d/mJCU5u96a/VDcr01aaUrX0o/i16UdInxz8qPTCDSxGtjk/Lps1sIr1RJFdiX3hxc0fxJo+cPA==", "cpu": [ "arm64" ], @@ -1411,9 +1520,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.2.0.tgz", - "integrity": "sha512-ka97/ssYE5nPH4Qs+8bd8RlYeNeUVBhcnsNUmFM6VWEob4jfN9FTr0NBhXVi1XEJpj3cMfgSRW+LdE3SUZbPrw==", + "version": "15.5.25", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.25.tgz", + "integrity": "sha512-ZVMrqLiJ7DiChgmbkQwFtdhAnUkSH/4p7tB29QY+giATb0Q/XGHNRSKAb/B8XGDHRUaA67NepOW5W8u3ZRJBAA==", "cpu": [ "arm64" ], @@ -1430,9 +1539,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.2.0.tgz", - "integrity": "sha512-zY1JduE4B3q0k2ZCE+DAF/1efjTXUsKP+VXRtrt/rJCTgDlUyyryx7aOgYXNc1d8gobys/Lof9P9ze8IyRDn7Q==", + "version": "15.5.25", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.25.tgz", + "integrity": "sha512-UOewtDGkTMJTiODrEdeLZ50yGb59xCZSriNpXkfPMxRRgwDkGc7i8mLWqV5076wEdb+Ca/XN7MhJyM3CupyNyQ==", "cpu": [ "x64" ], @@ -1449,9 +1558,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.2.0.tgz", - "integrity": "sha512-QqvLZpurBD46RhaVaVBepkVQzh8xtlUN00RlG4Iq1sBheNugamUNPuZEH1r9X1YGQo1KqAe1iiShF0acva3jHQ==", + "version": "15.5.25", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.25.tgz", + "integrity": "sha512-UBHwA8AhkCZgtRfU1aJpunuAJe/6gZv6jDESQe4p5MjTb5V0YEeJBWCdNqx15Vj3x+5jmauRfeMJSjfQj9HGFQ==", "cpu": [ "x64" ], @@ -1468,9 +1577,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.2.0.tgz", - "integrity": "sha512-ODZ0r9WMyylTHAN6pLtvUtQlGXBL9voljv6ujSlcsjOxhtXPI1Ag6AhZK0SE8hEpR1374WZZ5w33ChpJd5fsjw==", + "version": "15.5.25", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.25.tgz", + "integrity": "sha512-QcFcPRr16djk5IqK5+e8O80eZfgWzIvVBXfitIq0tQ/uc+eyfdoZ0NmKc0cnbIJyfVwREapKuG97YcxWA9gcpA==", "cpu": [ "arm64" ], @@ -1484,9 +1593,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.2.0.tgz", - "integrity": "sha512-8+4Z3Z7xa13NdUuUAcpVNA6o76lNPniBd9Xbo02bwXQXnZgFvEopwY2at5+z7yHl47X9qbZpvwatZ2BRo3EdZw==", + "version": "15.5.25", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.25.tgz", + "integrity": "sha512-zREeykps3ndWr9egJgvJKqVkkDuaw6Zrrg23cYBos0ygydFkAWYU4+PaPVwXzP1eAYQJe53ShSK45iDM529BOg==", "cpu": [ "x64" ], @@ -2042,12 +2151,6 @@ "devOptional": true, "license": "MIT" }, - "node_modules/@swc/counter": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "license": "Apache-2.0" - }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -2924,34 +3027,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true, - "license": "MIT" - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true, - "license": "MIT" - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -3156,43 +3231,6 @@ "node": ">= 0.4" } }, - "node_modules/autoprefixer": { - "version": "10.5.4", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.4.tgz", - "integrity": "sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.28.6", - "caniuse-lite": "^1.0.30001806", - "fraction.js": "^5.3.4", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -3239,32 +3277,6 @@ "node": "18 || 20 || >=22" } }, - "node_modules/baseline-browser-mapping": { - "version": "2.11.16", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.16.tgz", - "integrity": "sha512-H/bNPUFHewJHyCTdjn1n3Pit5+2GmWT6mmeHImPX+8MA9NA6b67jO4gYmi4jTbCJb2otq34KMZnovndDPqJwhQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/body-parser": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", @@ -3302,6 +3314,12 @@ "url": "https://opencollective.com/express" } }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, "node_modules/brace-expansion": { "version": "5.0.9", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", @@ -3328,51 +3346,6 @@ "node": ">=8" } }, - "node_modules/browserslist": { - "version": "4.28.8", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", - "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.11.12", - "caniuse-lite": "^1.0.30001809", - "electron-to-chromium": "^1.5.402", - "node-releases": "^2.0.53", - "update-browserslist-db": "^1.3.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -3479,16 +3452,6 @@ "node": ">=6" } }, - "node_modules/camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/caniuse-lite": { "version": "1.0.30001809", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", @@ -3553,6 +3516,48 @@ "node": ">= 16" } }, + "node_modules/cheerio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", + "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==", + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.1.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.19.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -3585,25 +3590,11 @@ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "license": "MIT", - "optional": true, - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -3616,29 +3607,8 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "license": "MIT", - "optional": true, - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } + "license": "MIT" }, "node_modules/concat-map": { "version": "0.0.1", @@ -3735,17 +3705,32 @@ "node": ">= 8" } }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", "engines": { - "node": ">=4" + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" } }, "node_modules/csstype": { @@ -3929,20 +3914,6 @@ "node": ">=8" } }, - "node_modules/didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true, - "license": "MIT" - }, "node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -3956,6 +3927,61 @@ "node": ">=0.10.0" } }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", @@ -4000,13 +4026,6 @@ "fast-check": "^3.23.1" } }, - "node_modules/electron-to-chromium": { - "version": "1.5.412", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.412.tgz", - "integrity": "sha512-z4rMe3esBzlzovKHj4gxJnsCGZRK5l4baUvm+gCGJBPE+gsyUMKsuU9tnEUtI1dOebXz1ytAPGjvXhmQ7rIPwA==", - "dev": true, - "license": "ISC" - }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -4033,6 +4052,43 @@ "node": ">= 0.8" } }, + "node_modules/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/encoding-sniffer/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-abstract": { "version": "1.24.2", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", @@ -4278,16 +4334,6 @@ "@esbuild/win32-x64": "0.28.2" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -4369,13 +4415,13 @@ } }, "node_modules/eslint-config-next": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.2.0.tgz", - "integrity": "sha512-LkG0KKpinAoNPk2HXSx0fImFb/hQ6RnhSxTkpJFTkQ0SmnzsbRsjjN95WC/mDY34nKOenpptYEVvfkCR/h+VjA==", + "version": "15.5.25", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.5.25.tgz", + "integrity": "sha512-hB1oClZzRO3vMAeMmBMch0FHNJ4irH74LvQWDEMHkJtG/Us9+SAsg1f5Pvcw9jz4tc8wSGnvbmF050BRAAIbBg==", "dev": true, "license": "MIT", "dependencies": { - "@next/eslint-plugin-next": "15.2.0", + "@next/eslint-plugin-next": "15.5.25", "@rushstack/eslint-patch": "^1.10.3", "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", @@ -5092,9 +5138,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", - "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", + "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", "funding": [ { "type": "github", @@ -5227,43 +5273,6 @@ "node": ">= 0.6" } }, - "node_modules/fraction.js": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", - "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/framer-motion": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-13.1.1.tgz", - "integrity": "sha512-B/xn2TPS4f61cEBLFjiYlQFnBZUW1YVj/LM+C+N4OP8Rs95VLEI2ot/RlfBg111la/EiyECFaJJi/A3FWA8MUA==", - "license": "MIT", - "dependencies": { - "motion-dom": "^13.1.1", - "motion-utils": "^13.0.0", - "tslib": "^2.4.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - }, - "react-dom": { - "optional": true - } - } - }, "node_modules/fresh": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", @@ -5575,14 +5584,45 @@ } }, "node_modules/hono": { - "version": "4.13.3", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.3.tgz", - "integrity": "sha512-r8AO2mYHoLxSHkgafNeC/BXyb2vWRxD3jem4Ts+ptav8oTG5FIRifAjuJEmZI4bSvvc2ns0GxmIYiZnHqN3mMw==", + "version": "4.13.7", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.7.tgz", + "integrity": "sha512-c8/gF9ac8Y78/agExVocyLevgR+JlpNB444Py0FSX8pJoPdYUfUzRcXtYEYGwt6l19qIlVZPN5Mfsw9jFShmQQ==", "license": "MIT", "engines": { "node": ">=16.9.0" } }, + "node_modules/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -5713,13 +5753,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-arrayish": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", - "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", - "license": "MIT", - "optional": true - }, "node_modules/is-async-function": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", @@ -5756,19 +5789,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/is-boolean-object": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", @@ -6181,9 +6201,9 @@ } }, "node_modules/jose": { - "version": "6.2.9", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.9.tgz", - "integrity": "sha512-XrchZOFZUl/T3vTwRe8XK+cJrGtMF4th1ARnDfwbBXFKThGhlsxEE4Zu03AD/bjJSt/9jT/mxrOCkJWOg77aPA==", + "version": "6.2.12", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.12.tgz", + "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -6197,9 +6217,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", - "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "dev": true, "funding": [ { @@ -6318,26 +6338,6 @@ "node": ">= 0.8.0" } }, - "node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -6500,61 +6500,12 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/motion": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/motion/-/motion-13.1.1.tgz", - "integrity": "sha512-WNZoK6xiF+kkTqkZ5K7FDDh6A8BG4i5Hc7KXtW8gtTxkpJFds+hIOrDaQGKjQj/AE/i4hJqAaUHEqp/Qo02y6Q==", - "license": "MIT", - "dependencies": { - "framer-motion": "^13.1.1", - "tslib": "^2.4.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - }, - "react-dom": { - "optional": true - } - } - }, - "node_modules/motion-dom": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-13.1.1.tgz", - "integrity": "sha512-XSf8VYWSB6G/0IY3rWVbyLcxWXtAVHkN1PQE2agTaCv3u8RGvbwu56TyyR/MNzBqqNavEBTZzErcxI1TxBrjcA==", - "license": "MIT", - "dependencies": { - "motion-utils": "^13.0.0" - } - }, - "node_modules/motion-utils": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-13.0.0.tgz", - "integrity": "sha512-7DnN7TmbLcYXcG4RVadXIihWlyuM9afoUww8Y5Agg431kGKiuL2/OMyP4mJ5wLz+pvN3t5ySClLOaVXJ+wekRQ==", - "license": "MIT" - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, "node_modules/nanoid": { "version": "3.3.18", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", @@ -6626,16 +6577,13 @@ } }, "node_modules/next": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/next/-/next-15.2.0.tgz", - "integrity": "sha512-VaiM7sZYX8KIAHBrRGSFytKknkrexNfGb8GlG6e93JqueCspuGte8i4ybn8z4ww1x3f2uzY4YpTaBEW4/hvsoQ==", - "deprecated": "This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/CVE-2025-66478 for more details.", + "version": "15.5.25", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.25.tgz", + "integrity": "sha512-OMWNulIIqKM2ykvC2qMjIt0IoavB4UB2SCs4iXJ6z6847FvyH8jBmBWcvrF5iuhTu8Przh20Fo/aoszIdqx4PA==", "license": "MIT", "dependencies": { - "@next/env": "15.2.0", - "@swc/counter": "0.1.3", + "@next/env": "15.5.25", "@swc/helpers": "0.5.15", - "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" @@ -6647,19 +6595,19 @@ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.2.0", - "@next/swc-darwin-x64": "15.2.0", - "@next/swc-linux-arm64-gnu": "15.2.0", - "@next/swc-linux-arm64-musl": "15.2.0", - "@next/swc-linux-x64-gnu": "15.2.0", - "@next/swc-linux-x64-musl": "15.2.0", - "@next/swc-win32-arm64-msvc": "15.2.0", - "@next/swc-win32-x64-msvc": "15.2.0", - "sharp": "^0.33.5" + "@next/swc-darwin-arm64": "15.5.25", + "@next/swc-darwin-x64": "15.5.25", + "@next/swc-linux-arm64-gnu": "15.5.25", + "@next/swc-linux-arm64-musl": "15.5.25", + "@next/swc-linux-x64-gnu": "15.5.25", + "@next/swc-linux-x64-musl": "15.5.25", + "@next/swc-win32-arm64-msvc": "15.5.25", + "@next/swc-win32-x64-msvc": "15.5.25", + "sharp": "^0.34.3 || ^0.35.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", - "@playwright/test": "^1.41.2", + "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", @@ -6744,24 +6692,16 @@ "devOptional": true, "license": "MIT" }, - "node_modules/node-releases": { - "version": "2.0.53", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz", - "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" } }, "node_modules/nypm": { @@ -6798,16 +6738,6 @@ "node": ">=0.10.0" } }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -7030,6 +6960,55 @@ "node": ">=6" } }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -7118,16 +7097,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/pkce-challenge": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", @@ -7188,162 +7157,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/postcss-import": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-import/node_modules/resolve": { - "version": "1.22.12", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", - "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/postcss-js": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", - "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "camelcase-css": "^2.0.1" - }, - "engines": { - "node": "^12 || ^14 || >= 16" - }, - "peerDependencies": { - "postcss": "^8.4.21" - } - }, - "node_modules/postcss-load-config": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", - "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "lilconfig": "^3.1.1" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "jiti": ">=1.21.0", - "postcss": ">=8.0.9", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - }, - "postcss": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/postcss-nested": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", - "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.1.1" - }, - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "postcss": "^8.2.14" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", - "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -7433,9 +7246,9 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.15.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", - "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", "license": "BSD-3-Clause", "dependencies": { "es-define-property": "^1.0.1", @@ -7536,13 +7349,6 @@ "dev": true, "license": "MIT" }, - "node_modules/read-cache": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.2.tgz", - "integrity": "sha512-/peqiBB/n07gQGLsWaHho3WfvUyRscw0gYTsEFMhrIe/nWLkYaf5SbKYjGYqtRV3aPwykJgF2VEMo1ac4bnsGA==", - "dev": true, - "license": "MIT" - }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -7932,43 +7738,53 @@ "license": "ISC" }, "node_modules/sharp": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", - "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", - "hasInstallScript": true, + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.4.tgz", + "integrity": "sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==", "license": "Apache-2.0", "optional": true, "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.3", - "semver": "^7.6.3" + "@img/colour": "^1.1.0", + "detect-libc": "^2.1.2", + "semver": "^7.8.5" }, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.33.5", - "@img/sharp-darwin-x64": "0.33.5", - "@img/sharp-libvips-darwin-arm64": "1.0.4", - "@img/sharp-libvips-darwin-x64": "1.0.4", - "@img/sharp-libvips-linux-arm": "1.0.5", - "@img/sharp-libvips-linux-arm64": "1.0.4", - "@img/sharp-libvips-linux-s390x": "1.0.4", - "@img/sharp-libvips-linux-x64": "1.0.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", - "@img/sharp-libvips-linuxmusl-x64": "1.0.4", - "@img/sharp-linux-arm": "0.33.5", - "@img/sharp-linux-arm64": "0.33.5", - "@img/sharp-linux-s390x": "0.33.5", - "@img/sharp-linux-x64": "0.33.5", - "@img/sharp-linuxmusl-arm64": "0.33.5", - "@img/sharp-linuxmusl-x64": "0.33.5", - "@img/sharp-wasm32": "0.33.5", - "@img/sharp-win32-ia32": "0.33.5", - "@img/sharp-win32-x64": "0.33.5" + "@img/sharp-darwin-arm64": "0.35.4", + "@img/sharp-darwin-x64": "0.35.4", + "@img/sharp-freebsd-wasm32": "0.35.4", + "@img/sharp-libvips-darwin-arm64": "1.3.3", + "@img/sharp-libvips-darwin-x64": "1.3.3", + "@img/sharp-libvips-linux-arm": "1.3.3", + "@img/sharp-libvips-linux-arm64": "1.3.3", + "@img/sharp-libvips-linux-ppc64": "1.3.3", + "@img/sharp-libvips-linux-riscv64": "1.3.3", + "@img/sharp-libvips-linux-s390x": "1.3.3", + "@img/sharp-libvips-linux-x64": "1.3.3", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.3", + "@img/sharp-libvips-linuxmusl-x64": "1.3.3", + "@img/sharp-linux-arm": "0.35.4", + "@img/sharp-linux-arm64": "0.35.4", + "@img/sharp-linux-ppc64": "0.35.4", + "@img/sharp-linux-riscv64": "0.35.4", + "@img/sharp-linux-s390x": "0.35.4", + "@img/sharp-linux-x64": "0.35.4", + "@img/sharp-linuxmusl-arm64": "0.35.4", + "@img/sharp-linuxmusl-x64": "0.35.4", + "@img/sharp-webcontainers-wasm32": "0.35.4", + "@img/sharp-win32-arm64": "0.35.4", + "@img/sharp-win32-ia32": "0.35.4", + "@img/sharp-win32-x64": "0.35.4" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/shebang-command": { @@ -8071,16 +7887,6 @@ "dev": true, "license": "ISC" }, - "node_modules/simple-swizzle": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", - "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", - "license": "MIT", - "optional": true, - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -8134,14 +7940,6 @@ "node": ">= 0.4" } }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/string.prototype.includes": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", @@ -8322,29 +8120,6 @@ } } }, - "node_modules/sucrase": { - "version": "3.35.1", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", - "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "tinyglobby": "^0.2.11", - "ts-interface-checker": "^0.1.9" - }, - "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -8371,180 +8146,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/tailwindcss": { - "version": "3.4.19", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", - "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "arg": "^5.0.2", - "chokidar": "^3.6.0", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.3.2", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "jiti": "^1.21.7", - "lilconfig": "^3.1.3", - "micromatch": "^4.0.8", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.1.1", - "postcss": "^8.4.47", - "postcss-import": "^15.1.0", - "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", - "postcss-nested": "^6.2.0", - "postcss-selector-parser": "^6.1.2", - "resolve": "^1.22.8", - "sucrase": "^3.35.0" - }, - "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tailwindcss/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/tailwindcss/node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/tailwindcss/node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/tailwindcss/node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/tailwindcss/node_modules/jiti": { - "version": "1.21.7", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", - "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", - "dev": true, - "license": "MIT", - "bin": { - "jiti": "bin/jiti.js" - } - }, - "node_modules/tailwindcss/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/tailwindcss/node_modules/resolve": { - "version": "1.22.12", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", - "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -8675,13 +8276,6 @@ "typescript": ">=4.8.4" } }, - "node_modules/ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -8701,6 +8295,25 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tsx": { + "version": "4.23.13", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.13.tgz", + "integrity": "sha512-BL5MGkRln6aDYhb0xbQlEAGw743BaZYWdbWtdJOBriYJboKgUUYCadFp2/FpBBZquBC/ezNBn7wMMPx7FDZUDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -8856,6 +8469,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.1.tgz", + "integrity": "sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -8910,37 +8532,6 @@ "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" } }, - "node_modules/update-browserslist-db": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz", - "integrity": "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -8951,13 +8542,6 @@ "punycode": "^2.1.0" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -9189,6 +8773,40 @@ "dev": true, "license": "MIT" }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 5849101..e5e3e25 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "web-contracts", + "name": "lippy-agent", "version": "0.1.0", "private": true, "scripts": { @@ -12,13 +12,18 @@ "test:watch": "vitest", "db:generate": "prisma generate", "db:migrate": "prisma migrate dev", - "db:studio": "prisma studio" + "db:studio": "prisma studio", + "worker": "node --env-file=.env --import tsx scripts/worker.ts", + "db:deploy": "prisma migrate deploy", + "test:integration": "vitest run tests/integration", + "demo": "node --env-file=.env --import tsx scripts/demo.ts" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.12.0", "@prisma/client": "^6.5.0", - "motion": "^13.1.1", - "next": "15.2.0", + "cheerio": "^1.2.0", + "jose": "^6.2.12", + "next": "^15.5.25", "react": "19.0.0", "react-dom": "19.0.0", "zod": "^3.24.0" @@ -29,13 +34,14 @@ "@types/react-dom": "^19.0.0", "@typescript-eslint/eslint-plugin": "^8.20.0", "@typescript-eslint/parser": "^8.20.0", - "autoprefixer": "^10.4.20", "eslint": "^9.18.0", - "eslint-config-next": "15.2.0", - "postcss": "^8.4.49", + "eslint-config-next": "^15.5.25", "prisma": "^6.5.0", - "tailwindcss": "^3.4.17", + "tsx": "^4.23.13", "typescript": "^5.7.3", "vitest": "^3.0.0" + }, + "engines": { + "node": ">=22.0.0" } } diff --git a/postcss.config.mjs b/postcss.config.mjs deleted file mode 100644 index 2aa7205..0000000 --- a/postcss.config.mjs +++ /dev/null @@ -1,6 +0,0 @@ -export default { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; diff --git a/prisma/migrations/202609140001_baseline/migration.sql b/prisma/migrations/202609140001_baseline/migration.sql new file mode 100644 index 0000000..b718741 --- /dev/null +++ b/prisma/migrations/202609140001_baseline/migration.sql @@ -0,0 +1,54 @@ +-- CreateSchema +CREATE SCHEMA IF NOT EXISTS "public"; + +-- CreateTable +CREATE TABLE "Contract" ( + "id" TEXT NOT NULL, + "url" TEXT NOT NULL, + "schema" TEXT NOT NULL, + "pollIntervalMs" INTEGER NOT NULL DEFAULT 300000, + "status" TEXT NOT NULL DEFAULT 'healthy', + "collectorId" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Contract_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Run" ( + "id" TEXT NOT NULL, + "contractId" TEXT NOT NULL, + "startedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "finishedAt" TIMESTAMP(3), + "outcome" TEXT, + "rawOutput" TEXT, + "validated" TEXT, + "violations" TEXT, + + CONSTRAINT "Run_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ContractEvent" ( + "id" TEXT NOT NULL, + "contractId" TEXT NOT NULL, + "type" TEXT NOT NULL, + "payload" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "ContractEvent_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "Run_contractId_startedAt_idx" ON "Run"("contractId", "startedAt"); + +-- CreateIndex +CREATE INDEX "ContractEvent_contractId_createdAt_idx" ON "ContractEvent"("contractId", "createdAt"); + +-- AddForeignKey +ALTER TABLE "Run" ADD CONSTRAINT "Run_contractId_fkey" FOREIGN KEY ("contractId") REFERENCES "Contract"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ContractEvent" ADD CONSTRAINT "ContractEvent_contractId_fkey" FOREIGN KEY ("contractId") REFERENCES "Contract"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + diff --git a/prisma/migrations/202609140002_durable_backend/migration.sql b/prisma/migrations/202609140002_durable_backend/migration.sql new file mode 100644 index 0000000..5c69766 --- /dev/null +++ b/prisma/migrations/202609140002_durable_backend/migration.sql @@ -0,0 +1,131 @@ +-- DropIndex +DROP INDEX "ContractEvent_contractId_createdAt_idx"; + +-- AlterTable +ALTER TABLE "Contract" ADD COLUMN "activeRunId" TEXT, +ADD COLUMN "enabled" BOOLEAN NOT NULL DEFAULT true, +ADD COLUMN "fixture" TEXT, +ADD COLUMN "nextRunAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "ownerId" TEXT, +ADD COLUMN "provider" TEXT NOT NULL DEFAULT 'fixture', +ALTER COLUMN "status" SET DEFAULT 'pending'; + +-- AlterTable +ALTER TABLE "Run" ADD COLUMN "availableAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "deadlineAt" TIMESTAMP(3) NOT NULL DEFAULT (now() + interval '30 minutes'), +ADD COLUMN "error" TEXT, +ADD COLUMN "fieldChanges" TEXT, +ADD COLUMN "healAttempts" INTEGER NOT NULL DEFAULT 0, +ADD COLUMN "idempotencyKey" TEXT, +ADD COLUMN "leaseToken" TEXT, +ADD COLUMN "leaseUntil" TIMESTAMP(3), +ADD COLUMN "providerJobId" TEXT, +ADD COLUMN "retryCount" INTEGER NOT NULL DEFAULT 0, +ADD COLUMN "state" TEXT NOT NULL DEFAULT 'queued'; + +-- AlterTable +ALTER TABLE "ContractEvent" ADD COLUMN "runId" TEXT, +ADD COLUMN "sequence" BIGSERIAL NOT NULL; + +-- CreateTable +CREATE TABLE "User" ( + "id" TEXT NOT NULL, + "googleSub" TEXT, + "email" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Session" ( + "id" TEXT NOT NULL, + "tokenHash" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "kind" TEXT NOT NULL DEFAULT 'session', + "expiresAt" TIMESTAMP(3) NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Session_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "OAuthAttempt" ( + "stateHash" TEXT NOT NULL, + "verifier" TEXT NOT NULL, + "nonce" TEXT NOT NULL, + "expiresAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "OAuthAttempt_pkey" PRIMARY KEY ("stateHash") +); + +-- CreateTable +CREATE TABLE "RepairAttempt" ( + "id" TEXT NOT NULL, + "runId" TEXT NOT NULL, + "attempt" INTEGER NOT NULL, + "status" TEXT NOT NULL, + "proposal" TEXT, + "violations" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "RepairAttempt_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "User_googleSub_key" ON "User"("googleSub"); + +-- CreateIndex +CREATE UNIQUE INDEX "Session_tokenHash_key" ON "Session"("tokenHash"); + +-- CreateIndex +CREATE INDEX "Session_userId_idx" ON "Session"("userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "RepairAttempt_runId_attempt_key" ON "RepairAttempt"("runId", "attempt"); + +-- Legacy mock IDs were derived from the URL and may be duplicated. These +-- unowned collectors require explicit reassignment before live use. +UPDATE "Contract" SET "collectorId" = NULL WHERE "collectorId" LIKE 'c_mock_%'; + +-- CreateIndex +CREATE UNIQUE INDEX "Contract_collectorId_key" ON "Contract"("collectorId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Contract_activeRunId_key" ON "Contract"("activeRunId"); + +-- CreateIndex +CREATE INDEX "Contract_ownerId_createdAt_idx" ON "Contract"("ownerId", "createdAt"); + +-- CreateIndex +CREATE INDEX "Contract_enabled_nextRunAt_idx" ON "Contract"("enabled", "nextRunAt"); + +-- CreateIndex +CREATE INDEX "Run_finishedAt_availableAt_leaseUntil_idx" ON "Run"("finishedAt", "availableAt", "leaseUntil"); + +-- CreateIndex +CREATE UNIQUE INDEX "Run_contractId_idempotencyKey_key" ON "Run"("contractId", "idempotencyKey"); + +-- CreateIndex +CREATE UNIQUE INDEX "ContractEvent_sequence_key" ON "ContractEvent"("sequence"); + +-- CreateIndex +CREATE INDEX "ContractEvent_contractId_sequence_idx" ON "ContractEvent"("contractId", "sequence"); + +-- AddForeignKey +ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Contract" ADD CONSTRAINT "Contract_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "RepairAttempt" ADD CONSTRAINT "RepairAttempt_runId_fkey" FOREIGN KEY ("runId") REFERENCES "Run"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ContractEvent" ADD CONSTRAINT "ContractEvent_runId_fkey" FOREIGN KEY ("runId") REFERENCES "Run"("id") ON DELETE SET NULL ON UPDATE CASCADE; + + +-- Preserve legacy history but never automatically run unowned contracts. +UPDATE "Contract" SET "enabled" = false WHERE "ownerId" IS NULL; +UPDATE "Run" SET "state" = COALESCE("outcome", 'failed'), "outcome" = COALESCE("outcome", 'failed'), "finishedAt" = COALESCE("finishedAt", CURRENT_TIMESTAMP), "error" = CASE WHEN "finishedAt" IS NULL THEN 'Legacy in-process run interrupted before migration' ELSE "error" END; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..2fe25d8 --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1 @@ +provider = "postgresql" diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 7d5eaaa..a7f93e1 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,50 +1,103 @@ -// Datasource is whatever DATABASE_URL points at (Postgres in deployment, -// works with any Postgres provider). No hardcoded connection info here — -// the URL comes exclusively from .env. generator client { provider = "prisma-client-js" } - datasource db { provider = "postgresql" url = env("DATABASE_URL") } - +model User { + id String @id @default(cuid()) + googleSub String? @unique + email String + createdAt DateTime @default(now()) + contracts Contract[] + sessions Session[] +} +model Session { + id String @id @default(cuid()) + tokenHash String @unique + userId String + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + kind String @default("session") + expiresAt DateTime + createdAt DateTime @default(now()) + @@index([userId]) +} +model OAuthAttempt { + stateHash String @id + verifier String + nonce String + expiresAt DateTime +} model Contract { - id String @id @default(cuid()) - url String - schema String // JSON-encoded ContractField[] - pollIntervalMs Int @default(300000) - status String @default("healthy") // healthy | degraded | healing | broken - collectorId String? // Bright Data collector id (c_*) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - runs Run[] - events ContractEvent[] -} - + id String @id @default(cuid()) + ownerId String? + owner User? @relation(fields: [ownerId], references: [id]) + url String + schema String + pollIntervalMs Int @default(300000) + status String @default("pending") + collectorId String? @unique + provider String @default("fixture") + fixture String? + enabled Boolean @default(true) + nextRunAt DateTime @default(now()) + activeRunId String? @unique + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + runs Run[] + events ContractEvent[] + @@index([ownerId, createdAt]) + @@index([enabled, nextRunAt]) +} model Run { - id String @id @default(cuid()) - contractId String - contract Contract @relation(fields: [contractId], references: [id]) - startedAt DateTime @default(now()) - finishedAt DateTime? - outcome String? // valid | violated | healed | escalated - rawOutput String? // JSON-encoded raw extraction - validated String? // JSON-encoded validated fields - violations String? // JSON-encoded list of field-level violations - + id String @id @default(cuid()) + contractId String + contract Contract @relation(fields: [contractId], references: [id]) + idempotencyKey String? + state String @default("queued") + startedAt DateTime @default(now()) + finishedAt DateTime? + outcome String? + rawOutput String? + validated String? + violations String? + fieldChanges String? + providerJobId String? + healAttempts Int @default(0) + retryCount Int @default(0) + availableAt DateTime @default(now()) + deadlineAt DateTime @default(dbgenerated("(now() + interval '30 minutes')")) + leaseToken String? + leaseUntil DateTime? + error String? + events ContractEvent[] + repairs RepairAttempt[] + @@unique([contractId, idempotencyKey]) @@index([contractId, startedAt]) + @@index([finishedAt, availableAt, leaseUntil]) +} +model RepairAttempt { + id String @id @default(cuid()) + runId String + run Run @relation(fields: [runId], references: [id]) + attempt Int + status String + proposal String? + violations String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + @@unique([runId, attempt]) } - model ContractEvent { - id String @id @default(cuid()) + id String @id @default(cuid()) + sequence BigInt @unique @default(autoincrement()) contractId String contract Contract @relation(fields: [contractId], references: [id]) - type String // contract.violated | contract.healed | contract.escalated | field.changed - payload String // JSON-encoded ContractEvent union member + runId String? + run Run? @relation(fields: [runId], references: [id]) + type String + payload String createdAt DateTime @default(now()) - - @@index([contractId, createdAt]) + @@index([contractId, sequence]) } diff --git a/scripts/demo.ts b/scripts/demo.ts new file mode 100644 index 0000000..ee15534 --- /dev/null +++ b/scripts/demo.ts @@ -0,0 +1,34 @@ +import { randomUUID } from "node:crypto"; +import { env } from "../lib/env"; +async function main() { + if (env.EXTRACTION_PROVIDER !== "fixture") throw new Error("This demo requires EXTRACTION_PROVIDER=fixture"); + const token = process.env.LIPPY_API_TOKEN; + async function request(path: string, input?: unknown) { + const response = await fetch(`${env.APP_URL}${path}`, { method: input === undefined ? "GET" : "POST", + headers: { "Content-Type": "application/json", ...(token ? { Authorization: `Bearer ${token}` } : {}), "Idempotency-Key": randomUUID() }, + ...(input === undefined ? {} : { body: JSON.stringify(input) }) }); + if (!response.ok) throw new Error(`${path}: ${response.status} ${await response.text()}`); + return response.json(); + } + async function run(id: string) { + const { runId } = await request(`/api/contracts/${id}/run`, {}); + for (let i = 0; i < 120; i++) { + const { run } = await request(`/api/runs/${runId}`); + if (run.finishedAt) { console.log(JSON.stringify({ runId, outcome: run.outcome, repairs: run.healAttempts })); return run; } + await new Promise(resolve => setTimeout(resolve, 500)); + } + throw new Error("Demo timed out. Start npm run worker in another terminal."); + } + const { contract } = await request("/api/contracts", { url: "https://example.com/product", enabled: false, + fields: [{ key: "product", type: "string", expectedValue: "Example product" }, { key: "price", type: "number", minimum: 0, maxRelativeChange: 0.5 }] }); + console.log(`Contract: ${contract.id}`); + if ((await run(contract.id)).outcome !== "valid") throw new Error("Baseline failed"); + await request("/api/dev/break", { contractId: contract.id, scenario: "layout" }); + if ((await run(contract.id)).outcome !== "healed") throw new Error("Selector recovery failed"); + await request("/api/dev/break", { contractId: contract.id, scenario: "value", field: "price", value: "900" }); + if ((await run(contract.id)).outcome !== "review_required") throw new Error("Suspicious change was not quarantined"); + const accepted = await request(`/api/contracts/${contract.id}/data`); + if (accepted.data.price !== 100) throw new Error("Last accepted value was overwritten"); + console.log(JSON.stringify({ result: "passed", accepted })); +} +main().catch(error => { console.error(error); process.exitCode = 1; }); diff --git a/scripts/worker.ts b/scripts/worker.ts new file mode 100644 index 0000000..7b458a2 --- /dev/null +++ b/scripts/worker.ts @@ -0,0 +1,16 @@ +import { env } from "../lib/env"; +import { db } from "../lib/db"; +import { tick } from "../lib/jobs/worker"; +async function main() { + let stopping = false; + process.on("SIGINT", () => { stopping = true; }); + process.on("SIGTERM", () => { stopping = true; }); + console.info(JSON.stringify({ event: "worker.started", provider: env.EXTRACTION_PROVIDER })); + try { + while (!stopping) { + try { if (await tick()) continue; } catch (error) { console.error("Worker tick failed", error); } + await new Promise(resolve => setTimeout(resolve, env.WORKER_POLL_MS)); + } + } finally { await db.$disconnect(); } +} +main().catch(error => { console.error(error); process.exitCode = 1; }); diff --git a/tailwind.config.ts b/tailwind.config.ts deleted file mode 100644 index 74ec52e..0000000 --- a/tailwind.config.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { Config } from "tailwindcss"; - -/** - * Tailwind scans the top-level dirs (app/, components/, mcp/) so every - * page, component, and any future module picks up utility classes - * without further config. - */ -const config: Config = { - content: [ - "./app/**/*.{ts,tsx}", - "./components/**/*.{ts,tsx}", - "./mcp/**/*.{ts,tsx}", - ], - theme: { - extend: {}, - }, - plugins: [], -}; - -export default config; diff --git a/tests/brightdata.test.ts b/tests/brightdata.test.ts new file mode 100644 index 0000000..9da2dfb --- /dev/null +++ b/tests/brightdata.test.ts @@ -0,0 +1,34 @@ +import { describe, it, expect, vi } from "vitest"; +import { BrightDataClient } from "../lib/brightdata/client"; +function setup(responses: Response[]) { + const fetcher = vi.fn().mockImplementation(async () => responses.shift()!); + return { client: new BrightDataClient(fetcher), fetcher }; +} +describe("documented Bright Data wire protocol", () => { + it("triggers a collection and polls 202 until results are ready", async () => { + const { client, fetcher } = setup([Response.json({ collection_id: "j_123" }), Response.json({ status: "building" }, { status: 202 }), Response.json([{ price: 100 }])]); + expect(await client.trigger("c_123", "https://example.com", "run_123")).toBe("j_123"); + expect(fetcher.mock.calls[0]?.[0]).toContain("collector=c_123"); + expect(JSON.parse(fetcher.mock.calls[0]?.[1]?.body as string)).toEqual([{ url: "https://example.com" }]); + expect(await client.results("j_123")).toBe(null); + expect(await client.results("j_123")).toEqual({ price: 100 }); + }); + it("accepts the empty approval response and sends auto_save", async () => { + const { client, fetcher } = setup([new Response(null, { status: 200 })]); + await expect(client.approve("c_123")).resolves.toBeUndefined(); + expect(fetcher.mock.calls[0]?.[0]).toContain("/dca/collectors/c_123/resume_automation_job"); + expect(JSON.parse(fetcher.mock.calls[0]?.[1]?.body as string)).toEqual({ message: true, auto_save: true }); + }); + it("rejects unexpected records rather than silently picking a row", async () => { + const { client } = setup([Response.json([{ price: 1 }, { price: 2 }])]); + await expect(client.results("j_123")).rejects.toThrow(); + }); + it("marks rate limits retryable and never treats errors as no-change", async () => { + const { client } = setup([new Response("rate limited", { status: 429 })]); + await expect(client.results("j_123")).rejects.toMatchObject({ retryable: true }); + }); + it("rejects malformed provider progress", async () => { + const { client } = setup([Response.json({ unexpected: true })]); + await expect(client.progress("c_123", true)).rejects.toThrow(); + }); +}); diff --git a/tests/integration/backend.test.ts b/tests/integration/backend.test.ts new file mode 100644 index 0000000..7a0d8b7 --- /dev/null +++ b/tests/integration/backend.test.ts @@ -0,0 +1,143 @@ +import { randomUUID } from "node:crypto"; +import { beforeAll, afterAll, describe, it, expect } from "vitest"; +import { db } from "../../lib/db"; +import { createContract, triggerRun, getData, getContract, reviewRun, reconcileRun, enqueueRun } from "../../lib/contracts/service"; +import { claimRun, processRun } from "../../lib/jobs/worker"; +import { readEvents } from "../../lib/events/bus"; +import { mutateFixture, type Fixture } from "../../lib/fixtures/scraper"; +import { issueToken, requireUser, completeGoogleLogin, beginGoogleLogin } from "../../lib/auth/service"; +import { env } from "../../lib/env"; +const enabled = process.env.RUN_DB_TESTS === "1"; +const owner = `test-${randomUUID()}`; +const definition = { url: "https://example.com/product", enabled: false, fields: [{ key: "product", type: "string", expectedValue: "Example product" }, { key: "price", type: "number", minimum: 0, maxRelativeChange: 0.5 }] }; +async function drain(id: string) { + for (let i = 0; i < 40; i++) { + const latest = await db.run.findUniqueOrThrow({ where: { id } }); + if (latest.finishedAt) return latest; + const run = await claimRun(); + if (!run) throw new Error("Expected claimable work"); + await processRun(run); + } + throw new Error("Workflow did not terminate"); +} +async function mutate(contractId: string, scenario: "layout" | "value" | "unrepairable", field?: string, value?: string) { + const c = await getContract(contractId, owner); + await db.contract.update({ where: { id: contractId }, data: { fixture: JSON.stringify(mutateFixture(JSON.parse(c.fixture!) as Fixture, scenario, field, value)) } }); +} +describe.skipIf(!enabled)("PostgreSQL backend integration", () => { + beforeAll(async () => { await db.user.create({ data: { id: owner, email: "test@example.com" } }); }); + afterAll(async () => { + const contracts = await db.contract.findMany({ where: { ownerId: owner }, select: { id: true } }); + const ids = contracts.map(c => c.id); + await db.contractEvent.deleteMany({ where: { contractId: { in: ids } } }); + await db.repairAttempt.deleteMany({ where: { run: { contractId: { in: ids } } } }); + await db.run.deleteMany({ where: { contractId: { in: ids } } }); + await db.contract.deleteMany({ where: { id: { in: ids } } }); + await db.user.delete({ where: { id: owner } }); + await db.$disconnect(); + }); + it("enqueues immediately, deduplicates concurrent requests, accepts baseline and repairs HTML drift", async () => { + const c = await createContract(definition, owner); + const [a, b] = await Promise.all([triggerRun(c.id, owner, "same"), triggerRun(c.id, owner, "same")]); + expect(a!.id).toBe(b!.id); expect(a!.state).toBe("queued"); + await expect(triggerRun(c.id, owner, "different")).rejects.toThrow(); + expect((await drain(a!.id)).outcome).toBe("valid"); + const baseline = await getData(c.id, owner); + await mutate(c.id, "layout"); + const next = await triggerRun(c.id, owner); + expect((await drain(next!.id)).outcome).toBe("healed"); + expect((await getData(c.id, owner)).data).toEqual(baseline.data); + const events = await readEvents(c.id); + expect(events.some(e => e.type === "contract.violated")).toBe(true); + expect(events.some(e => e.type === "contract.healed")).toBe(true); + expect(events.every(e => [a!.id, next!.id].includes(e.runId!))).toBe(true); + const cursor = events[2]!.sequence; + expect((await readEvents(c.id, BigInt(cursor))).every(e => BigInt(e.sequence) > BigInt(cursor))).toBe(true); + expect((await triggerRun(c.id, owner, "same"))!.id).toBe(a!.id); + }); + it("bounds repairs and preserves the last accepted result", async () => { + const c = await createContract(definition, owner), first = await triggerRun(c.id, owner); + await drain(first!.id); await mutate(c.id, "unrepairable"); + const second = await triggerRun(c.id, owner), failed = await drain(second!.id); + expect(failed.outcome).toBe("escalated"); expect(failed.healAttempts).toBe(3); + expect((await getData(c.id, owner)).runId).toBe(first!.id); + expect((await getContract(c.id, owner)).enabled).toBe(false); + }); + it("quarantines suspicious changes until an explicit review accepts them", async () => { + const c = await createContract(definition, owner), first = await triggerRun(c.id, owner); + await drain(first!.id); await mutate(c.id, "value", "price", "900"); + const next = await triggerRun(c.id, owner); + expect((await drain(next!.id)).outcome).toBe("review_required"); + expect((await getData(c.id, owner)).runId).toBe(first!.id); + await expect(triggerRun(c.id, owner)).rejects.toThrow(); + await reviewRun(next!.id, owner, "accept"); + expect((await getData(c.id, owner)).data.price).toBe(900); + }); + it("rejects suspicious candidates without changing accepted data", async () => { + const c = await createContract(definition, owner), first = await triggerRun(c.id, owner); + await drain(first!.id); await mutate(c.id, "value", "price", "900"); + const next = await triggerRun(c.id, owner); await drain(next!.id); + await reviewRun(next!.id, owner, "reject"); + expect((await getData(c.id, owner)).runId).toBe(first!.id); + }); + it("has one lease winner, resumes an expired lease and fences the stale worker", async () => { + const c = await createContract(definition, owner), queued = await triggerRun(c.id, owner); + const claims = await Promise.all([claimRun(), claimRun()]); + expect(claims.filter(Boolean)).toHaveLength(1); + const old = claims.find(Boolean)!; + await db.run.update({ where: { id: old.id }, data: { leaseUntil: new Date(Date.now() - 1000) } }); + const replacement = await claimRun(); expect(replacement!.leaseToken).not.toBe(old.leaseToken); + await processRun(old); + expect((await db.run.findUniqueOrThrow({ where: { id: old.id } })).state).toBe("queued"); + await processRun(replacement!); + expect((await drain(queued!.id)).outcome).toBe("valid"); + }); + it("does not replay interrupted external submissions", async () => { + const c = await createContract(definition, owner), queued = await triggerRun(c.id, owner); + await db.run.update({ where: { id: queued!.id }, data: { state: "extracting_submitting" } }); + expect((await drain(queued!.id)).outcome).toBe("indeterminate"); + expect((await getContract(c.id, owner)).enabled).toBe(false); + await reconcileRun(queued!.id, owner, "Confirmed the remote job was cancelled in Studio"); + expect((await getContract(c.id, owner)).status).toBe("broken"); + }); + it("does not queue duplicate scheduled runs", async () => { + const c = await createContract({ ...definition, enabled: true }, owner); + const runs = await Promise.all([enqueueRun(c.id, undefined, true), enqueueRun(c.id, undefined, true)]); + expect(runs.filter(Boolean)).toHaveLength(1); + await drain(runs.find(Boolean)!.id); + }); + it("fails expired workflows rather than polling forever", async () => { + const c = await createContract(definition, owner), run = await triggerRun(c.id, owner); + await db.run.update({ where: { id: run!.id }, data: { deadlineAt: new Date(Date.now() - 1000) } }); + expect((await drain(run!.id)).outcome).toBe("failed"); + }); + it("enforces ownership and hashed, revocable credentials", async () => { + const c = await createContract(definition, owner); + await expect(getContract(c.id, "other-user")).rejects.toThrow(); + await expect(triggerRun(c.id, "other-user")).rejects.toThrow(); + const issued = await issueToken(owner, "api"); + const stored = await db.session.findUniqueOrThrow({ where: { id: issued.id } }); + expect(stored.tokenHash).not.toBe(issued.token); + const req = new Request("http://localhost:3000/api/contracts", { headers: { Authorization: `Bearer ${issued.token}` } }); + expect(await requireUser(req)).toBe(owner); + await db.session.delete({ where: { id: issued.id } }); + await expect(requireUser(req)).rejects.toThrow(); + }); + it("rejects cookie-authenticated writes without origin and cross-origin bearer requests", async () => { + const s = await issueToken(owner, "session"); + await expect(requireUser(new Request("http://localhost:3000/api/contracts", { method: "POST", headers: { cookie: `lippy_session=${s.token}` } }))).rejects.toThrow(); + await expect(requireUser(new Request("http://localhost:3000/api/contracts", { headers: { origin: "https://attacker.example" } }))).rejects.toThrow(); + }); + it("rejects OAuth state mismatch before exchanging any authorization code", async () => { + const login = await beginGoogleLogin(); + expect(new URL(login.url).searchParams.get("code_challenge_method")).toBe("S256"); + await expect(completeGoogleLogin("code", login.state, "wrong-cookie")).rejects.toThrow("state mismatch"); + }); + it("disables the development authentication bypass in production", async () => { + const old = { auth: env.AUTH_MODE, node: env.NODE_ENV }; + try { + env.AUTH_MODE = "development"; env.NODE_ENV = "production"; + await expect(requireUser(new Request("https://example.com/api/contracts"))).rejects.toThrow(); + } finally { env.AUTH_MODE = old.auth; env.NODE_ENV = old.node; } + }); +}); diff --git a/tests/semantic.test.ts b/tests/semantic.test.ts new file mode 100644 index 0000000..c6e0d5a --- /dev/null +++ b/tests/semantic.test.ts @@ -0,0 +1,34 @@ +import { describe, it, expect } from "vitest"; +import { contractDefinitionSchema, contractFieldSchema } from "../lib/contracts/schema"; +import { diffFields } from "../lib/semantic/diff"; +import { validateAgainstContract } from "../lib/contracts/validator"; +import { createFixture, extractFixture, mutateFixture, repairFixture } from "../lib/fixtures/scraper"; +const fields = contractFieldSchema.array().parse([{ key: "product", type: "string", expectedValue: "Example product" }, { key: "price", type: "number", minimum: 0, maxRelativeChange: 0.5 }]); +describe("semantic contract verification", () => { + it("treats the first accepted run as baseline", () => expect(diffFields(fields, null, { price: 100 })).toEqual([])); + it("ignores equivalent whitespace and unicode formatting", () => expect(diffFields(fields, { product: "Example product" }, { product: " Example product " })).toEqual([])); + it("reports a real price change and flags a policy breach", () => { + expect(diffFields(fields, { price: 100 }, { price: 110 })[0]?.reviewRequired).toBe(false); + expect(diffFields(fields, { price: 100 }, { price: 500 })[0]?.reviewRequired).toBe(true); + }); + it("handles a zero baseline and optional-field removal", () => { + expect(diffFields(fields, { price: 0 }, { price: 1 })[0]?.reviewRequired).toBe(true); + expect(diffFields(fields, { price: 100 }, {})[0]?.currentValue).toBe(null); + }); + it("rejects schema-valid data from the wrong product", () => expect(validateAgainstContract(fields, { product: "Another product", price: 100 }).ok).toBe(false)); + it("rejects duplicate and prototype field names", () => { + for (const keys of [["a", "a"], ["__proto__"]]) expect(contractDefinitionSchema.safeParse({ url: "https://example.com", fields: keys.map(key => ({ key, type: "string" })) }).success).toBe(false); + }); + it("repairs changed HTML selectors, then actually re-extracts", () => { + const initial = createFixture(fields), broken = mutateFixture(initial, "layout"); + expect(validateAgainstContract(fields, extractFixture(broken, fields)).ok).toBe(false); + const repaired = repairFixture(broken, fields); + expect(repaired.diff).toHaveLength(2); + expect(validateAgainstContract(fields, extractFixture(repaired.fixture, fields)).ok).toBe(true); + expect(extractFixture(initial, fields).product).toBe("Example product"); + }); + it("does not fabricate values when the source removes them", () => { + const gone = mutateFixture(createFixture(fields), "unrepairable"); + expect(validateAgainstContract(fields, extractFixture(repairFixture(gone, fields).fixture, fields)).ok).toBe(false); + }); +}); diff --git a/tests/setup.ts b/tests/setup.ts new file mode 100644 index 0000000..21d682c --- /dev/null +++ b/tests/setup.ts @@ -0,0 +1,6 @@ +process.env.DATABASE_URL ??= "postgresql://localhost/lippy_unused"; +process.env.EXTRACTION_PROVIDER = "fixture"; +process.env.AUTH_MODE = "google"; +process.env.BRIGHTDATA_API_KEY = "test-key"; +process.env.GOOGLE_CLIENT_ID = "test-client"; +process.env.GOOGLE_CLIENT_SECRET = "test-secret"; diff --git a/vitest.config.ts b/vitest.config.ts index 9db6c1f..35f0f59 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -14,5 +14,8 @@ export default defineConfig({ }, test: { environment: "node", + setupFiles: ["./tests/setup.ts"], + fileParallelism: false, + testTimeout: 15000, }, });