From d261bac0ed092cced467639040cb0a087dbd87bc Mon Sep 17 00:00:00 2001 From: Myx Date: Wed, 29 Apr 2026 15:24:56 +0200 Subject: [PATCH] feat: plugins v1.7 --- electron/api/auth-store.ts | 70 + electron/api/docs-html.ts | 122 + electron/api/http-helpers.ts | 107 + electron/api/index.ts | 8 + electron/api/local-api-server.ts | 276 ++ electron/api/openapi.ts | 241 ++ electron/api/router.ts | 294 ++ electron/app/lifecycle.ts | 11 + .../commands/handlers/deletePluginData.ts | 2 +- .../cqrs/commands/handlers/savePluginData.ts | 2 +- .../cqrs/queries/handlers/getPluginData.ts | 2 +- electron/desktop-settings.ts | 82 +- electron/entities/PluginDataEntity.ts | 2 +- electron/ipc/system.ts | 22 + .../migrations/1000000000008-AddPluginData.ts | 2 +- electron/preload.ts | 29 + package-lock.json | 2918 ++++++++++++++++- package.json | 5 + server/data/metoyou.sqlite | Bin 249856 -> 253952 bytes ...00000000008-ServerPluginInstallMetadata.ts | 2 +- toju-app/src/app/app.ts | 3 + .../platform/electron/electron-api.models.ts | 25 + .../core/services/settings-modal.service.ts | 1 + .../chat-message-item.component.html | 23 +- toju-app/src/app/domains/plugins/README.md | 4 +- .../services/plugin-bootstrap.service.ts | 9 + .../services/plugin-client-api.service.ts | 36 +- .../services/plugin-host.service.ts | 136 +- .../services/plugin-message-bus.service.ts | 4 +- .../plugin-requirement-state.service.ts | 20 +- .../services/plugin-store.service.spec.ts | 47 +- .../services/plugin-store.service.ts | 382 ++- .../domain/models/plugin-store.models.ts | 4 + .../plugin-manager.component.ts | 3 +- .../plugin-store/plugin-store.component.html | 388 ++- .../plugin-store/plugin-store.component.scss | 607 ---- .../plugin-store/plugin-store.component.ts | 143 +- toju-app/src/app/domains/plugins/index.ts | 1 + .../rooms-side-panel.component.html | 14 +- .../local-api-settings.component.html | 177 + .../local-api-settings.component.ts | 237 ++ .../settings-modal.component.html | 11 +- .../settings-modal.component.ts | 11 + .../shell/title-bar/title-bar.component.html | 1 - .../shared-kernel/plugin-system.contracts.ts | 4 + 45 files changed, 5621 insertions(+), 867 deletions(-) create mode 100644 electron/api/auth-store.ts create mode 100644 electron/api/docs-html.ts create mode 100644 electron/api/http-helpers.ts create mode 100644 electron/api/index.ts create mode 100644 electron/api/local-api-server.ts create mode 100644 electron/api/openapi.ts create mode 100644 electron/api/router.ts create mode 100644 toju-app/src/app/domains/plugins/application/services/plugin-bootstrap.service.ts delete mode 100644 toju-app/src/app/domains/plugins/feature/plugin-store/plugin-store.component.scss create mode 100644 toju-app/src/app/features/settings/settings-modal/local-api-settings/local-api-settings.component.html create mode 100644 toju-app/src/app/features/settings/settings-modal/local-api-settings/local-api-settings.component.ts diff --git a/electron/api/auth-store.ts b/electron/api/auth-store.ts new file mode 100644 index 0000000..36d6878 --- /dev/null +++ b/electron/api/auth-store.ts @@ -0,0 +1,70 @@ +import { randomBytes } from 'crypto'; + +export interface IssuedToken { + token: string; + userId: string; + username: string; + displayName: string; + signalingServerUrl: string; + issuedAt: number; + expiresAt: number; +} + +const TOKEN_TTL_MS = 24 * 60 * 60 * 1000; + +const tokens = new Map(); + +export function issueToken(params: { + userId: string; + username: string; + displayName: string; + signalingServerUrl: string; +}): IssuedToken { + const token = randomBytes(32).toString('hex'); + const issuedAt = Date.now(); + const issued: IssuedToken = { + token, + issuedAt, + expiresAt: issuedAt + TOKEN_TTL_MS, + userId: params.userId, + username: params.username, + displayName: params.displayName, + signalingServerUrl: params.signalingServerUrl + }; + + tokens.set(token, issued); + return issued; +} + +export function consumeToken(token: string): IssuedToken | null { + const issued = tokens.get(token); + + if (!issued) { + return null; + } + + if (issued.expiresAt < Date.now()) { + tokens.delete(token); + return null; + } + + return issued; +} + +export function revokeToken(token: string): void { + tokens.delete(token); +} + +export function clearAllTokens(): void { + tokens.clear(); +} + +export function pruneExpiredTokens(): void { + const now = Date.now(); + + for (const [token, issued] of tokens) { + if (issued.expiresAt < now) { + tokens.delete(token); + } + } +} diff --git a/electron/api/docs-html.ts b/electron/api/docs-html.ts new file mode 100644 index 0000000..6b7bf0d --- /dev/null +++ b/electron/api/docs-html.ts @@ -0,0 +1,122 @@ +import { promises as fs } from 'fs'; +import * as path from 'path'; + +function getScalarBundleCandidates(): string[] { + const processWithResources = process as NodeJS.Process & { resourcesPath?: string }; + const candidates: string[] = []; + + if (processWithResources.resourcesPath) { + candidates.push(path.join(processWithResources.resourcesPath, 'scalar', 'api-reference.js')); + } + + candidates.push(path.join(process.cwd(), 'node_modules', '@scalar', 'api-reference', 'dist', 'browser', 'standalone.js')); + + try { + candidates.push(path.join(path.dirname(require.resolve('@scalar/api-reference')), 'browser', 'standalone.js')); + } catch { + // ignore; the packaged app path above is the production path + } + + return candidates; +} + +export async function getScalarApiReferenceBundlePath(): Promise { + for (const candidate of getScalarBundleCandidates()) { + try { + await fs.access(candidate); + return candidate; + } catch { + // try the next candidate + } + } + + return null; +} + +export function getDocsHtml(specUrl: string): string { + const scalarConfig = { + url: specUrl, + theme: 'default', + layout: 'modern', + proxyUrl: '', + telemetry: false, + persistAuth: false, + showDeveloperTools: 'never', + hideDownloadButton: false, + hideTestRequestButton: false, + hideClientButton: false, + externalUrls: { + dashboardUrl: '', + registryUrl: '', + proxyUrl: '', + apiBaseUrl: '' + }, + agent: { + disabled: true, + hideAddApi: true + }, + mcp: { + disabled: true + } + }; + + return ` + + + + + + MetoYou Local API + + + +
+ + + + +`; +} diff --git a/electron/api/http-helpers.ts b/electron/api/http-helpers.ts new file mode 100644 index 0000000..926a6bc --- /dev/null +++ b/electron/api/http-helpers.ts @@ -0,0 +1,107 @@ +import { IncomingMessage, ServerResponse } from 'http'; + +export interface RequestContext { + method: string; + url: URL; + pathname: string; + headers: IncomingMessage['headers']; + remoteAddress: string; + bearerToken: string | null; +} + +const MAX_BODY_BYTES = 1 * 1024 * 1024; // 1 MiB + +export function getBearerToken(headers: IncomingMessage['headers']): string | null { + const raw = headers.authorization; + + if (typeof raw !== 'string') { + return null; + } + + const trimmed = raw.trim(); + + if (!/^bearer\s+/iu.test(trimmed)) { + return null; + } + + const token = trimmed.replace(/^bearer\s+/iu, '').trim(); + + return token.length > 0 ? token : null; +} + +export async function readJsonBody(req: IncomingMessage): Promise { + const length = Number(req.headers['content-length'] ?? 0); + + if (length > MAX_BODY_BYTES) { + throw new HttpError(413, 'Request body too large', 'BODY_TOO_LARGE'); + } + + const chunks: Buffer[] = []; + let received = 0; + + for await (const chunk of req) { + const buffer = chunk instanceof Buffer ? chunk : Buffer.from(chunk as string); + + received += buffer.length; + + if (received > MAX_BODY_BYTES) { + throw new HttpError(413, 'Request body too large', 'BODY_TOO_LARGE'); + } + + chunks.push(buffer); + } + + if (chunks.length === 0) { + return {} as T; + } + + const raw = Buffer.concat(chunks).toString('utf8'); + + try { + return JSON.parse(raw) as T; + } catch { + throw new HttpError(400, 'Invalid JSON body', 'INVALID_JSON'); + } +} + +export function sendJson(res: ServerResponse, status: number, payload: unknown): void { + if (!res.headersSent) { + res.statusCode = status; + res.setHeader('Content-Type', 'application/json; charset=utf-8'); + res.setHeader('Cache-Control', 'no-store'); + } + + res.end(JSON.stringify(payload)); +} + +export function sendText(res: ServerResponse, status: number, text: string, contentType = 'text/plain; charset=utf-8'): void { + if (!res.headersSent) { + res.statusCode = status; + res.setHeader('Content-Type', contentType); + res.setHeader('Cache-Control', 'no-store'); + } + + res.end(text); +} + +export class HttpError extends Error { + readonly status: number; + readonly code: string; + + constructor(status: number, message: string, code: string) { + super(message); + this.status = status; + this.code = code; + } +} + +export function sendError(res: ServerResponse, error: unknown): void { + if (error instanceof HttpError) { + sendJson(res, error.status, { error: error.message, errorCode: error.code }); + return; + } + + const message = error instanceof Error ? error.message : 'Internal server error'; + + sendJson(res, 500, { error: message, errorCode: 'INTERNAL_ERROR' }); +} diff --git a/electron/api/index.ts b/electron/api/index.ts new file mode 100644 index 0000000..24b03bd --- /dev/null +++ b/electron/api/index.ts @@ -0,0 +1,8 @@ +export { + applyLocalApiSettings, + getLocalApiSnapshot, + startLocalApiServer, + stopLocalApiServer, + type LocalApiSnapshot, + type LocalApiStatus +} from './local-api-server'; diff --git a/electron/api/local-api-server.ts b/electron/api/local-api-server.ts new file mode 100644 index 0000000..8c6b1c6 --- /dev/null +++ b/electron/api/local-api-server.ts @@ -0,0 +1,276 @@ +import { createServer, IncomingMessage, Server, ServerResponse } from 'http'; +import { createReadStream } from 'fs'; +import { AddressInfo } from 'net'; +import { pipeline } from 'stream/promises'; +import { getDataSource } from '../db/database'; +import { LocalApiSettings, readDesktopSettings } from '../desktop-settings'; +import { authenticate, matchRoute } from './router'; +import { clearAllTokens } from './auth-store'; +import { + HttpError, + RequestContext, + getBearerToken, + readJsonBody, + sendError, + sendJson, + sendText +} from './http-helpers'; + +export type LocalApiStatus = 'stopped' | 'starting' | 'running' | 'error'; + +export interface LocalApiSnapshot { + status: LocalApiStatus; + host: string | null; + port: number | null; + baseUrl: string | null; + error: string | null; + exposeOnLan: boolean; + scalarEnabled: boolean; +} + +let server: Server | null = null; +let currentStatus: LocalApiStatus = 'stopped'; +let currentBindHost: string | null = null; +let currentBindPort: number | null = null; +let currentError: string | null = null; +let activeSettings: LocalApiSettings | null = null; + +function pickBindHost(settings: LocalApiSettings): string { + return settings.exposeOnLan ? '0.0.0.0' : '127.0.0.1'; +} + +function buildBaseUrl(host: string, port: number): string { + const safeHost = host === '0.0.0.0' ? '127.0.0.1' : host; + return `http://${safeHost}:${port}`; +} + +async function sendFile(res: ServerResponse, status: number, filePath: string, contentType: string): Promise { + if (!res.headersSent) { + res.statusCode = status; + res.setHeader('Content-Type', contentType); + res.setHeader('Cache-Control', 'no-store'); + } + + await pipeline(createReadStream(filePath), res); +} + +export function getLocalApiSnapshot(): LocalApiSnapshot { + const settings = activeSettings ?? readDesktopSettings().localApi; + + return { + status: currentStatus, + host: currentBindHost, + port: currentBindPort, + baseUrl: currentBindHost && currentBindPort ? buildBaseUrl(currentBindHost, currentBindPort) : null, + error: currentError, + exposeOnLan: settings.exposeOnLan, + scalarEnabled: settings.scalarEnabled + }; +} + +async function handleRequest(req: IncomingMessage, res: ServerResponse, settings: LocalApiSettings): Promise { + // CORS for loopback origin only. Local-first; not a public API. + const origin = req.headers.origin; + const allowOrigin = origin && /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/iu.test(origin) ? origin : 'null'; + + res.setHeader('Access-Control-Allow-Origin', allowOrigin); + res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); + res.setHeader('Access-Control-Max-Age', '600'); + + if (req.method === 'OPTIONS') { + res.statusCode = 204; + res.end(); + return; + } + + let urlObj: URL; + + try { + urlObj = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`); + } catch { + sendJson(res, 400, { error: 'Invalid URL', errorCode: 'INVALID_URL' }); + return; + } + + const requestContext: RequestContext = { + method: (req.method ?? 'GET').toUpperCase(), + url: urlObj, + pathname: urlObj.pathname, + headers: req.headers, + remoteAddress: req.socket.remoteAddress ?? '', + bearerToken: getBearerToken(req.headers) + }; + + const { match, methodNotAllowed } = matchRoute(requestContext.method, requestContext.pathname); + + if (!match) { + if (methodNotAllowed) { + sendJson(res, 405, { error: 'Method not allowed', errorCode: 'METHOD_NOT_ALLOWED' }); + } else { + sendJson(res, 404, { error: 'Not found', errorCode: 'NOT_FOUND' }); + } + return; + } + + if (match.requiresAuth) { + const issued = authenticate(requestContext.bearerToken); + + if (!issued) { + sendJson(res, 401, { error: 'Authentication required', errorCode: 'UNAUTHORIZED' }); + return; + } + } + + const dataSource = getDataSource(); + + if (!dataSource || !dataSource.isInitialized) { + sendJson(res, 503, { error: 'Database not initialised', errorCode: 'DB_UNAVAILABLE' }); + return; + } + + let bodyCache: unknown | undefined; + + try { + const baseUrl = buildBaseUrl(currentBindHost ?? '127.0.0.1', currentBindPort ?? settings.port); + const result = await match.handler({ + request: requestContext, + settings, + baseUrl, + dataSource, + bodyBuffer: async () => { + if (bodyCache === undefined) { + bodyCache = await readJsonBody(req); + } + return bodyCache; + } + }); + + if (result.status === 204) { + res.statusCode = 204; + res.end(); + return; + } + + if (result.filePath) { + await sendFile(res, result.status, result.filePath, result.contentType ?? 'application/octet-stream'); + return; + } + + if (result.rawBody !== undefined) { + sendText(res, result.status, result.rawBody, result.contentType ?? 'text/plain; charset=utf-8'); + return; + } + + sendJson(res, result.status, result.body); + } catch (error) { + if (!(error instanceof HttpError)) { + console.error('[LocalApi] Request handler error:', error); + } + sendError(res, error); + } +} + +export interface StartResult { + ok: boolean; + snapshot: LocalApiSnapshot; +} + +export async function startLocalApiServer(settings: LocalApiSettings): Promise { + if (server) { + await stopLocalApiServer(); + } + + activeSettings = { ...settings, allowedSignalingServers: [...settings.allowedSignalingServers] }; + currentStatus = 'starting'; + currentError = null; + currentBindHost = pickBindHost(settings); + currentBindPort = settings.port; + + const httpServer = createServer((req, res) => { + void handleRequest(req, res, activeSettings!).catch((error) => { + console.error('[LocalApi] Unhandled request error:', error); + try { + sendError(res, error); + } catch { + // ignore + } + }); + }); + + return await new Promise((resolve) => { + httpServer.once('error', (error) => { + currentStatus = 'error'; + currentError = (error as Error).message; + currentBindPort = null; + server = null; + activeSettings = null; + console.error('[LocalApi] Failed to start:', error); + resolve({ ok: false, snapshot: getLocalApiSnapshot() }); + }); + + httpServer.listen(settings.port, pickBindHost(settings), () => { + const address = httpServer.address() as AddressInfo | null; + + server = httpServer; + currentStatus = 'running'; + currentBindPort = address?.port ?? settings.port; + currentError = null; + console.log(`[LocalApi] Listening on http://${currentBindHost}:${currentBindPort}`); + resolve({ ok: true, snapshot: getLocalApiSnapshot() }); + }); + }); +} + +export async function stopLocalApiServer(): Promise { + const httpServer = server; + + if (!httpServer) { + currentStatus = 'stopped'; + currentBindHost = null; + currentBindPort = null; + activeSettings = null; + return getLocalApiSnapshot(); + } + + await new Promise((resolve) => { + httpServer.close(() => resolve()); + // close() waits for connections; force-close keep-alives so it returns promptly. + httpServer.closeAllConnections?.(); + }); + + server = null; + currentStatus = 'stopped'; + currentBindHost = null; + currentBindPort = null; + currentError = null; + activeSettings = null; + clearAllTokens(); + console.log('[LocalApi] Stopped'); + return getLocalApiSnapshot(); +} + +export async function applyLocalApiSettings(): Promise { + const settings = readDesktopSettings().localApi; + + if (!settings.enabled) { + return await stopLocalApiServer(); + } + + // If already running with the same bind config, no-op (settings like + // scalarEnabled / allowedSignalingServers are read on every request). + if ( + server + && activeSettings + && currentStatus === 'running' + && activeSettings.port === settings.port + && activeSettings.exposeOnLan === settings.exposeOnLan + ) { + activeSettings = { ...settings, allowedSignalingServers: [...settings.allowedSignalingServers] }; + return getLocalApiSnapshot(); + } + + const result = await startLocalApiServer(settings); + + return result.snapshot; +} diff --git a/electron/api/openapi.ts b/electron/api/openapi.ts new file mode 100644 index 0000000..3d90f7c --- /dev/null +++ b/electron/api/openapi.ts @@ -0,0 +1,241 @@ +export interface OpenApiBuildOptions { + baseUrl: string; + appVersion: string; +} + +export function buildOpenApiDocument(options: OpenApiBuildOptions): unknown { + const { baseUrl, appVersion } = options; + + return { + openapi: '3.1.0', + info: { + title: 'MetoYou Local Desktop API', + version: appVersion, + description: + 'Authenticated local HTTP API exposed by the MetoYou desktop app. ' + + 'Authentication is performed against a configured signaling server. ' + + 'Bearer tokens issued here are scoped to this device only.' + }, + servers: [{ url: baseUrl }], + components: { + securitySchemes: { + bearerAuth: { + type: 'http', + scheme: 'bearer', + bearerFormat: 'opaque' + } + }, + schemas: { + Error: { + type: 'object', + required: ['error'], + properties: { + error: { type: 'string' }, + errorCode: { type: 'string' } + } + }, + LoginRequest: { + type: 'object', + required: ['username', 'password', 'serverUrl'], + properties: { + username: { type: 'string' }, + password: { type: 'string' }, + serverUrl: { + type: 'string', + format: 'uri', + description: 'Base URL of the signaling server to authenticate against. Must be in the allowed list configured in the desktop app.' + } + } + }, + LoginResponse: { + type: 'object', + required: ['token', 'expiresAt', 'user'], + properties: { + token: { type: 'string' }, + expiresAt: { type: 'integer', format: 'int64' }, + user: { $ref: '#/components/schemas/AuthUser' } + } + }, + AuthUser: { + type: 'object', + required: ['id', 'username', 'displayName'], + properties: { + id: { type: 'string' }, + username: { type: 'string' }, + displayName: { type: 'string' } + } + }, + Profile: { + type: 'object', + properties: { + id: { type: 'string' }, + username: { type: 'string' }, + displayName: { type: 'string' }, + description: { type: 'string' }, + avatarUrl: { type: 'string' }, + status: { type: 'string' } + } + }, + Room: { + type: 'object', + properties: { + id: { type: 'string' }, + name: { type: 'string' } + }, + additionalProperties: true + }, + Message: { + type: 'object', + properties: { + id: { type: 'string' }, + roomId: { type: 'string' }, + channelId: { type: 'string' }, + senderId: { type: 'string' }, + senderName: { type: 'string' }, + content: { type: 'string' }, + timestamp: { type: 'integer', format: 'int64' }, + editedAt: { type: 'integer', format: 'int64' }, + isDeleted: { type: 'boolean' } + }, + additionalProperties: true + } + } + }, + security: [{ bearerAuth: [] }], + paths: { + '/api/health': { + get: { + security: [], + summary: 'Liveness probe', + responses: { + '200': { + description: 'Service is alive', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + version: { type: 'string' } + } + } + } + } + } + } + } + }, + '/api/openapi.json': { + get: { + security: [], + summary: 'OpenAPI specification', + responses: { '200': { description: 'This document' } } + } + }, + '/api/auth/login': { + post: { + security: [], + summary: 'Exchange username/password (validated by a signaling server) for a bearer token', + requestBody: { + required: true, + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/LoginRequest' } + } + } + }, + responses: { + '200': { + description: 'Token issued', + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/LoginResponse' } + } + } + }, + '401': { + description: 'Invalid credentials', + content: { + 'application/json': { schema: { $ref: '#/components/schemas/Error' } } + } + }, + '403': { + description: 'Signaling server URL not allowed', + content: { + 'application/json': { schema: { $ref: '#/components/schemas/Error' } } + } + } + } + } + }, + '/api/auth/logout': { + post: { + summary: 'Revoke the current bearer token', + responses: { '204': { description: 'Token revoked' } } + } + }, + '/api/profile': { + get: { + summary: 'Get the current user profile', + responses: { + '200': { + description: 'Current user profile', + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/Profile' } + } + } + }, + '404': { + description: 'No current user is set on this device', + content: { + 'application/json': { schema: { $ref: '#/components/schemas/Error' } } + } + } + } + } + }, + '/api/rooms': { + get: { + summary: 'List rooms (servers) known to this device', + responses: { + '200': { + description: 'Rooms array', + content: { + 'application/json': { + schema: { + type: 'array', + items: { $ref: '#/components/schemas/Room' } + } + } + } + } + } + } + }, + '/api/rooms/{roomId}/messages': { + get: { + summary: 'List messages for a room', + parameters: [ + { name: 'roomId', in: 'path', required: true, schema: { type: 'string' } }, + { name: 'limit', in: 'query', required: false, schema: { type: 'integer', minimum: 1, maximum: 500 } }, + { name: 'offset', in: 'query', required: false, schema: { type: 'integer', minimum: 0 } } + ], + responses: { + '200': { + description: 'Messages array', + content: { + 'application/json': { + schema: { + type: 'array', + items: { $ref: '#/components/schemas/Message' } + } + } + } + } + } + } + } + } + }; +} diff --git a/electron/api/router.ts b/electron/api/router.ts new file mode 100644 index 0000000..067d302 --- /dev/null +++ b/electron/api/router.ts @@ -0,0 +1,294 @@ +import { app, net } from 'electron'; +import { DataSource } from 'typeorm'; +import { buildQueryHandlers } from '../cqrs/queries'; +import { QueryType, QueryTypeKey, Query } from '../cqrs/types'; +import { issueToken, consumeToken, revokeToken, IssuedToken } from './auth-store'; +import { buildOpenApiDocument } from './openapi'; +import { HttpError, RequestContext, readJsonBody } from './http-helpers'; +import { getDocsHtml, getScalarApiReferenceBundlePath } from './docs-html'; +import { LocalApiSettings } from '../desktop-settings'; + +export interface RouteResponse { + status: number; + body: unknown; + contentType?: string; + filePath?: string; + rawBody?: string; +} + +export interface RouteContext { + request: RequestContext; + settings: LocalApiSettings; + baseUrl: string; + dataSource: DataSource; + bodyBuffer: () => Promise; +} + +type RouteHandler = (context: RouteContext) => Promise; + +interface RouteMatch { + handler: RouteHandler; + params: Record; + requiresAuth: boolean; +} + +interface RouteDefinition { + method: string; + pattern: RegExp; + paramKeys: string[]; + handler: RouteHandler; + requiresAuth: boolean; +} + +function compilePattern(template: string): { pattern: RegExp; paramKeys: string[] } { + const paramKeys: string[] = []; + const escaped = template.replace(/[.*+?^${}()|[\]\\]/g, (match) => { + if (match === '*' || match === '+' || match === '?') + return `\\${match}`; + return `\\${match}`; + }); + const source = template.replace(/\{([^}]+)\}/g, (_full, key: string) => { + paramKeys.push(key); + return '([^/]+)'; + }); + void escaped; + + return { pattern: new RegExp(`^${source}$`), paramKeys }; +} + +function defineRoute(method: string, template: string, handler: RouteHandler, requiresAuth: boolean): RouteDefinition { + const compiled = compilePattern(template); + + return { method: method.toUpperCase(), pattern: compiled.pattern, paramKeys: compiled.paramKeys, handler, requiresAuth }; +} + +function runQuery(dataSource: DataSource, query: Query): Promise { + const handlers = buildQueryHandlers(dataSource) as Record Promise>; + const handler = handlers[query.type as QueryTypeKey]; + + if (!handler) { + throw new HttpError(500, `No handler registered for query: ${query.type}`, 'UNKNOWN_QUERY'); + } + + return handler(query) as Promise; +} + +function clampInt(value: unknown, min: number, max: number, fallback: number): number { + const parsed = typeof value === 'string' ? Number(value) : NaN; + + if (!Number.isFinite(parsed)) + return fallback; + + return Math.max(min, Math.min(max, Math.floor(parsed))); +} + +const ROUTES: RouteDefinition[] = [ + defineRoute('GET', '/api/health', async (ctx): Promise => ({ + status: 200, + body: { status: 'ok', version: app.getVersion(), timestamp: Date.now(), exposeOnLan: ctx.settings.exposeOnLan } + }), false), + + defineRoute('GET', '/api/openapi.json', async (ctx): Promise => ({ + status: 200, + body: buildOpenApiDocument({ baseUrl: ctx.baseUrl, appVersion: app.getVersion() }) + }), false), + + defineRoute('GET', '/docs', async (ctx): Promise => { + if (!ctx.settings.scalarEnabled) { + return { + status: 404, + body: null, + contentType: 'text/plain; charset=utf-8', + rawBody: 'API documentation is disabled. Enable Scalar in desktop settings to view it.' + }; + } + + return { + status: 200, + body: null, + contentType: 'text/html; charset=utf-8', + rawBody: getDocsHtml(`${ctx.baseUrl}/api/openapi.json`) + }; + }, false), + + defineRoute('GET', '/scalar/api-reference.js', async (ctx): Promise => { + if (!ctx.settings.scalarEnabled) { + return { + status: 404, + body: null, + contentType: 'text/plain; charset=utf-8', + rawBody: 'API documentation is disabled. Enable Scalar in desktop settings to view it.' + }; + } + + const bundlePath = await getScalarApiReferenceBundlePath(); + + if (!bundlePath) { + throw new HttpError(503, 'Scalar API reference bundle is not available in this build', 'SCALAR_BUNDLE_MISSING'); + } + + return { + status: 200, + body: null, + contentType: 'application/javascript; charset=utf-8', + filePath: bundlePath + }; + }, false), + + defineRoute('POST', '/api/auth/login', async (ctx): Promise => { + const body = await ctx.bodyBuffer() as { username?: unknown; password?: unknown; serverUrl?: unknown }; + const username = typeof body.username === 'string' ? body.username.trim() : ''; + const password = typeof body.password === 'string' ? body.password : ''; + const serverUrl = typeof body.serverUrl === 'string' ? body.serverUrl.trim().replace(/\/+$/u, '') : ''; + + if (!username || !password || !serverUrl) { + throw new HttpError(400, 'username, password, and serverUrl are required', 'INVALID_REQUEST'); + } + + if (!/^https?:\/\//iu.test(serverUrl)) { + throw new HttpError(400, 'serverUrl must be an http or https URL', 'INVALID_REQUEST'); + } + + if (ctx.settings.allowedSignalingServers.length === 0) { + throw new HttpError(403, 'No signaling servers are allowed for local API authentication. Add one in desktop settings.', 'NO_ALLOWED_SERVERS'); + } + + if (!ctx.settings.allowedSignalingServers.includes(serverUrl)) { + throw new HttpError(403, 'Signaling server URL is not in the allowed list', 'SERVER_NOT_ALLOWED'); + } + + let response: Response; + + try { + response = await net.fetch(`${serverUrl}/api/users/login`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username, password }) + }); + } catch (error) { + throw new HttpError(502, `Failed to reach signaling server: ${(error as Error).message}`, 'UPSTREAM_UNREACHABLE'); + } + + if (response.status === 401 || response.status === 403) { + throw new HttpError(401, 'Invalid credentials', 'INVALID_CREDENTIALS'); + } + + if (!response.ok) { + throw new HttpError(502, `Signaling server rejected login (${response.status})`, 'UPSTREAM_ERROR'); + } + + const remote = await response.json() as { id?: string; username?: string; displayName?: string }; + + if (!remote.id || !remote.username) { + throw new HttpError(502, 'Signaling server returned an unexpected response', 'UPSTREAM_BAD_RESPONSE'); + } + + const issued = issueToken({ + userId: remote.id, + username: remote.username, + displayName: remote.displayName ?? remote.username, + signalingServerUrl: serverUrl + }); + + return { + status: 200, + body: { + token: issued.token, + expiresAt: issued.expiresAt, + user: { + id: issued.userId, + username: issued.username, + displayName: issued.displayName + } + } + }; + }, false), + + defineRoute('POST', '/api/auth/logout', async (ctx): Promise => { + if (ctx.request.bearerToken) { + revokeToken(ctx.request.bearerToken); + } + + return { status: 204, body: null }; + }, true), + + defineRoute('GET', '/api/profile', async (ctx): Promise => { + const user = await runQuery(ctx.dataSource, { + type: QueryType.GetCurrentUser, + payload: {} + }); + + if (!user) { + throw new HttpError(404, 'No current user is set on this device', 'NO_CURRENT_USER'); + } + + return { status: 200, body: user }; + }, true), + + defineRoute('GET', '/api/rooms', async (ctx): Promise => { + const rooms = await runQuery(ctx.dataSource, { + type: QueryType.GetAllRooms, + payload: {} + }); + + return { status: 200, body: rooms ?? [] }; + }, true), + + defineRoute('GET', '/api/rooms/{roomId}/messages', async (ctx): Promise => { + const roomId = ctx.request.url.pathname.match(/\/api\/rooms\/([^/]+)\/messages$/u)?.[1]; + + if (!roomId) + throw new HttpError(400, 'roomId is required', 'INVALID_REQUEST'); + + const limit = clampInt(ctx.request.url.searchParams.get('limit'), 1, 500, 100); + const offset = clampInt(ctx.request.url.searchParams.get('offset'), 0, Number.MAX_SAFE_INTEGER, 0); + + const messages = await runQuery(ctx.dataSource, { + type: QueryType.GetMessages, + payload: { roomId: decodeURIComponent(roomId), limit, offset } + }); + + return { status: 200, body: messages ?? [] }; + }, true) +]; + +export interface RoutingResult { + match: RouteMatch | null; + methodNotAllowed: boolean; +} + +export function matchRoute(method: string, pathname: string): RoutingResult { + let methodNotAllowed = false; + + for (const route of ROUTES) { + const result = route.pattern.exec(pathname); + + if (!result) + continue; + + if (route.method !== method) { + methodNotAllowed = true; + continue; + } + + const params: Record = {}; + + for (let index = 0; index < route.paramKeys.length; index++) { + params[route.paramKeys[index]] = result[index + 1]; + } + + return { + match: { handler: route.handler, params, requiresAuth: route.requiresAuth }, + methodNotAllowed: false + }; + } + + return { match: null, methodNotAllowed }; +} + +export function authenticate(token: string | null): IssuedToken | null { + if (!token) + return null; + + return consumeToken(token); +} diff --git a/electron/app/lifecycle.ts b/electron/app/lifecycle.ts index 8621657..2202308 100644 --- a/electron/app/lifecycle.ts +++ b/electron/app/lifecycle.ts @@ -2,6 +2,7 @@ import { app, BrowserWindow } from 'electron'; import { cleanupLinuxScreenShareAudioRouting } from '../audio/linux-screen-share-routing'; import { initializeDesktopUpdater, shutdownDesktopUpdater } from '../update/desktop-updater'; import { synchronizeAutoStartSetting } from './auto-start'; +import { applyLocalApiSettings, stopLocalApiServer } from '../api'; import { initializeDatabase, destroyDatabase, @@ -21,6 +22,14 @@ import { } from '../ipc'; import { startIdleMonitor, stopIdleMonitor } from '../idle/idle-monitor'; +function startLocalApiAfterWindowReady(): void { + setImmediate(() => { + void applyLocalApiSettings().catch((error: unknown) => { + console.error('[LocalApi] Failed to apply settings after window startup:', error); + }); + }); +} + export function registerAppLifecycle(): void { app.whenReady().then(async () => { const dockIconPath = getDockIconPath(); @@ -35,6 +44,7 @@ export function registerAppLifecycle(): void { await synchronizeAutoStartSetting(); initializeDesktopUpdater(); await createWindow(); + startLocalApiAfterWindowReady(); startIdleMonitor(); app.on('activate', () => { @@ -60,6 +70,7 @@ export function registerAppLifecycle(): void { event.preventDefault(); shutdownDesktopUpdater(); stopIdleMonitor(); + await stopLocalApiServer(); await cleanupLinuxScreenShareAudioRouting(); await destroyDatabase(); app.quit(); diff --git a/electron/cqrs/commands/handlers/deletePluginData.ts b/electron/cqrs/commands/handlers/deletePluginData.ts index f7c7071..9b83873 100644 --- a/electron/cqrs/commands/handlers/deletePluginData.ts +++ b/electron/cqrs/commands/handlers/deletePluginData.ts @@ -11,4 +11,4 @@ export async function handleDeletePluginData(command: DeletePluginDataCommand, d scope: payload.scope, serverId: payload.serverId ?? '' }); -} \ No newline at end of file +} diff --git a/electron/cqrs/commands/handlers/savePluginData.ts b/electron/cqrs/commands/handlers/savePluginData.ts index 4b57347..f2b3cec 100644 --- a/electron/cqrs/commands/handlers/savePluginData.ts +++ b/electron/cqrs/commands/handlers/savePluginData.ts @@ -13,4 +13,4 @@ export async function handleSavePluginData(command: SavePluginDataCommand, dataS updatedAt: Date.now(), valueJson: JSON.stringify(payload.value ?? null) }); -} \ No newline at end of file +} diff --git a/electron/cqrs/queries/handlers/getPluginData.ts b/electron/cqrs/queries/handlers/getPluginData.ts index a0a4035..c557b02 100644 --- a/electron/cqrs/queries/handlers/getPluginData.ts +++ b/electron/cqrs/queries/handlers/getPluginData.ts @@ -22,4 +22,4 @@ export async function handleGetPluginData(query: GetPluginDataQuery, dataSource: } catch { return null; } -} \ No newline at end of file +} diff --git a/electron/desktop-settings.ts b/electron/desktop-settings.ts index 041248b..d3319fe 100644 --- a/electron/desktop-settings.ts +++ b/electron/desktop-settings.ts @@ -4,11 +4,20 @@ import * as path from 'path'; export type AutoUpdateMode = 'auto' | 'off' | 'version'; +export interface LocalApiSettings { + enabled: boolean; + port: number; + exposeOnLan: boolean; + scalarEnabled: boolean; + allowedSignalingServers: string[]; +} + export interface DesktopSettings { autoUpdateMode: AutoUpdateMode; autoStart: boolean; closeToTray: boolean; hardwareAcceleration: boolean; + localApi: LocalApiSettings; manifestUrls: string[]; preferredVersion: string | null; vaapiVideoEncode: boolean; @@ -19,11 +28,20 @@ export interface DesktopSettingsSnapshot extends DesktopSettings { restartRequired: boolean; } +const DEFAULT_LOCAL_API_SETTINGS: LocalApiSettings = { + enabled: false, + port: 17878, + exposeOnLan: false, + scalarEnabled: false, + allowedSignalingServers: [] +}; + const DEFAULT_DESKTOP_SETTINGS: DesktopSettings = { autoUpdateMode: 'auto', autoStart: true, closeToTray: true, hardwareAcceleration: true, + localApi: { ...DEFAULT_LOCAL_API_SETTINGS }, manifestUrls: [], preferredVersion: null, vaapiVideoEncode: false @@ -61,6 +79,60 @@ function normalizeManifestUrls(value: unknown): string[] { return manifestUrls; } +function normalizePort(value: unknown, fallback: number): number { + if (typeof value !== 'number' || !Number.isFinite(value)) { + return fallback; + } + + const port = Math.floor(value); + + if (port < 1 || port > 65535) { + return fallback; + } + + return port; +} + +function normalizeAllowedSignalingServers(value: unknown): string[] { + if (!Array.isArray(value)) { + return []; + } + + const urls: string[] = []; + + for (const entry of value) { + if (typeof entry !== 'string') { + continue; + } + + const trimmed = entry.trim().replace(/\/+$/u, ''); + + if (!trimmed || urls.includes(trimmed)) { + continue; + } + + if (!/^https?:\/\//iu.test(trimmed)) { + continue; + } + + urls.push(trimmed); + } + + return urls; +} + +function normalizeLocalApiSettings(value: unknown): LocalApiSettings { + const source = (value && typeof value === 'object') ? value as Partial : {}; + + return { + enabled: typeof source.enabled === 'boolean' ? source.enabled : DEFAULT_LOCAL_API_SETTINGS.enabled, + port: normalizePort(source.port, DEFAULT_LOCAL_API_SETTINGS.port), + exposeOnLan: typeof source.exposeOnLan === 'boolean' ? source.exposeOnLan : DEFAULT_LOCAL_API_SETTINGS.exposeOnLan, + scalarEnabled: typeof source.scalarEnabled === 'boolean' ? source.scalarEnabled : DEFAULT_LOCAL_API_SETTINGS.scalarEnabled, + allowedSignalingServers: normalizeAllowedSignalingServers(source.allowedSignalingServers) + }; +} + export function getDesktopSettingsSnapshot(): DesktopSettingsSnapshot { const storedSettings = readDesktopSettings(); const runtimeHardwareAcceleration = app.isHardwareAccelerationEnabled(); @@ -97,6 +169,7 @@ export function readDesktopSettings(): DesktopSettings { hardwareAcceleration: typeof parsed.hardwareAcceleration === 'boolean' ? parsed.hardwareAcceleration : DEFAULT_DESKTOP_SETTINGS.hardwareAcceleration, + localApi: normalizeLocalApiSettings(parsed.localApi), manifestUrls: normalizeManifestUrls(parsed.manifestUrls), preferredVersion: normalizePreferredVersion(parsed.preferredVersion) }; @@ -106,9 +179,13 @@ export function readDesktopSettings(): DesktopSettings { } export function updateDesktopSettings(patch: Partial): DesktopSettingsSnapshot { + const previousSettings = readDesktopSettings(); const mergedSettings = { - ...readDesktopSettings(), - ...patch + ...previousSettings, + ...patch, + localApi: patch.localApi + ? { ...previousSettings.localApi, ...patch.localApi } + : previousSettings.localApi }; const nextSettings: DesktopSettings = { autoUpdateMode: normalizeAutoUpdateMode(mergedSettings.autoUpdateMode), @@ -121,6 +198,7 @@ export function updateDesktopSettings(patch: Partial): DesktopS hardwareAcceleration: typeof mergedSettings.hardwareAcceleration === 'boolean' ? mergedSettings.hardwareAcceleration : DEFAULT_DESKTOP_SETTINGS.hardwareAcceleration, + localApi: normalizeLocalApiSettings(mergedSettings.localApi), manifestUrls: normalizeManifestUrls(mergedSettings.manifestUrls), preferredVersion: normalizePreferredVersion(mergedSettings.preferredVersion), vaapiVideoEncode: typeof mergedSettings.vaapiVideoEncode === 'boolean' diff --git a/electron/entities/PluginDataEntity.ts b/electron/entities/PluginDataEntity.ts index 35cfe52..fb09b4e 100644 --- a/electron/entities/PluginDataEntity.ts +++ b/electron/entities/PluginDataEntity.ts @@ -23,4 +23,4 @@ export class PluginDataEntity { @Column('integer') updatedAt!: number; -} \ No newline at end of file +} diff --git a/electron/ipc/system.ts b/electron/ipc/system.ts index 4f44ea3..cbf335f 100644 --- a/electron/ipc/system.ts +++ b/electron/ipc/system.ts @@ -18,6 +18,10 @@ import { updateDesktopSettings, type DesktopSettings } from '../desktop-settings'; +import { + applyLocalApiSettings, + getLocalApiSnapshot +} from '../api'; import { activateLinuxScreenShareAudioRouting, deactivateLinuxScreenShareAudioRouting, @@ -452,9 +456,27 @@ export function setupSystemHandlers(): void { await synchronizeAutoStartSetting(snapshot.autoStart); updateCloseToTraySetting(snapshot.closeToTray); await handleDesktopSettingsChanged(); + await applyLocalApiSettings(); return snapshot; }); + ipcMain.handle('get-local-api-status', () => getLocalApiSnapshot()); + + ipcMain.handle('open-local-api-docs', async () => { + const snapshot = getLocalApiSnapshot(); + + if (snapshot.status !== 'running' || !snapshot.baseUrl) { + return { opened: false, reason: 'Local API is not running' }; + } + + if (!snapshot.scalarEnabled) { + return { opened: false, reason: 'Scalar docs are disabled' }; + } + + await shell.openExternal(`${snapshot.baseUrl}/docs`); + return { opened: true }; + }); + ipcMain.handle('relaunch-app', () => { app.relaunch(); app.exit(0); diff --git a/electron/migrations/1000000000008-AddPluginData.ts b/electron/migrations/1000000000008-AddPluginData.ts index 7275cfe..fbcbe6a 100644 --- a/electron/migrations/1000000000008-AddPluginData.ts +++ b/electron/migrations/1000000000008-AddPluginData.ts @@ -22,4 +22,4 @@ export class AddPluginData1000000000008 implements MigrationInterface { await queryRunner.query(`DROP INDEX IF EXISTS "idx_plugin_data_plugin_scope"`); await queryRunner.query(`DROP TABLE IF EXISTS "plugin_data"`); } -} \ No newline at end of file +} diff --git a/electron/preload.ts b/electron/preload.ts index 3b96d27..bc2a034 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -119,6 +119,26 @@ export interface LocalPluginManifestDescriptor { readmePath?: string; } +export interface LocalApiSettings { + enabled: boolean; + port: number; + exposeOnLan: boolean; + scalarEnabled: boolean; + allowedSignalingServers: string[]; +} + +export type LocalApiStatus = 'stopped' | 'starting' | 'running' | 'error'; + +export interface LocalApiSnapshot { + status: LocalApiStatus; + host: string | null; + port: number | null; + baseUrl: string | null; + error: string | null; + exposeOnLan: boolean; + scalarEnabled: boolean; +} + export interface LocalPluginDiscoveryError { manifestPath?: string; message: string; @@ -215,10 +235,12 @@ export interface ElectronAPI { autoStart: boolean; closeToTray: boolean; hardwareAcceleration: boolean; + localApi: LocalApiSettings; manifestUrls: string[]; preferredVersion: string | null; runtimeHardwareAcceleration: boolean; restartRequired: boolean; + vaapiVideoEncode: boolean; }>; showDesktopNotification: (payload: DesktopNotificationPayload) => Promise; requestWindowAttention: () => Promise; @@ -235,6 +257,7 @@ export interface ElectronAPI { autoStart?: boolean; closeToTray?: boolean; hardwareAcceleration?: boolean; + localApi?: Partial; manifestUrls?: string[]; preferredVersion?: string | null; vaapiVideoEncode?: boolean; @@ -243,11 +266,15 @@ export interface ElectronAPI { autoStart: boolean; closeToTray: boolean; hardwareAcceleration: boolean; + localApi: LocalApiSettings; manifestUrls: string[]; preferredVersion: string | null; runtimeHardwareAcceleration: boolean; restartRequired: boolean; + vaapiVideoEncode: boolean; }>; + getLocalApiStatus: () => Promise; + openLocalApiDocs: () => Promise<{ opened: boolean; reason?: string }>; relaunchApp: () => Promise; onDeepLinkReceived: (listener: (url: string) => void) => () => void; readClipboardFiles: () => Promise; @@ -357,6 +384,8 @@ const electronAPI: ElectronAPI = { }; }, setDesktopSettings: (patch) => ipcRenderer.invoke('set-desktop-settings', patch), + getLocalApiStatus: () => ipcRenderer.invoke('get-local-api-status'), + openLocalApiDocs: () => ipcRenderer.invoke('open-local-api-docs'), relaunchApp: () => ipcRenderer.invoke('relaunch-app'), onDeepLinkReceived: (listener) => { const wrappedListener = (_event: Electron.IpcRendererEvent, url: string) => { diff --git a/package-lock.json b/package-lock.json index 6047d28..5c77629 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "@ngrx/entity": "^21.0.1", "@ngrx/store": "^21.0.1", "@ngrx/store-devtools": "^21.0.1", + "@scalar/api-reference": "^1.53.1", "@spartan-ng/brain": "^0.0.1-alpha.589", "@spartan-ng/cli": "^0.0.1-alpha.589", "@spartan-ng/ui-core": "^0.0.1-alpha.380", @@ -86,6 +87,69 @@ "wait-on": "^7.2.0" } }, + "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/@algolia/abtesting": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.14.1.tgz", @@ -1100,13 +1164,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", - "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -1412,12 +1476,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.28.5" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -2646,9 +2710,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -2746,6 +2810,38 @@ "@lezer/css": "^1.1.7" } }, + "node_modules/@codemirror/lang-html": { + "version": "6.4.11", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz", + "integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/css": "^1.1.0", + "@lezer/html": "^1.3.12" + } + }, + "node_modules/@codemirror/lang-javascript": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.5.tgz", + "integrity": "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/javascript": "^1.0.0" + } + }, "node_modules/@codemirror/lang-json": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz", @@ -2756,6 +2852,35 @@ "@lezer/json": "^1.0.0" } }, + "node_modules/@codemirror/lang-xml": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.1.0.tgz", + "integrity": "sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/xml": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-yaml": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.3.tgz", + "integrity": "sha512-AZ8DJBuXGVHybpBQhmZtgew5//4hv3tdkXnr3vDmOUMJRuB6vn/uuwtmTOTlqEaQFg3hQSVeA90NmvIQyUV6FQ==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.2.0", + "@lezer/lr": "^1.0.0", + "@lezer/yaml": "^1.0.0" + } + }, "node_modules/@codemirror/language": { "version": "6.12.3", "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.3.tgz", @@ -4952,6 +5077,80 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/core/node_modules/@floating-ui/utils": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "license": "MIT" + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/dom/node_modules/@floating-ui/utils": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "license": "MIT" + }, + "node_modules/@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/@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/@gar/promise-retry": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@gar/promise-retry/-/promise-retry-1.0.2.tgz", @@ -4999,6 +5198,33 @@ "@hapi/hoek": "^9.0.0" } }, + "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/@hono/node-server": { "version": "1.19.11", "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", @@ -5449,6 +5675,24 @@ } } }, + "node_modules/@internationalized/date": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.1.tgz", + "integrity": "sha512-6IedsVWXyq4P9Tj+TxuU8WGWM70hYLl12nbYU8jkikVpa6WXapFazPUcHUMDMoWftIDE2ILDkFFte6W2nFCkRQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/number": { + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.6.tgz", + "integrity": "sha512-iFgmQaXHE0vytNfpLZWOC2mEJCBRzcUxt53Xf/yCXG93lRvqas237i3r7X4RKMwO3txiyZD4mQjKAByFv6UGSQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -5826,6 +6070,28 @@ "@lezer/common": "^1.3.0" } }, + "node_modules/@lezer/html": { + "version": "1.3.13", + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.13.tgz", + "integrity": "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/javascript": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz", + "integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" + } + }, "node_modules/@lezer/json": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz", @@ -5846,6 +6112,28 @@ "@lezer/common": "^1.0.0" } }, + "node_modules/@lezer/xml": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.6.tgz", + "integrity": "sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/yaml": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@lezer/yaml/-/yaml-1.0.4.tgz", + "integrity": "sha512-2lrrHqxalACEbxIbsjhqGpSW8kWpUKuY6RHgnSAFZa6qK62wvnPxA8hGOwOoDbwHcOFs5M4o27mjGu+P7TvBmw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.4.0" + } + }, "node_modules/@listr2/prompt-adapter-inquirer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-3.0.5.tgz", @@ -9004,6 +9292,15 @@ "node": ">=12" } }, + "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/@oxc-project/types": { "version": "0.96.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.96.0.tgz", @@ -9343,6 +9640,12 @@ "typescript": "^3 || ^4 || ^5" } }, + "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/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -9382,6 +9685,17 @@ "node": ">=18" } }, + "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/binding-android-arm64": { "version": "1.0.0-beta.47", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.47.tgz", @@ -10233,6 +10547,565 @@ } } }, + "node_modules/@scalar/agent-chat": { + "version": "0.10.10", + "resolved": "https://registry.npmjs.org/@scalar/agent-chat/-/agent-chat-0.10.10.tgz", + "integrity": "sha512-UYyjq6VfWzTPm1hXykyvI/F3oLjrmy2cWG9fdAc5d8XDRMeHo87s62wC7sCsupKqDfyNYxbeaUT0RLx2iXPntQ==", + "license": "MIT", + "dependencies": { + "@ai-sdk/vue": "3.0.33", + "@scalar/api-client": "3.3.1", + "@scalar/components": "0.22.5", + "@scalar/helpers": "0.5.3", + "@scalar/icons": "0.7.2", + "@scalar/json-magic": "0.12.9", + "@scalar/openapi-types": "0.8.0", + "@scalar/themes": "0.15.3", + "@scalar/types": "0.9.3", + "@scalar/use-toasts": "0.10.2", + "@scalar/workspace-store": "0.47.1", + "@vueuse/core": "13.9.0", + "ai": "6.0.33", + "js-base64": "^3.7.8", + "neverpanic": "0.0.7", + "truncate-json": "3.0.1", + "vue": "^3.5.30", + "zod": "^4.3.5" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/api-client": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@scalar/api-client/-/api-client-3.3.1.tgz", + "integrity": "sha512-baqrMM9T7uToNILwnqVolutDFN1YKwnErVj9+TriEwNJ3X1McVxh1qd8zCFs30/dB30hxgQWYFixdm0Zlf+NZw==", + "license": "MIT", + "dependencies": { + "@headlessui/tailwindcss": "^0.2.2", + "@headlessui/vue": "1.7.23", + "@scalar/components": "0.22.5", + "@scalar/helpers": "0.5.3", + "@scalar/icons": "0.7.2", + "@scalar/json-magic": "0.12.9", + "@scalar/oas-utils": "0.13.4", + "@scalar/openapi-types": "0.8.0", + "@scalar/postman-to-openapi": "0.7.2", + "@scalar/sidebar": "0.9.6", + "@scalar/snippetz": "0.9.3", + "@scalar/themes": "0.15.3", + "@scalar/typebox": "^0.1.3", + "@scalar/types": "0.9.3", + "@scalar/use-codemirror": "0.14.11", + "@scalar/use-hooks": "0.4.3", + "@scalar/use-toasts": "0.10.2", + "@scalar/validation": "0.3.0", + "@scalar/workspace-store": "0.47.1", + "@types/har-format": "^1.2.16", + "@vueuse/core": "13.9.0", + "@vueuse/integrations": "13.9.0", + "cookie": "1.1.1", + "focus-trap": "^7.8.0", + "fuse.js": "^7.1.0", + "js-base64": "^3.7.8", + "monaco-editor": "0.55.1", + "monaco-yaml": "5.4.1", + "nanoid": "^5.1.6", + "pretty-ms": "^9.3.0", + "radix-vue": "^1.9.17", + "set-cookie-parser": "3.1.0", + "shell-quote": "^1.8.3", + "vite-plugin-monaco-editor": "^1.1.0", + "vue": "^3.5.30", + "vue-router": "5.0.4", + "yaml": "^2.8.0", + "zod": "^4.3.5" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/api-client/node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@scalar/api-client/node_modules/nanoid": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.9.tgz", + "integrity": "sha512-ZUvP7KeBLe3OZ1ypw6dI/TzYJuvHP77IM4Ry73waSQTLn8/g8rpdjfyVAh7t1/+FjBtG4lCP42MEbDxOsRpBMw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@scalar/api-reference": { + "version": "1.53.1", + "resolved": "https://registry.npmjs.org/@scalar/api-reference/-/api-reference-1.53.1.tgz", + "integrity": "sha512-4z2UMrVx3EWeSjOzRA9tNonRL1NpJsd9w798HwaAxP+1rfpaBbjUjcqu0fP0eE2OfYCF4S1wS0libRAQdbhrHg==", + "license": "MIT", + "dependencies": { + "@headlessui/vue": "1.7.23", + "@scalar/agent-chat": "0.10.10", + "@scalar/api-client": "3.3.1", + "@scalar/code-highlight": "0.3.4", + "@scalar/components": "0.22.5", + "@scalar/helpers": "0.5.3", + "@scalar/icons": "0.7.2", + "@scalar/oas-utils": "0.13.4", + "@scalar/sidebar": "0.9.6", + "@scalar/snippetz": "0.9.3", + "@scalar/themes": "0.15.3", + "@scalar/types": "0.9.3", + "@scalar/use-hooks": "0.4.3", + "@scalar/use-toasts": "0.10.2", + "@scalar/workspace-store": "0.47.1", + "@unhead/vue": "^2.1.4", + "@vueuse/core": "13.9.0", + "fuse.js": "^7.1.0", + "github-slugger": "2.0.0", + "microdiff": "^1.5.0", + "nanoid": "^5.1.6", + "vue": "^3.5.30", + "yaml": "^2.8.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/api-reference/node_modules/nanoid": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.9.tgz", + "integrity": "sha512-ZUvP7KeBLe3OZ1ypw6dI/TzYJuvHP77IM4Ry73waSQTLn8/g8rpdjfyVAh7t1/+FjBtG4lCP42MEbDxOsRpBMw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@scalar/code-highlight": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@scalar/code-highlight/-/code-highlight-0.3.4.tgz", + "integrity": "sha512-gGr3D8bfInwZDHsxamYIaG72Wr+kRNX8d4zcOflAfQJ0ZfvqoVbYhhkiSd6K+DLySItK9lWl/cgjJdtKWlT2ig==", + "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.22.5", + "resolved": "https://registry.npmjs.org/@scalar/components/-/components-0.22.5.tgz", + "integrity": "sha512-Mjg8mGJdXZN+FTCrIwGvDkqrCMgSr3155dQrQX5AeECUNpfR+aNcoWq/I/i+9ws1Jrkz6pQw0GokCHrOI58ZDw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "0.2.10", + "@floating-ui/vue": "1.1.9", + "@headlessui/vue": "1.7.23", + "@scalar/code-highlight": "0.3.4", + "@scalar/helpers": "0.5.3", + "@scalar/icons": "0.7.2", + "@scalar/themes": "0.15.3", + "@scalar/use-hooks": "0.4.3", + "@vueuse/core": "13.9.0", + "cva": "1.0.0-beta.4", + "nanoid": "^5.1.6", + "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/nanoid": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.9.tgz", + "integrity": "sha512-ZUvP7KeBLe3OZ1ypw6dI/TzYJuvHP77IM4Ry73waSQTLn8/g8rpdjfyVAh7t1/+FjBtG4lCP42MEbDxOsRpBMw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@scalar/helpers": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@scalar/helpers/-/helpers-0.5.3.tgz", + "integrity": "sha512-PgQmhuV0oRoHtaqH0OhyCcSY9t35qm8ThNeuUMEAKeN+hW1ijBnJiUADpxaIfXPbLrpN9sjyYza0A16WFbLttg==", + "license": "MIT", + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/icons": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@scalar/icons/-/icons-0.7.2.tgz", + "integrity": "sha512-21L2y/D6oU7wZHHa9i6FK98cZ+XH4HX9+e69uNpvlp4awRUpz6ifNHOLlxI607bq+Yz4G313gnV0uyUHwZ/pig==", + "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.12.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", + "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@scalar/json-magic": { + "version": "0.12.9", + "resolved": "https://registry.npmjs.org/@scalar/json-magic/-/json-magic-0.12.9.tgz", + "integrity": "sha512-wabHE3heo0usLlneDeOjMNs2ES8bREJ3ySc2WPiHIXdzAmy+ERU6g9Al4w3mwgJueOceAkIP6W+yY/DmSCI4uA==", + "license": "MIT", + "dependencies": { + "@scalar/helpers": "0.5.3", + "pathe": "^2.0.3", + "yaml": "^2.8.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/oas-utils": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/@scalar/oas-utils/-/oas-utils-0.13.4.tgz", + "integrity": "sha512-kAtxCbs+JMVKmpuvzMVXCxBchvpp/l0VNLvNGtkLAbddccG4aqi0d10TZ8n9IQTdy/AASPa/2p5IHAj9W/frgQ==", + "license": "MIT", + "dependencies": { + "@scalar/helpers": "0.5.3", + "@scalar/themes": "0.15.3", + "@scalar/types": "0.9.3", + "@scalar/workspace-store": "0.47.1", + "flatted": "^3.4.0", + "github-slugger": "2.0.0", + "vue": "^3.5.30", + "yaml": "^2.8.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/openapi-types": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@scalar/openapi-types/-/openapi-types-0.8.0.tgz", + "integrity": "sha512-WmaxVSfvY5K/TwcG2B2TU1WOe1As1uc2s7myswtP6dBlcjU3hM08SApxv/jmyGaCE8t4gO5BBhmHY4pDUfmr2g==", + "license": "MIT", + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/openapi-upgrader": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@scalar/openapi-upgrader/-/openapi-upgrader-0.2.6.tgz", + "integrity": "sha512-pvEmfSCDNYR4+lygidUqfo+shzyp4OSh9+UgK110rzA8Oot6WbJBM03Fuq3M255G7G6R9iXyfsebB7MBUocPkw==", + "license": "MIT", + "dependencies": { + "@scalar/openapi-types": "0.8.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/postman-to-openapi": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@scalar/postman-to-openapi/-/postman-to-openapi-0.7.2.tgz", + "integrity": "sha512-+nZ8xLYuudqmDkY0X2rGX3BGOwshGA/4UpG5KvP5s+H+5cnH1IhTB5QL0nb4doqClPjLN2lBxb3AI53TQXutGQ==", + "license": "MIT", + "dependencies": { + "@scalar/helpers": "0.5.3", + "@scalar/openapi-types": "0.8.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/sidebar": { + "version": "0.9.6", + "resolved": "https://registry.npmjs.org/@scalar/sidebar/-/sidebar-0.9.6.tgz", + "integrity": "sha512-RSzUl1U3eI8QPU2AgGFvv+RWPzTdfKdZyIau4ggjzY2pvaK0MketuybpBV5sqlGnhMesAyUYItEdkBf3gui4VQ==", + "license": "MIT", + "dependencies": { + "@scalar/components": "0.22.5", + "@scalar/helpers": "0.5.3", + "@scalar/icons": "0.7.2", + "@scalar/themes": "0.15.3", + "@scalar/use-hooks": "0.4.3", + "@scalar/workspace-store": "0.47.1", + "vue": "^3.5.30" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/snippetz": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@scalar/snippetz/-/snippetz-0.9.3.tgz", + "integrity": "sha512-y9a/Kyw5DOIv2QxA3KBjKL49px8fS1KPoNf3og7/ok1L3xs26tUh1KsCdPntHnYnMyVdeiuNv0S/4wME7bsTlQ==", + "license": "MIT", + "dependencies": { + "@scalar/types": "0.9.3", + "js-base64": "^3.7.8", + "stringify-object": "^6.0.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/themes": { + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/@scalar/themes/-/themes-0.15.3.tgz", + "integrity": "sha512-KIGMVglWKxVcdPsdjiXDgyAYhCh53w0qoKRG/cmfP+N4OwR0pk0WzFaMzBscu+sKoZ8SMvZqbXyODO5CBtyD3w==", + "license": "MIT", + "dependencies": { + "nanoid": "^5.1.6" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/themes/node_modules/nanoid": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.9.tgz", + "integrity": "sha512-ZUvP7KeBLe3OZ1ypw6dI/TzYJuvHP77IM4Ry73waSQTLn8/g8rpdjfyVAh7t1/+FjBtG4lCP42MEbDxOsRpBMw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "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.9.3", + "resolved": "https://registry.npmjs.org/@scalar/types/-/types-0.9.3.tgz", + "integrity": "sha512-/cEFjVa8PxRIDyhcWKh7McT8pm5O0kbafzd1jvpVq69sgIIq0gJ0P1sCcPye6qJ2k478PK7VmpK9FxZcr6D4Kw==", + "license": "MIT", + "dependencies": { + "@scalar/helpers": "0.5.3", + "nanoid": "^5.1.6", + "type-fest": "^5.3.1", + "zod": "^4.3.5" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/types/node_modules/nanoid": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.9.tgz", + "integrity": "sha512-ZUvP7KeBLe3OZ1ypw6dI/TzYJuvHP77IM4Ry73waSQTLn8/g8rpdjfyVAh7t1/+FjBtG4lCP42MEbDxOsRpBMw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@scalar/types/node_modules/type-fest": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.6.0.tgz", + "integrity": "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==", + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@scalar/use-codemirror": { + "version": "0.14.11", + "resolved": "https://registry.npmjs.org/@scalar/use-codemirror/-/use-codemirror-0.14.11.tgz", + "integrity": "sha512-5wtC4pUjzhy72j3aAueJg+fh9KflevJvXMn0YscsxiDTvqwIzeZcHe1N9VNtvzDXgLblEeBT6D0+Vs+boyExxg==", + "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.3", + "resolved": "https://registry.npmjs.org/@scalar/use-hooks/-/use-hooks-0.4.3.tgz", + "integrity": "sha512-dhDWGwqtiVshrAv/bpJ9qPt2Mdbbqyqvtvl2Fau+S9iv7Trsc2XDbfBc40cckSj6EhajgR4EHiuCR0E4DyaveQ==", + "license": "MIT", + "dependencies": { + "@scalar/use-toasts": "0.10.2", + "@vueuse/core": "13.9.0", + "cva": "1.0.0-beta.4", + "tailwind-merge": "3.5.0", + "vue": "^3.5.30", + "zod": "^4.3.5" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/use-hooks/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/@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.3.0", + "resolved": "https://registry.npmjs.org/@scalar/validation/-/validation-0.3.0.tgz", + "integrity": "sha512-4X/AP3JO23DuYxs1MMjn6IlT9gyrKPCuZj8ybTB9QIjC+3tSJLpQOwZg7HEyyz2HoVwOt9jdef2jO3RXW7DqTw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@scalar/workspace-store": { + "version": "0.47.1", + "resolved": "https://registry.npmjs.org/@scalar/workspace-store/-/workspace-store-0.47.1.tgz", + "integrity": "sha512-Qo1jzKQtYwm4kYdTb0HtbdIpsAKtkG19DX/Jy20ZvRk+OshyC0e/YLkHAAEjII89HW8VdpJcIem6fLYbBk1XgQ==", + "license": "MIT", + "dependencies": { + "@scalar/helpers": "0.5.3", + "@scalar/json-magic": "0.12.9", + "@scalar/openapi-upgrader": "0.2.6", + "@scalar/snippetz": "0.9.3", + "@scalar/typebox": "0.1.3", + "@scalar/types": "0.9.3", + "@scalar/validation": "0.3.0", + "github-slugger": "2.0.0", + "js-base64": "^3.7.8", + "type-fest": "^5.3.1", + "vue": "^3.5.30", + "yaml": "^2.8.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/workspace-store/node_modules/type-fest": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.6.0.tgz", + "integrity": "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==", + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@schematics/angular": { "version": "21.2.1", "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-21.2.1.tgz", @@ -10908,6 +11781,15 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@swc/helpers": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.21.tgz", + "integrity": "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, "node_modules/@szmarczak/http-timer": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", @@ -10921,6 +11803,32 @@ "node": ">=10" } }, + "node_modules/@tanstack/virtual-core": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.14.0.tgz", + "integrity": "sha512-JLANqGy/D6k4Ujmh8Tr25lGimuOXNiaVyXaCAZS0W+1390sADdGnyUdSWNIfd49gebtIxGMij4IktRVzrdr12Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/vue-virtual": { + "version": "3.13.24", + "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.24.tgz", + "integrity": "sha512-A0k2qF0zFSUStXSZkGXABouXr2Tw2Ztl/cVIYG9qy84uR8W7UNjAcX3DvzBS3YnDcwvLxab8v7dbmYBZ39itDA==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.14.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "vue": "^2.7.0 || ^3.0.0" + } + }, "node_modules/@timephy/rnnoise-wasm": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@timephy/rnnoise-wasm/-/rnnoise-wasm-1.0.0.tgz", @@ -11434,6 +12342,21 @@ "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", "license": "MIT" }, + "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", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/http-cache-semantics": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", @@ -11680,6 +12603,12 @@ "license": "MIT", "optional": true }, + "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/ws": { "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", @@ -12310,6 +13239,37 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/@unhead/vue": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@unhead/vue/-/vue-2.1.13.tgz", + "integrity": "sha512-HYy0shaHRnLNW9r85gppO8IiGz0ONWVV3zGdlT8CQ0tbTwixznJCIiyqV4BSV1aIF1jJIye0pd1p/k6Eab8Z/A==", + "license": "MIT", + "dependencies": { + "hookable": "^6.0.1", + "unhead": "2.1.13" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + }, + "peerDependencies": { + "vue": ">=3.5.18" + } + }, + "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/@vitejs/plugin-basic-ssl": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.1.0.tgz", @@ -12463,6 +13423,303 @@ "dev": true, "license": "MIT" }, + "node_modules/@vue-macros/common": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-3.1.2.tgz", + "integrity": "sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng==", + "license": "MIT", + "dependencies": { + "@vue/compiler-sfc": "^3.5.22", + "ast-kit": "^2.1.2", + "local-pkg": "^1.1.2", + "magic-string-ast": "^1.0.2", + "unplugin-utils": "^0.3.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/vue-macros" + }, + "peerDependencies": { + "vue": "^2.7.0 || ^3.2.25" + }, + "peerDependenciesMeta": { + "vue": { + "optional": true + } + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.33", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.33.tgz", + "integrity": "sha512-3PZLQwFw4Za3TC8t0FvTy3wI16Kt+pmwcgNZca4Pj9iWL2E72a/gZlpBtAJvEdDMdCxdG/qq0C7PN0bsJuv0Rw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.2", + "@vue/shared": "3.5.33", + "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-core/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/@vue/compiler-dom": { + "version": "3.5.33", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.33.tgz", + "integrity": "sha512-PXq0yrfCLzzL07rbXO4awtXY1Z06LG2eu6Adg3RJFa/j3Cii217XxxLXG22N330gw7GmALCY0Z8RgXEviwgpjA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.33", + "@vue/shared": "3.5.33" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.33", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.33.tgz", + "integrity": "sha512-UTUvRO9cY+rROrx/pvN9P5Z7FgA6QGfokUCfhQE4EnmUj3rVnK+CHI0LsEO1pg+I7//iRYMUfcNcCPe7tg0CoA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.2", + "@vue/compiler-core": "3.5.33", + "@vue/compiler-dom": "3.5.33", + "@vue/compiler-ssr": "3.5.33", + "@vue/shared": "3.5.33", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.10", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-sfc/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/@vue/compiler-sfc/node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.33", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.33.tgz", + "integrity": "sha512-IErjYdnj1qIupG5xxiVIYiiRvDhGWV4zuh/RCrwfYpuL+HWQzeU6lCk/nF9r7olWMnjKxCAkOctT2qFWFkzb1A==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.33", + "@vue/shared": "3.5.33" + } + }, + "node_modules/@vue/devtools-api": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.1.1.tgz", + "integrity": "sha512-bsDMJ07b3GN1puVwJb/fyFnj/U2imyswK5UQVLZwVl7O05jDrt6BHxeG5XffmOOdasOj/bOmIjxJvGPxU7pcqw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^8.1.1" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.1.1.tgz", + "integrity": "sha512-gVBaBv++i+adg4JpH71k9ppl4soyR7Y2McEqO5YNgv0BI1kMZ7BDX5gnwkZ5COYgiCyhejZG+yGNrBAjj6Coqg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^8.1.1", + "birpc": "^2.6.1", + "hookable": "^5.5.3", + "perfect-debounce": "^2.0.0" + } + }, + "node_modules/@vue/devtools-kit/node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/@vue/devtools-shared": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.1.1.tgz", + "integrity": "sha512-+h4ttmJYl/txpxHKaoZcaKpC+pvckgLzIDiSQlaQ7kKthKh8KuwoLW2D8hPJEnqKzXOvu15UHEoGyngAXCz0EQ==", + "license": "MIT" + }, + "node_modules/@vue/reactivity": { + "version": "3.5.33", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.33.tgz", + "integrity": "sha512-p8UfIqyIhb0rYGlSgSBV+lPhF2iUSBcRy7enhTmPqKWadHy9kcOFYF1AejYBP9P+avnd3OBbD49DU4pLWX/94A==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.33" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.33", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.33.tgz", + "integrity": "sha512-UpFF45RI9//a7rvq7RdOQblb4tup7hHG9QsmIrxkFQLzQ7R8/iNQ5LE15NhLZ1/WcHMU2b47u6P33CPUelHyIQ==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.33", + "@vue/shared": "3.5.33" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.33", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.33.tgz", + "integrity": "sha512-IOxMsAOwquhfITgmOgaPYl7/j8gKUxUFoflRc+u4LxyD3+783xne8vNta1PONVCvCV9A0w7hkyEepINDqfO0tw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.33", + "@vue/runtime-core": "3.5.33", + "@vue/shared": "3.5.33", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.33", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.33.tgz", + "integrity": "sha512-0xylq/8/h44lVG0pZFknv1XIdEgymq2E9n59uTWJBG+dIgiT0TMCSsxrN7nO16Z0MU0MPjFcguBbZV8Itk52Hw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.33", + "@vue/shared": "3.5.33" + }, + "peerDependencies": { + "vue": "3.5.33" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.33", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.33.tgz", + "integrity": "sha512-5vR2QIlmaLG77Ygd4pMP6+SGQ5yox9VhtnbDWTy9DzMzdmeLxZ1QqxrywEZ9sa1AVubfIJyaCG3ytyWU81ufcQ==", + "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/@webassemblyjs/ast": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", @@ -12809,6 +14066,24 @@ "node": ">=8" } }, + "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": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", @@ -13262,6 +14537,18 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/aria-query": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", @@ -13311,6 +14598,38 @@ "node": ">=12" } }, + "node_modules/ast-kit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-2.2.0.tgz", + "integrity": "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "pathe": "^2.0.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/ast-walker-scope": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/ast-walker-scope/-/ast-walker-scope-0.8.3.tgz", + "integrity": "sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.4", + "ast-kit": "^2.1.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -13654,6 +14973,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -14239,6 +15567,26 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chardet": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", @@ -14616,6 +15964,16 @@ "node": ">= 0.8" } }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", @@ -14958,6 +16316,18 @@ "node": ">= 0.6" } }, + "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": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", @@ -15476,6 +16846,32 @@ "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", "license": "CC0-1.0" }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "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/cytoscape": { "version": "3.33.1", "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", @@ -16227,6 +17623,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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/delaunator": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", @@ -17757,7 +19159,6 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", - "dev": true, "license": "MIT", "engines": { "node": ">=18.0.0" @@ -17865,6 +19266,12 @@ "express": ">= 4.11" } }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "license": "MIT" + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -18187,11 +19594,20 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "license": "ISC" }, + "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.15.11", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", @@ -18671,6 +20087,31 @@ "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.3.0", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.3.0.tgz", + "integrity": "sha512-plz8RVjfcDedTGfVngWH1jmJvBvAwi1v2jecfDerbEnMcmOYUEEwKFTHbNoCiYyzaK2Ws8lABkTCcRSqCY1q4w==", + "license": "Apache-2.0", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/krisk" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -18731,6 +20172,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-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -18767,6 +20220,12 @@ "dev": true, "license": "MIT" }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "license": "ISC" + }, "node_modules/glob": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", @@ -19007,6 +20466,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/hachure-fill": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", @@ -19089,6 +20557,339 @@ "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-html/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/hast-util-from-html/node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/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-raw/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/hast-util-raw/node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/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-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", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "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", @@ -19098,6 +20899,15 @@ "he": "bin/he" } }, + "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/homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -19120,6 +20930,12 @@ "node": ">=16.9.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.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", @@ -19213,6 +21029,26 @@ ], "license": "MIT" }, + "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.0.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", @@ -19666,6 +21502,21 @@ "postcss": "^8.1.0" } }, + "node_modules/identifier-regex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/identifier-regex/-/identifier-regex-1.0.1.tgz", + "integrity": "sha512-ZrYyM0sozNPZlvBvE7Oq9Bn44n0qKGrYu5sQ0JzMUnjIhpgWYE2JB6aBoFwEYdPjqj7jPyxXTMJiHDOxDfd8yw==", + "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", @@ -19842,6 +21693,18 @@ "node": ">= 0.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-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -19952,6 +21815,22 @@ "node": ">=0.10.0" } }, + "node_modules/is-identifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-identifier/-/is-identifier-1.0.1.tgz", + "integrity": "sha512-HQ5v4rEJ7REUV54bCd2l5FaD299SGDEn2UPoVXaTHAyGviLq2menVUD2udi3trQ32uvB6LdAh/0ck2EuizrtpA==", + "license": "MIT", + "dependencies": { + "identifier-regex": "^1.0.0", + "super-regex": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-inside-container": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", @@ -20025,6 +21904,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": "3.0.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", @@ -20053,6 +21944,18 @@ "dev": true, "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-typed-array": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", @@ -20378,6 +22281,12 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/js-base64": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", + "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==", + "license": "BSD-3-Clause" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -20424,6 +22333,12 @@ "node": "^20.17.0 || >=22.9.0" } }, + "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": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -21215,6 +23130,40 @@ "node": ">=8.9.0" } }, + "node_modules/local-pkg": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", + "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/local-pkg/node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "license": "MIT" + }, + "node_modules/local-pkg/node_modules/pkg-types": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz", + "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", + "license": "MIT", + "dependencies": { + "confbox": "^0.2.4", + "exsolve": "^1.0.8", + "pathe": "^2.0.3" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -21401,6 +23350,21 @@ "node": ">=8" } }, + "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", @@ -21423,12 +23387,55 @@ "version": "0.30.19", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", - "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/magic-string-ast": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/magic-string-ast/-/magic-string-ast-1.0.3.tgz", + "integrity": "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==", + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.19" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "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-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", @@ -21702,6 +23709,27 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-to-markdown": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", @@ -21841,6 +23869,12 @@ "node": ">= 0.6" } }, + "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", @@ -22764,6 +24798,109 @@ "ufo": "^1.6.1" } }, + "node_modules/monaco-editor": { + "version": "0.55.1", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", + "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", + "license": "MIT", + "dependencies": { + "dompurify": "3.2.7", + "marked": "14.0.0" + } + }, + "node_modules/monaco-editor/node_modules/dompurify": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", + "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/monaco-editor/node_modules/marked": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", + "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/monaco-languageserver-types": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/monaco-languageserver-types/-/monaco-languageserver-types-0.4.0.tgz", + "integrity": "sha512-QQ3BZiU5LYkJElGncSNb5AKoJ/LCs6YBMCJMAz9EA7v+JaOdn3kx2cXpPTcZfKA5AEsR0vc97sAw+5mdNhVBmw==", + "license": "MIT", + "dependencies": { + "monaco-types": "^0.1.0", + "vscode-languageserver-protocol": "^3.0.0", + "vscode-uri": "^3.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/remcohaszing" + } + }, + "node_modules/monaco-marker-data-provider": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/monaco-marker-data-provider/-/monaco-marker-data-provider-1.2.5.tgz", + "integrity": "sha512-5ZdcYukhPwgYMCvlZ9H5uWs5jc23BQ8fFF5AhSIdrz5mvYLsqGZ58ZLxTv8rCX6+AxdJ8+vxg1HVSk+F2bLosg==", + "license": "MIT", + "dependencies": { + "monaco-types": "^0.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/remcohaszing" + } + }, + "node_modules/monaco-types": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/monaco-types/-/monaco-types-0.1.2.tgz", + "integrity": "sha512-8LwfrlWXsedHwAL41xhXyqzPibS8IqPuIXr9NdORhonS495c2/wky+sI1PRLvMCuiI0nqC2NH1six9hdiRY4Xg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/remcohaszing" + } + }, + "node_modules/monaco-worker-manager": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/monaco-worker-manager/-/monaco-worker-manager-2.0.1.tgz", + "integrity": "sha512-kdPL0yvg5qjhKPNVjJoym331PY/5JC11aPJXtCZNwWRvBr6jhkIamvYAyiY5P1AWFmNOy0aRDRoMdZfa71h8kg==", + "license": "MIT", + "peerDependencies": { + "monaco-editor": ">=0.30.0" + } + }, + "node_modules/monaco-yaml": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/monaco-yaml/-/monaco-yaml-5.4.1.tgz", + "integrity": "sha512-YQ6d/Ei98Uk073SJLFbwuSi95qhnl8F8NNmIUqN2XhDt9psZN2LqQ1T7pPQ866NJb2wFj44IrjnANgpa2jTfag==", + "license": "MIT", + "workspaces": [ + "examples/*" + ], + "dependencies": { + "jsonc-parser": "^3.0.0", + "monaco-languageserver-types": "^0.4.0", + "monaco-marker-data-provider": "^1.0.0", + "monaco-types": "^0.1.0", + "monaco-worker-manager": "^2.0.0", + "path-browserify": "^1.0.0", + "prettier": "^3.0.0", + "vscode-languageserver-textdocument": "^1.0.0", + "vscode-languageserver-types": "^3.0.0", + "vscode-uri": "^3.0.0", + "yaml": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/remcohaszing" + }, + "peerDependencies": { + "monaco-editor": ">=0.36" + } + }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", @@ -22814,6 +24951,12 @@ "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" } }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "license": "MIT" + }, "node_modules/multicast-dns": { "version": "7.2.5", "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", @@ -22950,6 +25093,15 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "license": "MIT" }, + "node_modules/neverpanic": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/neverpanic/-/neverpanic-0.0.7.tgz", + "integrity": "sha512-GFRTSX2JAEATOCQYlyFkR+9FJPl0pD24toE1foqYAsL6aPLlRKn6L0UFOtJhZCxEbDv+SUsiW4AcPs9cIFwkFw==", + "license": "MIT", + "peerDependencies": { + "typescript": "5" + } + }, "node_modules/ngx-remark": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/ngx-remark/-/ngx-remark-0.2.2.tgz", @@ -23924,6 +26076,21 @@ "node": ">=8" } }, + "node_modules/p-event": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-6.0.1.tgz", + "integrity": "sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==", + "license": "MIT", + "dependencies": { + "p-timeout": "^6.1.2" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-is-promise": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", @@ -24003,6 +26170,18 @@ "node": ">= 4" } }, + "node_modules/p-timeout": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", + "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -24089,6 +26268,18 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "license": "MIT" }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parse-node-version": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", @@ -24296,6 +26487,12 @@ "dev": true, "license": "MIT" }, + "node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -25014,9 +27211,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", + "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==", "funding": [ { "type": "opencollective", @@ -25796,7 +27993,6 @@ "version": "3.8.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", - "dev": true, "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" @@ -25847,6 +28043,21 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/pretty-ms": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/prismjs": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", @@ -25903,6 +28114,16 @@ "node": ">=10" } }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -25964,6 +28185,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -25997,6 +28234,140 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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/radix-vue/node_modules/nanoid": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.9.tgz", + "integrity": "sha512-ZUvP7KeBLe3OZ1ypw6dI/TzYJuvHP77IM4Ry73waSQTLn8/g8rpdjfyVAh7t1/+FjBtG4lCP42MEbDxOsRpBMw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, "node_modules/rambda": { "version": "9.4.2", "resolved": "https://registry.npmjs.org/rambda/-/rambda-9.4.2.tgz", @@ -26226,6 +28597,97 @@ "regjsparser": "bin/parser" } }, + "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": { "version": "15.0.1", "resolved": "https://registry.npmjs.org/remark/-/remark-15.0.1.tgz", @@ -26291,6 +28753,23 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/remark-stringify": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", @@ -26348,6 +28827,18 @@ "url": "https://github.com/sponsors/jet2jet" } }, + "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.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -27224,6 +29715,12 @@ } } }, + "node_modules/scule": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", + "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", + "license": "MIT" + }, "node_modules/secure-compare": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", @@ -27463,6 +29960,12 @@ "url": "https://opencollective.com/express" } }, + "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-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -27920,6 +30423,16 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/spawn-command": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", @@ -28141,6 +30654,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": "8.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", @@ -28209,6 +30740,38 @@ "node": ">=8" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "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": "7.1.2", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", @@ -28361,6 +30924,23 @@ "node": ">= 8.0" } }, + "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/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -28447,6 +31027,15 @@ "url": "https://github.com/sponsors/fb55" } }, + "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/sync-child-process": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz", @@ -28484,6 +31073,24 @@ "url": "https://opencollective.com/synckit" } }, + "node_modules/tabbable": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", + "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", + "license": "MIT" + }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tailwind-merge": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz", @@ -28910,6 +31517,21 @@ "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "license": "MIT" }, + "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/tiny-async-pool": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/tiny-async-pool/-/tiny-async-pool-1.3.0.tgz", @@ -29079,6 +31701,16 @@ "tree-kill": "cli.js" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/trough": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", @@ -29089,6 +31721,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/truncate-utf8-bytes": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", @@ -29941,6 +32587,18 @@ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "license": "MIT" }, + "node_modules/unhead": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/unhead/-/unhead-2.1.13.tgz", + "integrity": "sha512-jO9M1sI6b2h/1KpIu4Jeu+ptumLmUKboRRLxys5pYHFeT+lqTzfNHbYUX9bxVDhC1FBszAGuWcUVlmvIPsah8Q==", + "license": "MIT", + "dependencies": { + "hookable": "^6.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", @@ -30049,6 +32707,20 @@ "node": "^20.17.0 || >=22.9.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", @@ -30062,6 +32734,19 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unist-util-stringify-position": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", @@ -30122,6 +32807,36 @@ "node": ">= 0.8" } }, + "node_modules/unplugin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.0.0.tgz", + "integrity": "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/unplugin-utils": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.1.tgz", + "integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==", + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, "node_modules/untildify": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.3.tgz", @@ -30276,6 +32991,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", @@ -30365,6 +33094,15 @@ } } }, + "node_modules/vite-plugin-monaco-editor": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vite-plugin-monaco-editor/-/vite-plugin-monaco-editor-1.1.0.tgz", + "integrity": "sha512-IvtUqZotrRoVqwT0PBBDIZPNraya3BxN/bfcNfnxZ5rkJiGcNtO5eAOWWSgT7zullIAEqQwxMU83yL9J5k7gww==", + "license": "MIT", + "peerDependencies": { + "monaco-editor": ">=0.33.0" + } + }, "node_modules/vite/node_modules/@esbuild/aix-ppc64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", @@ -30998,6 +33736,121 @@ "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", "license": "MIT" }, + "node_modules/vue": { + "version": "3.5.33", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.33.tgz", + "integrity": "sha512-1AgChhx5w3ALgT4oK3acm2Es/7jyZhWSVUfs3rOBlGQC0rjEDkS7G4lWlJJGGNQD+BV3reCwbQrOe1mPNwKHBQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.33", + "@vue/compiler-sfc": "3.5.33", + "@vue/runtime-dom": "3.5.33", + "@vue/server-renderer": "3.5.33", + "@vue/shared": "3.5.33" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-component-type-helpers": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-3.2.7.tgz", + "integrity": "sha512-+gPp5YGmhfsj1IN+xUo7y0fb4clfnOiiUA39y07yW1VzCRjzVgwLbtmdWlghh7mXrPsEaYc7rrIir/HT6C8vYQ==", + "license": "MIT" + }, + "node_modules/vue-router": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.0.4.tgz", + "integrity": "sha512-lCqDLCI2+fKVRl2OzXuzdSWmxXFLQRxQbmHugnRpTMyYiT+hNaycV0faqG5FBHDXoYrZ6MQcX87BvbY8mQ20Bg==", + "license": "MIT", + "dependencies": { + "@babel/generator": "^7.28.6", + "@vue-macros/common": "^3.1.1", + "@vue/devtools-api": "^8.0.6", + "ast-walker-scope": "^0.8.3", + "chokidar": "^5.0.0", + "json5": "^2.2.3", + "local-pkg": "^1.1.2", + "magic-string": "^0.30.21", + "mlly": "^1.8.0", + "muggle-string": "^0.4.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "scule": "^1.3.0", + "tinyglobby": "^0.2.15", + "unplugin": "^3.0.0", + "unplugin-utils": "^0.3.1", + "yaml": "^2.8.2" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "@pinia/colada": ">=0.21.2", + "@vue/compiler-sfc": "^3.5.17", + "pinia": "^3.0.4", + "vue": "^3.5.0" + }, + "peerDependenciesMeta": { + "@pinia/colada": { + "optional": true + }, + "@vue/compiler-sfc": { + "optional": true + }, + "pinia": { + "optional": true + } + } + }, + "node_modules/vue-router/node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/vue-router/node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/vue-router/node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "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", @@ -31069,6 +33922,22 @@ "license": "MIT", "optional": true }, + "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", @@ -31679,6 +34548,12 @@ } } }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "license": "MIT" + }, "node_modules/webpack/node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -32224,7 +35099,6 @@ "version": "4.3.6", "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 4809d11..4d3aef1 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "@ngrx/entity": "^21.0.1", "@ngrx/store": "^21.0.1", "@ngrx/store-devtools": "^21.0.1", + "@scalar/api-reference": "^1.53.1", "@spartan-ng/brain": "^0.0.1-alpha.589", "@spartan-ng/cli": "^0.0.1-alpha.589", "@spartan-ng/ui-core": "^0.0.1-alpha.380", @@ -169,6 +170,10 @@ "filter": [ "**/*" ] + }, + { + "from": "node_modules/@scalar/api-reference/dist/browser/standalone.js", + "to": "scalar/api-reference.js" } ], "nodeGypRebuild": false, diff --git a/server/data/metoyou.sqlite b/server/data/metoyou.sqlite index 14a5f57dec35b9ff43339537aa9f53c33226b01f..78ebdb4e7a8a69b744ebc234aa9b44f735631d43 100644 GIT binary patch delta 9847 zcmb7Jdw3Jql^@M$B+ckC4#pv_Lo6GFY(Pe%w+tqw0h~v-un^}(2#lo>7M3L;$>#0x zU>*q}!NGv?AOupAH+k3r=k=v2*<`zv5cszJFeDJ*%eG0AHcd9^Hf@sKb7$sCHtn{~ zA0Geiy}x_Tx#yg@ntN{7KCbHxuIC2^$IApk%)HM&lXSfN!qUEJV~mzf1h>idt!=gS zJ8QR9Z`m|ZRg$tu>y5^yak907_6L1of5;bc`2;cG^7$gXt2ylET_IkK`h<|XSqyl8 z+#ZdJA#Yd|_@Lh%@y9~mW_Qdj26$gE<`qMMW`E4fH}jD|%)kY=Ha@kr=_s)M|;sD?9pQO!byGU?mZZY>9H)OjwQ1ZhQ zwtSCGr>`B2iN+eLNrztJUBNwP>#<(TEoK`me_~&< zEn~OaUN%2#_FC4OH(3@iX-lp7cH2zw>g9}Lt`u|of}v=0vnv>jMqNHJ=y8Q3A-5|W zj)5-&LBTJErXX`Yv7kQ?fDyMC@c@wzx|#!iw<{KoxxId`7>)|!WJL7&!S|jh3QEB3 z@_G5NE8HB2xoSmqf(!2dkjc#)91$fr>t~gtx ztI0q2nXX3X%=cVH*61do@qv5KbkGzS&}%$jYhfGMvCJpT5#|9VJ>Wj?(}FaboKk`` z8ln=U(Gb-j!5b(2vsX?D(r82~K^hHF2@)>|-aw$)n=ikIymH{)-*xi^;TF#N`BT3BLeat6nd`IA?Y6Q_u)gtl}- zbP4UzcxsN?u+Ksb`vNg3w29G#VB>OHSxcSo8sXlQS=$)j4b<^p)h-pVdv%ap+$y zl4YQ6UVT%QLS;K5u4Q6U%m|6WXU^(w0nyeK1f&(Dn)N~Jioufex+PZSWzFyXy>6ne z1f|X3k>BfnL|RDhE7(GbncQEwuLev0sQWKYf@Xv4f#-~cEIbFPZo-r_UgWl$mYC+5 zuBH#re$zC=Y-`x^-_}~L(=<+hvvG&v=dcdO*llzea|WMbqG7B-Z+wk5aiU?3@o8=$ zy~q$Z+(Q4m;V1O8u}HhpcZHZ`yS&>6xJ~vm~hREQ>7HTf&wa z%Q%a{{FV7b>Qm}JsKe%?=BL;;_ICDP*y+@x=H1i|s++B4$Fnx(E9Qb}(xEjtx;l-qpF~}JS{G>N5u$nxRk1ec04KO?6Hin%)W93acB(Xgg!AUMC|EQ zA|Ymo7ptj8=!&-4JDL+hveh0*C3Bfn!oD(ic{ zDkFKmK14jL4Dt0rJan*}@{{g*kJbjuMRASLoDhj;G-?1-rKhzXKkj)->+s=@C$$b9 zcRYc_^YvK9F!7ITR4=AJrgdObhy!TRU^Q=VYqO`tj1)(k&lCGJRRsqI})Q-ZEA5z-| zs~<$xfkJGxX59mdc5i)%C+^qm_l~qQuL>&oW4F4V;wmL&^}7(wT_477+o`rgxZSI^ z3jyAtws8cA`!t7mi5_jyK4QD#4|jb*_q|$2VO94)heYzQF05x8lFJwExLa)(?AWTB zf5{^6LcK`Qh#=jmsH^7-PTit)6b#y|>W4UD{hJhr`s(>Gu~AiBFs)lv;v1=C1CGC& zkktu=@OPn4C_luEmk{E8^ed z%ArFp!?9S!Q4>%H*1NKdnx^CHy`B+HWp6JRSzuv9tn4}1r;Dhu_(@|J5$ z_Il%kdrah0-TI+3uMZAhzm0sQlo~XCnm6tvPfF%abJpBqUTj`qZZyv@^X5tB3Ui5> zX8*!|#(v12W{1u8+S#$JiTQ^4Bl96M#2jPZ zU|wXNVD>OQ%tmGvlVFxIH!{~UGZ;5Bk-37gFofw#(?_QFO(#tMW_ro=jHz;;X{YH< z(>hby6gAyq`myPUCZDOsbfu}pq&NQA_$hn=bQmNvMU%R{*;T*XMhs&7*m&YF#s_X7 z4q@E4lXwy1ZXYp#@rGkWe*rHcUck7MA)eQ8%RNKHa~R*H{gsE>HEfGde7G<<8~G2(HI8#&@JjOVrx2QZ$qh3Lb$s*L!lnUa*% z-)iq69>oKd_Y?aIm?8FQ_@+m1CLY0f|F4L>1w5O07~_41h&>puJxDx+@tX6*g9RKV z9w=Z*N8GP9uDkn2B9C!2N9@Kp@-?xmfL9VbF`j8BdNFS3Cw3HY192b5Gu|e8G~7sk zLu@bLjl{hOOAz};{YS(-ctF37*rwsRA513h#`s(xv9*B9h`TU88zb)2@HMCB5nC`G zdW+axz;lRA7@s;yY}9Z=+D}N`xRH9B*igVBLe{X|bB5?*DOv}H+9w1h3`=))Vn}_l zUW1kIZeCY_W7cALe9xK!tX-|b%y)~9tYRVlAQ10{_?%4<_C|nu7|e5WIUe zJmc5B**RHNFq)&Xa;_M?h6beJ+AW>6s)o^=vGc+^y6>@u>57I;oi(ZkbU6QKXY;&J z`F%756%E~;PF2Hb4)wh!-~S#<@F@!9&Pl3*(G&xEXLU|=k2=n7MS-;YI0>2Akl&Q= zQjmSqH@n{@zbS$2{R_%MlK7S|nu%|Ft4cnnwwcNNSEtFpvO_3tbpNz*ms%jH>+nX*wf@13HghNPV!>x4wYC8Xo7=2RSx9O;CR6X77y9?yu; zJNPar+uQUWIrSecqIzm|E}lz>)%Df05(zuf`=jC#?WzhOMa66+6Hn*jsU)I)uf%pt}0cHw$BE&i4>f2s@E^hFFi|E%}ZxeF$iDX%1k^b!lK%p&kRF3Fk+#Fs3#NV{KwK3x~!0x4WtG>$FC;PK?HL^HwHdZlNnGCd8b0NoQLs z3S|bxfC4eQIvL69f>fyl=h0+ROl0c{+Q3N27Hp>0DvYK*>AI+p6F@D3P(q0#to*No zJYB7nR|-8sG^%tg%)zqThIii-kBX^<6LG&%qN(dhB_OAPrZ&Y`OpN5R`Kmm%n1(aE zkjR_Qku6nItJP2DI%I;9)!7!9RP4;32vUV|Ouf2>{JwMK?1rgpRoYLW`U9Dzy>Im( z2n0mo{fcNg2R+xulR4p^#`xVos-r5fhS-w~oVhhfplWU4(G5z$*cKO@HCtE%hQR_9hL+Tm4>Czl~3#hB2Z z$blH`ZOO0wfV|R~f}nN8#g)~IK{NPvzJS77UB9SWD^Ix6s3-rk56E#+E|r2YU<&E< ze6UgB!G@yrL7JyiVA?Fj70M8{3{63En}y+{*`$!pwxn`sRLl8cbK}Wu8oY`I#kOWK z3If{}VaL}+giN%0@p_o3>@H297cDv(&!S|~qNMFP+V%iD1`10;XciOTh2i^0imO^!EjQL~p8|J)uow><8)l9J` z*oW8*+sIC4Ddv6V5OY7XmbrroFjts9G`(ipYs#6fH`$G!8ecN@7+Z`pjbjbJGaNVc z8@3vn4fTdG^f~%4-9m}YKVH2N>D$dt{~qh%lgSX$va4& z?$5g8x_;fAx*K&@5nsR$(p0Hd_RtOoO%E?hEf`jyg1!FhSB{~B(}rVSnWv{sL)=TM zRSPcdP-ZPrW-Z}mH|=tvStE;C3s74cR^39A4l`&{C*zc-(Gw=n^srjjFbK?#-x>lY zoVLp4I=ZY3m{0^me`xH@Ry1~Zt5bHVQi^3Upx{G0VoFC2AnMiwVRE13Pg zJ~a7AP?;Na%2Ti!T#NxV7$WAb62x3w;*=+2rh43$a#^6_qd%hVY(&)C8=Z13rs8@8 zFfl~T-af=!)aR6IFcTl{fr+8QtW=nlVcDrHvS^%CtlJeki+wQpOy6TL8BU!}c@kEG zDm_gBbvdfYfANuj`m>W|7OO5s~8A%!ck7jOv%7KI@&Po240C+*F`dAnS`O3h)& zG*|*sa_aQ2K+H}1f8~(J(^XZVq>xZWr0`}dyx9)&m-QN^Io}J2?ls)WM%YkI*_^jQDs)NLcRj| zPNI2KIaMCYFgpI;v_|KZ$KeQ%tSEweMs~ogqi@~_v!JZtkT2Iv#%VbsL67dqAzn1c z%Vk<7NF$4JC_Xqfv`jeq(AQ{E2*n)p)pK>aNEK@jC2? zH`DHr$Iw+0MAlu{k%|Zr+xro*q2D22rU}Gbio1{t3a>%oHI&OGI90vidoLQxy^Y3Z zyj?DHIKzD4LO2@@{i5L;-CijR)e@&{Q!}+#Csy37em5!9jUc9x-sqIAm{~}Q;_ir; zGd@C0{YOsOf|+p%yqz&8ujZOvRR87E`1k^&xkl~G7zCg z4=R>_J~!I=;4D1!`r{#GG~|#8O*P)_M(lh0m!AP1)VnGqT0;A$QlD2IAZ7m|^s5RL zu!>H+Ixnx(?a|;3yU<|iu8Q?oe-P;pBK`ZPe}M+6FC6O>^-$Q?vZII)&A1t8P(gF7 z#qS>u8Cr57>GvDQD7-O_HJUgay5ehs#2NM=+VMRVt5uWG1p)f?Lc92WL#;AcTf0j6 z%0jOI{`^odzIgzy)O$p!&>eBE#BWZ)eZ`vvg?PPM%JX)pd*r3}43Ov}FCNgO9kBmr zzPs%Z->yYl+36(sELQKaF8wK0=0GT5PSBatQnGMK0V=R_5wUGPBKqe$vzS=S-NHo% zA|~L+CG(x{pd+LDR4RNHjMzq`gh%0d%2z1c7P{|P0DnkC>+@*H`@A!)=mL*oUBe&{ zvwBF!3=}DyDNMv~0uVJQWf7A)iI~4U=}clK_B=2#REY3l3B*=Mo0by8Bo;F#OP)ov zqh}onjfQ26h-2on+jMsTZ}7J9t#o-g{KPA_FX|JtW&Ti##2hZISgvLkx{-ljN*+pQ zLz#;aH274cB97C}3-3w27j65Q3*Bh&$?n0Ik5P@(7*vL$IRnAB2P>Sh>i_@% delta 960 zcmX|=acEUl9LLW&@4ma;bI$Mgd+*7pm&G&}I7PHJ!Z|leisnEZ8xN9gsEln=8d)%i zro0drg1I+ey$g>S77J>#t$Cr_wAfH^ByYA=TO*P7#{Ni2GMo0tv^sZ+|D5}~zx(C$ z{r-OU-sBi1PgCj%yEw`itLj-*EsCSVd-^s;Y_J#|#ryaU{0m3G@>WM~Y3Gn#kchtr z@o>6sxWs$5>L(GgNikB?OhYtF<<#XxSN((OH@~+6NxE$3lx5Q>^JLus_0(x^boa}9 zYS+j%23eYgV%bLJdV;!?go~uG2ZUgP2W^9LIs0qYM%De&0;|Qiq$RVtTtnSnGfel>L z69Gzs}jg~BahC*tuU z$Ia(>iQApy5r5?bzb-UEoWYlH2;#8ak4^GPo!%6wur^wWxI27}KchNYHtcPCoyx4l z>g5KRMtkcUt#9q!kB?LV(e}?==(YMWZL2$KL0wk;@_=lTJ7lHG$Wr-~ zMCXo^_qTEJqA>gR!+vj}=s#jjQ*X?#Juc3z2<=nH+=A0$%ItMm`o{*u+qN~+bZPeF z+0NiPlTJP;uAcOQ#`xD>((5#m#+t#i=Q>4DeC8*=6%Wc!Go#|(3e)QcGpmJ+QIZ%Y z(cu4r%oSl6jWxcTT&|ifEo6QW_t5yv2YT+@X3lLR{oM&2S3EN%mgG$Rfm{4kaJnCV z7<%5X{83C=GT0=iaYp0jZ~2bGo2|r#hi`x2PZ4?ee38+j7fs$EfAK#N84DHZ5w~D0 PwB9(pul; manifestUrls?: string[]; preferredVersion?: string | null; vaapiVideoEncode?: boolean; } +export interface LocalApiSettings { + enabled: boolean; + port: number; + exposeOnLan: boolean; + scalarEnabled: boolean; + allowedSignalingServers: string[]; +} + +export type LocalApiStatus = 'stopped' | 'starting' | 'running' | 'error'; + +export interface LocalApiSnapshot { + status: LocalApiStatus; + host: string | null; + port: number | null; + baseUrl: string | null; + error: string | null; + exposeOnLan: boolean; + scalarEnabled: boolean; +} + export interface DesktopNotificationPayload { body: string; requestAttention: boolean; @@ -230,6 +253,8 @@ export interface ElectronApi { restartToApplyUpdate: () => Promise; onAutoUpdateStateChanged: (listener: (state: DesktopUpdateState) => void) => () => void; setDesktopSettings: (patch: DesktopSettingsPatch) => Promise; + getLocalApiStatus: () => Promise; + openLocalApiDocs: () => Promise<{ opened: boolean; reason?: string }>; relaunchApp: () => Promise; onDeepLinkReceived: (listener: (url: string) => void) => () => void; readClipboardFiles: () => Promise; diff --git a/toju-app/src/app/core/services/settings-modal.service.ts b/toju-app/src/app/core/services/settings-modal.service.ts index 140053d..fe73b1d 100644 --- a/toju-app/src/app/core/services/settings-modal.service.ts +++ b/toju-app/src/app/core/services/settings-modal.service.ts @@ -8,6 +8,7 @@ export type SettingsPage = | 'notifications' | 'voice' | 'updates' + | 'localApi' | 'data' | 'debugging' | 'server' diff --git a/toju-app/src/app/domains/chat/feature/chat-messages/components/message-item/chat-message-item.component.html b/toju-app/src/app/domains/chat/feature/chat-messages/components/message-item/chat-message-item.component.html index 0d13261..c308900 100644 --- a/toju-app/src/app/domains/chat/feature/chat-messages/components/message-item/chat-message-item.component.html +++ b/toju-app/src/app/domains/chat/feature/chat-messages/components/message-item/chat-message-item.component.html @@ -91,16 +91,18 @@ @if (msg.isDeleted) {
{{ deletedMessageContent }}
} @else { - @if (requiresRichMarkdown(msg.content)) { - @defer { -
- -
- } @placeholder { + @if (pluginEmbeds().length === 0) { + @if (requiresRichMarkdown(msg.content)) { + @defer { +
+ +
+ } @placeholder { +
{{ msg.content }}
+ } + } @else {
{{ msg.content }}
} - } @else { -
{{ msg.content }}
} @if (msg.linkMetadata?.length) { @@ -116,7 +118,10 @@ } @if (pluginEmbeds().length > 0) { -
+
@for (embed of pluginEmbeds(); track embed.id) {
diff --git a/toju-app/src/app/domains/plugins/README.md b/toju-app/src/app/domains/plugins/README.md index 9a3f27e..757c708 100644 --- a/toju-app/src/app/domains/plugins/README.md +++ b/toju-app/src/app/domains/plugins/README.md @@ -10,7 +10,9 @@ The standalone plugin store is available from the title bar Plugins button, the The plugin manager UI is split between Settings -> Client plugins for global client plugins and Settings -> Server -> Server plugins for chat-server plugins. The two pages filter by manifest `scope` and include installed plugins, capability grant toggles, per-plugin activate/reload/unload actions, runtime logs, extension-point counts, server requirements, generated settings, and docs. -The Store tab consumes user-managed HTTP(S), `file://`, or absolute local-path source manifests. Local-path sources and entrypoints are read through the Electron desktop file bridge. A source manifest can expose a `plugins` array whose entries include `id`, `title`, `description`, `version`, `scope`, `author`/`authors`, `image`/`imageUrl`, `github`/`githubUrl`, `install`/`installUrl`/`manifestUrl`, and `readme`/`readmeUrl`. Installing a `scope: "server"` plugin fetches the linked plugin manifest, validates it, registers it with the client registry, and persists the basic install metadata as a server plugin requirement. Required server plugins are installed on each member client when that chat server opens; optional server plugins stay listed as server requirements but are not auto-installed. Installing a `scope: "client"` plugin persists it locally for the current desktop/browser client. +The Store tab consumes user-managed HTTP(S), `file://`, or absolute local-path source manifests. Local-path sources and entrypoints are read through the Electron desktop file bridge. A source manifest can expose a `plugins` array whose entries include `id`, `title`, `description`, `version`, `scope`, `author`/`authors`, `image`/`imageUrl`, `github`/`githubUrl`, `install`/`installUrl`/`manifestUrl`, `bundle`/`bundleUrl`, and `readme`/`readmeUrl`. Installing a `scope: "server"` plugin fetches the linked plugin manifest, validates it, registers it with the client registry, and persists the basic install metadata as a server plugin requirement. Required server plugins are installed on each member client when that chat server opens; optional server plugins stay listed as server requirements but are not auto-installed. Installing a `scope: "client"` plugin persists it locally for the current desktop/browser client. + +Store plugins can be published as cached browser bundles by adding `bundle` or `bundleUrl` to the source manifest entry. The bundle is a browser-safe ESM JavaScript file. During install, Electron downloads the bundle into app data under `plugin-bundles///main.js`, writes a cached manifest next to it, and registers the plugin from that local cached manifest path. If no bundle URL is provided and the manifest entrypoint is a relative browser module, Electron caches that entrypoint path instead. Browser-only clients still load directly from the source URL. Saved store sources refresh during app bootstrap; when a source advertises a higher version for an installed plugin, the store attempts to update the local cached bundle and persisted install metadata automatically. The server-side plugin support API is metadata-only. The signal server can keep plugin id, requirement status, version range, install/source URLs, and the validated manifest snapshot needed for member clients to install required plugins. Plugin `serverData` API calls are handled as local per-user/per-server client state; HTTP plugin data persistence on the signal server returns `PLUGIN_DATA_DISABLED`. diff --git a/toju-app/src/app/domains/plugins/application/services/plugin-bootstrap.service.ts b/toju-app/src/app/domains/plugins/application/services/plugin-bootstrap.service.ts new file mode 100644 index 0000000..e4216ec --- /dev/null +++ b/toju-app/src/app/domains/plugins/application/services/plugin-bootstrap.service.ts @@ -0,0 +1,9 @@ +import { Injectable, inject } from '@angular/core'; +import { PluginRequirementStateService } from './plugin-requirement-state.service'; +import { PluginStoreService } from './plugin-store.service'; + +@Injectable({ providedIn: 'root' }) +export class PluginBootstrapService { + readonly requirementState = inject(PluginRequirementStateService); + readonly store = inject(PluginStoreService); +} diff --git a/toju-app/src/app/domains/plugins/application/services/plugin-client-api.service.ts b/toju-app/src/app/domains/plugins/application/services/plugin-client-api.service.ts index a6e894a..037c9eb 100644 --- a/toju-app/src/app/domains/plugins/application/services/plugin-client-api.service.ts +++ b/toju-app/src/app/domains/plugins/application/services/plugin-client-api.service.ts @@ -2,6 +2,7 @@ import { Injectable, inject } from '@angular/core'; import { Store } from '@ngrx/store'; import { Subscription } from 'rxjs'; import { RealtimeSessionFacade } from '../../../../core/realtime'; +import { DatabaseService } from '../../../../infrastructure/persistence'; import { VoiceConnectionFacade } from '../../../voice-connection/application/facades/voice-connection.facade'; import type { Channel, @@ -40,6 +41,7 @@ import { PluginUiRegistryService } from './plugin-ui-registry.service'; @Injectable({ providedIn: 'root' }) export class PluginClientApiService { private readonly capabilities = inject(PluginCapabilityService); + private readonly db = inject(DatabaseService); private readonly logger = inject(PluginLoggerService); private readonly messageBus = inject(PluginMessageBusService); private readonly realtime = inject(RealtimeSessionFacade); @@ -159,11 +161,11 @@ export class PluginClientApiService { messages: { delete: (messageId) => { requireCapability('messages.deleteOwn'); - this.deletePluginMessage(messageId); + this.deletePluginMessage(pluginId, messageId); }, edit: (messageId, content) => { requireCapability('messages.editOwn'); - this.editPluginMessage(messageId, content); + this.editPluginMessage(pluginId, messageId, content); }, moderateDelete: (messageId) => { requireCapability('messages.moderate'); @@ -175,7 +177,7 @@ export class PluginClientApiService { }, send: (content, channelId) => { requireCapability('messages.send'); - return this.sendPluginMessage(content, channelId); + return this.sendPluginMessage(pluginId, content, channelId); }, sendAsPluginUser: (request) => { requireCapability('messages.send'); @@ -481,11 +483,18 @@ export class PluginClientApiService { }; this.logger.info(pluginId, 'Plugin user message emitted', { messageId: message.id }); + this.persistPluginMessage(pluginId, message); this.store.dispatch(MessagesActions.receiveMessage({ message })); this.voice.broadcastMessage({ type: 'chat-message', message } as unknown as ChatEvent); } - private deletePluginMessage(messageId: string): void { + private deletePluginMessage(pluginId: string, messageId: string): void { + this.persistPluginMessageUpdate(pluginId, messageId, { + content: '[Message deleted]', + editedAt: Date.now(), + isDeleted: true + }); + this.store.dispatch(MessagesActions.deleteMessageSuccess({ messageId })); this.voice.broadcastMessage({ deletedAt: Date.now(), @@ -494,9 +503,11 @@ export class PluginClientApiService { } as unknown as ChatEvent); } - private editPluginMessage(messageId: string, content: string): void { + private editPluginMessage(pluginId: string, messageId: string, content: string): void { const editedAt = Date.now(); + this.persistPluginMessageUpdate(pluginId, messageId, { content, editedAt }); + this.store.dispatch(MessagesActions.editMessageSuccess({ content, editedAt, @@ -511,7 +522,7 @@ export class PluginClientApiService { } as unknown as ChatEvent); } - private sendPluginMessage(content: string, channelId?: string): Message { + private sendPluginMessage(pluginId: string, content: string, channelId?: string): Message { const currentUser = this.currentUser(); const roomId = this.requireRoomId(); const message: Message = { @@ -526,12 +537,25 @@ export class PluginClientApiService { timestamp: Date.now() }; + this.persistPluginMessage(pluginId, message); this.store.dispatch(MessagesActions.sendMessageSuccess({ message })); this.voice.broadcastMessage({ type: 'chat-message', message } as unknown as ChatEvent); return message; } + private persistPluginMessage(pluginId: string, message: Message): void { + void this.db.saveMessage(message).catch((error: unknown) => { + this.logger.warn(pluginId, 'Failed to persist plugin message', error); + }); + } + + private persistPluginMessageUpdate(pluginId: string, messageId: string, updates: Partial): void { + void this.db.updateMessage(messageId, updates).catch((error: unknown) => { + this.logger.warn(pluginId, 'Failed to persist plugin message update', error); + }); + } + private rememberSubscription(pluginId: string, eventName: string) { this.logger.info(pluginId, `Subscribed to ${eventName}`); diff --git a/toju-app/src/app/domains/plugins/application/services/plugin-host.service.ts b/toju-app/src/app/domains/plugins/application/services/plugin-host.service.ts index 628b990..729cee9 100644 --- a/toju-app/src/app/domains/plugins/application/services/plugin-host.service.ts +++ b/toju-app/src/app/domains/plugins/application/services/plugin-host.service.ts @@ -44,6 +44,7 @@ export class PluginHostService { private readonly registry = inject(PluginRegistryService); private readonly uiRegistry = inject(PluginUiRegistryService); private readonly activePlugins = new Map(); + private readonly activationRequests = new Map>(); private readonly activationStateReady: Promise; private activatedPluginIds = new Set(); @@ -96,11 +97,10 @@ export class PluginHostService { continue; } - await this.activatePlugin(entry); - + const didActivate = await this.activatePlugin(entry); const active = this.activePlugins.get(manifest.id); - if (active) { + if (didActivate && active) { activated.push(active.context); this.activatedPluginIds.add(active.context.pluginId); } @@ -126,11 +126,10 @@ export class PluginHostService { return; } - await this.activatePlugin(entry); - + const didActivate = await this.activatePlugin(entry); const active = this.activePlugins.get(pluginId); - if (!active) { + if (!didActivate || !active) { return; } @@ -161,11 +160,10 @@ export class PluginHostService { continue; } - await this.activatePlugin(entry); - + const didActivate = await this.activatePlugin(entry); const active = this.activePlugins.get(manifest.id); - if (active) { + if (didActivate && active) { activated.push(active.context); } } @@ -265,19 +263,46 @@ export class PluginHostService { } } - private async activatePlugin(entry: RegisteredPlugin): Promise { + private async activatePlugin(entry: RegisteredPlugin): Promise { + const pluginId = entry.manifest.id; + + if (this.activePlugins.has(pluginId)) { + return false; + } + + const pendingActivation = this.activationRequests.get(pluginId); + + if (pendingActivation) { + await pendingActivation; + return false; + } + + const activation = this.activatePluginInternal(entry); + + this.activationRequests.set(pluginId, activation); + + try { + return await activation; + } finally { + if (this.activationRequests.get(pluginId) === activation) { + this.activationRequests.delete(pluginId); + } + } + } + + private async activatePluginInternal(entry: RegisteredPlugin): Promise { const manifest = entry.manifest; const missingCapabilities = this.capabilities.missing(manifest); if (missingCapabilities.length > 0) { this.registry.setFailed(manifest.id, `Missing capabilities: ${missingCapabilities.join(', ')}`); this.logger.warn(manifest.id, 'Plugin blocked by missing capability grants', missingCapabilities); - return; + return false; } if (!manifest.entrypoint) { this.registry.setState(manifest.id, 'ready'); - return; + return false; } this.registry.setState(manifest.id, 'loading'); @@ -291,12 +316,14 @@ export class PluginHostService { subscriptions: [] }; - await module.activate?.(context); + await this.runWithPluginRuntimeGuards(manifest.id, () => module.activate?.(context)); this.activePlugins.set(manifest.id, { context, module, moduleObjectUrl }); this.registry.setState(manifest.id, 'loaded'); this.logger.info(manifest.id, 'Plugin activated'); + return true; } catch (error) { this.failPlugin(manifest.id, error); + return false; } } @@ -310,6 +337,27 @@ export class PluginHostService { this.revokeModuleObjectUrl(pluginId); } + private async runWithPluginRuntimeGuards(pluginId: string, activate: () => Promise | void): Promise { + const originalMutationObserver = globalThis.MutationObserver; + + if (!originalMutationObserver) { + await activate(); + return; + } + + const guardedMutationObserver = createGuardedMutationObserver(originalMutationObserver, pluginId, this.logger); + + globalThis.MutationObserver = guardedMutationObserver; + + try { + await activate(); + } finally { + if (globalThis.MutationObserver === guardedMutationObserver) { + globalThis.MutationObserver = originalMutationObserver; + } + } + } + private async loadPluginModule( manifest: TojuPluginManifest, sourcePath?: string @@ -391,6 +439,10 @@ export class PluginHostService { return new URL(manifest.entrypoint).toString(); } catch {} + if (manifest.bundle?.url && !sourcePath?.startsWith('file://')) { + return manifest.bundle.url; + } + if (sourcePath?.startsWith('http://') || sourcePath?.startsWith('https://') || sourcePath?.startsWith('file://')) { return new URL(manifest.entrypoint, sourcePath).toString(); } @@ -421,3 +473,61 @@ function safeDispose(disposable: TojuPluginDisposable, pluginId: string, logger: logger.warn(pluginId, 'Plugin disposable failed', error); } } + +function createGuardedMutationObserver( + NativeMutationObserver: typeof MutationObserver, + pluginId: string, + logger: PluginLoggerService +): typeof MutationObserver { + return class GuardedPluginMutationObserver implements MutationObserver { + private readonly nativeObserver: MutationObserver; + private readonly observations: { options?: MutationObserverInit; target: Node }[] = []; + private isDispatching = false; + + constructor(private readonly callback: MutationCallback) { + this.nativeObserver = new NativeMutationObserver((records) => this.dispatch(records)); + } + + observe(target: Node, options?: MutationObserverInit): void { + const existing = this.observations.find((observation) => observation.target === target); + + if (existing) { + existing.options = options; + } else { + this.observations.push({ options, target }); + } + + this.nativeObserver.observe(target, options); + } + + disconnect(): void { + this.observations.length = 0; + this.nativeObserver.disconnect(); + } + + takeRecords(): MutationRecord[] { + return this.nativeObserver.takeRecords(); + } + + private dispatch(records: MutationRecord[]): void { + if (this.isDispatching) { + return; + } + + this.isDispatching = true; + this.nativeObserver.disconnect(); + + try { + this.callback(records, this); + } catch (error) { + logger.warn(pluginId, 'Plugin MutationObserver callback failed', error); + } finally { + this.isDispatching = false; + + for (const observation of this.observations) { + this.nativeObserver.observe(observation.target, observation.options); + } + } + } + }; +} diff --git a/toju-app/src/app/domains/plugins/application/services/plugin-message-bus.service.ts b/toju-app/src/app/domains/plugins/application/services/plugin-message-bus.service.ts index f480b0e..77ec922 100644 --- a/toju-app/src/app/domains/plugins/application/services/plugin-message-bus.service.ts +++ b/toju-app/src/app/domains/plugins/application/services/plugin-message-bus.service.ts @@ -100,7 +100,7 @@ export class PluginMessageBusService { request: PluginApiMessageBusLatestRequest, includeMessages: boolean ): PluginApiMessageBusEnvelope { - const currentUser = this.currentUser(); + const currentUser = this.currentUser() ?? null; const envelope: PluginApiMessageBusEnvelope = { eventId: createId(), pluginId, @@ -233,4 +233,4 @@ function isMessage(value: unknown): value is Message { function createId(): string { return globalThis.crypto?.randomUUID?.() ?? `plugin-bus-${Date.now()}-${Math.random().toString(36) .slice(2)}`; -} \ No newline at end of file +} diff --git a/toju-app/src/app/domains/plugins/application/services/plugin-requirement-state.service.ts b/toju-app/src/app/domains/plugins/application/services/plugin-requirement-state.service.ts index fc180d4..fb7b3f6 100644 --- a/toju-app/src/app/domains/plugins/application/services/plugin-requirement-state.service.ts +++ b/toju-app/src/app/domains/plugins/application/services/plugin-requirement-state.service.ts @@ -15,8 +15,8 @@ import type { TojuPluginManifest } from '../../../../shared-kernel'; import { RealtimeSessionFacade } from '../../../../core/realtime'; -import { selectCurrentRoomId } from '../../../../store/rooms/rooms.selectors'; -import { ServerDirectoryFacade } from '../../../server-directory'; +import { selectCurrentRoom, selectCurrentRoomId } from '../../../../store/rooms/rooms.selectors'; +import { ServerDirectoryFacade, type ServerSourceSelector } from '../../../server-directory'; import { PluginRegistryService } from './plugin-registry.service'; import { PluginRequirementService } from './plugin-requirement.service'; @@ -44,6 +44,7 @@ export class PluginRequirementStateService { private readonly serverDirectory = inject(ServerDirectoryFacade); private readonly store = inject(Store); + private readonly currentRoom = this.store.selectSignal(selectCurrentRoom); private readonly currentRoomId = this.store.selectSignal(selectCurrentRoomId); private readonly snapshotsSignal = signal>({}); private readonly refreshErrorsSignal = signal>({}); @@ -111,7 +112,7 @@ export class PluginRequirementStateService { } try { - const apiBaseUrl = this.serverDirectory.getApiBaseUrl(); + const apiBaseUrl = this.serverDirectory.getApiBaseUrl(this.currentRoomSourceSelector()); const snapshot = await new Promise((resolve, reject) => { this.pluginRequirements.getSnapshot(apiBaseUrl, roomId).subscribe({ error: reject, @@ -144,6 +145,19 @@ export class PluginRequirementStateService { })); } + private currentRoomSourceSelector(): ServerSourceSelector | undefined { + const room = this.currentRoom(); + + if (!room?.sourceId && !room?.sourceUrl) { + return undefined; + } + + return { + sourceId: room.sourceId, + sourceUrl: room.sourceUrl + }; + } + private resolveStatus( requirement: PluginRequirementSummary, entry: { enabled: boolean; manifest: TojuPluginManifest } | undefined diff --git a/toju-app/src/app/domains/plugins/application/services/plugin-store.service.spec.ts b/toju-app/src/app/domains/plugins/application/services/plugin-store.service.spec.ts index dc18c6b..deae055 100644 --- a/toju-app/src/app/domains/plugins/application/services/plugin-store.service.spec.ts +++ b/toju-app/src/app/domains/plugins/application/services/plugin-store.service.spec.ts @@ -129,6 +129,45 @@ describe('PluginStoreService', () => { expect(service.installedPlugins()).toEqual([]); }); + it('caches plugin bundle entrypoints locally before registering installed plugins', async () => { + const manifest = createManifest({ entrypoint: './dist/main.js' }); + const plugin = createStoreEntry({ + bundleUrl: 'https://plugins.example.test/better/bundle.js', + version: '1.0.0' + }); + const electronApi = { + ensureDir: vi.fn(async () => true), + getAppDataPath: vi.fn(async () => '/tmp/metoyou-user-data'), + writeFile: vi.fn(async () => true) + }; + + fetchMock + .mockResolvedValueOnce(jsonResponse(manifest)) + .mockResolvedValueOnce(textResponse('export function activate() {}')); + + const service = createService(registerLocalManifest, unregister, electronApi); + + await service.installPlugin(plugin); + + expect(electronApi.ensureDir).toHaveBeenCalledWith('/tmp/metoyou-user-data/plugin-bundles/example.better-channels/1.0.0'); + expect(electronApi.writeFile).toHaveBeenCalledWith( + '/tmp/metoyou-user-data/plugin-bundles/example.better-channels/1.0.0/main.js', + expect.any(String) + ); + + expect(registerLocalManifest).toHaveBeenCalledWith( + expect.objectContaining({ + bundle: { + entrypoint: './main.js', + url: plugin.bundleUrl + }, + entrypoint: './main.js', + id: manifest.id + }), + 'file:///tmp/metoyou-user-data/plugin-bundles/example.better-channels/1.0.0/toju-plugin.json' + ); + }); + it('loads plugin readmes as markdown text', async () => { const plugin = createStoreEntry({ readmeUrl: 'https://plugins.example.test/better/README.md' }); @@ -149,7 +188,12 @@ describe('PluginStoreService', () => { function createService( registerLocalManifest: ReturnType, unregister: ReturnType, - electronApi: { readFile: (filePath: string) => Promise } | null = null + electronApi: { + ensureDir?: (dirPath: string) => Promise; + getAppDataPath?: () => Promise; + readFile?: (filePath: string) => Promise; + writeFile?: (filePath: string, data: string) => Promise; + } | null = null ): PluginStoreService { const injector = Injector.create({ providers: [ @@ -165,6 +209,7 @@ function createService( useValue: { activatePersistedPlugins: vi.fn(async () => {}), deactivatePlugin: vi.fn(async () => {}), + isPluginActive: vi.fn(() => false), registerLocalManifest } }, diff --git a/toju-app/src/app/domains/plugins/application/services/plugin-store.service.ts b/toju-app/src/app/domains/plugins/application/services/plugin-store.service.ts index 85409de..99a494b 100644 --- a/toju-app/src/app/domains/plugins/application/services/plugin-store.service.ts +++ b/toju-app/src/app/domains/plugins/application/services/plugin-store.service.ts @@ -18,9 +18,14 @@ import type { TojuPluginInstallScope, TojuPluginManifest } from '../../../../shared-kernel'; -import { selectCurrentRoomId, selectCurrentRoomName } from '../../../../store/rooms/rooms.selectors'; +import { + selectCurrentRoom, + selectCurrentRoomId, + selectCurrentRoomName, + selectSavedRooms +} from '../../../../store/rooms/rooms.selectors'; import { selectCurrentUser } from '../../../../store/users/users.selectors'; -import { ServerDirectoryFacade } from '../../../server-directory'; +import { ServerDirectoryFacade, type ServerSourceSelector } from '../../../server-directory'; import { getPluginInstallScope } from '../../domain/logic/plugin-install-scope.logic'; import { validateTojuPluginManifest } from '../../domain/logic/plugin-manifest-validation.logic'; import type { @@ -39,6 +44,7 @@ import { PluginRegistryService } from './plugin-registry.service'; const STORE_SCHEMA_VERSION = 1; const STORAGE_KEY_PLUGIN_STORE = 'metoyou_plugin_store'; +const PLUGIN_CACHE_DIR = 'plugin-bundles'; const DEFAULT_STORE_STATE: PersistedPluginStoreState = { installedPlugins: [], sourceUrls: [] @@ -62,8 +68,10 @@ export class PluginStoreService { private readonly registry = inject(PluginRegistryService); private readonly serverDirectory = inject(ServerDirectoryFacade, { optional: true }); private readonly store = inject(Store, { optional: true }); + private readonly currentRoom = this.store?.selectSignal(selectCurrentRoom) ?? null; private readonly currentRoomId = this.store?.selectSignal(selectCurrentRoomId) ?? null; private readonly currentRoomName = this.store?.selectSignal(selectCurrentRoomName) ?? null; + private readonly savedRooms = this.store?.selectSignal(selectSavedRooms) ?? null; private readonly currentUser = this.store?.selectSignal(selectCurrentUser) ?? null; private readonly sourceUrlsSignal = signal([]); private readonly sourcesSignal = signal([]); @@ -73,6 +81,7 @@ export class PluginStoreService { private refreshAbortController: AbortController | null = null; private refreshVersion = 0; private installedLoadVersion = 0; + private autoUpdateInProgress = false; private stateMutated = false; readonly sourceUrls = this.sourceUrlsSignal.asReadonly(); @@ -94,6 +103,10 @@ export class PluginStoreService { this.sourceUrlsSignal.set(state.sourceUrls); void this.applyInstalledPlugins(state.installedPlugins, 'client'); + if (state.sourceUrls.length > 0) { + void this.refreshSources(); + } + if (this.currentRoomId && this.currentUser && this.serverDirectory) { effect(() => { const roomId = this.currentRoomId?.() ?? null; @@ -147,6 +160,7 @@ export class PluginStoreService { if (this.refreshVersion === currentRefresh) { this.sourcesSignal.set(sources); + void this.autoUpdateInstalledPlugins(); } } finally { if (this.refreshVersion === currentRefresh) { @@ -161,54 +175,88 @@ export class PluginStoreService { throw new Error('Plugin does not provide an install manifest URL'); } - const manifest = options.manifest ?? await this.fetchPluginManifest(plugin.installUrl); + const manifest = this.withStoreBundleMetadata(options.manifest ?? await this.fetchPluginManifest(plugin.installUrl), plugin); const installScope = getPluginInstallScope(manifest); - const targetServerId = installScope === 'server' ? (options.serverId ?? this.currentRoomId?.() ?? null) : null; - - if (installScope === 'server' && !targetServerId) { - throw new Error('Open a chat server before installing server-scoped plugins'); - } - + const targetServerId = this.resolveInstallTargetServerId(installScope, options.serverId); const now = Date.now(); const currentScopePlugins = installScope === 'server' ? await this.installedPluginsForServer(targetServerId) : this.installedPluginsForScope(installScope); const existing = currentScopePlugins.find((candidate) => candidate.manifest.id === manifest.id); - const installedPlugin: InstalledStorePlugin = { + const installedPlugin = await this.cacheInstalledPlugin({ + bundleUrl: manifest.bundle?.url ?? plugin.bundleUrl, installedAt: existing?.installedAt ?? now, installUrl: plugin.installUrl, manifest, sourceUrl: plugin.sourceUrl, updatedAt: now - }; + }); const nextInstalledPlugins = currentScopePlugins .filter((candidate) => candidate.manifest.id !== manifest.id) .concat(installedPlugin) .sort(sortInstalledPlugins); - if (installScope === 'server') { - await this.saveServerPluginRequirement(installedPlugin, targetServerId, options.optional === true ? 'optional' : 'required'); - } else { - await this.persistInstalledPlugins(nextInstalledPlugins, installScope); - } - - if (installScope === 'client' || targetServerId === this.currentRoomId?.()) { - this.host.registerLocalManifest(manifest, plugin.installUrl); - - if (installScope === 'client' || options.optional !== true) { - this.setInstalledPluginsForScope(installScope, nextInstalledPlugins); - } - - if (options.activate) { - await this.host.activatePluginById(manifest.id); - } - } else if (options.activate) { - await this.host.rememberActivation(manifest.id); - } + await this.persistInstallResult(installScope, targetServerId, nextInstalledPlugins, installedPlugin, options); + await this.registerInstallResult(installScope, targetServerId, nextInstalledPlugins, installedPlugin, options); return installedPlugin; } + private resolveInstallTargetServerId(installScope: TojuPluginInstallScope, requestedServerId: string | undefined): string | null { + if (installScope !== 'server') { + return null; + } + + const targetServerId = requestedServerId ?? this.currentRoomId?.() ?? null; + + if (!targetServerId) { + throw new Error('Open a chat server before installing server-scoped plugins'); + } + + return targetServerId; + } + + private async persistInstallResult( + installScope: TojuPluginInstallScope, + targetServerId: string | null, + nextInstalledPlugins: InstalledStorePlugin[], + installedPlugin: InstalledStorePlugin, + options: PluginStoreInstallOptions + ): Promise { + if (installScope === 'server') { + await this.saveServerPluginRequirement(installedPlugin, targetServerId, options.optional === true ? 'optional' : 'required'); + return; + } + + await this.persistInstalledPlugins(nextInstalledPlugins, installScope); + } + + private async registerInstallResult( + installScope: TojuPluginInstallScope, + targetServerId: string | null, + nextInstalledPlugins: InstalledStorePlugin[], + installedPlugin: InstalledStorePlugin, + options: PluginStoreInstallOptions + ): Promise { + if (installScope !== 'client' && targetServerId !== this.currentRoomId?.()) { + if (options.activate) { + await this.host.rememberActivation(installedPlugin.manifest.id); + } + + return; + } + + this.host.registerLocalManifest(installedPlugin.manifest, installedPlugin.cachedSourcePath ?? installedPlugin.installUrl); + + if (installScope === 'client' || options.optional !== true) { + this.setInstalledPluginsForScope(installScope, nextInstalledPlugins); + } + + if (options.activate) { + await this.host.activatePluginById(installedPlugin.manifest.id); + } + } + async loadInstallManifest(plugin: PluginStoreEntry): Promise { if (!plugin.installUrl) { throw new Error('Plugin does not provide an install manifest URL'); @@ -217,21 +265,32 @@ export class PluginStoreService { return await this.fetchPluginManifest(plugin.installUrl); } - async uninstallPlugin(pluginId: string, scope?: TojuPluginInstallScope): Promise { + async uninstallPlugin(pluginId: string, scope?: TojuPluginInstallScope, options: { serverId?: string } = {}): Promise { const installScope = scope ?? this.findInstalledPluginScope(pluginId) ?? 'client'; - const nextInstalledPlugins = this.installedPluginsForScope(installScope).filter((installedPlugin) => installedPlugin.manifest.id !== pluginId); + const currentInstalledPlugins = installScope === 'server' + ? await this.installedPluginsForServer(options.serverId ?? this.currentRoomId?.() ?? null) + : this.installedPluginsForScope(installScope); + const nextInstalledPlugins = currentInstalledPlugins.filter((installedPlugin) => installedPlugin.manifest.id !== pluginId); if (installScope === 'server') { - await this.deleteServerPluginRequirement(pluginId); + await this.deleteServerPluginRequirement(pluginId, options.serverId); } else { await this.persistInstalledPlugins(nextInstalledPlugins, installScope); } + if (installScope === 'server' && options.serverId && options.serverId !== this.currentRoomId?.()) { + return; + } + await this.host.deactivatePlugin(pluginId, { forgetActivation: true }); this.registry.unregister(pluginId); this.setInstalledPluginsForScope(installScope, nextInstalledPlugins); } + async loadInstalledPluginsForServer(serverId: string): Promise { + return await this.installedPluginsForServer(serverId); + } + async loadReadme(plugin: PluginStoreEntry): Promise { if (!plugin.readmeUrl) { throw new Error('Plugin does not provide a readme URL'); @@ -332,6 +391,107 @@ export class PluginStoreService { return new TextDecoder().decode(bytes); } + private withStoreBundleMetadata(manifest: TojuPluginManifest, plugin: PluginStoreEntry): TojuPluginManifest { + if (!plugin.bundleUrl || manifest.bundle?.url) { + return manifest; + } + + return { + ...manifest, + bundle: { + entrypoint: './main.js', + url: plugin.bundleUrl + } + }; + } + + private async cacheInstalledPlugin(installedPlugin: InstalledStorePlugin): Promise { + if (installedPlugin.cachedSourcePath) { + return installedPlugin; + } + + const api = this.electronBridge.getApi(); + const entrypointSourceUrl = this.resolvePluginBundleSourceUrl(installedPlugin); + const cachedEntrypoint = this.resolveCachedEntrypointPath(installedPlugin.manifest); + + if (!api || !entrypointSourceUrl || !cachedEntrypoint) { + return installedPlugin; + } + + const cachedManifest = this.toCachedRuntimeManifest(installedPlugin.manifest, cachedEntrypoint); + const appDataPath = await api.getAppDataPath(); + const pluginCacheDir = joinLocalPath( + appDataPath, + PLUGIN_CACHE_DIR, + sanitizePathSegment(installedPlugin.manifest.id), + sanitizePathSegment(installedPlugin.manifest.version) + ); + const manifestPath = joinLocalPath(pluginCacheDir, 'toju-plugin.json'); + const entrypointPath = joinLocalPath(pluginCacheDir, cachedEntrypoint); + const cacheRootUrl = localPathToFileUrl(manifestPath); + + if (!cacheRootUrl) { + return installedPlugin; + } + + await api.ensureDir(dirnameLocalPath(entrypointPath)); + await api.writeFile(entrypointPath, bytesToBase64(new TextEncoder().encode(await this.fetchText(entrypointSourceUrl, 'text/javascript,*/*')))); + await api.writeFile(manifestPath, bytesToBase64(new TextEncoder().encode(JSON.stringify(cachedManifest, null, 2)))); + + return { + ...installedPlugin, + bundleUrl: installedPlugin.bundleUrl ?? installedPlugin.manifest.bundle?.url, + cachedAt: Date.now(), + cachedSourcePath: cacheRootUrl, + manifest: cachedManifest + }; + } + + private toCachedRuntimeManifest(manifest: TojuPluginManifest, cachedEntrypoint: string): TojuPluginManifest { + if (!manifest.bundle?.url) { + return manifest; + } + + return { + ...manifest, + entrypoint: cachedEntrypoint.startsWith('./') ? cachedEntrypoint : `./${cachedEntrypoint}` + }; + } + + private resolvePluginBundleSourceUrl(installedPlugin: InstalledStorePlugin): string | null { + const bundleUrl = installedPlugin.bundleUrl ?? installedPlugin.manifest.bundle?.url; + + if (bundleUrl) { + return bundleUrl; + } + + const entrypoint = installedPlugin.manifest.entrypoint; + + if (!entrypoint || !installedPlugin.installUrl || isAbsolutePluginUrl(entrypoint)) { + return null; + } + + return resolveOptionalUrl(installedPlugin.installUrl, entrypoint) ?? null; + } + + private resolveCachedEntrypointPath(manifest: TojuPluginManifest): string | null { + const entrypoint = manifest.bundle?.url + ? manifest.bundle.entrypoint ?? './main.js' + : manifest.entrypoint; + + if (!entrypoint || isAbsolutePluginUrl(entrypoint)) { + return null; + } + + const normalized = entrypoint.replace(/^\.\//, '').replace(/\\/g, '/'); + + if (!normalized || normalized.startsWith('/') || normalized.split('/').includes('..')) { + return null; + } + + return normalized; + } + private async applyInstalledPlugins(installedPlugins: InstalledStorePlugin[], scope: TojuPluginInstallScope): Promise { const usableInstalledPlugins: InstalledStorePlugin[] = []; const scopedInstalledPlugins = installedPlugins.filter((installedPlugin) => getPluginInstallScope(installedPlugin.manifest) === scope); @@ -346,8 +506,10 @@ export class PluginStoreService { for (const installedPlugin of scopedInstalledPlugins) { try { - this.host.registerLocalManifest(installedPlugin.manifest, installedPlugin.installUrl); - usableInstalledPlugins.push(installedPlugin); + const cachedPlugin = await this.cacheInstalledPlugin(installedPlugin); + + this.host.registerLocalManifest(cachedPlugin.manifest, cachedPlugin.cachedSourcePath ?? cachedPlugin.installUrl); + usableInstalledPlugins.push(cachedPlugin); } catch { // Corrupt persisted manifests are ignored so the store can recover on next install. } @@ -355,15 +517,77 @@ export class PluginStoreService { this.setInstalledPluginsForScope(scope, usableInstalledPlugins); - await this.host.activatePersistedPlugins(); + if (scope === 'server') { + await this.activateServerPlugins(usableInstalledPlugins); + } else { + await this.host.activatePersistedPlugins(); + } if (usableInstalledPlugins.length !== scopedInstalledPlugins.length) { if (scope === 'client') { await this.persistInstalledPlugins(usableInstalledPlugins, scope); } + } else if ( + scope === 'client' + && usableInstalledPlugins.some((plugin, index) => plugin.cachedSourcePath !== scopedInstalledPlugins[index]?.cachedSourcePath) + ) { + await this.persistInstalledPlugins(usableInstalledPlugins, scope); } } + private async activateServerPlugins(installedPlugins: InstalledStorePlugin[]): Promise { + for (const installedPlugin of installedPlugins) { + await this.host.activatePluginById(installedPlugin.manifest.id); + } + } + + private async autoUpdateInstalledPlugins(): Promise { + if (this.autoUpdateInProgress || this.sources().length === 0) { + return; + } + + this.autoUpdateInProgress = true; + + try { + await this.autoUpdateScope('client'); + + if (this.currentRoomId?.()) { + await this.autoUpdateScope('server'); + } + } finally { + this.autoUpdateInProgress = false; + } + } + + private async autoUpdateScope(scope: TojuPluginInstallScope): Promise { + for (const installedPlugin of this.installedPluginsForScope(scope)) { + const update = this.findUpdateCandidate(installedPlugin, scope); + + if (!update) { + continue; + } + + try { + await this.installPlugin(update, { + activate: this.host.isPluginActive(installedPlugin.manifest.id), + serverId: scope === 'server' ? this.currentRoomId?.() ?? undefined : undefined + }); + } catch {} + } + } + + private findUpdateCandidate(installedPlugin: InstalledStorePlugin, scope: TojuPluginInstallScope): PluginStoreEntry | null { + const candidates = this.availablePlugins().filter((plugin) => { + return plugin.id === installedPlugin.manifest.id + && getStoreEntryInstallScope(plugin) === scope + && (!installedPlugin.sourceUrl || plugin.sourceUrl === installedPlugin.sourceUrl); + }); + + return candidates + .filter((plugin) => compareVersions(plugin.version, installedPlugin.manifest.version) > 0) + .sort((left, right) => compareVersions(right.version, left.version))[0] ?? null; + } + private async loadInstalledPluginsForScope(roomId: string | null, actorUserId: string | null): Promise { const currentLoad = this.installedLoadVersion + 1; @@ -418,7 +642,7 @@ export class PluginStoreService { return []; } - const snapshot = await firstValueFrom(this.pluginRequirements.getSnapshot(this.serverDirectory.getApiBaseUrl(), roomId)); + const snapshot = await firstValueFrom(this.pluginRequirements.getSnapshot(this.getPluginApiBaseUrl(roomId), roomId)); return snapshot.requirements .map((requirement) => installedPluginFromRequirement(requirement)) @@ -438,7 +662,7 @@ export class PluginStoreService { } await firstValueFrom(this.pluginRequirements.upsertRequirement( - this.serverDirectory.getApiBaseUrl(), + this.getPluginApiBaseUrl(roomId), roomId, installedPlugin.manifest.id, { @@ -453,15 +677,51 @@ export class PluginStoreService { )); } - private async deleteServerPluginRequirement(pluginId: string): Promise { - const roomId = this.currentRoomId?.() ?? null; + private async deleteServerPluginRequirement(pluginId: string, serverId?: string): Promise { + const roomId = serverId ?? this.currentRoomId?.() ?? null; const actorUserId = this.currentActorUserId(); if (!roomId || !actorUserId || !this.serverDirectory) { throw new Error('Open a chat server before removing server-scoped plugins'); } - await firstValueFrom(this.pluginRequirements.deleteRequirement(this.serverDirectory.getApiBaseUrl(), roomId, pluginId, actorUserId)); + await firstValueFrom(this.pluginRequirements.deleteRequirement(this.getPluginApiBaseUrl(roomId), roomId, pluginId, actorUserId)); + } + + private getPluginApiBaseUrl(serverId: string): string { + const selector = this.serverSourceSelector(serverId); + + return this.serverDirectory?.getApiBaseUrl(selector) ?? ''; + } + + private serverSourceSelector(serverId: string): ServerSourceSelector | undefined { + if (serverId === this.currentRoomId?.()) { + return this.currentRoomSourceSelector(); + } + + const room = this.savedRooms?.().find((candidate) => candidate.id === serverId) ?? null; + + if (!room?.sourceId && !room?.sourceUrl) { + return undefined; + } + + return { + sourceId: room.sourceId, + sourceUrl: room.sourceUrl + }; + } + + private currentRoomSourceSelector(): ServerSourceSelector | undefined { + const room = this.currentRoom?.() ?? null; + + if (!room?.sourceId && !room?.sourceUrl) { + return undefined; + } + + return { + sourceId: room.sourceId, + sourceUrl: room.sourceUrl + }; } private currentActorUserId(): string | null { @@ -591,6 +851,7 @@ function installedPluginFromRequirement(requirement: PluginRequirementSummary): } return { + bundleUrl: manifest.bundle?.url, installedAt: requirement.updatedAt, installUrl: requirement.installUrl, manifest, @@ -640,6 +901,7 @@ function parsePluginEntry(sourceUrl: string, sourceTitle: string, value: unknown return { author: readAuthor(value), + bundleUrl: resolveOptionalUrl(sourceUrl, readString(value, 'bundle', 'bundleUrl')), description: readString(value, 'description', 'summary') ?? '', githubUrl: resolveOptionalUrl(sourceUrl, readGithubUrl(value)), homepageUrl: resolveOptionalUrl(sourceUrl, readString(value, 'homepage', 'homepageUrl', 'website')), @@ -794,6 +1056,44 @@ function isAllowedPluginSourceProtocol(protocol: string): boolean { return protocol === 'http:' || protocol === 'https:' || protocol === 'file:'; } +function isAbsolutePluginUrl(value: string): boolean { + try { + const url = new URL(value); + + return isAllowedPluginSourceProtocol(url.protocol); + } catch { + return false; + } +} + +function sanitizePathSegment(value: string): string { + return value.replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 128) || 'plugin'; +} + +function joinLocalPath(...parts: string[]): string { + return parts + .map((part, index) => index === 0 ? part.replace(/[\\/]+$/, '') : part.replace(/^[\\/]+|[\\/]+$/g, '')) + .filter(Boolean) + .join('/'); +} + +function dirnameLocalPath(filePath: string): string { + const normalized = filePath.replace(/\\/g, '/'); + const index = normalized.lastIndexOf('/'); + + return index > 0 ? normalized.slice(0, index) : normalized; +} + +function bytesToBase64(bytes: Uint8Array): string { + let binary = ''; + + for (const byte of bytes) { + binary += String.fromCharCode(byte); + } + + return btoa(binary); +} + function localPathToFileUrl(filePath: string): string | undefined { if (!isAbsoluteLocalPath(filePath)) { return undefined; diff --git a/toju-app/src/app/domains/plugins/domain/models/plugin-store.models.ts b/toju-app/src/app/domains/plugins/domain/models/plugin-store.models.ts index bff2a99..cd4f9a3 100644 --- a/toju-app/src/app/domains/plugins/domain/models/plugin-store.models.ts +++ b/toju-app/src/app/domains/plugins/domain/models/plugin-store.models.ts @@ -5,6 +5,7 @@ export type PluginStoreActionLabel = 'Install' | 'Install to Server' | 'Remove f export interface PluginStoreEntry { author?: string; + bundleUrl?: string; description: string; githubUrl?: string; homepageUrl?: string; @@ -28,6 +29,9 @@ export interface PluginStoreSourceResult { } export interface InstalledStorePlugin { + bundleUrl?: string; + cachedAt?: number; + cachedSourcePath?: string; installedAt: number; installUrl?: string; manifest: TojuPluginManifest; diff --git a/toju-app/src/app/domains/plugins/feature/plugin-manager/plugin-manager.component.ts b/toju-app/src/app/domains/plugins/feature/plugin-manager/plugin-manager.component.ts index 99212be..80eb064 100644 --- a/toju-app/src/app/domains/plugins/feature/plugin-manager/plugin-manager.component.ts +++ b/toju-app/src/app/domains/plugins/feature/plugin-manager/plugin-manager.component.ts @@ -61,6 +61,7 @@ type PluginManagerTab = 'docs' | 'extensions' | 'installed' | 'logs' | 'requirem }) export class PluginManagerComponent { @Output() readonly closed = new EventEmitter(); + @Output() readonly storeOpened = new EventEmitter(); readonly scope = input('client'); @@ -149,7 +150,7 @@ export class PluginManagerComponent { openStore(): void { const returnUrl = this.router.url.startsWith('/plugin-store') ? '/search' : this.router.url; - this.closed.emit(); + this.storeOpened.emit(); void this.router.navigate(['/plugin-store'], { queryParams: { returnUrl } }); } diff --git a/toju-app/src/app/domains/plugins/feature/plugin-store/plugin-store.component.html b/toju-app/src/app/domains/plugins/feature/plugin-store/plugin-store.component.html index 36543bd..3da3da2 100644 --- a/toju-app/src/app/domains/plugins/feature/plugin-store/plugin-store.component.html +++ b/toju-app/src/app/domains/plugins/feature/plugin-store/plugin-store.component.html @@ -1,255 +1,332 @@
-
-
+
+
-
- +
+
-
-

Plugin Store

-

+

+

Plugin Store

+

{{ installedCount() }} installed for {{ store.installScopeLabel() }} · {{ totalSourcePlugins() }} available · {{ sourceCount() }} sources

-
+
-
-
-