From 617391cf05fb50030155a4b16477f8f223a4a3ca Mon Sep 17 00:00:00 2001 From: jakeaturner Date: Wed, 8 Jul 2026 17:11:09 +0000 Subject: [PATCH] feat: auto-generating OpenAPI docs with Scalar UI --- admin/app/controllers/openapi_controller.ts | 66 + admin/app/validators/responses/chat.ts | 33 + admin/app/validators/responses/common.ts | 37 + admin/docs/api-reference.md | 207 +- admin/package-lock.json | 2213 ++++++++++++++++++- admin/package.json | 3 +- admin/start/openapi/documented.ts | 75 + admin/start/openapi/generator.ts | 234 ++ admin/start/routes.ts | 760 +++++-- admin/tests/functional/openapi.spec.ts | 70 + 10 files changed, 3355 insertions(+), 343 deletions(-) create mode 100644 admin/app/controllers/openapi_controller.ts create mode 100644 admin/app/validators/responses/chat.ts create mode 100644 admin/app/validators/responses/common.ts create mode 100644 admin/start/openapi/documented.ts create mode 100644 admin/start/openapi/generator.ts create mode 100644 admin/tests/functional/openapi.spec.ts diff --git a/admin/app/controllers/openapi_controller.ts b/admin/app/controllers/openapi_controller.ts new file mode 100644 index 0000000..01afea9 --- /dev/null +++ b/admin/app/controllers/openapi_controller.ts @@ -0,0 +1,66 @@ +import { createRequire } from 'node:module' +import { readFileSync } from 'node:fs' +import type { HttpContext } from '@adonisjs/core/http' +import { buildOpenApiDocument } from '#start/openapi/generator' + +const require = createRequire(import.meta.url) + +/** + * The self-contained Scalar browser bundle, read from the installed + * `@scalar/api-reference` package and cached in memory. Served from this app + * (not a CDN) so the API reference works on an offline appliance. + */ +let scalarBundle: string | null = null + +function getScalarBundle(): string { + if (scalarBundle === null) { + const entry = require.resolve('@scalar/api-reference') + const marker = '/node_modules/@scalar/api-reference/' + const root = entry.slice(0, entry.lastIndexOf(marker) + marker.length - 1) + scalarBundle = readFileSync(`${root}/dist/browser/standalone.js`, 'utf-8') + } + return scalarBundle +} + +/** + * The reference page mounts Scalar against our locally served bundle and spec. + * Scalar auto-detects the `#api-reference` element and reads `data-url`. + */ +const REFERENCE_HTML = ` + + + + + Nomad Admin API Reference + + + + + +` + +export default class OpenApiController { + /** + * The generated OpenAPI 3.1 document. + */ + async spec({ response }: HttpContext) { + return response.json(buildOpenApiDocument()) + } + + /** + * The interactive Scalar API reference page. + */ + async reference({ response }: HttpContext) { + return response.header('content-type', 'text/html').send(REFERENCE_HTML) + } + + /** + * The locally bundled Scalar JS (kept off any CDN for offline use). + */ + async standalone({ response }: HttpContext) { + return response + .header('content-type', 'application/javascript; charset=utf-8') + .header('cache-control', 'public, max-age=86400') + .send(getScalarBundle()) + } +} diff --git a/admin/app/validators/responses/chat.ts b/admin/app/validators/responses/chat.ts new file mode 100644 index 0000000..627c653 --- /dev/null +++ b/admin/app/validators/responses/chat.ts @@ -0,0 +1,33 @@ +/* +|-------------------------------------------------------------------------- +| Chat response schemas +|-------------------------------------------------------------------------- +| +| Shapes mirror the serialized `ChatSession` / `ChatMessage` Lucid models +| (app/models/chat_session.ts, app/models/chat_message.ts). `messages` is only +| present when the relation is preloaded, so it is optional. +| +*/ +import vine from '@vinejs/vine' + +const chatMessage = vine.object({ + id: vine.number(), + session_id: vine.number(), + role: vine.enum(['system', 'user', 'assistant'] as const), + content: vine.string(), + created_at: vine.string(), + updated_at: vine.string(), +}) + +const chatSession = vine.object({ + id: vine.number(), + title: vine.string(), + model: vine.string().nullable(), + created_at: vine.string(), + updated_at: vine.string(), + messages: vine.array(chatMessage).optional(), +}) + +export const chatSessionResponse = vine.compile(chatSession) +export const chatSessionListResponse = vine.compile(vine.array(chatSession.clone())) +export const chatMessageResponse = vine.compile(chatMessage.clone()) diff --git a/admin/app/validators/responses/common.ts b/admin/app/validators/responses/common.ts new file mode 100644 index 0000000..975f500 --- /dev/null +++ b/admin/app/validators/responses/common.ts @@ -0,0 +1,37 @@ +/* +|-------------------------------------------------------------------------- +| Shared response schemas +|-------------------------------------------------------------------------- +| +| Response schemas are authored as VineJS validators purely so the OpenAPI +| generator can turn them into documented response bodies via `toJSONSchema()`. +| They are not (currently) used to validate outgoing responses — though they +| could be asserted against in tests. +| +| Only add a schema here once its shape is verified against the controller / +| model it documents; a wrong response schema is worse than none. +| +*/ +import vine from '@vinejs/vine' + +/** `{ status: 'ok' }` health probes. */ +export const healthResponse = vine.compile( + vine.object({ + status: vine.string(), + }) +) + +/** The `{ error: '...' }` envelope returned on most failure paths. */ +export const errorResponse = vine.compile( + vine.object({ + error: vine.string(), + }) +) + +/** The `{ success, message? }` envelope used by many mutation endpoints. */ +export const successMessageResponse = vine.compile( + vine.object({ + success: vine.boolean(), + message: vine.string().optional(), + }) +) diff --git a/admin/docs/api-reference.md b/admin/docs/api-reference.md index c14348c..2539342 100644 --- a/admin/docs/api-reference.md +++ b/admin/docs/api-reference.md @@ -4,6 +4,19 @@ N.O.M.A.D. exposes a REST API for all operations. All endpoints are under `/api/ --- +## Interactive reference + +The full, always-current endpoint reference is generated directly from the application's +routes and validators and served as an interactive [Scalar](https://scalar.com) UI: + +- **[/reference](/reference)** — browse every endpoint, request/response schema, and try calls live +- **[/api/openapi.json](/api/openapi.json)** — the raw OpenAPI 3.1 document (import into Postman, Insomnia, codegen, etc.) + +Because it is derived from the same VineJS validators the API validates against, it never drifts +from the implementation. Prefer it over any hand-written endpoint list. + +--- + ## Conventions **Base URL:** `http:///api` @@ -15,195 +28,5 @@ N.O.M.A.D. exposes a REST API for all operations. All endpoints are under `/api/ **Async pattern:** Submit a job → receive an ID → poll a status endpoint until complete. ---- - -## Health - -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/health` | Returns `{ "status": "ok" }` | - ---- - -## System - -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/system/info` | CPU, memory, disk, and platform info | -| GET | `/api/system/internet-status` | Check internet connectivity | -| GET | `/api/system/debug-info` | Detailed debug information | -| GET | `/api/system/latest-version` | Check for the latest N.O.M.A.D. version | -| POST | `/api/system/update` | Trigger a system update | -| GET | `/api/system/update/status` | Get update progress | -| GET | `/api/system/update/logs` | Get update operation logs | -| GET | `/api/system/settings` | Get a setting value (query param: `key`) | -| PATCH | `/api/system/settings` | Update a setting (`{ key, value }`) | -| POST | `/api/system/subscribe-release-notes` | Subscribe an email to release notes | - -### Services - -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/system/services` | List all services with status | -| POST | `/api/system/services/install` | Install a service | -| POST | `/api/system/services/force-reinstall` | Force reinstall a service | -| POST | `/api/system/services/affect` | Start, stop, or restart a service (body: `{ name, action }`) | -| POST | `/api/system/services/check-updates` | Check for available service updates | -| POST | `/api/system/services/update` | Update a service to a specific version | -| GET | `/api/system/services/:name/available-versions` | List available versions for a service | - ---- - -## AI Chat - -### Models - -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/ollama/models` | List available models (supports filtering, sorting, pagination) | -| GET | `/api/ollama/installed-models` | List locally installed models | -| POST | `/api/ollama/models` | Download a model (async, returns job) | -| DELETE | `/api/ollama/models` | Delete an installed model | - -### Chat - -| Method | Path | Description | -|--------|------|-------------| -| POST | `/api/ollama/chat` | Send a chat message. Supports streaming (SSE) and RAG context injection. Body: `{ model, messages, stream?, useRag? }` | -| GET | `/api/chat/suggestions` | Get suggested chat prompts | - -### Remote Ollama - -| Method | Path | Description | -|--------|------|-------------| -| POST | `/api/ollama/configure-remote` | Configure a remote Ollama or LM Studio instance | -| GET | `/api/ollama/remote-status` | Check remote Ollama connection status | - -### Chat Sessions - -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/chat/sessions` | List all chat sessions | -| POST | `/api/chat/sessions` | Create a new session | -| GET | `/api/chat/sessions/:id` | Get a session with its messages | -| PUT | `/api/chat/sessions/:id` | Update session metadata (title, etc.) | -| DELETE | `/api/chat/sessions/:id` | Delete a session | -| DELETE | `/api/chat/sessions/all` | Delete all sessions | -| POST | `/api/chat/sessions/:id/messages` | Add a message to a session | - -**Streaming:** The `/api/ollama/chat` endpoint supports Server-Sent Events (SSE) when `stream: true` is passed. Connect using `EventSource` or `fetch` with a streaming reader. - ---- - -## Knowledge Base (RAG) - -Upload documents to enable AI-powered retrieval during chat. - -| Method | Path | Description | -|--------|------|-------------| -| POST | `/api/rag/upload` | Upload a file for embedding (async, 202 response) | -| GET | `/api/rag/files` | List stored RAG files | -| DELETE | `/api/rag/files` | Delete a file (query param: `source`) | -| GET | `/api/rag/active-jobs` | List active embedding jobs | -| GET | `/api/rag/job-status` | Get status for a specific file embedding job | -| GET | `/api/rag/failed-jobs` | List failed embedding jobs | -| DELETE | `/api/rag/failed-jobs` | Clean up failed jobs and delete associated files | -| POST | `/api/rag/sync` | Scan storage and sync database with filesystem | - ---- - -## ZIM Files (Offline Content) - -ZIM files provide offline Wikipedia, books, and other content via Kiwix. - -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/zim/list` | List locally stored ZIM files | -| GET | `/api/zim/list-remote` | List remote ZIM files (paginated, supports search) | -| GET | `/api/zim/curated-categories` | List curated categories with Essential/Standard/Comprehensive tiers | -| POST | `/api/zim/download-remote` | Download a remote ZIM file (async) | -| POST | `/api/zim/download-category-tier` | Download a full category tier | -| DELETE | `/api/zim/:filename` | Delete a local ZIM file | - -### Wikipedia - -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/zim/wikipedia` | Get current Wikipedia selection state | -| POST | `/api/zim/wikipedia/select` | Select a Wikipedia edition and tier | - ---- - -## Maps - -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/maps/regions` | List available map regions | -| GET | `/api/maps/styles` | Get map styles JSON | -| GET | `/api/maps/curated-collections` | List curated map collections | -| POST | `/api/maps/fetch-latest-collections` | Fetch latest collection metadata from source | -| POST | `/api/maps/download-base-assets` | Download base map assets | -| POST | `/api/maps/download-remote` | Download a remote map file (async) | -| POST | `/api/maps/download-remote-preflight` | Check download size/info before starting | -| POST | `/api/maps/download-collection` | Download an entire collection by slug (async) | -| DELETE | `/api/maps/:filename` | Delete a local map file | - -### Map Markers - -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/maps/markers` | List map markers | -| POST | `/api/maps/markers` | Add map marker (body: {"name": "Test Marker", "notes": "Example note", "longitude": 0.0, "latitude": 0.0, "color": "yellow", "marker_type": "pin"} ) | -| PATCH | `/api/maps/markers/{id}` | Update a map marker (body: {"name": "Test Marker", "notes": "Example note", "longitude": 0.0, "latitude": 0.0, "color": "yellow", "marker_type": "pin"} ) fields that don't change can be omitted| -| DELETE | `/api/maps/markers/{id}` | Delete a map marker | - ---- - -## Downloads - -Manage background download jobs for maps, ZIM files, and models. - -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/downloads/jobs` | List all download jobs | -| GET | `/api/downloads/jobs/:filetype` | List jobs filtered by type (`zim`, `map`, etc.) | -| DELETE | `/api/downloads/jobs/:jobId` | Cancel and remove a download job | - ---- - -## Benchmarks - -| Method | Path | Description | -|--------|------|-------------| -| POST | `/api/benchmark/run` | Run a benchmark (`full`, `system`, or `ai`; can be async) | -| POST | `/api/benchmark/run/system` | Run system-only benchmark | -| POST | `/api/benchmark/run/ai` | Run AI-only benchmark | -| GET | `/api/benchmark/status` | Get current benchmark status (`idle` or `running`) | -| GET | `/api/benchmark/results` | Get all benchmark results | -| GET | `/api/benchmark/results/latest` | Get the most recent result | -| GET | `/api/benchmark/results/:id` | Get a specific result | -| POST | `/api/benchmark/submit` | Submit a result to the central repository | -| POST | `/api/benchmark/builder-tag` | Update builder tag metadata for a result | -| GET | `/api/benchmark/comparison` | Get comparison stats from the repository | -| GET | `/api/benchmark/settings` | Get benchmark settings | -| POST | `/api/benchmark/settings` | Update benchmark settings | - ---- - -## Easy Setup & Content Updates - -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/easy-setup/curated-categories` | List curated content categories for setup wizard | -| POST | `/api/manifests/refresh` | Refresh manifest caches (`zim_categories`, `maps`, `wikipedia`) | -| POST | `/api/content-updates/check` | Check for available collection updates | -| POST | `/api/content-updates/apply` | Apply a single content update | -| POST | `/api/content-updates/apply-all` | Apply multiple content updates | - ---- - -## Documentation - -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/docs/list` | List all available documentation files | +**Streaming:** The `/api/ollama/chat` endpoint supports Server-Sent Events (SSE) when `stream: true` +is passed. Connect using `EventSource` or `fetch` with a streaming reader. diff --git a/admin/package-lock.json b/admin/package-lock.json index 0a18db6..e8b6c92 100644 --- a/admin/package-lock.json +++ b/admin/package-lock.json @@ -27,6 +27,7 @@ "@openzim/libzim": "4.0.0", "@protomaps/basemaps": "5.7.0", "@qdrant/js-client-rest": "1.16.2", + "@scalar/api-reference": "^1.62.4", "@tabler/icons-react": "3.36.1", "@tailwindcss/vite": "4.3.0", "@tanstack/react-query": "5.90.20", @@ -36,7 +37,7 @@ "@uppy/dashboard": "5.1.0", "@uppy/react": "5.1.1", "@uppy/xhr-upload": "5.2.0", - "@vinejs/vine": "3.0.1", + "@vinejs/vine": "^4.2.0", "@vitejs/plugin-react": "4.7.0", "autoprefixer": "10.5.0", "axios": "1.17.0", @@ -840,6 +841,69 @@ } } }, + "node_modules/@ai-sdk/gateway": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.13.tgz", + "integrity": "sha512-g7nE4PFtngOZNZSy1lOPpkC+FAiHxqBJXqyRMEG7NUrEVZlz5goBdtHg1YgWRJIX776JTXAmbOI5JreAKVAsVA==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.2", + "@ai-sdk/provider-utils": "4.0.5", + "@vercel/oidc": "3.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/provider": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.2.tgz", + "integrity": "sha512-HrEmNt/BH/hkQ7zpi2o6N3k1ZR1QTb7z85WYhYygiTxOQuaml4CMtHCWRbric5WPU+RNsYI7r1EpyVQMKO1pYw==", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/provider-utils": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.5.tgz", + "integrity": "sha512-Ow/X/SEkeExTTc1x+nYLB9ZHK2WUId8+9TlkamAx7Tl9vxU+cKzWx2dwjgMHeCN6twrgwkLrrtqckQeO4mxgVA==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.2", + "@standard-schema/spec": "^1.1.0", + "eventsource-parser": "^3.0.6" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/vue": { + "version": "3.0.33", + "resolved": "https://registry.npmjs.org/@ai-sdk/vue/-/vue-3.0.33.tgz", + "integrity": "sha512-czM9Js3a7f+Eo35gjEYEeJYUoPvMg5Dfi4bOLyDBghLqn0gaVg8yTmTaSuHCg+3K/+1xPjyXd4+2XcQIohWWiQ==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider-utils": "4.0.5", + "ai": "6.0.33", + "swrv": "^1.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "vue": "^3.3.4" + } + }, "node_modules/@antfu/install-pkg": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-0.4.1.tgz", @@ -1270,6 +1334,160 @@ "@chonkiejs/chunk": "^0.9.3" } }, + "node_modules/@codemirror/autocomplete": { + "version": "6.20.3", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.3.tgz", + "integrity": "sha512-tlosUqb+3BbxCxZdu4tKeRghPFC+QM7q4X5YhKV2eCmPG+1r2F3f4AaSz5sCrFqUtX4Jh20VFTKecl16MgiV9g==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.4.tgz", + "integrity": "sha512-Ryk9y9T0FFVF0cUGhAknveAyUOl/A1qReTFi+qPKtOh2Z9F4AUBz3XOrYD4ZEgZirdugVzHvd/2/Wcwy5OliTg==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.7.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-css": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz", + "integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.2", + "@lezer/css": "^1.1.7" + } + }, + "node_modules/@codemirror/lang-html": { + "version": "6.4.11", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz", + "integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/css": "^1.1.0", + "@lezer/html": "^1.3.12" + } + }, + "node_modules/@codemirror/lang-javascript": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.5.tgz", + "integrity": "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/javascript": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-json": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz", + "integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/json": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-xml": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.1.0.tgz", + "integrity": "sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/xml": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-yaml": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.3.tgz", + "integrity": "sha512-AZ8DJBuXGVHybpBQhmZtgew5//4hv3tdkXnr3vDmOUMJRuB6vn/uuwtmTOTlqEaQFg3hQSVeA90NmvIQyUV6FQ==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.2.0", + "@lezer/lr": "^1.0.0", + "@lezer/yaml": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.12.4", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.4.tgz", + "integrity": "sha512-1q4PaT+o6PbgpkJt4Q8Fv5XJxTy4FUZ4MWETtyiDw3J0Pyr9E2vqcKL+k9wcvjNTIsauxvE7OfmWj3FRPHQ76A==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.7.tgz", + "integrity": "sha512-28/+iWLYxKxsvGYhSYL7zaCZqLz5+FFFDq9tVsvGv9kv8RY4fFAchJ5WX9M3YrrRlTIsECjsXPqeNgnSmNP2dg==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.42.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.7.1.tgz", + "integrity": "sha512-9QzNDgE4EYDnAHfrTlR2lwiPciiOymLtwKK+8yHQzCc7GXhAP9xdEbEJFy2IWB1j9UGUl9BsgMmTo/ImA02T7A==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.43.6", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.6.tgz", + "integrity": "sha512-EVunGSYN1wz1p75WY1s3Xg7t3i8Yol0kGZGizNdX9BUFgMFILYVe8/u6EVpo7Ff5PwbZuILb4QAq7IZoKzIEQA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.7.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -2003,6 +2221,43 @@ "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", "license": "MIT" }, + "node_modules/@floating-ui/vue": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@floating-ui/vue/-/vue-1.1.9.tgz", + "integrity": "sha512-BfNqNW6KA83Nexspgb9DZuz578R7HT8MZw1CfK9I6Ah4QReNWEJsXWHN+SdmOVLNGmTPDi+fDT535Df5PzMLbQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.4", + "@floating-ui/utils": "^0.2.10", + "vue-demi": ">=0.13.0" + } + }, + "node_modules/@floating-ui/vue/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/@grpc/grpc-js": { "version": "1.14.4", "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.4.tgz", @@ -2072,6 +2327,33 @@ "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, + "node_modules/@headlessui/tailwindcss": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@headlessui/tailwindcss/-/tailwindcss-0.2.2.tgz", + "integrity": "sha512-xNe42KjdyA4kfUKLLPGzME9zkH7Q3rOZ5huFihWNWOQFxnItxPB3/67yBI8/qBfY8nwBRx5GHn4VprsoluVMGw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "tailwindcss": "^3.0 || ^4.0" + } + }, + "node_modules/@headlessui/vue": { + "version": "1.7.23", + "resolved": "https://registry.npmjs.org/@headlessui/vue/-/vue-1.7.23.tgz", + "integrity": "sha512-JzdCNqurrtuu0YW6QaDtR2PIYCKPUWq28csDyMvN4zmGccmE7lz40Is6hc3LA4HFeCI7sekZ/PQMTNmn9I/4Wg==", + "license": "MIT", + "dependencies": { + "@tanstack/vue-virtual": "^3.0.0-beta.60" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, "node_modules/@humanfs/core": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", @@ -3142,6 +3424,96 @@ "url": "https://opencollective.com/js-sdsl" } }, + "node_modules/@lezer/common": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz", + "integrity": "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==", + "license": "MIT" + }, + "node_modules/@lezer/css": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.4.tgz", + "integrity": "sha512-N+tn9tej2hPvyKgHEApMOQfHczDJCwxrRFS3SPn9QjYN+uwHvEDnCgKRrb3mxDYxRS8sKMM8fhC3+lc04Abz5Q==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/highlight": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/html": { + "version": "1.3.13", + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.13.tgz", + "integrity": "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/javascript": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz", + "integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/json": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz", + "integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.10.tgz", + "integrity": "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/xml": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.6.tgz", + "integrity": "sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/yaml": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@lezer/yaml/-/yaml-1.0.4.tgz", + "integrity": "sha512-2lrrHqxalACEbxIbsjhqGpSW8kWpUKuY6RHgnSAFZa6qK62wvnPxA8hGOwOoDbwHcOFs5M4o27mjGu+P7TvBmw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.4.0" + } + }, "node_modules/@mapbox/geojson-rewind": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz", @@ -3237,6 +3609,12 @@ "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==", "license": "ISC" }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.3.tgz", + "integrity": "sha512-FY+MKLBoTsLNJF/eLWaOsXGdz6uh3Iu1axjPf6TUq92IYumcTcXWHoS747JARLkcdlJ/Waiaxc5wQfFO8jC6NA==", + "license": "MIT" + }, "node_modules/@markdoc/markdoc": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/@markdoc/markdoc/-/markdoc-0.5.4.tgz", @@ -3657,6 +4035,15 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/@openzim/libzim": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@openzim/libzim/-/libzim-4.0.0.tgz", @@ -3727,6 +4114,12 @@ "node": ">=10" } }, + "node_modules/@phosphor-icons/core": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@phosphor-icons/core/-/core-2.1.1.tgz", + "integrity": "sha512-v4ARvrip4qBCImOE5rmPUylOEK4iiED9ZyKjcvzuezqMaiRASCHKcRIuvvxL/twvLpkfnEODCOJp5dM4eZilxQ==", + "license": "MIT" + }, "node_modules/@pinojs/redact": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", @@ -4108,6 +4501,17 @@ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, + "node_modules/@replit/codemirror-css-color-picker": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@replit/codemirror-css-color-picker/-/codemirror-css-color-picker-6.3.0.tgz", + "integrity": "sha512-19biDANghUm7Fz7L1SNMIhK48tagaWuCOHj4oPPxc7hxPGkTVY2lU/jVZ8tsbTKQPVG7BO2CBDzs7CBwb20t4A==", + "license": "MIT", + "peerDependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", @@ -4478,6 +4882,475 @@ "win32" ] }, + "node_modules/@scalar/agent-chat": { + "version": "0.12.17", + "resolved": "https://registry.npmjs.org/@scalar/agent-chat/-/agent-chat-0.12.17.tgz", + "integrity": "sha512-rz/PW+VFQX4vGVsRfoP2FYb7gU40lR/KnpBRJEiael6HR+NbpTu3cUZhUOk9+MXki46+X5sHWKNfBdr9HLCb1Q==", + "license": "MIT", + "dependencies": { + "@ai-sdk/vue": "3.0.33", + "@scalar/api-client": "3.13.2", + "@scalar/components": "0.27.5", + "@scalar/helpers": "0.9.0", + "@scalar/icons": "0.7.3", + "@scalar/json-magic": "0.12.17", + "@scalar/openapi-types": "0.9.1", + "@scalar/schemas": "0.7.1", + "@scalar/themes": "0.16.2", + "@scalar/types": "0.16.1", + "@scalar/use-toasts": "0.10.2", + "@scalar/validation": "0.6.0", + "@scalar/workspace-store": "0.55.2", + "@vueuse/core": "13.9.0", + "ai": "6.0.33", + "js-base64": "^3.7.8", + "neverpanic": "0.0.8", + "truncate-json": "3.0.1", + "vue": "^3.5.30" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/api-client": { + "version": "3.13.2", + "resolved": "https://registry.npmjs.org/@scalar/api-client/-/api-client-3.13.2.tgz", + "integrity": "sha512-g5kCuZaCdLakJrfavBb8/gRi5Yldq77qkismSFI1CUartT+Jo5EpOMH2hNyZZJ9K7qzhK5R5gcPjYjaccJ4HNw==", + "license": "MIT", + "dependencies": { + "@headlessui/tailwindcss": "^0.2.2", + "@headlessui/vue": "1.7.23", + "@scalar/blocks": "0.1.3", + "@scalar/components": "0.27.5", + "@scalar/helpers": "0.9.0", + "@scalar/icons": "0.7.3", + "@scalar/oas-utils": "0.19.3", + "@scalar/openapi-types": "0.9.1", + "@scalar/sidebar": "0.9.28", + "@scalar/snippetz": "0.9.20", + "@scalar/themes": "0.16.2", + "@scalar/typebox": "^0.1.3", + "@scalar/types": "0.16.1", + "@scalar/use-codemirror": "0.14.12", + "@scalar/use-hooks": "0.4.7", + "@scalar/use-toasts": "0.10.2", + "@scalar/workspace-store": "0.55.2", + "@vueuse/core": "13.9.0", + "@vueuse/integrations": "13.9.0", + "focus-trap": "^7.8.0", + "fuse.js": "^7.1.0", + "js-base64": "^3.7.8", + "jsonc-parser": "3.3.1", + "nanoid": "^5.1.6", + "pretty-ms": "^9.3.0", + "radix-vue": "^1.9.17", + "set-cookie-parser": "3.1.0", + "vue": "^3.5.30", + "yaml": "^2.8.3", + "zod": "^4.3.5" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/api-reference": { + "version": "1.62.4", + "resolved": "https://registry.npmjs.org/@scalar/api-reference/-/api-reference-1.62.4.tgz", + "integrity": "sha512-/wYzZzr85K2mzsNt7uvLsugtvFn40QOesBLghubiU6hl0sw4Wog+TKvRPI0G8hcjqyWsEeZaTwtVZPuWBC9w3w==", + "license": "MIT", + "dependencies": { + "@headlessui/vue": "1.7.23", + "@scalar/agent-chat": "0.12.17", + "@scalar/api-client": "3.13.2", + "@scalar/blocks": "0.1.3", + "@scalar/code-highlight": "0.4.1", + "@scalar/components": "0.27.5", + "@scalar/helpers": "0.9.0", + "@scalar/icons": "0.7.3", + "@scalar/oas-utils": "0.19.3", + "@scalar/schemas": "0.7.1", + "@scalar/sidebar": "0.9.28", + "@scalar/snippetz": "0.9.20", + "@scalar/themes": "0.16.2", + "@scalar/types": "0.16.1", + "@scalar/use-hooks": "0.4.7", + "@scalar/use-toasts": "0.10.2", + "@scalar/validation": "0.6.0", + "@scalar/workspace-store": "0.55.2", + "@unhead/vue": "^2.1.4", + "@vueuse/core": "13.9.0", + "fuse.js": "^7.1.0", + "microdiff": "^1.5.0", + "nanoid": "^5.1.6", + "vue": "^3.5.30", + "yaml": "^2.8.3" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/asyncapi-upgrader": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@scalar/asyncapi-upgrader/-/asyncapi-upgrader-0.1.2.tgz", + "integrity": "sha512-h6NUhsctrhucrbO2XHWbTXp9EWt7eL5vvlsooUEw2bB014KSPd41JrBQ6t0h/dFdmhwxdbBI7Hmg9eZa/mlCXA==", + "license": "MIT", + "dependencies": { + "@scalar/helpers": "0.9.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/blocks": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@scalar/blocks/-/blocks-0.1.3.tgz", + "integrity": "sha512-jHpyO7wnfoS7fCBvDVVIX3vIvLd5Uf7DNFKn0vMK66x0CF+g7mjoqjQcUju2eWe1fYK9N8M87xoPr7jvBczjWA==", + "license": "MIT", + "dependencies": { + "@scalar/components": "0.27.5", + "@scalar/helpers": "0.9.0", + "@scalar/icons": "0.7.3", + "@scalar/snippetz": "0.9.20", + "@scalar/themes": "0.16.2", + "@scalar/types": "0.16.1", + "@scalar/workspace-store": "0.55.2", + "@types/har-format": "^1.2.16", + "js-base64": "^3.7.8", + "vue": "^3.5.30" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/code-highlight": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@scalar/code-highlight/-/code-highlight-0.4.1.tgz", + "integrity": "sha512-pnr8gbQuvSXu4fcPPNkFNjAxxMWiJLffN57lRgVN3FCk+Q8hKHWWT01ulAT1/0Kg3/VmgtObrMHhX2qYnpM9+g==", + "license": "MIT", + "dependencies": { + "hast-util-to-text": "^4.0.2", + "highlight.js": "^11.11.1", + "lowlight": "^3.3.0", + "rehype-external-links": "^3.0.0", + "rehype-format": "^5.0.1", + "rehype-parse": "^9.0.1", + "rehype-raw": "^7.0.0", + "rehype-sanitize": "^6.0.0", + "rehype-stringify": "^10.0.1", + "remark-gfm": "^4.0.1", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", + "remark-stringify": "^11.0.0", + "unified": "^11.0.5", + "unist-util-visit": "^5.1.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/components": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/@scalar/components/-/components-0.27.5.tgz", + "integrity": "sha512-ifqXJUpPUsAJrU7w5XeT3CujoqPrTDhJcbjSozxMyA9xEP9PaQ+WffP4wNbuPrvTRpYdDZnZTfvl23SHCYkihA==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "0.2.10", + "@floating-ui/vue": "1.1.9", + "@headlessui/tailwindcss": "^0.2.2", + "@headlessui/vue": "1.7.23", + "@scalar/code-highlight": "0.4.1", + "@scalar/helpers": "0.9.0", + "@scalar/icons": "0.7.3", + "@scalar/themes": "0.16.2", + "@scalar/use-hooks": "0.4.7", + "@vueuse/core": "13.9.0", + "cva": "1.0.0-beta.4", + "radix-vue": "^1.9.17", + "vue": "^3.5.30", + "vue-component-type-helpers": "^3.2.6" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/components/node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, + "node_modules/@scalar/helpers": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@scalar/helpers/-/helpers-0.9.0.tgz", + "integrity": "sha512-M34CLRCttqC1bXthI/QSzQj0s5C6nrU2PFWf/vOT3RpycbiGDGQbqR+5RfFzpOIQvRqbHfNdcRbeiZBw+vCbkQ==", + "license": "MIT", + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/icons": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@scalar/icons/-/icons-0.7.3.tgz", + "integrity": "sha512-5uSUvumj6yJEAZT7/MKpgrkNl76waDXUpu0kUBBFJ83GhZirBIK+Z9SktShEvJT0+rk/j9Zaer3BHoEhYwAEBQ==", + "license": "MIT", + "dependencies": { + "@phosphor-icons/core": "^2.1.1", + "@types/node": "^24.1.0", + "chalk": "^5.6.2", + "vue": "^3.5.30" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/icons/node_modules/@types/node": { + "version": "24.13.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz", + "integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@scalar/icons/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@scalar/icons/node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "license": "MIT" + }, + "node_modules/@scalar/json-magic": { + "version": "0.12.17", + "resolved": "https://registry.npmjs.org/@scalar/json-magic/-/json-magic-0.12.17.tgz", + "integrity": "sha512-Vw2nrUDIjhvMP6vxFtkiiFlabJ6SyTtfn1BsOxgnr1hIB+/rkngMguiDzl5em21VjyfFGIoADia+QWKM2hdcdA==", + "license": "MIT", + "dependencies": { + "@scalar/helpers": "0.9.0", + "pathe": "^2.0.3", + "yaml": "^2.8.3" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/oas-utils": { + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@scalar/oas-utils/-/oas-utils-0.19.3.tgz", + "integrity": "sha512-+h/vLMfGj/mpr5FYgDLFIc+X75sYH2rc2MJNSvFTmmjMeerJDVINEYs+aYOx1Mn9Z3HnoP+qQohoWTJpg2gh7A==", + "license": "MIT", + "dependencies": { + "@scalar/helpers": "0.9.0", + "@scalar/themes": "0.16.2", + "@scalar/types": "0.16.1", + "@scalar/workspace-store": "0.55.2", + "flatted": "^3.4.0", + "vue": "^3.5.30", + "yaml": "^2.8.3" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/openapi-types": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@scalar/openapi-types/-/openapi-types-0.9.1.tgz", + "integrity": "sha512-gkGhSkxSzADaBiNg+ZAbJuwj+ZUmzP2Pg9CWZ7ZP+0fck2WjPeDDM7aAbouAm0aQQMF9xBjSPXSA9a/qTHYaTw==", + "license": "MIT", + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/openapi-upgrader": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@scalar/openapi-upgrader/-/openapi-upgrader-0.2.9.tgz", + "integrity": "sha512-D5b0rGLLZgmkO9mdW2j/ND1KBlH1u3RCpr87HPxv9P9ZSr6PtM5iLqFOJq0ACiaHjY2mikCrxgDmnUEhTzRpHQ==", + "license": "MIT", + "dependencies": { + "@scalar/openapi-types": "0.9.1" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/schemas": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@scalar/schemas/-/schemas-0.7.1.tgz", + "integrity": "sha512-80bxEp4ZOWxOm8kqhPi3kdJ2gipz2lZBSEHStAh3Z6NZPkFAOlZZYnDFwodhY0LwTihgvv4FVNmi64gziM0F1g==", + "license": "MIT", + "dependencies": { + "@scalar/helpers": "0.9.0", + "@scalar/validation": "0.6.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/sidebar": { + "version": "0.9.28", + "resolved": "https://registry.npmjs.org/@scalar/sidebar/-/sidebar-0.9.28.tgz", + "integrity": "sha512-4iLN5a5YCQp/R0fgHhKeR8Bx75CbQolz35EpgUjwjCKGwxEVGqfp44Cs1okgr/QfBATLnju88R5qqxoXFKrnSw==", + "license": "MIT", + "dependencies": { + "@scalar/components": "0.27.5", + "@scalar/helpers": "0.9.0", + "@scalar/icons": "0.7.3", + "@scalar/themes": "0.16.2", + "@scalar/use-hooks": "0.4.7", + "@scalar/workspace-store": "0.55.2", + "vue": "^3.5.30" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/snippetz": { + "version": "0.9.20", + "resolved": "https://registry.npmjs.org/@scalar/snippetz/-/snippetz-0.9.20.tgz", + "integrity": "sha512-A3gSBYtoTmW3m511d02vUyl/W/eabX5y/EPAwz/u88LTzdwuDrKWbZ8iY+5dkz5e5EErhNOwQDwngmOCAA91hA==", + "license": "MIT", + "dependencies": { + "@scalar/helpers": "0.9.0", + "@scalar/types": "0.16.1", + "js-base64": "^3.7.8", + "stringify-object": "^6.0.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/themes": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@scalar/themes/-/themes-0.16.2.tgz", + "integrity": "sha512-4mAn7z2W5/ASi1OF06dqf04xjxTHnMzESC2E+qVMukJsEsV4kDlYSIfm/g5hwWxhqWsBMjorF9Dtlqd0ycJ92g==", + "license": "MIT", + "dependencies": { + "nanoid": "^5.1.6" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/typebox": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@scalar/typebox/-/typebox-0.1.3.tgz", + "integrity": "sha512-lU055AUccECZMIfGA0z/C1StYmboAYIPJLDFBzOO81yXBi35Pxdq+I4fWX6iUZ8qcoHneiLGk9jAUM1rA93iEg==", + "license": "MIT" + }, + "node_modules/@scalar/types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@scalar/types/-/types-0.16.1.tgz", + "integrity": "sha512-zzApf0dtEqztdY//3gmRJTgySGMpKnAVqcZltAzt95yuQPXbkSqxXDTitBLd1abeSeik+W6GFTIjffL8K+1wqQ==", + "license": "MIT", + "dependencies": { + "@scalar/helpers": "0.9.0", + "nanoid": "^5.1.6", + "type-fest": "^5.3.1", + "zod": "^4.3.5" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/use-codemirror": { + "version": "0.14.12", + "resolved": "https://registry.npmjs.org/@scalar/use-codemirror/-/use-codemirror-0.14.12.tgz", + "integrity": "sha512-m+0tmVOHZVAybbcNEizR3m9RYOLtU59AStRkk29J5qaW4J+tsjrz4BDMceeTpoVawL/lxp2vDvQR+w/gKC3miQ==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.18.3", + "@codemirror/commands": "^6.7.1", + "@codemirror/lang-css": "^6.3.1", + "@codemirror/lang-html": "^6.4.8", + "@codemirror/lang-json": "^6.0.0", + "@codemirror/lang-xml": "^6.0.0", + "@codemirror/lang-yaml": "^6.1.2", + "@codemirror/language": "^6.10.7", + "@codemirror/lint": "^6.8.4", + "@codemirror/state": "^6.5.0", + "@codemirror/view": "^6.35.3", + "@lezer/common": "^1.2.3", + "@lezer/highlight": "^1.2.1", + "@replit/codemirror-css-color-picker": "^6.3.0", + "vue": "^3.5.30" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/use-hooks": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@scalar/use-hooks/-/use-hooks-0.4.7.tgz", + "integrity": "sha512-8zajxhnKMJuO1HF36y8TeVuSIol2pueYdRvITZTEY8TuRbnwxrvJZk25II7YpxGMORAEDr0bwNF88Dr+QUfw1w==", + "license": "MIT", + "dependencies": { + "@scalar/use-toasts": "0.10.2", + "@scalar/validation": "0.6.0", + "@vueuse/core": "13.9.0", + "cva": "1.0.0-beta.4", + "tailwind-merge": "3.5.0", + "vue": "^3.5.30" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/use-toasts": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@scalar/use-toasts/-/use-toasts-0.10.2.tgz", + "integrity": "sha512-1iHQFbDXv0YQRp13aa63S5EcTJ5K8T0ocnLxk+nziloPrLjKt6jdRt6vOHsLSv5sm9kFKcVKNQTQgialmKCOGA==", + "license": "MIT", + "dependencies": { + "vue": "^3.5.30", + "vue-sonner": "^1.3.2" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/validation": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@scalar/validation/-/validation-0.6.0.tgz", + "integrity": "sha512-tpmmG+/xRE2Kn9RpflU3AIyZv08v10+E1ZrJCx7z6+/91zHVxy0M73kC1LT4/8PbYNt85ywyC8+n+D99JdMcGA==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@scalar/workspace-store": { + "version": "0.55.2", + "resolved": "https://registry.npmjs.org/@scalar/workspace-store/-/workspace-store-0.55.2.tgz", + "integrity": "sha512-/8BfJkave9vmweLdzH7w2TCaUFNcLu1vmE87id0QIi08enOwOExmPsz8YpUtaKLHM2bi/R2Tj/s9Uwp9i8Lttw==", + "license": "MIT", + "dependencies": { + "@scalar/asyncapi-upgrader": "0.1.2", + "@scalar/helpers": "0.9.0", + "@scalar/json-magic": "0.12.17", + "@scalar/openapi-upgrader": "0.2.9", + "@scalar/schemas": "0.7.1", + "@scalar/snippetz": "0.9.20", + "@scalar/typebox": "0.1.3", + "@scalar/types": "0.16.1", + "@scalar/validation": "0.6.0", + "js-base64": "^3.7.8", + "type-fest": "^5.3.1", + "vue": "^3.5.30", + "yaml": "^2.8.3" + }, + "engines": { + "node": ">=22" + } + }, "node_modules/@sec-ant/readable-stream": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", @@ -4523,6 +5396,12 @@ "devOptional": true, "license": "CC0-1.0" }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, "node_modules/@stylistic/eslint-plugin": { "version": "5.10.0", "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.10.0.tgz", @@ -5191,6 +6070,32 @@ "url": "https://github.com/sponsors/tannerlinsley" } }, + "node_modules/@tanstack/vue-virtual": { + "version": "3.13.31", + "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.31.tgz", + "integrity": "sha512-wZMEoSf852jQqaf3Ika1J7PiBae6341LNy/2CxmIyn0XKDQXMuK41wVX+xp6G0yx8jyR95Ef+Tdr13DK7mbJtQ==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.17.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "vue": "^2.7.0 || ^3.0.0" + } + }, + "node_modules/@tanstack/vue-virtual/node_modules/@tanstack/virtual-core": { + "version": "3.17.3", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.17.3.tgz", + "integrity": "sha512-8Np/TFELpI0ySuJoVmjvOrQYXH/8sTX0Biv9szhFhY39xOdAAY+smrMxjxOum/ux3eM8MUJQsEJ0/R0UpvC8dw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tokenizer/inflate": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", @@ -5483,6 +6388,12 @@ "@types/geojson": "*" } }, + "node_modules/@types/har-format": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.16.tgz", + "integrity": "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==", + "license": "MIT" + }, "node_modules/@types/hast": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", @@ -5773,6 +6684,12 @@ "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==", "license": "MIT" }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "license": "MIT" + }, "node_modules/@types/yargs": { "version": "17.0.35", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", @@ -6037,6 +6954,22 @@ "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", "license": "ISC" }, + "node_modules/@unhead/vue": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@unhead/vue/-/vue-2.1.15.tgz", + "integrity": "sha512-SSByXfEjhzPn8gXdEdgpYqpLMPSkLUH2HVE0GxZfOtNsJ0GgOHQs0g9T67ZZ1z0kTELLKdtOtYrzrbv9+ffF7g==", + "license": "MIT", + "dependencies": { + "hookable": "^6.0.1", + "unhead": "2.1.15" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + }, + "peerDependencies": { + "vue": ">=3.5.18" + } + }, "node_modules/@uppy/companion-client": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@uppy/companion-client/-/companion-client-5.1.1.tgz", @@ -6239,29 +7172,40 @@ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "license": "MIT" }, + "node_modules/@vercel/oidc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.1.0.tgz", + "integrity": "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==", + "license": "Apache-2.0", + "engines": { + "node": ">= 20" + } + }, "node_modules/@vinejs/compiler": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@vinejs/compiler/-/compiler-3.0.0.tgz", - "integrity": "sha512-v9Lsv59nR56+bmy2p0+czjZxsLHwaibJ+SV5iK9JJfehlJMa501jUJQqqz4X/OqKXrxtE3uTQmSqjUqzF3B2mw==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vinejs/compiler/-/compiler-4.1.3.tgz", + "integrity": "sha512-UyH7Zn8dkTMLeU+PF2WjCnWkFb2qYaOxAcvp/uXW0njtKNcJOnVJaPsnWYwqewkTcHN47yvOdzosj3kj3RAP5w==", "license": "MIT", "engines": { "node": ">=18.0.0" } }, "node_modules/@vinejs/vine": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@vinejs/vine/-/vine-3.0.1.tgz", - "integrity": "sha512-ZtvYkYpZOYdvbws3uaOAvTFuvFXoQGAtmzeiXu+XSMGxi5GVsODpoI9Xu9TplEMuD/5fmAtBbKb9cQHkWkLXDQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@vinejs/vine/-/vine-4.4.0.tgz", + "integrity": "sha512-cfnNXjs9+f+22d3Eb8koyg2qlCbaT394XBlL4AeEr1WM+NH1omFcXwe1zrkW4hHwPDSfUd4lFUdNO6E+YnTeWQ==", "license": "MIT", "dependencies": { - "@poppinss/macroable": "^1.0.4", - "@types/validator": "^13.12.2", - "@vinejs/compiler": "^3.0.0", - "camelcase": "^8.0.0", - "dayjs": "^1.11.13", + "@poppinss/macroable": "^1.1.0", + "@poppinss/types": "^1.2.1", + "@standard-schema/spec": "^1.1.0", + "@types/validator": "^13.15.10", + "@vinejs/compiler": "^4.1.3", + "camelcase": "^9.0.0", + "dayjs": "^1.11.19", "dlv": "^1.1.3", - "normalize-url": "^8.0.1", - "validator": "^13.12.0" + "normalize-url": "^8.1.1", + "validator": "^13.15.26" }, "engines": { "node": ">=18.16.0" @@ -6347,6 +7291,222 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/@vue/compiler-core": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.39.tgz", + "integrity": "sha512-16KBTEXAJCpDr0mwlw+AZyhu8iyC7R3S2vBwsI7QnWJU6X3WKc9VKeNEZpiMdZ569qWhz9574L3vV55qRL0Vtw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/shared": "3.5.39", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-core/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/@vue/compiler-dom": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.39.tgz", + "integrity": "sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.39.tgz", + "integrity": "sha512-d0ki86iOyN8LoZPBmk5SJWNwHP19CnDDCfuo//+2WJa2g5Ke0Jay983PIBIcSSzldC68I8DrD5GrHV3OSDfodg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/compiler-core": "3.5.39", + "@vue/compiler-dom": "3.5.39", + "@vue/compiler-ssr": "3.5.39", + "@vue/shared": "3.5.39", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.15", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.39.tgz", + "integrity": "sha512-Ce7/wvwMHai74bdszfXExdazFigYnlF9zgCmEQUcM1j0fOymlouZ7XilTYNo8oUjhlnjYOZbGrcYKuqjz89Ucw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.39.tgz", + "integrity": "sha512-TpsuBJ9gGlZa5d23XcM2y8EXanz9dZeVDQBXRwzy46ItgvM+rWpzs+UVM0wcRLxGvcav0HE5jz2gNL53xlRAog==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.39.tgz", + "integrity": "sha512-9GLtNyRvPAUMbX+7ono0RC2j0guo2LXVi8LvcmAooImACUKm0oFf0jjwbX8/H0AE/t1nxhAkn8RSl9PMCzzxZw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.39.tgz", + "integrity": "sha512-7Y6aAGboKcXAZ3ECuUy7RrS5yy2r47dhTp2SKaJmYxjopImaVFaNa5Ne66NwGovsrxVAl5S5rwc7m22UG7Lmww==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.39", + "@vue/runtime-core": "3.5.39", + "@vue/shared": "3.5.39", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.39.tgz", + "integrity": "sha512-yZSakiAGw85rZfG7UM8akMnIF+FmeiNk47uvHf2nVBBSe+dIKUhZuZq9+XgJhbV3nS5Z4ALH23/MpXofW+mbcw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.39", + "@vue/shared": "3.5.39" + }, + "peerDependencies": { + "vue": "3.5.39" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.39.tgz", + "integrity": "sha512-l1rrBtBfTnmxvtsvdQDXltUUy8S1Y+ZaqdfUzmAnJkTd8Z8rv5v/ytW+TKiqEOWyHPoqtPlNFSs0lhRmYVSHVA==", + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-13.9.0.tgz", + "integrity": "sha512-ts3regBQyURfCE2BcytLqzm8+MmLlo5Ln/KLoxDVcsZ2gzIwVNnQpQOL/UKV8alUqjSZOlpFZcRNsLRqj+OzyA==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "13.9.0", + "@vueuse/shared": "13.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/@vueuse/integrations": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-13.9.0.tgz", + "integrity": "sha512-SDobKBbPIOe0cVL7QxMzGkuUGHvWTdihi9zOrrWaWUgFKe15cwEcwfWmgrcNzjT6kHnNmWuTajPHoIzUjYNYYQ==", + "license": "MIT", + "dependencies": { + "@vueuse/core": "13.9.0", + "@vueuse/shared": "13.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "async-validator": "^4", + "axios": "^1", + "change-case": "^5", + "drauu": "^0.4", + "focus-trap": "^7", + "fuse.js": "^7", + "idb-keyval": "^6", + "jwt-decode": "^4", + "nprogress": "^0.2", + "qrcode": "^1.5", + "sortablejs": "^1", + "universal-cookie": "^7 || ^8", + "vue": "^3.5.0" + }, + "peerDependenciesMeta": { + "async-validator": { + "optional": true + }, + "axios": { + "optional": true + }, + "change-case": { + "optional": true + }, + "drauu": { + "optional": true + }, + "focus-trap": { + "optional": true + }, + "fuse.js": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "jwt-decode": { + "optional": true + }, + "nprogress": { + "optional": true + }, + "qrcode": { + "optional": true + }, + "sortablejs": { + "optional": true + }, + "universal-cookie": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-13.9.0.tgz", + "integrity": "sha512-1AFRvuiGphfF7yWixZa0KwjYH8ulyjDCC0aFgrGRz8+P4kvDFSdXLVfTk5xAN9wEuD1J6z4/myMoYbnHoX07zg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-13.9.0.tgz", + "integrity": "sha512-e89uuTLMh0U5cZ9iDpEI2senqPGfbPRTHM/0AaQkcxnpqjkZqDYP8rpfm7edOz8s+pOCOROEy1PIveSW8+fL5g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, "node_modules/abbrev": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", @@ -6442,6 +7602,24 @@ "node": ">= 6.0.0" } }, + "node_modules/ai": { + "version": "6.0.33", + "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.33.tgz", + "integrity": "sha512-bVokbmy2E2QF6Efl+5hOJx5MRWoacZ/CZY/y1E+VcewknvGlgaiCzMu8Xgddz6ArFJjiMFNUPHKxAhIePE4rmg==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/gateway": "3.0.13", + "@ai-sdk/provider": "3.0.2", + "@ai-sdk/provider-utils": "4.0.5", + "@opentelemetry/api": "1.9.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, "node_modules/ajv": { "version": "6.15.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", @@ -7065,12 +8243,12 @@ } }, "node_modules/camelcase": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", - "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-9.0.0.tgz", + "integrity": "sha512-TO9xmyXTZ9HUHI8M1OnvExxYB0eYVS/1e5s7IDMTAoIcwUd+aNcFODs6Xk83mobk0velyHFQgA1yIrvYc6wclw==", "license": "MIT", "engines": { - "node": ">=16" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -7160,7 +8338,7 @@ "version": "5.4.4", "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/character-entities": { @@ -7717,6 +8895,18 @@ "url": "https://opencollective.com/express" } }, + "node_modules/convert-hrtime": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", + "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -7821,6 +9011,12 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "license": "MIT" }, + "node_modules/crelt": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.7.tgz", + "integrity": "sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA==", + "license": "MIT" + }, "node_modules/cron-parser": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", @@ -7895,6 +9091,26 @@ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, + "node_modules/cva": { + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/cva/-/cva-1.0.0-beta.4.tgz", + "integrity": "sha512-F/JS9hScapq4DBVQXcK85l9U91M6ePeXoBMSp7vypzShoefUBxjQTo3g3935PUHgQd+IW77DjbPRIxugy4/GCQ==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + }, + "peerDependencies": { + "typescript": ">= 4.5.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/data-uri-to-buffer": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", @@ -7992,6 +9208,12 @@ "dev": true, "license": "MIT" }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "license": "MIT" + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -8875,6 +10097,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -8900,6 +10128,15 @@ "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "license": "MIT" }, + "node_modules/eventsource-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", + "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/exec-then": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/exec-then/-/exec-then-1.3.1.tgz", @@ -9331,7 +10568,6 @@ "version": "3.4.2", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", - "dev": true, "license": "ISC" }, "node_modules/flattie": { @@ -9343,6 +10579,15 @@ "node": ">=8" } }, + "node_modules/focus-trap": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.8.0.tgz", + "integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==", + "license": "MIT", + "dependencies": { + "tabbable": "^6.4.0" + } + }, "node_modules/follow-redirects": { "version": "1.16.0", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", @@ -9488,6 +10733,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/function-timeout": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-1.0.2.tgz", + "integrity": "sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/fuse.js": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.1.0.tgz", @@ -9566,6 +10823,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-own-enumerable-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-own-enumerable-keys/-/get-own-enumerable-keys-1.0.0.tgz", + "integrity": "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -9856,6 +11125,15 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, + "node_modules/guess-json-indent": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/guess-json-indent/-/guess-json-indent-3.0.1.tgz", + "integrity": "sha512-LWZ3Vr8BG7DHE3TzPYFqkhjNRw4vYgFSsv2nfMuHklAlOfiy54/EwiDQuQfFVLxENCVv20wpbjfTayooQHrEhQ==", + "license": "MIT", + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -9904,6 +11182,226 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-embedded": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-embedded/-/hast-util-embedded-3.0.0.tgz", + "integrity": "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-format": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hast-util-format/-/hast-util-format-1.1.0.tgz", + "integrity": "sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-minify-whitespace": "^1.0.0", + "hast-util-phrasing": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "html-whitespace-sensitive-tag-names": "^3.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-has-property": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", + "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-body-ok-link": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-3.0.1.tgz", + "integrity": "sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-minify-whitespace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hast-util-minify-whitespace/-/hast-util-minify-whitespace-1.0.1.tgz", + "integrity": "sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-phrasing/-/hast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-is-body-ok-link": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-sanitize": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-5.0.2.tgz", + "integrity": "sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "unist-util-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-to-jsx-runtime": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", @@ -9931,6 +11429,41 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz", + "integrity": "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-whitespace": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", @@ -9944,6 +11477,23 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -9968,6 +11518,21 @@ "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", "license": "MIT" }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/hookable": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-6.1.1.tgz", + "integrity": "sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==", + "license": "MIT" + }, "node_modules/hosted-git-info": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz", @@ -10130,6 +11695,26 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/html-whitespace-sensitive-tag-names": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-whitespace-sensitive-tag-names/-/html-whitespace-sensitive-tag-names-3.0.1.tgz", + "integrity": "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/htmlparser2": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", @@ -10250,6 +11835,21 @@ "integrity": "sha512-eKQkTnS0relYsSOYomx8ozIbmdsQCKUdhyuIaQ2DZgKuaxtyQQMkyD/wlnQN32pO3yutN1b1L8uqwcDKaJd7/Q==", "license": "Apache-2.0" }, + "node_modules/identifier-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/identifier-regex/-/identifier-regex-1.1.0.tgz", + "integrity": "sha512-SLX4H/vtcYlYnL7XqnuJKHU7Z8517TgsW9nmQiGOgMCjQ8V/deLYu6bEmbGoXe7WMMhc9+EUGyFFneHja8KabA==", + "license": "MIT", + "dependencies": { + "reserved-identifiers": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -10428,6 +12028,18 @@ "node": ">= 10" } }, + "node_modules/is-absolute-url": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-4.0.1.tgz", + "integrity": "sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-alphabetical": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", @@ -10548,6 +12160,22 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-identifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-identifier/-/is-identifier-1.1.0.tgz", + "integrity": "sha512-NhOds0mDx9lJu+1lBRO0xbwFo5nobA7GCk/0e5xjr6+6XugX985+0OyGX35BNrTkPAsdLcIKg02HUQJOK8D8kw==", + "license": "MIT", + "dependencies": { + "identifier-regex": "^1.1.0", + "super-regex": "^1.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-network-error": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.1.tgz", @@ -10569,6 +12197,18 @@ "node": ">=0.12.0" } }, + "node_modules/is-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-3.0.0.tgz", + "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", @@ -10599,6 +12239,18 @@ "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", "license": "MIT" }, + "node_modules/is-regexp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz", + "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-stream": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", @@ -10785,6 +12437,12 @@ "node": ">=10" } }, + "node_modules/js-base64": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.8.0.tgz", + "integrity": "sha512-65kvbemyZhj+ExQt1PEFyBEjL5vAHysu1lJdW1AwhhChkO8ZBPizYk/m9GVrpbS2Je1hF+UYZ+6KywqtZV8mHw==", + "license": "BSD-3-Clause" + }, "node_modules/js-stringify": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", @@ -10839,6 +12497,12 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -10871,6 +12535,12 @@ "node": ">=6" } }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "license": "MIT" + }, "node_modules/jsonschema": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.5.0.tgz", @@ -11491,6 +13161,21 @@ "loose-envify": "cli.js" } }, + "node_modules/lowlight": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz", + "integrity": "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.0.0", + "highlight.js": "~11.11.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -11533,6 +13218,35 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/make-asynchronous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/make-asynchronous/-/make-asynchronous-1.1.0.tgz", + "integrity": "sha512-ayF7iT+44LXdxJLTrTd3TLQpFDDvPCBxXxbv+pMUSuHA5Q8zyAfwkRP6aHHwNVFBUFWtxAHqwNJxF8vMZLAbVg==", + "license": "MIT", + "dependencies": { + "p-event": "^6.0.0", + "type-fest": "^4.6.0", + "web-worker": "^1.5.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-asynchronous/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -11959,6 +13673,12 @@ "node": ">= 8" } }, + "node_modules/microdiff": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/microdiff/-/microdiff-1.5.0.tgz", + "integrity": "sha512-Drq+/THMvDdzRYrK0oxJmOKiC24ayUV8ahrt8l3oRK51PWt6gdtrIGrlIH3pT/lFh1z93FbAcidtsHcWbnRz8Q==", + "license": "MIT" + }, "node_modules/micromark": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", @@ -12958,6 +14678,12 @@ "node": ">= 0.6" } }, + "node_modules/neverpanic": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/neverpanic/-/neverpanic-0.0.8.tgz", + "integrity": "sha512-vVdkelrLxaow/fdWDumzNBO+jwm6X8bxeLJc34THtpj70u0C5QBkcV6CRCu2X726km7XD45N0A3QtYCla4RvKw==", + "license": "MIT" + }, "node_modules/node-abi": { "version": "3.92.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz", @@ -13306,7 +15032,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/p-event/-/p-event-6.0.1.tgz", "integrity": "sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==", - "devOptional": true, "license": "MIT", "dependencies": { "p-timeout": "^6.1.2" @@ -13529,7 +15254,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=18" @@ -13678,6 +15402,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, "node_modules/pbf": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.3.0.tgz", @@ -14069,7 +15799,6 @@ "version": "9.3.0", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", - "devOptional": true, "license": "MIT", "dependencies": { "parse-ms": "^4.0.0" @@ -14307,6 +16036,122 @@ "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==", "license": "ISC" }, + "node_modules/radix-vue": { + "version": "1.9.17", + "resolved": "https://registry.npmjs.org/radix-vue/-/radix-vue-1.9.17.tgz", + "integrity": "sha512-mVCu7I2vXt1L2IUYHTt0sZMz7s1K2ZtqKeTIxG3yC5mMFfLBG4FtE1FDeRMpDd+Hhg/ybi9+iXmAP1ISREndoQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.6.7", + "@floating-ui/vue": "^1.1.0", + "@internationalized/date": "^3.5.4", + "@internationalized/number": "^3.5.3", + "@tanstack/vue-virtual": "^3.8.1", + "@vueuse/core": "^10.11.0", + "@vueuse/shared": "^10.11.0", + "aria-hidden": "^1.2.4", + "defu": "^6.1.4", + "fast-deep-equal": "^3.1.3", + "nanoid": "^5.0.7" + }, + "peerDependencies": { + "vue": ">= 3.2.0" + } + }, + "node_modules/radix-vue/node_modules/@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", + "license": "MIT" + }, + "node_modules/radix-vue/node_modules/@vueuse/core": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz", + "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "10.11.1", + "@vueuse/shared": "10.11.1", + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/radix-vue/node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/radix-vue/node_modules/@vueuse/metadata": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz", + "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/radix-vue/node_modules/@vueuse/shared": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.1.tgz", + "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==", + "license": "MIT", + "dependencies": { + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/radix-vue/node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/random-bytes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", @@ -14757,6 +16602,97 @@ "node": ">=6" } }, + "node_modules/rehype-external-links": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/rehype-external-links/-/rehype-external-links-3.0.0.tgz", + "integrity": "sha512-yp+e5N9V3C6bwBeAC4n796kc86M4gJCdlVhiMTxIrJG5UHDMh+PJANf9heqORJbt1nrCbDwIlAZKjANIaVBbvw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-is-element": "^3.0.0", + "is-absolute-url": "^4.0.0", + "space-separated-tokens": "^2.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-format": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/rehype-format/-/rehype-format-5.0.1.tgz", + "integrity": "sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-format": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-parse": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz", + "integrity": "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-html": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-sanitize": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/rehype-sanitize/-/rehype-sanitize-6.0.0.tgz", + "integrity": "sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-sanitize": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-stringify": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz", + "integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-to-html": "^9.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/remark-gfm": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", @@ -14832,6 +16768,18 @@ "node": ">=0.10.0" } }, + "node_modules/reserved-identifiers": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/reserved-identifiers/-/reserved-identifiers-1.2.0.tgz", + "integrity": "sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/resolve": { "version": "1.22.12", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", @@ -15140,6 +17088,12 @@ "node": ">= 0.8.0" } }, + "node_modules/set-cookie-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.0.tgz", + "integrity": "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==", + "license": "MIT" + }, "node_modules/set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -15746,6 +17700,24 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string-byte-length": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/string-byte-length/-/string-byte-length-3.0.1.tgz", + "integrity": "sha512-yJ8vP0HMwZ54CcA8S8mKoXbkezpZHANFtmafFo8lGxZThCQcAwRHjdFabuSLgOzxj9OFJcmssmiAvmcOK4O2Hw==", + "license": "MIT", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/string-byte-slice": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/string-byte-slice/-/string-byte-slice-3.0.1.tgz", + "integrity": "sha512-GWv2K4lYyd2+AhmKH3BV+OVx62xDX+99rSLfKpaqFiQU7uOMaUY1tDjdrRD4gsrCr9lTyjMgjna7tZcCOw+Smg==", + "license": "MIT", + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/string-width": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", @@ -15849,6 +17821,24 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/stringify-object": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-6.0.0.tgz", + "integrity": "sha512-6f94vIED6vmJJfh3lyVsVWxCYSfI5uM+16ntED/Ql37XIyV6kj0mRAAiTeMMc/QLYIaizC3bUprQ8pQnDDrKfA==", + "license": "BSD-2-Clause", + "dependencies": { + "get-own-enumerable-keys": "^1.0.0", + "is-identifier": "^1.0.1", + "is-obj": "^3.0.0", + "is-regexp": "^3.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -15953,6 +17943,12 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/style-mod": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", + "license": "MIT" + }, "node_modules/style-to-js": { "version": "1.1.21", "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", @@ -15971,6 +17967,23 @@ "inline-style-parser": "0.2.7" } }, + "node_modules/super-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.1.0.tgz", + "integrity": "sha512-WHkws2ZflZe41zj6AolvvmaTrWds/VuyeYr9iPVv/oQeaIoVxMKaushfFWpOGDT+GuBrM/sVqF8KUCYQlSSTdQ==", + "license": "MIT", + "dependencies": { + "function-timeout": "^1.0.1", + "make-asynchronous": "^1.0.1", + "time-span": "^5.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/supercluster": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz", @@ -16004,6 +18017,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swrv": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/swrv/-/swrv-1.2.0.tgz", + "integrity": "sha512-lH/g4UcNyj+7lzK4eRGT4C68Q4EhQ6JtM9otPRIASfhhzfLWtbZPHcMuhuba7S9YVYuxkMUGImwMyGpfbkH07A==", + "license": "Apache-2.0", + "peerDependencies": { + "vue": ">=3.2.26 < 4" + } + }, "node_modules/synckit": { "version": "0.11.13", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.13.tgz", @@ -16056,7 +18078,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", - "dev": true, "license": "MIT", "engines": { "node": ">=20" @@ -16065,6 +18086,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/tailwind-merge": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz", + "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", @@ -16224,6 +18255,21 @@ "node": ">=8" } }, + "node_modules/time-span": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", + "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==", + "license": "MIT", + "dependencies": { + "convert-hrtime": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tinyexec": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", @@ -16333,6 +18379,20 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/truncate-json": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/truncate-json/-/truncate-json-3.0.1.tgz", + "integrity": "sha512-QVsbr1WhGLq2F0oDyYbqtOXcf3gcnL8C9H5EX8bBwAr8ZWvWGJzukpPrDrWgJMrNtgDbo74BIjI4kJu3q2xQWw==", + "license": "MIT", + "dependencies": { + "guess-json-indent": "^3.0.1", + "string-byte-length": "^3.0.1", + "string-byte-slice": "^3.0.1" + }, + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/ts-api-utils": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", @@ -16508,7 +18568,6 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.7.0.tgz", "integrity": "sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==", - "dev": true, "license": "(MIT OR CC0-1.0)", "dependencies": { "tagged-tag": "^1.0.0" @@ -16642,6 +18701,18 @@ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT" }, + "node_modules/unhead": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/unhead/-/unhead-2.1.15.tgz", + "integrity": "sha512-MCt5T90mCWyr3Z6pUCdM9lVRXoMoVBlL7z7U4CYVIiaDiuzad/UCfLuMqz5MeNmpZUgoBCQnrucJimU7EZR+XA==", + "license": "MIT", + "dependencies": { + "hookable": "^6.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, "node_modules/unicorn-magic": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", @@ -16713,6 +18784,20 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unist-util-is": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", @@ -16917,6 +19002,20 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vfile-message": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", @@ -17031,12 +19130,67 @@ "pbf": "^3.2.1" } }, + "node_modules/vue": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.39.tgz", + "integrity": "sha512-xmZCYabFGcirU8r0fTuvl/LICc1OU620rnqepaJDL/a141ZigkG7AyaxQLdqJ02ZRYzWe6YPaDHeQx7MfknQfA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.39", + "@vue/compiler-sfc": "3.5.39", + "@vue/runtime-dom": "3.5.39", + "@vue/server-renderer": "3.5.39", + "@vue/shared": "3.5.39" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-component-type-helpers": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-3.3.6.tgz", + "integrity": "sha512-FkljacAwJ9BUoSUdpFe3VDy0sGigNlTH9+2zcXUWmZOjN8swiCkl3t48wOJun0OsUd2cEIda1l04tsxMiKIIrQ==", + "license": "MIT" + }, + "node_modules/vue-sonner": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/vue-sonner/-/vue-sonner-1.3.2.tgz", + "integrity": "sha512-UbZ48E9VIya3ToiRHAZUbodKute/z/M1iT8/3fU8zEbwBRE11AKuHikssv18LMk2gTTr6eMQT4qf6JoLHWuj/A==", + "license": "MIT" + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, "node_modules/wasm-feature-detect": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.8.0.tgz", "integrity": "sha512-zksaLKM2fVlnB5jQQDqKXXwYHLQUVH9es+5TOOHwGOVJOCeRBCiPjwSg+3tN2AdTCzjgli4jijCH290kXb/zWQ==", "license": "Apache-2.0" }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/web-worker": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz", + "integrity": "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==", + "license": "Apache-2.0" + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -17458,6 +19612,15 @@ "node": "*" } }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/admin/package.json b/admin/package.json index 18254d8..d4fa48d 100644 --- a/admin/package.json +++ b/admin/package.json @@ -80,6 +80,7 @@ "@openzim/libzim": "4.0.0", "@protomaps/basemaps": "5.7.0", "@qdrant/js-client-rest": "1.16.2", + "@scalar/api-reference": "^1.62.4", "@tabler/icons-react": "3.36.1", "@tailwindcss/vite": "4.3.0", "@tanstack/react-query": "5.90.20", @@ -89,7 +90,7 @@ "@uppy/dashboard": "5.1.0", "@uppy/react": "5.1.1", "@uppy/xhr-upload": "5.2.0", - "@vinejs/vine": "3.0.1", + "@vinejs/vine": "^4.2.0", "@vitejs/plugin-react": "4.7.0", "autoprefixer": "10.5.0", "axios": "1.17.0", diff --git a/admin/start/openapi/documented.ts b/admin/start/openapi/documented.ts new file mode 100644 index 0000000..1522d39 --- /dev/null +++ b/admin/start/openapi/documented.ts @@ -0,0 +1,75 @@ +/* +|-------------------------------------------------------------------------- +| OpenAPI route documentation helper +|-------------------------------------------------------------------------- +| +| `documented()` wraps a route defined in `start/routes.ts` and records the +| schemas that describe it, so the OpenAPI generator can derive the spec from +| the same VineJS validators the controllers already validate against. +| +| The helper stores the descriptor against the live `Route` instance and +| returns the route unchanged, so it stays chainable (e.g. `.middleware(...)`) +| and the enclosing group's `.prefix()` still applies. The generator reads the +| final, prefixed pattern back off the same instance at build time. +| +*/ +import type { Route } from '@adonisjs/core/http' +import type { VineValidator } from '@vinejs/vine' + +/** + * A compiled VineJS validator, as returned by `vine.compile(...)`. The schema + * and metadata generics are irrelevant here — we only ever call `toJSONSchema()`. + */ +export type AnyValidator = VineValidator + +/** + * Describes a single documented response. A bare validator is shorthand for + * `{ schema: validator }` with a default description. + */ +export interface ResponseDoc { + description?: string + schema?: AnyValidator +} + +export interface DocDescriptor { + /** One-line summary shown as the operation title in Scalar. */ + summary?: string + /** Longer prose description (Markdown supported by Scalar). */ + description?: string + /** Tags used to group operations in the UI (usually the domain, e.g. `zim`). */ + tags?: string[] + /** Validator for the JSON request body. */ + request?: AnyValidator + /** Validator for the query string (each top-level property → a query param). */ + query?: AnyValidator + /** + * Validator for route params. Follows the AdonisJS convention of wrapping + * fields under a top-level `params` object (e.g. `filenameParamValidator`); + * the generator unwraps it. A flat object is also accepted. + */ + params?: AnyValidator + /** Response schemas keyed by HTTP status code. */ + responses?: Record +} + +/** + * Registry of documented routes. Keyed by the live `Route` instance so the + * generator can read each route's final pattern/methods via `route.toJSON()`. + */ +const registry = new Map() + +/** + * Attach OpenAPI documentation to a route and return the route for chaining. + */ +export function documented(route: Route, descriptor: DocDescriptor): Route { + registry.set(route, descriptor) + return route +} + +/** + * The documented-route registry, consumed by the generator and the + * completeness test. + */ +export function getDocRegistry(): ReadonlyMap { + return registry +} diff --git a/admin/start/openapi/generator.ts b/admin/start/openapi/generator.ts new file mode 100644 index 0000000..242a1be --- /dev/null +++ b/admin/start/openapi/generator.ts @@ -0,0 +1,234 @@ +/* +|-------------------------------------------------------------------------- +| OpenAPI document generator +|-------------------------------------------------------------------------- +| +| Assembles an OpenAPI 3.1 document from the `documented()` registry. Request, +| query, param, and response schemas are produced by VineJS's native +| `validator.toJSONSchema()` (JSON Schema Draft 7), which is a compatible +| subset of the JSON Schema dialect OpenAPI 3.1 uses — so almost no +| transformation is required beyond stripping the `$schema` keyword. +| +| The document is built lazily and cached, since routes and validators are +| static once the app has booted. +| +*/ +import { SystemService } from '#services/system_service' +import { getDocRegistry, type AnyValidator, type DocDescriptor, type ResponseDoc } from './documented.js' + +type JsonObject = Record + +/** + * OpenAPI methods we emit operations for. `HEAD` (auto-added by Adonis + * alongside `GET`) and `OPTIONS` are skipped. + */ +const DOCUMENTED_METHODS = new Set(['get', 'post', 'put', 'patch', 'delete']) +const BODY_METHODS = new Set(['post', 'put', 'patch', 'delete']) + +let cached: JsonObject | null = null + +/** + * Build (or return the cached) OpenAPI 3.1 document for all documented routes. + */ +export function buildOpenApiDocument(force = false): JsonObject { + if (cached && !force) { + return cached + } + + const paths: JsonObject = {} + const tagSet = new Set() + + for (const [route, descriptor] of getDocRegistry()) { + const json = route.toJSON() + const oaPath = toOpenApiPath(json.pattern) + const methods = json.methods.map((m) => m.toLowerCase()).filter((m) => DOCUMENTED_METHODS.has(m)) + + for (const method of methods) { + const operation = buildOperation(descriptor, oaPath, method) + for (const tag of operation.tags ?? []) { + tagSet.add(tag) + } + paths[oaPath] = paths[oaPath] ?? {} + paths[oaPath][method] = operation + } + } + + cached = { + openapi: '3.1.0', + info: { + title: 'Nomad Admin API', + description: + 'HTTP API for the Nomad admin appliance. Generated from the application ' + + 'routes and VineJS validators — see `/reference` for the interactive UI.', + version: SystemService.getAppVersion(), + }, + servers: [{ url: '/', description: 'This appliance' }], + tags: [...tagSet].sort().map((name) => ({ name })), + paths, + } + + return cached +} + +/** + * Clear the cached document (used after HMR or in tests that need a rebuild). + */ +export function resetOpenApiCache(): void { + cached = null +} + +/** + * Build a single OpenAPI operation object. + */ +function buildOperation(descriptor: DocDescriptor, oaPath: string, method: string): JsonObject { + const operation: JsonObject = { + operationId: operationId(method, oaPath), + } + + if (descriptor.summary) operation.summary = descriptor.summary + if (descriptor.description) operation.description = descriptor.description + if (descriptor.tags?.length) operation.tags = descriptor.tags + + const parameters = [ + ...pathParameters(oaPath, descriptor.params), + ...queryParameters(descriptor.query), + ] + if (parameters.length) operation.parameters = parameters + + if (descriptor.request && BODY_METHODS.has(method)) { + const schema = normalizeSchema(descriptor.request.toJSONSchema()) + operation.requestBody = { + required: Array.isArray(schema.required) && schema.required.length > 0, + content: { 'application/json': { schema } }, + } + } + + operation.responses = buildResponses(descriptor.responses) + + return operation +} + +/** + * Path parameters come from the pattern itself (always required); their schema + * is taken from the `params` validator when provided, otherwise defaulted to a + * plain string. + */ +function pathParameters(oaPath: string, paramsValidator?: AnyValidator): JsonObject[] { + const names = [...oaPath.matchAll(/\{([^}]+)\}/g)].map((m) => m[1]) + if (names.length === 0) return [] + + const schemas = paramsValidator ? unwrapParamsSchema(paramsValidator) : {} + + return names.map((name) => ({ + name, + in: 'path', + required: true, + schema: schemas[name] ? normalizeSchema(schemas[name]) : { type: 'string' }, + })) +} + +/** + * Each top-level property of the query validator becomes a query parameter. + */ +function queryParameters(queryValidator?: AnyValidator): JsonObject[] { + if (!queryValidator) return [] + + const schema = queryValidator.toJSONSchema() + const properties: JsonObject = schema.properties ?? {} + const required: string[] = Array.isArray(schema.required) ? schema.required : [] + + return Object.entries(properties).map(([name, propSchema]) => ({ + name, + in: 'query', + required: required.includes(name), + schema: normalizeSchema(propSchema as JsonObject), + })) +} + +/** + * Build the `responses` object. Defaults to a bare 200 when none are declared. + */ +function buildResponses(responses?: DocDescriptor['responses']): JsonObject { + if (!responses || Object.keys(responses).length === 0) { + return { '200': { description: 'Successful response' } } + } + + const out: JsonObject = {} + for (const [code, value] of Object.entries(responses)) { + const doc = normalizeResponse(value) + const entry: JsonObject = { description: doc.description ?? 'Response' } + if (doc.schema) { + entry.content = { 'application/json': { schema: normalizeSchema(doc.schema.toJSONSchema()) } } + } + out[code] = entry + } + return out +} + +/** + * Accept either a bare validator or a `{ description, schema }` object. + */ +function normalizeResponse(value: ResponseDoc | AnyValidator): ResponseDoc { + if (value && typeof (value as AnyValidator).toJSONSchema === 'function') { + return { schema: value as AnyValidator } + } + return value as ResponseDoc +} + +/** + * Unwrap the AdonisJS `params` convention: `filenameParamValidator` produces + * `{ properties: { params: { properties: { filename } } } }`. Return the inner + * per-param schemas. A flat validator (no `params` wrapper) is passed through. + */ +function unwrapParamsSchema(paramsValidator: AnyValidator): JsonObject { + const schema = paramsValidator.toJSONSchema() as JsonObject + const inner = schema.properties?.params ?? schema + return (inner.properties ?? {}) as JsonObject +} + +/** + * Convert an AdonisJS route pattern to an OpenAPI path: + * `/api/zim/:filename` → `/api/zim/{filename}` + * `/api/zim/:id?` → `/api/zim/{id}` + * `/api/files/*` → `/api/files/{wildcard}` + */ +function toOpenApiPath(pattern: string): string { + return pattern + .replace(/:([A-Za-z0-9_]+)\??/g, '{$1}') + .replace(/\*/g, '{wildcard}') +} + +/** + * Derive a stable operationId, e.g. `get_api_zim_list`. + */ +function operationId(method: string, oaPath: string): string { + const slug = oaPath + .replace(/[{}]/g, '') + .split('/') + .filter(Boolean) + .join('_') + return slug ? `${method}_${slug}` : `${method}_root` +} + +/** + * Normalize a Vine JSON Schema for OpenAPI 3.1. Vine emits clean Draft-7 + * (no `$defs`/`$ref`), so we only need to strip the `$schema` keyword, which + * is not allowed on OpenAPI schema objects. Done recursively for safety. + */ +function normalizeSchema(schema: JsonObject): JsonObject { + if (Array.isArray(schema)) { + return schema.map((item) => (isObject(item) ? normalizeSchema(item) : item)) + } + if (!isObject(schema)) return schema + + const out: JsonObject = {} + for (const [key, value] of Object.entries(schema)) { + if (key === '$schema') continue + out[key] = isObject(value) || Array.isArray(value) ? normalizeSchema(value) : value + } + return out +} + +function isObject(value: unknown): value is JsonObject { + return typeof value === 'object' && value !== null +} diff --git a/admin/start/routes.ts b/admin/start/routes.ts index ca068cc..df13779 100644 --- a/admin/start/routes.ts +++ b/admin/start/routes.ts @@ -14,6 +14,7 @@ import EasySetupController from '#controllers/easy_setup_controller' import HomeController from '#controllers/home_controller' import MapsController from '#controllers/maps_controller' import OllamaController from '#controllers/ollama_controller' +import OpenApiController from '#controllers/openapi_controller' import RagController from '#controllers/rag_controller' import SettingsController from '#controllers/settings_controller' import SupplyDepotController from '#controllers/supply_depot_controller' @@ -22,6 +23,68 @@ import CollectionUpdatesController from '#controllers/collection_updates_control import ZimController from '#controllers/zim_controller' import router from '@adonisjs/core/services/router' import transmit from '@adonisjs/transmit/services/main' +import { documented } from '#start/openapi/documented' +import { + remoteDownloadValidator, + remoteDownloadWithMetadataValidator, + remoteDownloadValidatorOptional, + filenameParamValidator, + downloadCollectionValidator, + downloadCategoryTierValidator, + selectWikipediaValidator, + applyContentUpdateValidator, + applyAllContentUpdatesValidator, + mapExtractPreflightValidator, + mapExtractValidator, +} from '#validators/common' +import { + listRemoteZimValidator, + addCustomLibraryValidator, + browseLibraryValidator, + idParamValidator, +} from '#validators/zim' +import { + getJobStatusSchema, + deleteFileSchema, + embedFileSchema, + fileSourceSchema, + estimateBatchSchema, +} from '#validators/rag' +import { + installServiceValidator, + affectServiceValidator, + subscribeToReleaseNotesValidator, + checkLatestVersionValidator, + updateServiceValidator, + preflightValidator, + setServiceAutoUpdateValidator, + preflightCustomValidator, + customAppValidator, + setServiceCustomUrlValidator, + deleteCustomAppValidator, + uninstallServiceValidator, + serviceLogsValidator, + updateCustomAppValidator, +} from '#validators/system' +import { + chatSchema, + unloadChatModelsSchema, + getAvailableModelsSchema, +} from '#validators/ollama' +import { getSettingSchema, updateSettingSchema } from '#validators/settings' +import { + createSessionSchema, + updateSessionSchema, + addMessageSchema, +} from '#validators/chat' +import { downloadJobsByFiletypeSchema, modelNameSchema } from '#validators/download' +import { runBenchmarkValidator, submitBenchmarkValidator } from '#validators/benchmark' +import { healthResponse, errorResponse } from '#validators/responses/common' +import { + chatSessionResponse, + chatSessionListResponse, + chatMessageResponse, +} from '#validators/responses/chat' transmit.registerRoutes() @@ -35,13 +98,33 @@ router.on('/knowledge-base').redirectToPath('/chat?knowledge_base=true') // redi router.get('/easy-setup', [EasySetupController, 'index']) router.get('/easy-setup/complete', [EasySetupController, 'complete']) -router.get('/api/easy-setup/curated-categories', [EasySetupController, 'listCuratedCategories']) -router.post('/api/manifests/refresh', [EasySetupController, 'refreshManifests']) +documented( + router.get('/api/easy-setup/curated-categories', [EasySetupController, 'listCuratedCategories']), + { + summary: 'List curated easy-setup categories', + tags: ['easy-setup'], + } +) +documented(router.post('/api/manifests/refresh', [EasySetupController, 'refreshManifests']), { + summary: 'Refresh content manifests', + tags: ['easy-setup'], +}) router .group(() => { - router.post('/check', [CollectionUpdatesController, 'checkForUpdates']) - router.post('/apply', [CollectionUpdatesController, 'applyUpdate']) - router.post('/apply-all', [CollectionUpdatesController, 'applyAllUpdates']) + documented(router.post('/check', [CollectionUpdatesController, 'checkForUpdates']), { + summary: 'Check for available content updates', + tags: ['content-updates'], + }) + documented(router.post('/apply', [CollectionUpdatesController, 'applyUpdate']), { + summary: 'Apply a content update', + tags: ['content-updates'], + request: applyContentUpdateValidator, + }) + documented(router.post('/apply-all', [CollectionUpdatesController, 'applyAllUpdates']), { + summary: 'Apply all available content updates', + tags: ['content-updates'], + request: applyAllContentUpdatesValidator, + }) }) .prefix('/api/content-updates') @@ -73,171 +156,598 @@ router router .group(() => { - router.get('/regions', [MapsController, 'listRegions']) - router.get('/styles', [MapsController, 'styles']) - router.get('/curated-collections', [MapsController, 'listCuratedCollections']) - router.post('/fetch-latest-collections', [MapsController, 'fetchLatestCollections']) - router.post('/download-base-assets', [MapsController, 'downloadBaseAssets']) - router.post('/download-remote', [MapsController, 'downloadRemote']) - router.post('/download-remote-preflight', [MapsController, 'downloadRemotePreflight']) - router.post('/download-collection', [MapsController, 'downloadCollection']) - router.get('/global-map-info', [MapsController, 'globalMapInfo']) - router.post('/download-global-map', [MapsController, 'downloadGlobalMap']) - router.get('/countries', [MapsController, 'listCountries']) - router.get('/country-groups', [MapsController, 'listCountryGroups']) - router.post('/extract-preflight', [MapsController, 'extractPreflight']) - router.post('/extract', [MapsController, 'extractRegion']) - router.get('/markers', [MapsController, 'listMarkers']) - router.post('/markers', [MapsController, 'createMarker']) - router.patch('/markers/:id', [MapsController, 'updateMarker']) - router.delete('/markers/:id', [MapsController, 'deleteMarker']) - router.delete('/:filename', [MapsController, 'delete']) + documented(router.get('/regions', [MapsController, 'listRegions']), { + summary: 'List available map regions', + tags: ['maps'], + }) + documented(router.get('/styles', [MapsController, 'styles']), { + summary: 'List available map styles', + tags: ['maps'], + }) + documented(router.get('/curated-collections', [MapsController, 'listCuratedCollections']), { + summary: 'List curated map collections', + tags: ['maps'], + }) + documented(router.post('/fetch-latest-collections', [MapsController, 'fetchLatestCollections']), { + summary: 'Fetch the latest map collections', + tags: ['maps'], + }) + documented(router.post('/download-base-assets', [MapsController, 'downloadBaseAssets']), { + summary: 'Download base map assets', + tags: ['maps'], + request: remoteDownloadValidatorOptional, + }) + documented(router.post('/download-remote', [MapsController, 'downloadRemote']), { + summary: 'Queue a remote map download', + tags: ['maps'], + request: remoteDownloadValidator, + }) + documented(router.post('/download-remote-preflight', [MapsController, 'downloadRemotePreflight']), { + summary: 'Preflight a remote map download', + tags: ['maps'], + request: remoteDownloadValidator, + }) + documented(router.post('/download-collection', [MapsController, 'downloadCollection']), { + summary: 'Download a map collection', + tags: ['maps'], + request: downloadCollectionValidator, + }) + documented(router.get('/global-map-info', [MapsController, 'globalMapInfo']), { + summary: 'Get global map information', + tags: ['maps'], + }) + documented(router.post('/download-global-map', [MapsController, 'downloadGlobalMap']), { + summary: 'Download the global map', + tags: ['maps'], + }) + documented(router.get('/countries', [MapsController, 'listCountries']), { + summary: 'List available countries', + tags: ['maps'], + }) + documented(router.get('/country-groups', [MapsController, 'listCountryGroups']), { + summary: 'List country groups', + tags: ['maps'], + }) + documented(router.post('/extract-preflight', [MapsController, 'extractPreflight']), { + summary: 'Preflight a map region extraction', + tags: ['maps'], + request: mapExtractPreflightValidator, + }) + documented(router.post('/extract', [MapsController, 'extractRegion']), { + summary: 'Extract a map region', + tags: ['maps'], + request: mapExtractValidator, + }) + documented(router.get('/markers', [MapsController, 'listMarkers']), { + summary: 'List map markers', + tags: ['maps'], + }) + documented(router.post('/markers', [MapsController, 'createMarker']), { + summary: 'Create a map marker', + tags: ['maps'], + }) + documented(router.patch('/markers/:id', [MapsController, 'updateMarker']), { + summary: 'Update a map marker', + tags: ['maps'], + }) + documented(router.delete('/markers/:id', [MapsController, 'deleteMarker']), { + summary: 'Delete a map marker', + tags: ['maps'], + }) + documented(router.delete('/:filename', [MapsController, 'delete']), { + summary: 'Delete a map file', + tags: ['maps'], + params: filenameParamValidator, + }) }) .prefix('/api/maps') router .group(() => { - router.get('/list', [DocsController, 'list']) + documented(router.get('/list', [DocsController, 'list']), { + summary: 'List documentation pages', + tags: ['docs'], + }) }) .prefix('/api/docs') router .group(() => { - router.get('/jobs', [DownloadsController, 'index']) - router.get('/jobs/:filetype', [DownloadsController, 'filetype']) - router.delete('/jobs/:jobId', [DownloadsController, 'removeJob']) - router.post('/jobs/:jobId/cancel', [DownloadsController, 'cancelJob']) + documented(router.get('/jobs', [DownloadsController, 'index']), { + summary: 'List download jobs', + tags: ['downloads'], + }) + documented(router.get('/jobs/:filetype', [DownloadsController, 'filetype']), { + summary: 'List download jobs by filetype', + tags: ['downloads'], + params: downloadJobsByFiletypeSchema, + }) + documented(router.delete('/jobs/:jobId', [DownloadsController, 'removeJob']), { + summary: 'Remove a download job', + tags: ['downloads'], + }) + documented(router.post('/jobs/:jobId/cancel', [DownloadsController, 'cancelJob']), { + summary: 'Cancel a download job', + tags: ['downloads'], + }) }) .prefix('/api/downloads') -router.get('/api/health', () => { - return { status: 'ok' } -}) +documented( + router.get('/api/health', () => { + return { status: 'ok' } + }), + { summary: 'Health check', tags: ['meta'], responses: { 200: healthResponse } } +) + +// Self-generating API docs: OpenAPI spec + Scalar UI. Registered top-level so +// they stay clear of the `/docs/:slug` markdown catch-all above. The Scalar +// bundle is served from this app (not a CDN) for offline appliances. +router.get('/api/openapi.json', [OpenApiController, 'spec']) +router.get('/reference', [OpenApiController, 'reference']) +router.get('/reference/assets/standalone.js', [OpenApiController, 'standalone']) router .group(() => { - router.post('/chat', [OllamaController, 'chat']) - router.get('/models', [OllamaController, 'availableModels']) - router.post('/models', [OllamaController, 'dispatchModelDownload']) - router.delete('/models', [OllamaController, 'deleteModel']) - router.get('/installed-models', [OllamaController, 'installedModels']) - router.post('/unload-chat-models', [OllamaController, 'unloadChatModels']) - router.post('/configure-remote', [OllamaController, 'configureRemote']) - router.get('/remote-status', [OllamaController, 'remoteStatus']) + documented(router.post('/chat', [OllamaController, 'chat']), { + summary: 'Send a chat completion request', + tags: ['ollama'], + request: chatSchema, + }) + documented(router.get('/models', [OllamaController, 'availableModels']), { + summary: 'List available models', + tags: ['ollama'], + query: getAvailableModelsSchema, + }) + documented(router.post('/models', [OllamaController, 'dispatchModelDownload']), { + summary: 'Queue a model download', + tags: ['ollama'], + request: modelNameSchema, + }) + documented(router.delete('/models', [OllamaController, 'deleteModel']), { + summary: 'Delete a model', + tags: ['ollama'], + request: modelNameSchema, + }) + documented(router.get('/installed-models', [OllamaController, 'installedModels']), { + summary: 'List installed models', + tags: ['ollama'], + }) + documented(router.post('/unload-chat-models', [OllamaController, 'unloadChatModels']), { + summary: 'Unload chat models from memory', + tags: ['ollama'], + request: unloadChatModelsSchema, + }) + documented(router.post('/configure-remote', [OllamaController, 'configureRemote']), { + summary: 'Configure a remote Ollama endpoint', + tags: ['ollama'], + }) + documented(router.get('/remote-status', [OllamaController, 'remoteStatus']), { + summary: 'Get remote Ollama status', + tags: ['ollama'], + }) }) .prefix('/api/ollama') router .group(() => { - router.get('/', [ChatsController, 'index']) - router.post('/', [ChatsController, 'store']) - router.delete('/all', [ChatsController, 'destroyAll']) - router.get('/:id', [ChatsController, 'show']) - router.put('/:id', [ChatsController, 'update']) - router.delete('/:id', [ChatsController, 'destroy']) - router.post('/:id/messages', [ChatsController, 'addMessage']) + documented(router.get('/', [ChatsController, 'index']), { + summary: 'List chat sessions', + tags: ['chat'], + responses: { 200: chatSessionListResponse }, + }) + documented(router.post('/', [ChatsController, 'store']), { + summary: 'Create a chat session', + tags: ['chat'], + request: createSessionSchema, + responses: { + 201: { description: 'The created session', schema: chatSessionResponse }, + 500: errorResponse, + }, + }) + documented(router.delete('/all', [ChatsController, 'destroyAll']), { + summary: 'Delete all chat sessions', + tags: ['chat'], + responses: { 204: { description: 'All sessions deleted' } }, + }) + documented(router.get('/:id', [ChatsController, 'show']), { + summary: 'Get a chat session', + tags: ['chat'], + responses: { + 200: chatSessionResponse, + 404: { description: 'Session not found', schema: errorResponse }, + }, + }) + documented(router.put('/:id', [ChatsController, 'update']), { + summary: 'Update a chat session', + tags: ['chat'], + request: updateSessionSchema, + responses: { 200: chatSessionResponse, 500: errorResponse }, + }) + documented(router.delete('/:id', [ChatsController, 'destroy']), { + summary: 'Delete a chat session', + tags: ['chat'], + responses: { 204: { description: 'Session deleted' } }, + }) + documented(router.post('/:id/messages', [ChatsController, 'addMessage']), { + summary: 'Add a message to a chat session', + tags: ['chat'], + request: addMessageSchema, + responses: { + 201: { description: 'The created message', schema: chatMessageResponse }, + 500: errorResponse, + }, + }) }) .prefix('/api/chat/sessions') -router.get('/api/chat/suggestions', [ChatsController, 'suggestions']) +documented(router.get('/api/chat/suggestions', [ChatsController, 'suggestions']), { + summary: 'List chat suggestions', + tags: ['chat'], +}) router .group(() => { - router.post('/upload', [RagController, 'upload']) - router.get('/files', [RagController, 'getStoredFiles']) - router.get('/file-warnings', [RagController, 'getFileWarnings']) - router.delete('/files', [RagController, 'deleteFile']) - router.post('/files/embed', [RagController, 'embedFile']) - router.get('/files/content', [RagController, 'getFileContent']) - router.get('/files/download', [RagController, 'downloadFile']) - router.get('/active-jobs', [RagController, 'getActiveJobs']) - router.get('/failed-jobs', [RagController, 'getFailedJobs']) - router.delete('/failed-jobs', [RagController, 'cleanupFailedJobs']) - router.delete('/jobs', [RagController, 'cancelAllJobs']) - router.get('/job-status', [RagController, 'getJobStatus']) - router.post('/sync', [RagController, 'scanAndSync']) - router.post('/re-embed-all', [RagController, 'reembedAll']) - router.post('/reset-and-rebuild', [RagController, 'resetAndRebuild']) - router.post('/estimate-batch', [RagController, 'estimateBatch']) - router.get('/policy-prompt-state', [RagController, 'policyPromptState']) - router.get('/health', [RagController, 'health']) + documented(router.post('/upload', [RagController, 'upload']), { + summary: 'Upload a file for RAG', + tags: ['rag'], + }) + documented(router.get('/files', [RagController, 'getStoredFiles']), { + summary: 'List stored RAG files', + tags: ['rag'], + }) + documented(router.get('/file-warnings', [RagController, 'getFileWarnings']), { + summary: 'List RAG file warnings', + tags: ['rag'], + }) + documented(router.delete('/files', [RagController, 'deleteFile']), { + summary: 'Delete a RAG file', + tags: ['rag'], + query: deleteFileSchema, + }) + documented(router.post('/files/embed', [RagController, 'embedFile']), { + summary: 'Embed a RAG file', + tags: ['rag'], + request: embedFileSchema, + }) + documented(router.get('/files/content', [RagController, 'getFileContent']), { + summary: 'Get RAG file content', + tags: ['rag'], + query: fileSourceSchema, + }) + documented(router.get('/files/download', [RagController, 'downloadFile']), { + summary: 'Download a RAG file', + tags: ['rag'], + query: fileSourceSchema, + }) + documented(router.get('/active-jobs', [RagController, 'getActiveJobs']), { + summary: 'List active RAG jobs', + tags: ['rag'], + }) + documented(router.get('/failed-jobs', [RagController, 'getFailedJobs']), { + summary: 'List failed RAG jobs', + tags: ['rag'], + }) + documented(router.delete('/failed-jobs', [RagController, 'cleanupFailedJobs']), { + summary: 'Clean up failed RAG jobs', + tags: ['rag'], + }) + documented(router.delete('/jobs', [RagController, 'cancelAllJobs']), { + summary: 'Cancel all RAG jobs', + tags: ['rag'], + }) + documented(router.get('/job-status', [RagController, 'getJobStatus']), { + summary: 'Get RAG job status', + tags: ['rag'], + query: getJobStatusSchema, + }) + documented(router.post('/sync', [RagController, 'scanAndSync']), { + summary: 'Scan and sync RAG files', + tags: ['rag'], + }) + documented(router.post('/re-embed-all', [RagController, 'reembedAll']), { + summary: 'Re-embed all RAG files', + tags: ['rag'], + }) + documented(router.post('/reset-and-rebuild', [RagController, 'resetAndRebuild']), { + summary: 'Reset and rebuild the RAG index', + tags: ['rag'], + }) + documented(router.post('/estimate-batch', [RagController, 'estimateBatch']), { + summary: 'Estimate a RAG embedding batch', + tags: ['rag'], + request: estimateBatchSchema, + }) + documented(router.get('/policy-prompt-state', [RagController, 'policyPromptState']), { + summary: 'Get RAG policy prompt state', + tags: ['rag'], + }) + documented(router.get('/health', [RagController, 'health']), { + summary: 'RAG health check', + tags: ['rag'], + }) }) .prefix('/api/rag') router .group(() => { - router.get('/debug-info', [SystemController, 'getDebugInfo']) - router.get('/info', [SystemController, 'getSystemInfo']) - router.get('/internet-status', [SystemController, 'getInternetStatus']) - router.get('/services', [SystemController, 'getServices']) - router.post('/services/affect', [SystemController, 'affectService']) - router.post('/services/install', [SystemController, 'installService']) - router.post('/services/force-reinstall', [SystemController, 'forceReinstallService']) - router.post('/services/uninstall', [SystemController, 'uninstallService']) - router.post('/services/check-updates', [SystemController, 'checkServiceUpdates']) - router.get('/services/preflight', [SystemController, 'preflightCheck']) - router.get('/services/suggest-port', [SystemController, 'suggestCustomPort']) - router.post('/services/preflight-custom', [SystemController, 'preflightCustomApp']) - router.post('/services/custom', [SystemController, 'createCustomApp']) - router.put('/services/custom', [SystemController, 'updateCustomApp']) - router.post('/services/custom/update', [SystemController, 'updateCustomApp_pullLatest']) - router.delete('/services/custom', [SystemController, 'deleteCustomApp']) - router.get('/services/custom/:name', [SystemController, 'getCustomApp']) - router.put('/services/custom-url', [SystemController, 'setServiceCustomUrl']) - router.get('/services/:name/logs', [SystemController, 'getServiceLogs']) - router.get('/services/:name/stats', [SystemController, 'getServiceStats']) - router.get('/services/:name/available-versions', [SystemController, 'getAvailableVersions']) - router.post('/services/update', [SystemController, 'updateService']) - router.post('/services/auto-update', [SystemController, 'setServiceAutoUpdate']) - router.get('/apps/auto-update/status', [SystemController, 'getAppAutoUpdateStatus']) - router.get('/content/auto-update/status', [SystemController, 'getContentAutoUpdateStatus']) - router.post('/subscribe-release-notes', [SystemController, 'subscribeToReleaseNotes']) - router.get('/latest-version', [SystemController, 'checkLatestVersion']) - router.post('/update', [SystemController, 'requestSystemUpdate']) - router.get('/update/status', [SystemController, 'getSystemUpdateStatus']) - router.get('/update/logs', [SystemController, 'getSystemUpdateLogs']) - router.get('/auto-update/status', [SystemController, 'getAutoUpdateStatus']) - router.get('/settings', [SettingsController, 'getSetting']) - router.patch('/settings', [SettingsController, 'updateSetting']) + documented(router.get('/debug-info', [SystemController, 'getDebugInfo']), { + summary: 'Get system debug information', + tags: ['system'], + }) + documented(router.get('/info', [SystemController, 'getSystemInfo']), { + summary: 'Get system information', + tags: ['system'], + }) + documented(router.get('/internet-status', [SystemController, 'getInternetStatus']), { + summary: 'Get internet connectivity status', + tags: ['system'], + }) + documented(router.get('/services', [SystemController, 'getServices']), { + summary: 'List services', + tags: ['system'], + }) + documented(router.post('/services/affect', [SystemController, 'affectService']), { + summary: 'Start, stop, or restart a service', + tags: ['system'], + request: affectServiceValidator, + }) + documented(router.post('/services/install', [SystemController, 'installService']), { + summary: 'Install a service', + tags: ['system'], + request: installServiceValidator, + }) + documented(router.post('/services/force-reinstall', [SystemController, 'forceReinstallService']), { + summary: 'Force reinstall a service', + tags: ['system'], + request: installServiceValidator, + }) + documented(router.post('/services/uninstall', [SystemController, 'uninstallService']), { + summary: 'Uninstall a service', + tags: ['system'], + request: uninstallServiceValidator, + }) + documented(router.post('/services/check-updates', [SystemController, 'checkServiceUpdates']), { + summary: 'Check for service updates', + tags: ['system'], + }) + documented(router.get('/services/preflight', [SystemController, 'preflightCheck']), { + summary: 'Preflight a service install', + tags: ['system'], + query: preflightValidator, + }) + documented(router.get('/services/suggest-port', [SystemController, 'suggestCustomPort']), { + summary: 'Suggest an available custom port', + tags: ['system'], + }) + documented(router.post('/services/preflight-custom', [SystemController, 'preflightCustomApp']), { + summary: 'Preflight a custom app install', + tags: ['system'], + request: preflightCustomValidator, + }) + documented(router.post('/services/custom', [SystemController, 'createCustomApp']), { + summary: 'Create a custom app', + tags: ['system'], + request: customAppValidator, + }) + documented(router.put('/services/custom', [SystemController, 'updateCustomApp']), { + summary: 'Update a custom app', + tags: ['system'], + request: updateCustomAppValidator, + }) + documented(router.post('/services/custom/update', [SystemController, 'updateCustomApp_pullLatest']), { + summary: 'Pull the latest version of a custom app', + tags: ['system'], + request: installServiceValidator, + }) + documented(router.delete('/services/custom', [SystemController, 'deleteCustomApp']), { + summary: 'Delete a custom app', + tags: ['system'], + request: deleteCustomAppValidator, + }) + documented(router.get('/services/custom/:name', [SystemController, 'getCustomApp']), { + summary: 'Get a custom app', + tags: ['system'], + }) + documented(router.put('/services/custom-url', [SystemController, 'setServiceCustomUrl']), { + summary: 'Set a service custom URL', + tags: ['system'], + request: setServiceCustomUrlValidator, + }) + documented(router.get('/services/:name/logs', [SystemController, 'getServiceLogs']), { + summary: 'Get service logs', + tags: ['system'], + query: serviceLogsValidator, + }) + documented(router.get('/services/:name/stats', [SystemController, 'getServiceStats']), { + summary: 'Get service stats', + tags: ['system'], + }) + documented(router.get('/services/:name/available-versions', [SystemController, 'getAvailableVersions']), { + summary: 'List available service versions', + tags: ['system'], + }) + documented(router.post('/services/update', [SystemController, 'updateService']), { + summary: 'Update a service', + tags: ['system'], + request: updateServiceValidator, + }) + documented(router.post('/services/auto-update', [SystemController, 'setServiceAutoUpdate']), { + summary: 'Set service auto-update', + tags: ['system'], + request: setServiceAutoUpdateValidator, + }) + documented(router.get('/apps/auto-update/status', [SystemController, 'getAppAutoUpdateStatus']), { + summary: 'Get app auto-update status', + tags: ['system'], + }) + documented(router.get('/content/auto-update/status', [SystemController, 'getContentAutoUpdateStatus']), { + summary: 'Get content auto-update status', + tags: ['system'], + }) + documented(router.post('/subscribe-release-notes', [SystemController, 'subscribeToReleaseNotes']), { + summary: 'Subscribe to release notes', + tags: ['system'], + request: subscribeToReleaseNotesValidator, + }) + documented(router.get('/latest-version', [SystemController, 'checkLatestVersion']), { + summary: 'Check the latest available version', + tags: ['system'], + query: checkLatestVersionValidator, + }) + documented(router.post('/update', [SystemController, 'requestSystemUpdate']), { + summary: 'Request a system update', + tags: ['system'], + }) + documented(router.get('/update/status', [SystemController, 'getSystemUpdateStatus']), { + summary: 'Get system update status', + tags: ['system'], + }) + documented(router.get('/update/logs', [SystemController, 'getSystemUpdateLogs']), { + summary: 'Get system update logs', + tags: ['system'], + }) + documented(router.get('/auto-update/status', [SystemController, 'getAutoUpdateStatus']), { + summary: 'Get system auto-update status', + tags: ['system'], + }) + documented(router.get('/settings', [SettingsController, 'getSetting']), { + summary: 'Get a system setting', + tags: ['system'], + query: getSettingSchema, + }) + documented(router.patch('/settings', [SettingsController, 'updateSetting']), { + summary: 'Update a system setting', + tags: ['system'], + request: updateSettingSchema, + }) }) .prefix('/api/system') router .group(() => { - router.get('/list', [ZimController, 'list']) - router.get('/list-remote', [ZimController, 'listRemote']) - router.get('/curated-categories', [ZimController, 'listCuratedCategories']) - router.post('/download-remote', [ZimController, 'downloadRemote']) - router.post('/download-category-tier', [ZimController, 'downloadCategoryTier']) + documented(router.get('/list', [ZimController, 'list']), { + summary: 'List installed ZIM files', + tags: ['zim'], + }) + documented(router.get('/list-remote', [ZimController, 'listRemote']), { + summary: 'List remote ZIM files', + tags: ['zim'], + query: listRemoteZimValidator, + }) + documented(router.get('/curated-categories', [ZimController, 'listCuratedCategories']), { + summary: 'List curated ZIM categories', + tags: ['zim'], + }) + documented(router.post('/download-remote', [ZimController, 'downloadRemote']), { + summary: 'Queue a remote ZIM download', + tags: ['zim'], + request: remoteDownloadWithMetadataValidator, + }) + documented(router.post('/download-category-tier', [ZimController, 'downloadCategoryTier']), { + summary: 'Download a ZIM category tier', + tags: ['zim'], + request: downloadCategoryTierValidator, + }) - router.post('/upload', [ZimController, 'upload']) - router.get('/wikipedia', [ZimController, 'getWikipediaState']) - router.post('/wikipedia/select', [ZimController, 'selectWikipedia']) + documented(router.post('/upload', [ZimController, 'upload']), { + summary: 'Upload a ZIM file', + tags: ['zim'], + }) + documented(router.get('/wikipedia', [ZimController, 'getWikipediaState']), { + summary: 'Get Wikipedia ZIM state', + tags: ['zim'], + }) + documented(router.post('/wikipedia/select', [ZimController, 'selectWikipedia']), { + summary: 'Select a Wikipedia ZIM edition', + tags: ['zim'], + request: selectWikipediaValidator, + }) - router.get('/custom-libraries', [ZimController, 'listCustomLibraries']) - router.post('/custom-libraries', [ZimController, 'addCustomLibrary']) - router.delete('/custom-libraries/:id', [ZimController, 'removeCustomLibrary']) - router.get('/browse-library', [ZimController, 'browseLibrary']) + documented(router.get('/custom-libraries', [ZimController, 'listCustomLibraries']), { + summary: 'List custom ZIM libraries', + tags: ['zim'], + }) + documented(router.post('/custom-libraries', [ZimController, 'addCustomLibrary']), { + summary: 'Add a custom ZIM library', + tags: ['zim'], + request: addCustomLibraryValidator, + }) + documented(router.delete('/custom-libraries/:id', [ZimController, 'removeCustomLibrary']), { + summary: 'Remove a custom ZIM library', + tags: ['zim'], + params: idParamValidator, + }) + documented(router.get('/browse-library', [ZimController, 'browseLibrary']), { + summary: 'Browse a ZIM library', + tags: ['zim'], + query: browseLibraryValidator, + }) - router.post('/rescan-library', [ZimController, 'rescanLibrary']) + documented(router.post('/rescan-library', [ZimController, 'rescanLibrary']), { + summary: 'Rescan the ZIM library', + tags: ['zim'], + }) - router.delete('/:filename', [ZimController, 'delete']) + documented(router.delete('/:filename', [ZimController, 'delete']), { + summary: 'Delete a ZIM file', + tags: ['zim'], + params: filenameParamValidator, + }) }) .prefix('/api/zim') router .group(() => { - router.post('/run', [BenchmarkController, 'run']) - router.post('/run/system', [BenchmarkController, 'runSystem']) - router.post('/run/ai', [BenchmarkController, 'runAI']) - router.get('/results', [BenchmarkController, 'results']) - router.get('/results/latest', [BenchmarkController, 'latest']) - router.get('/results/:id', [BenchmarkController, 'show']) - router.post('/submit', [BenchmarkController, 'submit']) - router.post('/builder-tag', [BenchmarkController, 'updateBuilderTag']) - router.get('/comparison', [BenchmarkController, 'comparison']) - router.get('/status', [BenchmarkController, 'status']) - router.get('/settings', [BenchmarkController, 'settings']) - router.post('/settings', [BenchmarkController, 'updateSettings']) + documented(router.post('/run', [BenchmarkController, 'run']), { + summary: 'Run a benchmark', + tags: ['benchmark'], + request: runBenchmarkValidator, + }) + documented(router.post('/run/system', [BenchmarkController, 'runSystem']), { + summary: 'Run a system benchmark', + tags: ['benchmark'], + }) + documented(router.post('/run/ai', [BenchmarkController, 'runAI']), { + summary: 'Run an AI benchmark', + tags: ['benchmark'], + }) + documented(router.get('/results', [BenchmarkController, 'results']), { + summary: 'List benchmark results', + tags: ['benchmark'], + }) + documented(router.get('/results/latest', [BenchmarkController, 'latest']), { + summary: 'Get the latest benchmark result', + tags: ['benchmark'], + }) + documented(router.get('/results/:id', [BenchmarkController, 'show']), { + summary: 'Get a benchmark result', + tags: ['benchmark'], + }) + documented(router.post('/submit', [BenchmarkController, 'submit']), { + summary: 'Submit a benchmark result', + tags: ['benchmark'], + request: submitBenchmarkValidator, + }) + documented(router.post('/builder-tag', [BenchmarkController, 'updateBuilderTag']), { + summary: 'Update the builder tag', + tags: ['benchmark'], + }) + documented(router.get('/comparison', [BenchmarkController, 'comparison']), { + summary: 'Get benchmark comparison data', + tags: ['benchmark'], + }) + documented(router.get('/status', [BenchmarkController, 'status']), { + summary: 'Get benchmark status', + tags: ['benchmark'], + }) + documented(router.get('/settings', [BenchmarkController, 'settings']), { + summary: 'Get benchmark settings', + tags: ['benchmark'], + }) + documented(router.post('/settings', [BenchmarkController, 'updateSettings']), { + summary: 'Update benchmark settings', + tags: ['benchmark'], + }) }) .prefix('/api/benchmark') diff --git a/admin/tests/functional/openapi.spec.ts b/admin/tests/functional/openapi.spec.ts new file mode 100644 index 0000000..2b50d4f --- /dev/null +++ b/admin/tests/functional/openapi.spec.ts @@ -0,0 +1,70 @@ +import { test } from '@japa/runner' +import router from '@adonisjs/core/services/router' +import { getDocRegistry } from '#start/openapi/documented' +import { buildOpenApiDocument, resetOpenApiCache } from '#start/openapi/generator' + +/** + * Routes under `/api` that are intentionally NOT part of the documented API + * surface (the docs machinery itself). + */ +const UNDOCUMENTED_ALLOWLIST = new Set(['GET /api/openapi.json']) + +const RELEVANT_METHODS = new Set(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']) + +/** + * Build a `${METHOD} ${pattern}` key set for a list of route JSON nodes, + * expanding multi-method routes and dropping HEAD/OPTIONS. + */ +function routeKeys(nodes: { methods: string[]; pattern: string }[]): Set { + const keys = new Set() + for (const node of nodes) { + for (const method of node.methods) { + if (RELEVANT_METHODS.has(method)) { + keys.add(`${method} ${node.pattern}`) + } + } + } + return keys +} + +test.group('OpenAPI docs', () => { + test('generates a structurally valid OpenAPI 3.1 document', ({ assert }) => { + resetOpenApiCache() + const doc = buildOpenApiDocument() + + assert.equal(doc.openapi, '3.1.0') + assert.isString(doc.info.title) + assert.isString(doc.info.version) + assert.isAbove(Object.keys(doc.paths).length, 0) + + // Every operation must declare at least one response. + for (const [path, methods] of Object.entries(doc.paths)) { + for (const [method, op] of Object.entries(methods)) { + assert.isObject(op.responses, `${method.toUpperCase()} ${path} is missing responses`) + assert.isAbove(Object.keys(op.responses).length, 0, `${method.toUpperCase()} ${path} has empty responses`) + } + } + }) + + test('every /api route is documented (completeness gate)', ({ assert }) => { + // All committed routes under /api. + const apiRoutes = router + .toJSON() + .root.filter((node) => node.pattern.startsWith('/api')) + + const allApiKeys = routeKeys(apiRoutes) + + // Keys for the routes registered through documented(). + const documentedKeys = routeKeys([...getDocRegistry().keys()].map((route) => route.toJSON())) + + const missing = [...allApiKeys].filter( + (key) => !documentedKeys.has(key) && !UNDOCUMENTED_ALLOWLIST.has(key) + ) + + assert.deepEqual( + missing, + [], + `These /api routes are missing a documented() descriptor:\n ${missing.join('\n ')}` + ) + }) +})