From e4b5f49d9152bd792fd361eff80250311e35cd55 Mon Sep 17 00:00:00 2001
From: Jake Turner <52841588+jakeaturner@users.noreply.github.com>
Date: Tue, 21 Jul 2026 12:36:40 -0700
Subject: [PATCH] feat: auto-generating OpenAPI docs with Scalar UI (#1128)
---
admin/app/controllers/openapi_controller.ts | 73 +
admin/app/validators/responses/chat.ts | 33 +
admin/app/validators/responses/common.ts | 37 +
admin/docs/api-reference.md | 206 +-
admin/package-lock.json | 2061 ++++++++++++++++++-
admin/package.json | 3 +-
admin/start/openapi/documented.ts | 75 +
admin/start/openapi/generator.ts | 234 +++
admin/start/routes.ts | 872 ++++++--
admin/tests/functional/openapi.spec.ts | 70 +
10 files changed, 3299 insertions(+), 365 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..85e0f8c
--- /dev/null
+++ b/admin/app/controllers/openapi_controller.ts
@@ -0,0 +1,73 @@
+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 8d7134f..e012ac4 100644
--- a/admin/docs/api-reference.md
+++ b/admin/docs/api-reference.md
@@ -4,6 +4,19 @@ NOMAD exposes a REST API for all operations. All endpoints are under `/api/` and
---
+## 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`
@@ -14,196 +27,3 @@ NOMAD exposes a REST API for all operations. All endpoints are under `/api/` and
- Long-running operations (downloads, benchmarks, embeddings) return 201 or 202 with a job/benchmark ID for polling
**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 NOMAD 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 |
diff --git a/admin/package-lock.json b/admin/package-lock.json
index cad062a..1885b5c 100644
--- a/admin/package-lock.json
+++ b/admin/package-lock.json
@@ -31,6 +31,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",
@@ -40,7 +41,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.4.0",
"@vitejs/plugin-react": "4.7.0",
"autoprefixer": "10.5.0",
"axios": "1.17.0",
@@ -850,6 +851,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",
@@ -1349,6 +1413,16 @@
"@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-markdown": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.5.0.tgz",
@@ -1364,6 +1438,35 @@
"@lezer/markdown": "^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",
@@ -2166,6 +2269,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",
@@ -2235,6 +2375,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",
@@ -3353,6 +3520,17 @@
"@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",
@@ -3372,6 +3550,28 @@
"@lezer/highlight": "^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",
@@ -3893,6 +4093,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",
@@ -3963,6 +4172,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",
@@ -4344,6 +4559,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",
@@ -4714,6 +4940,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.3",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz",
+ "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==",
+ "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",
@@ -4759,6 +5454,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",
@@ -5427,6 +6128,32 @@
"url": "https://github.com/sponsors/tannerlinsley"
}
},
+ "node_modules/@tanstack/vue-virtual": {
+ "version": "3.13.33",
+ "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.33.tgz",
+ "integrity": "sha512-R1j/o/5pdgzDqV3Us/s7VEV1Il6ocTsElKxK9g04rq66V3m7kIULgKXD04BAQwCO0IMemXsw3ex6LSH4ug3Feg==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/virtual-core": "3.17.5"
+ },
+ "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.5",
+ "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.17.5.tgz",
+ "integrity": "sha512-AXfBC3sq6PuYSwyxYORqqgHCNjPGAvKJvZuBBJ1klhztWBB5cgqgwsq8+fNfaQJG7/K4xYBja9S90QFn2zmQAg==",
+ "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",
@@ -5719,6 +6446,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",
@@ -6030,6 +6763,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",
@@ -6304,6 +7043,22 @@
"integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==",
"license": "ISC"
},
+ "node_modules/@unhead/vue": {
+ "version": "2.1.16",
+ "resolved": "https://registry.npmjs.org/@unhead/vue/-/vue-2.1.16.tgz",
+ "integrity": "sha512-t6QykImeMJcrUTbFB0Coy0cB/OZItHdQFRFLoH8GAVXKWmQORGPrwvueP9me7adOCuMH2uVvrv0nCkbi6zksaA==",
+ "license": "MIT",
+ "dependencies": {
+ "hookable": "^6.0.1",
+ "unhead": "2.1.16"
+ },
+ "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",
@@ -6506,29 +7261,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"
@@ -6614,6 +7380,266 @@
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
}
},
+ "node_modules/@vue/compiler-core": {
+ "version": "3.5.40",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.40.tgz",
+ "integrity": "sha512-39E8IgOhTbVDnoJFMKc2DvYnypcZwUqgUhQkccva/0m6FUwtIKSGV7n1hpVmYcFaoRAwf9pBcwnKlCEsN63ZEQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.7",
+ "@vue/shared": "3.5.40",
+ "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.40",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.40.tgz",
+ "integrity": "sha512-pwkx4vqlqOspFstrcmzwkKLePVMD3PT65imRzLhanU2V1Fj4K13g6OXjanOyzw3aTAuRk84BOmY8f3rEHqPaVA==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-core": "3.5.40",
+ "@vue/shared": "3.5.40"
+ }
+ },
+ "node_modules/@vue/compiler-sfc": {
+ "version": "3.5.40",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.40.tgz",
+ "integrity": "sha512-gIf497P4kpuALcvs5n3AEg1Vdn0pSY4XbjASIfHNYF1/MP3T2Mf2STERTubysBxCRxzJGJYtF/O7vwJrxFB3Vw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.7",
+ "@vue/compiler-core": "3.5.40",
+ "@vue/compiler-dom": "3.5.40",
+ "@vue/compiler-ssr": "3.5.40",
+ "@vue/shared": "3.5.40",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.30.21",
+ "postcss": "^8.5.19",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-sfc/node_modules/nanoid": {
+ "version": "3.3.16",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
+ "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/@vue/compiler-sfc/node_modules/postcss": {
+ "version": "8.5.21",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.21.tgz",
+ "integrity": "sha512-v4sDNP3fdNiWMfabO7OwOQdOX8TiQSztKyT1Wj0w+j7LDallJThJRBBBmzVGyYj0crMh7jlV4zepPkiNu9UwDQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.16",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/@vue/compiler-ssr": {
+ "version": "3.5.40",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.40.tgz",
+ "integrity": "sha512-rrE5xiXG663+vHCHa3J9p2z5OcBRjXmoqenprJxAFQxg5pSshzeBiCE6pu46axapRJ2Adk0YDA2BRZVjiHXnhg==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.40",
+ "@vue/shared": "3.5.40"
+ }
+ },
+ "node_modules/@vue/reactivity": {
+ "version": "3.5.40",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.40.tgz",
+ "integrity": "sha512-B7ot9UlUZOi1zbq61/LvE88ZLTV8IlajTdiZTAEiDQgrnIMIZoPr9kGw0Zw46ObW62O9+H/Be3kMbfb7kYPQZA==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/shared": "3.5.40"
+ }
+ },
+ "node_modules/@vue/runtime-core": {
+ "version": "3.5.40",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.40.tgz",
+ "integrity": "sha512-KAZLweuZ6uUJPK1PMSQPgBU5gCjgrrfjUhSglmU9NhH+Zjepa8cnwSydPWDWHDwOgY4g3VcZ+PljbiHlURNCbw==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/reactivity": "3.5.40",
+ "@vue/shared": "3.5.40"
+ }
+ },
+ "node_modules/@vue/runtime-dom": {
+ "version": "3.5.40",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.40.tgz",
+ "integrity": "sha512-ZfrX8ssZQds900L9pr8AuK05ddnMsR4MPMZr8cPN9GoqoPWcXLhjvvbIA2SMv+7a97sJ1vv9pj/zxK0Cq/eEFQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/reactivity": "3.5.40",
+ "@vue/runtime-core": "3.5.40",
+ "@vue/shared": "3.5.40",
+ "csstype": "^3.2.3"
+ }
+ },
+ "node_modules/@vue/server-renderer": {
+ "version": "3.5.40",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.40.tgz",
+ "integrity": "sha512-XNJym9WpevhTVt1HuwOrCRJ5Q+9z4BjTMrDtjTrvx74SmUll8spNTw6whWJa9mEkO4PKn5TihI/bm/8ds2QVJw==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-ssr": "3.5.40",
+ "@vue/runtime-dom": "3.5.40",
+ "@vue/shared": "3.5.40"
+ }
+ },
+ "node_modules/@vue/shared": {
+ "version": "3.5.40",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.40.tgz",
+ "integrity": "sha512-WxnBtruIqOoV3rA4jeKDWzrYI5h7Cp4+pjwDi8kWGHz+IslhiN+wguLVVhtv2l8VoU02rzDCVfDjgCl1lNpZVg==",
+ "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/@xmldom/xmldom": {
"version": "0.8.13",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz",
@@ -6718,6 +7744,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",
@@ -7356,12 +8400,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"
@@ -7451,7 +8495,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": {
@@ -8023,6 +9067,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",
@@ -8207,6 +9263,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",
@@ -8304,6 +9380,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",
@@ -9201,6 +10283,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",
@@ -9226,6 +10314,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",
@@ -9657,7 +10754,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": {
@@ -9669,6 +10765,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",
@@ -9814,6 +10919,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",
@@ -9892,6 +11009,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",
@@ -10182,6 +11311,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",
@@ -10230,6 +11368,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",
@@ -10257,6 +11615,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",
@@ -10270,6 +11663,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",
@@ -10294,6 +11704,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",
@@ -10456,6 +11881,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",
@@ -10576,6 +12021,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",
@@ -10754,6 +12214,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",
@@ -10874,6 +12346,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",
@@ -10895,6 +12383,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",
@@ -10925,6 +12425,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",
@@ -11111,6 +12623,12 @@
"node": ">=10"
}
},
+ "node_modules/js-base64": {
+ "version": "3.9.1",
+ "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.9.1.tgz",
+ "integrity": "sha512-U73qptcvf/HIOauFOmqT3a0mDUp0MYlfd15oqoe9kqZt5XhiXVb+HG09sLvI9PQ9tZIBFS4nlErai8zbWazP0g==",
+ "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",
@@ -11165,6 +12683,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",
@@ -11197,6 +12721,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",
@@ -11828,6 +13358,21 @@
"underscore": "^1.13.1"
}
},
+ "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",
@@ -11870,6 +13415,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",
@@ -12329,6 +13903,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",
@@ -13328,6 +14908,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",
@@ -13682,7 +15268,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"
@@ -13905,7 +15490,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"
@@ -14063,6 +15647,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",
@@ -14460,7 +16050,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"
@@ -14698,6 +16287,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",
@@ -15148,6 +16853,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",
@@ -15223,6 +17019,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",
@@ -15531,6 +17339,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",
@@ -16158,6 +17972,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",
@@ -16261,6 +18093,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",
@@ -16389,6 +18239,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",
@@ -16422,6 +18289,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",
@@ -16474,7 +18350,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"
@@ -16483,6 +18358,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",
@@ -16642,6 +18527,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",
@@ -16751,6 +18651,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",
@@ -16926,7 +18840,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"
@@ -17066,6 +18979,18 @@
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"license": "MIT"
},
+ "node_modules/unhead": {
+ "version": "2.1.16",
+ "resolved": "https://registry.npmjs.org/unhead/-/unhead-2.1.16.tgz",
+ "integrity": "sha512-cD2Q4a6SQHhW9/bPp17NT4GJ1yS0DSLe75WEHKQ8bKa/wjB6cIki3KeL86hhllNBcpv8i6bxdwtwQunneXPqKQ==",
+ "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",
@@ -17137,6 +19062,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",
@@ -17327,6 +19266,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",
@@ -17441,6 +19394,39 @@
"pbf": "^3.2.1"
}
},
+ "node_modules/vue": {
+ "version": "3.5.40",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.40.tgz",
+ "integrity": "sha512-+8PJ4SJXdn/cHGImF4CKdxlWHIN5Dkt7DoufRREM6h6uVCx2m7QxgcEQmmzyOK8A9mcafg7sFbJFYsdFVubTig==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.40",
+ "@vue/compiler-sfc": "3.5.40",
+ "@vue/runtime-dom": "3.5.40",
+ "@vue/server-renderer": "3.5.40",
+ "@vue/shared": "3.5.40"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue-component-type-helpers": {
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-3.3.7.tgz",
+ "integrity": "sha512-Skkhw9agYSgsWqv7bxSOGJZa9SaiJbZVGdXuFWnrzKaQYHnw9qbjD630rw6RyMqDbp54nfLCLw5SZA55if7JLg==",
+ "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",
@@ -17453,6 +19439,22 @@
"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",
@@ -17896,6 +19898,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 389fa8a..17262ae 100644
--- a/admin/package.json
+++ b/admin/package.json
@@ -86,6 +86,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",
@@ -95,7 +96,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.4.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 6df8cb5..af2c415 100644
--- a/admin/start/routes.ts
+++ b/admin/start/routes.ts
@@ -17,6 +17,7 @@ import HomeController from '#controllers/home_controller'
import MapsController from '#controllers/maps_controller'
import NomadMdController from '#controllers/nomad_md_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'
@@ -26,6 +27,71 @@ import CreatorPacksController from '#controllers/creator_packs_controller'
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'
+import { updateNomadMdSchema } from '#validators/nomad_md'
+import { searchDrugValidator, interactionsValidator } from '#validators/drug_reference'
+import { conditionDrugsValidator } from '#validators/conditions'
transmit.registerRoutes()
@@ -39,13 +105,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')
@@ -78,194 +164,658 @@ 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('/setup-world-basemap', [MapsController, 'setupWorldBasemap'])
- 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('/setup-world-basemap', [MapsController, 'setupWorldBasemap']), {
+ summary: 'Provision the world base map',
+ tags: ['maps'],
+ })
+ 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'])
- router.post('/jobs/:jobId/retry', [DownloadsController, 'retryJob'])
+ 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'],
+ })
+ documented(router.post('/jobs/:jobId/retry', [DownloadsController, 'retryJob']), {
+ summary: 'Retry 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('/nomad-md', [NomadMdController, 'show'])
- router.put('/nomad-md', [NomadMdController, 'update'])
+ documented(router.get('/nomad-md', [NomadMdController, 'show']), {
+ summary: 'Get the NOMAD.md system prompt',
+ tags: ['ai'],
+ })
+ documented(router.put('/nomad-md', [NomadMdController, 'update']), {
+ summary: 'Update the NOMAD.md system prompt',
+ tags: ['ai'],
+ request: updateNomadMdSchema,
+ })
})
.prefix('/api/ai')
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'])
- router.get('/collections', [RagController, 'getKnowledgeCollections'])
- router.post('/update-collection', [RagController, 'updateFileCollection'])
- router.post('/rename-collection', [RagController, 'renameKnowledgeCollection'])
- router.post('/delete-collection', [RagController, 'deleteKnowledgeCollection'])
+ 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'],
+ })
+ documented(router.get('/collections', [RagController, 'getKnowledgeCollections']), {
+ summary: 'List knowledge collections',
+ tags: ['rag'],
+ })
+ documented(router.post('/update-collection', [RagController, 'updateFileCollection']), {
+ summary: "Update a file's knowledge collection",
+ tags: ['rag'],
+ })
+ documented(router.post('/rename-collection', [RagController, 'renameKnowledgeCollection']), {
+ summary: 'Rename a knowledge collection',
+ tags: ['rag'],
+ })
+ documented(router.post('/delete-collection', [RagController, 'deleteKnowledgeCollection']), {
+ summary: 'Delete a knowledge collection',
+ 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.get('/', [CreatorPacksController, 'index'])
- router.post('/:id/install', [CreatorPacksController, 'install'])
- router.delete('/:id', [CreatorPacksController, 'uninstall'])
+ documented(router.get('/', [CreatorPacksController, 'index']), {
+ summary: 'List creator packs',
+ tags: ['creator-packs'],
+ })
+ documented(router.post('/:id/install', [CreatorPacksController, 'install']), {
+ summary: 'Install a creator pack',
+ tags: ['creator-packs'],
+ })
+ documented(router.delete('/:id', [CreatorPacksController, 'uninstall']), {
+ summary: 'Uninstall a creator pack',
+ tags: ['creator-packs'],
+ })
})
.prefix('/api/creator-packs')
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('/rerun-banner', [BenchmarkController, 'rerunBanner'])
- 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('/rerun-banner', [BenchmarkController, 'rerunBanner']), {
+ summary: 'Get the benchmark rerun banner state',
+ 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')
@@ -280,14 +830,40 @@ router.get('/drug-reference/interactions', [DrugReferenceController, 'interactio
router.get('/drug-reference/:id', [DrugReferenceController, 'show'])
router
.group(() => {
- router.get('/search', [DrugReferenceController, 'search'])
- router.get('/status', [DrugReferenceController, 'status'])
- router.get('/interactions', [DrugReferenceController, 'interactionsApi'])
- router.post('/download', [DrugReferenceController, 'download'])
- router.post('/ingest', [DrugReferenceController, 'ingest'])
- router.post('/reset-ingest', [DrugReferenceController, 'resetIngest'])
- router.post('/uninstall', [DrugReferenceController, 'uninstall'])
- router.get('/ingest-log', [DrugReferenceController, 'ingestLog'])
+ documented(router.get('/search', [DrugReferenceController, 'search']), {
+ summary: 'Search drug labels',
+ tags: ['drug-reference'],
+ query: searchDrugValidator,
+ })
+ documented(router.get('/status', [DrugReferenceController, 'status']), {
+ summary: 'Get drug reference ingest status',
+ tags: ['drug-reference'],
+ })
+ documented(router.get('/interactions', [DrugReferenceController, 'interactionsApi']), {
+ summary: 'Check drug interactions',
+ tags: ['drug-reference'],
+ query: interactionsValidator,
+ })
+ documented(router.post('/download', [DrugReferenceController, 'download']), {
+ summary: 'Download the drug label dataset',
+ tags: ['drug-reference'],
+ })
+ documented(router.post('/ingest', [DrugReferenceController, 'ingest']), {
+ summary: 'Ingest drug labels',
+ tags: ['drug-reference'],
+ })
+ documented(router.post('/reset-ingest', [DrugReferenceController, 'resetIngest']), {
+ summary: 'Reset the drug label ingest',
+ tags: ['drug-reference'],
+ })
+ documented(router.post('/uninstall', [DrugReferenceController, 'uninstall']), {
+ summary: 'Uninstall the drug reference dataset',
+ tags: ['drug-reference'],
+ })
+ documented(router.get('/ingest-log', [DrugReferenceController, 'ingestLog']), {
+ summary: 'Get the drug reference ingest log',
+ tags: ['drug-reference'],
+ })
})
.prefix('/api/drug-reference')
@@ -301,6 +877,10 @@ router.get('/conditions', [ConditionsController, 'index'])
router.get('/conditions/:slug', [ConditionsController, 'show'])
router
.group(() => {
- router.get('/drugs', [ConditionsController, 'drugsApi'])
+ documented(router.get('/drugs', [ConditionsController, 'drugsApi']), {
+ summary: 'List OTC drugs for a condition',
+ tags: ['conditions'],
+ query: conditionDrugsValidator,
+ })
})
.prefix('/api/conditions')
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 ')}`
+ )
+ })
+})