diff --git a/apps/web/lib/control-plane.ts b/apps/web/lib/control-plane.ts index 0e36e56..3a4b723 100644 --- a/apps/web/lib/control-plane.ts +++ b/apps/web/lib/control-plane.ts @@ -1,326 +1 @@ -/** - * Control plane HTTP client. - * - * The control plane (vectorless-control-plane, Go) is the single source - * of truth for users, sessions, orgs, API keys, OAuth tokens, billing, - * and the document API proxy. The dashboard talks to it for everything - * backend-y. - * - * Two auth contexts: - * - * - Service token: used for /admin/internal/* endpoints. The dashboard - * proves it's the dashboard. Used by Better Auth's custom adapter - * and by OAuth consent server actions. - * - * - Session cookie: used for /admin/v1/* endpoints called on behalf - * of a logged-in user. The browser's vls_session cookie is - * forwarded. - * - * This module never imports React or Next.js — it's pure fetch wrapper. - * Callers (server actions, route handlers) supply the request context. - */ - -const CONTROL_PLANE_URL = - process.env.CONTROL_PLANE_URL || - process.env.VECTORLESS_API_URL || - "http://localhost:9090"; - -const SERVICE_TOKEN = process.env.CONTROL_PLANE_SERVICE_TOKEN || ""; - -export class ControlPlaneError extends Error { - constructor( - public readonly status: number, - message: string, - public readonly body?: unknown, - ) { - super(message); - this.name = "ControlPlaneError"; - } -} - -interface CallOptions { - /** HTTP method. Defaults to POST. */ - method?: "GET" | "POST" | "PATCH" | "DELETE"; - /** JSON body — serialized for non-GET. */ - body?: unknown; - /** Override base URL (rarely needed). */ - baseUrl?: string; - /** Additional headers. */ - headers?: Record; -} - -/** - * Service-context call: authenticated with the dashboard service token. - * - * Used by Better Auth's custom adapter (identity CRUD) and by OAuth - * consent server actions (validate-request, issue-code, introspect). - * - * Throws ControlPlaneError on non-2xx responses. - */ -export async function controlPlaneServiceCall( - path: string, - opts: CallOptions = {}, -): Promise { - if (!SERVICE_TOKEN) { - throw new ControlPlaneError( - 503, - "CONTROL_PLANE_SERVICE_TOKEN is not configured", - ); - } - - const baseUrl = opts.baseUrl ?? CONTROL_PLANE_URL; - const url = `${baseUrl}${path}`; - const method = opts.method ?? "POST"; - - const headers: Record = { - Authorization: `Bearer ${SERVICE_TOKEN}`, - ...opts.headers, - }; - let body: BodyInit | undefined; - - if (opts.body !== undefined && method !== "GET") { - headers["Content-Type"] = "application/json"; - body = JSON.stringify(opts.body); - } - - const res = await fetch(url, { method, headers, body }); - - if (!res.ok) { - let errorBody: unknown; - try { - errorBody = await res.json(); - } catch { - errorBody = await res.text().catch(() => undefined); - } - throw new ControlPlaneError( - res.status, - `control plane ${method} ${path} failed: ${res.status}`, - errorBody, - ); - } - - if (res.status === 204) return undefined as T; - - // Some 200 responses have empty bodies (notably `null` for a missing - // findOne). JSON parse and pass through. - const text = await res.text(); - if (text === "" || text === "null") return null as T; - return JSON.parse(text) as T; -} - -/** - * User-context call: authenticated with the user's session cookie. - * - * Used by server actions on behalf of the logged-in dashboard user. - * Pass the request headers from `next/headers` so the cookie flows - * through. - */ -export async function controlPlaneUserCall( - path: string, - cookieHeader: string, - opts: CallOptions = {}, -): Promise { - const baseUrl = opts.baseUrl ?? CONTROL_PLANE_URL; - const url = `${baseUrl}${path}`; - const method = opts.method ?? "GET"; - - const headers: Record = { - Cookie: cookieHeader, - ...opts.headers, - }; - let body: BodyInit | undefined; - - if (opts.body !== undefined && method !== "GET") { - headers["Content-Type"] = "application/json"; - body = JSON.stringify(opts.body); - } - - const res = await fetch(url, { method, headers, body }); - - if (!res.ok) { - let errorBody: unknown; - try { - errorBody = await res.json(); - } catch { - errorBody = undefined; - } - throw new ControlPlaneError( - res.status, - `control plane ${method} ${path} failed: ${res.status}`, - errorBody, - ); - } - - if (res.status === 204) return undefined as T; - - const text = await res.text(); - if (text === "") return null as T; - return JSON.parse(text) as T; -} - -/** - * Pre-baked typed wrappers for common control-plane operations. - */ -export const controlPlane = { - /** Identity CRUD endpoints used by Better Auth's custom adapter. */ - identity: { - create: >(model: string, data: Record) => - controlPlaneServiceCall(`/admin/internal/identity/${model}`, { - method: "POST", - body: { data }, - }), - - findOne: >( - model: string, - where: Array<{ field: string; value: unknown; operator?: string }>, - ) => - controlPlaneServiceCall(`/admin/internal/identity/${model}/find`, { - method: "POST", - body: { where }, - }), - - findMany: >( - model: string, - where: Array<{ field: string; value: unknown; operator?: string }>, - opts: { - limit?: number; - offset?: number; - sortBy?: Array<{ field: string; direction: "asc" | "desc" }>; - } = {}, - ) => - controlPlaneServiceCall(`/admin/internal/identity/${model}/find-many`, { - method: "POST", - body: { - where, - limit: opts.limit, - offset: opts.offset, - sortBy: opts.sortBy, - }, - }), - - update: >( - model: string, - where: Array<{ field: string; value: unknown; operator?: string }>, - update: Record, - ) => - controlPlaneServiceCall(`/admin/internal/identity/${model}`, { - method: "PATCH", - body: { where, update }, - }), - - updateMany: ( - model: string, - where: Array<{ field: string; value: unknown; operator?: string }>, - update: Record, - ) => - controlPlaneServiceCall<{ count: number }>( - `/admin/internal/identity/${model}/many`, - { method: "PATCH", body: { where, update } }, - ), - - delete: (model: string, where: Array<{ field: string; value: unknown; operator?: string }>) => - controlPlaneServiceCall<{ count: number }>(`/admin/internal/identity/${model}`, { - method: "DELETE", - body: { where }, - }), - - deleteMany: (model: string, where: Array<{ field: string; value: unknown; operator?: string }>) => - controlPlaneServiceCall<{ count: number }>( - `/admin/internal/identity/${model}/many`, - { method: "DELETE", body: { where } }, - ), - - count: (model: string, where: Array<{ field: string; value: unknown; operator?: string }>) => - controlPlaneServiceCall<{ count: number }>(`/admin/internal/identity/${model}/count`, { - method: "POST", - body: { where }, - }), - }, - - /** OAuth endpoints called by the dashboard. */ - oauth: { - validateRequest: (params: { - client_id: string; - redirect_uri: string; - scope: string; - }) => - controlPlaneServiceCall<{ - client: { - client_id: string; - name: string; - logo_uri: string; - client_uri: string; - policy_uri: string; - tos_uri: string; - }; - scopes: string[]; - }>(`/oauth/internal/validate-request`, { - method: "POST", - body: params, - }), - - issueCode: (params: { - client_id: string; - user_id: string; - org_id: string; - scopes: string[]; - redirect_uri: string; - code_challenge: string; - code_challenge_method: string; - state?: string; - }) => - controlPlaneServiceCall<{ - code: string; - redirect_uri: string; - state?: string; - }>(`/oauth/internal/issue-code`, { - method: "POST", - body: params, - }), - - /** - * RFC 7662 token introspection. - * - * Called by the MCP route on every request to validate the bearer - * token. Returns claims if active, `{active: false}` otherwise. - */ - introspect: async ( - token: string, - ): Promise< - | { active: false } - | { - active: true; - sub: string; - client_id: string; - org_id: string; - scope: string; - exp: number; - iat: number; - iss: string; - aud: string; - jti: string; - } - > => { - if (!SERVICE_TOKEN) { - throw new ControlPlaneError(503, "service token not configured"); - } - const res = await fetch(`${CONTROL_PLANE_URL}/oauth/introspect`, { - method: "POST", - headers: { - Authorization: `Bearer ${SERVICE_TOKEN}`, - "Content-Type": "application/x-www-form-urlencoded", - }, - body: new URLSearchParams({ token }).toString(), - }); - if (!res.ok) { - throw new ControlPlaneError(res.status, "introspect failed"); - } - return res.json(); - }, - }, -}; - -/** Convenience: the configured control plane base URL. */ -export function controlPlaneUrl(): string { - return CONTROL_PLANE_URL; -} +export * from "@vectorless/control-plane"; diff --git a/apps/web/package.json b/apps/web/package.json index d4b189f..fdbaa2b 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -31,6 +31,7 @@ "@radix-ui/react-tabs": "^1.1.12", "@radix-ui/react-tooltip": "^1.2.7", "@vectorless/shared": "workspace:*", + "@vectorless/control-plane": "workspace:*", "@xyflow/react": "^12.10.2", "autoprefixer": "^10.4.21", "class-variance-authority": "^0.7.1", diff --git a/packages/control-plane/package.json b/packages/control-plane/package.json new file mode 100644 index 0000000..b46bbe2 --- /dev/null +++ b/packages/control-plane/package.json @@ -0,0 +1,26 @@ +{ + "name": "@vectorless/control-plane", + "version": "0.1.0", + "private": true, + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.js" + } + }, + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "typecheck": "tsc --noEmit", + "lint": "tsc --noEmit" + }, + "devDependencies": { + "@types/node": "^20.17.0", + "tsup": "^8.5.0", + "typescript": "^5.9.0" + } +} diff --git a/packages/control-plane/src/index.ts b/packages/control-plane/src/index.ts new file mode 100644 index 0000000..0e36e56 --- /dev/null +++ b/packages/control-plane/src/index.ts @@ -0,0 +1,326 @@ +/** + * Control plane HTTP client. + * + * The control plane (vectorless-control-plane, Go) is the single source + * of truth for users, sessions, orgs, API keys, OAuth tokens, billing, + * and the document API proxy. The dashboard talks to it for everything + * backend-y. + * + * Two auth contexts: + * + * - Service token: used for /admin/internal/* endpoints. The dashboard + * proves it's the dashboard. Used by Better Auth's custom adapter + * and by OAuth consent server actions. + * + * - Session cookie: used for /admin/v1/* endpoints called on behalf + * of a logged-in user. The browser's vls_session cookie is + * forwarded. + * + * This module never imports React or Next.js — it's pure fetch wrapper. + * Callers (server actions, route handlers) supply the request context. + */ + +const CONTROL_PLANE_URL = + process.env.CONTROL_PLANE_URL || + process.env.VECTORLESS_API_URL || + "http://localhost:9090"; + +const SERVICE_TOKEN = process.env.CONTROL_PLANE_SERVICE_TOKEN || ""; + +export class ControlPlaneError extends Error { + constructor( + public readonly status: number, + message: string, + public readonly body?: unknown, + ) { + super(message); + this.name = "ControlPlaneError"; + } +} + +interface CallOptions { + /** HTTP method. Defaults to POST. */ + method?: "GET" | "POST" | "PATCH" | "DELETE"; + /** JSON body — serialized for non-GET. */ + body?: unknown; + /** Override base URL (rarely needed). */ + baseUrl?: string; + /** Additional headers. */ + headers?: Record; +} + +/** + * Service-context call: authenticated with the dashboard service token. + * + * Used by Better Auth's custom adapter (identity CRUD) and by OAuth + * consent server actions (validate-request, issue-code, introspect). + * + * Throws ControlPlaneError on non-2xx responses. + */ +export async function controlPlaneServiceCall( + path: string, + opts: CallOptions = {}, +): Promise { + if (!SERVICE_TOKEN) { + throw new ControlPlaneError( + 503, + "CONTROL_PLANE_SERVICE_TOKEN is not configured", + ); + } + + const baseUrl = opts.baseUrl ?? CONTROL_PLANE_URL; + const url = `${baseUrl}${path}`; + const method = opts.method ?? "POST"; + + const headers: Record = { + Authorization: `Bearer ${SERVICE_TOKEN}`, + ...opts.headers, + }; + let body: BodyInit | undefined; + + if (opts.body !== undefined && method !== "GET") { + headers["Content-Type"] = "application/json"; + body = JSON.stringify(opts.body); + } + + const res = await fetch(url, { method, headers, body }); + + if (!res.ok) { + let errorBody: unknown; + try { + errorBody = await res.json(); + } catch { + errorBody = await res.text().catch(() => undefined); + } + throw new ControlPlaneError( + res.status, + `control plane ${method} ${path} failed: ${res.status}`, + errorBody, + ); + } + + if (res.status === 204) return undefined as T; + + // Some 200 responses have empty bodies (notably `null` for a missing + // findOne). JSON parse and pass through. + const text = await res.text(); + if (text === "" || text === "null") return null as T; + return JSON.parse(text) as T; +} + +/** + * User-context call: authenticated with the user's session cookie. + * + * Used by server actions on behalf of the logged-in dashboard user. + * Pass the request headers from `next/headers` so the cookie flows + * through. + */ +export async function controlPlaneUserCall( + path: string, + cookieHeader: string, + opts: CallOptions = {}, +): Promise { + const baseUrl = opts.baseUrl ?? CONTROL_PLANE_URL; + const url = `${baseUrl}${path}`; + const method = opts.method ?? "GET"; + + const headers: Record = { + Cookie: cookieHeader, + ...opts.headers, + }; + let body: BodyInit | undefined; + + if (opts.body !== undefined && method !== "GET") { + headers["Content-Type"] = "application/json"; + body = JSON.stringify(opts.body); + } + + const res = await fetch(url, { method, headers, body }); + + if (!res.ok) { + let errorBody: unknown; + try { + errorBody = await res.json(); + } catch { + errorBody = undefined; + } + throw new ControlPlaneError( + res.status, + `control plane ${method} ${path} failed: ${res.status}`, + errorBody, + ); + } + + if (res.status === 204) return undefined as T; + + const text = await res.text(); + if (text === "") return null as T; + return JSON.parse(text) as T; +} + +/** + * Pre-baked typed wrappers for common control-plane operations. + */ +export const controlPlane = { + /** Identity CRUD endpoints used by Better Auth's custom adapter. */ + identity: { + create: >(model: string, data: Record) => + controlPlaneServiceCall(`/admin/internal/identity/${model}`, { + method: "POST", + body: { data }, + }), + + findOne: >( + model: string, + where: Array<{ field: string; value: unknown; operator?: string }>, + ) => + controlPlaneServiceCall(`/admin/internal/identity/${model}/find`, { + method: "POST", + body: { where }, + }), + + findMany: >( + model: string, + where: Array<{ field: string; value: unknown; operator?: string }>, + opts: { + limit?: number; + offset?: number; + sortBy?: Array<{ field: string; direction: "asc" | "desc" }>; + } = {}, + ) => + controlPlaneServiceCall(`/admin/internal/identity/${model}/find-many`, { + method: "POST", + body: { + where, + limit: opts.limit, + offset: opts.offset, + sortBy: opts.sortBy, + }, + }), + + update: >( + model: string, + where: Array<{ field: string; value: unknown; operator?: string }>, + update: Record, + ) => + controlPlaneServiceCall(`/admin/internal/identity/${model}`, { + method: "PATCH", + body: { where, update }, + }), + + updateMany: ( + model: string, + where: Array<{ field: string; value: unknown; operator?: string }>, + update: Record, + ) => + controlPlaneServiceCall<{ count: number }>( + `/admin/internal/identity/${model}/many`, + { method: "PATCH", body: { where, update } }, + ), + + delete: (model: string, where: Array<{ field: string; value: unknown; operator?: string }>) => + controlPlaneServiceCall<{ count: number }>(`/admin/internal/identity/${model}`, { + method: "DELETE", + body: { where }, + }), + + deleteMany: (model: string, where: Array<{ field: string; value: unknown; operator?: string }>) => + controlPlaneServiceCall<{ count: number }>( + `/admin/internal/identity/${model}/many`, + { method: "DELETE", body: { where } }, + ), + + count: (model: string, where: Array<{ field: string; value: unknown; operator?: string }>) => + controlPlaneServiceCall<{ count: number }>(`/admin/internal/identity/${model}/count`, { + method: "POST", + body: { where }, + }), + }, + + /** OAuth endpoints called by the dashboard. */ + oauth: { + validateRequest: (params: { + client_id: string; + redirect_uri: string; + scope: string; + }) => + controlPlaneServiceCall<{ + client: { + client_id: string; + name: string; + logo_uri: string; + client_uri: string; + policy_uri: string; + tos_uri: string; + }; + scopes: string[]; + }>(`/oauth/internal/validate-request`, { + method: "POST", + body: params, + }), + + issueCode: (params: { + client_id: string; + user_id: string; + org_id: string; + scopes: string[]; + redirect_uri: string; + code_challenge: string; + code_challenge_method: string; + state?: string; + }) => + controlPlaneServiceCall<{ + code: string; + redirect_uri: string; + state?: string; + }>(`/oauth/internal/issue-code`, { + method: "POST", + body: params, + }), + + /** + * RFC 7662 token introspection. + * + * Called by the MCP route on every request to validate the bearer + * token. Returns claims if active, `{active: false}` otherwise. + */ + introspect: async ( + token: string, + ): Promise< + | { active: false } + | { + active: true; + sub: string; + client_id: string; + org_id: string; + scope: string; + exp: number; + iat: number; + iss: string; + aud: string; + jti: string; + } + > => { + if (!SERVICE_TOKEN) { + throw new ControlPlaneError(503, "service token not configured"); + } + const res = await fetch(`${CONTROL_PLANE_URL}/oauth/introspect`, { + method: "POST", + headers: { + Authorization: `Bearer ${SERVICE_TOKEN}`, + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams({ token }).toString(), + }); + if (!res.ok) { + throw new ControlPlaneError(res.status, "introspect failed"); + } + return res.json(); + }, + }, +}; + +/** Convenience: the configured control plane base URL. */ +export function controlPlaneUrl(): string { + return CONTROL_PLANE_URL; +} diff --git a/packages/control-plane/tsconfig.json b/packages/control-plane/tsconfig.json new file mode 100644 index 0000000..6abc014 --- /dev/null +++ b/packages/control-plane/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "lib": ["ES2022", "dom"], + "types": ["node"] + }, + "include": ["src"] +} diff --git a/packages/control-plane/tsup.config.ts b/packages/control-plane/tsup.config.ts new file mode 100644 index 0000000..38cc561 --- /dev/null +++ b/packages/control-plane/tsup.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/index.ts"], + format: ["cjs", "esm"], + dts: true, + clean: true, + splitting: false, + sourcemap: true, + target: "es2022", +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5643b88..7513201 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -46,7 +46,7 @@ importers: version: 12.40.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) next: specifier: ^15.4.9 - version: 15.5.14(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 15.5.14(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) postcss: specifier: ^8.5.6 version: 8.5.15 @@ -80,7 +80,7 @@ importers: version: 9.39.1(jiti@2.7.0) eslint-config-next: specifier: 16.0.8 - version: 16.0.8(@typescript-eslint/parser@8.58.1(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3) + version: 16.0.8(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3) firebase-tools: specifier: ^15.0.0 version: 15.13.0(@types/node@20.19.39)(typescript@5.9.3) @@ -101,13 +101,13 @@ importers: version: 0.0.76(@types/react@19.2.17)(react@19.2.7) fumadocs-core: specifier: 16.9.3 - version: 16.9.3(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.21.0(react@19.2.7))(next@16.2.7(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3) + version: 16.9.3(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.21.0(react@19.2.7))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@3.25.76) fumadocs-mdx: specifier: 15.0.11 - version: 15.0.11(@types/mdast@4.0.4)(@types/mdx@2.0.14)(@types/react@19.2.17)(fumadocs-core@16.9.3(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.21.0(react@19.2.7))(next@16.2.7(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3))(next@16.2.7(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)(vite@7.3.2(@types/node@25.9.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.16.9)(tsx@4.21.0)(yaml@2.8.3)) + version: 15.0.11(@types/mdast@4.0.4)(@types/mdx@2.0.14)(@types/react@19.2.17)(fumadocs-core@16.9.3(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.21.0(react@19.2.7))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@3.25.76))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)(vite@7.3.2(@types/node@25.9.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.16.9)(tsx@4.21.0)(yaml@2.8.3)) fumadocs-ui: specifier: 16.9.3 - version: 16.9.3(@tailwindcss/oxide@4.3.1)(@types/mdx@2.0.14)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(fumadocs-core@16.9.3(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.21.0(react@19.2.7))(next@16.2.7(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3))(next@16.2.7(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(tailwindcss@4.3.1) + version: 16.9.3(@tailwindcss/oxide@4.3.1)(@types/mdx@2.0.14)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(fumadocs-core@16.9.3(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.21.0(react@19.2.7))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@3.25.76))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(tailwindcss@4.3.1) gsap: specifier: ^3.15.0 version: 3.15.0 @@ -116,7 +116,7 @@ importers: version: 1.21.0(react@19.2.7) next: specifier: 16.2.7 - version: 16.2.7(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) react: specifier: ^19.2.7 version: 19.2.7 @@ -150,7 +150,7 @@ importers: version: 9.39.4(jiti@2.7.0) eslint-config-next: specifier: 16.2.7 - version: 16.2.7(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3) + version: 16.2.7(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3) postcss: specifier: ^8.5.15 version: 8.5.15 @@ -220,6 +220,9 @@ importers: '@radix-ui/react-tooltip': specifier: ^1.2.7 version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@vectorless/control-plane': + specifier: workspace:* + version: link:../../packages/control-plane '@vectorless/shared': specifier: workspace:* version: link:../../packages/shared @@ -252,7 +255,7 @@ importers: version: 12.38.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) next: specifier: ^15.4.9 - version: 15.5.14(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 15.5.14(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -339,6 +342,18 @@ importers: specifier: 5.9.3 version: 5.9.3 + packages/control-plane: + devDependencies: + '@types/node': + specifier: ^20.17.0 + version: 20.19.39 + tsup: + specifier: ^8.5.0 + version: 8.5.1(jiti@2.7.0)(postcss@8.5.15)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3) + typescript: + specifier: ^5.9.0 + version: 5.9.3 + packages/shared: dependencies: zod: @@ -11020,7 +11035,7 @@ snapshots: cookie: 1.1.1 esbuild: 0.25.4 express: 5.2.1 - next: 15.5.14(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + next: 15.5.14(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) path-to-regexp: 6.3.0 urlpattern-polyfill: 10.1.0 yaml: 2.8.3 @@ -11037,7 +11052,7 @@ snapshots: comment-json: 4.6.2 enquirer: 2.4.1 glob: 12.0.0 - next: 15.5.14(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + next: 15.5.14(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) ts-tqdm: 0.8.6 wrangler: 4.81.1 yargs: 18.0.0 @@ -12645,7 +12660,7 @@ snapshots: '@types/node-fetch@2.6.13': dependencies: - '@types/node': 25.9.4 + '@types/node': 20.19.39 form-data: 4.0.5 '@types/node@18.19.130': @@ -12662,13 +12677,13 @@ snapshots: '@types/pg@8.11.6': dependencies: - '@types/node': 25.9.4 + '@types/node': 20.19.39 pg-protocol: 1.13.0 pg-types: 4.1.0 '@types/pg@8.20.0': dependencies: - '@types/node': 25.9.4 + '@types/node': 20.19.39 pg-protocol: 1.13.0 pg-types: 2.2.0 optional: true @@ -14233,33 +14248,13 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-config-next@16.0.8(@typescript-eslint/parser@8.58.1(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3): - dependencies: - '@next/eslint-plugin-next': 16.0.8 - eslint: 9.39.1(jiti@2.7.0) - eslint-import-resolver-node: 0.3.10 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.7.0)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.58.1(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.7.0)) - eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.1(jiti@2.7.0)) - eslint-plugin-react: 7.37.5(eslint@9.39.1(jiti@2.7.0)) - eslint-plugin-react-hooks: 7.0.1(eslint@9.39.1(jiti@2.7.0)) - globals: 16.4.0 - typescript-eslint: 8.58.1(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3) - optionalDependencies: - typescript: 5.9.3 - transitivePeerDependencies: - - '@typescript-eslint/parser' - - eslint-import-resolver-webpack - - eslint-plugin-import-x - - supports-color - eslint-config-next@16.0.8(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3): dependencies: '@next/eslint-plugin-next': 16.0.8 eslint: 9.39.1(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.7.0)) - eslint-plugin-import: 2.32.0(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.7.0)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(eslint@9.39.1(jiti@2.7.0)))(eslint@9.39.1(jiti@2.7.0)) + eslint-plugin-import: 2.32.0(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@9.39.1(jiti@2.7.0)))(eslint@9.39.1(jiti@2.7.0)))(eslint@9.39.1(jiti@2.7.0)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.1(jiti@2.7.0)) eslint-plugin-react: 7.37.5(eslint@9.39.1(jiti@2.7.0)) eslint-plugin-react-hooks: 7.0.1(eslint@9.39.1(jiti@2.7.0)) @@ -14273,13 +14268,13 @@ snapshots: - eslint-plugin-import-x - supports-color - eslint-config-next@16.2.7(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3): + eslint-config-next@16.2.7(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3): dependencies: '@next/eslint-plugin-next': 16.2.7 eslint: 9.39.4(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)) - eslint-plugin-import: 2.32.0(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.7.0)) eslint-plugin-react: 7.37.5(eslint@9.39.4(jiti@2.7.0)) eslint-plugin-react-hooks: 7.0.1(eslint@9.39.4(jiti@2.7.0)) @@ -14301,87 +14296,58 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@9.39.1(jiti@2.7.0)))(eslint@9.39.1(jiti@2.7.0)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3 - eslint: 9.39.4(jiti@2.7.0) + eslint: 9.39.1(jiti@2.7.0) get-tsconfig: 4.13.7 is-bun-module: 2.0.0 stable-hash: 0.0.5 tinyglobby: 0.2.16 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)) + eslint-plugin-import: 2.32.0(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@9.39.1(jiti@2.7.0)))(eslint@9.39.1(jiti@2.7.0)))(eslint@9.39.1(jiti@2.7.0)) transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.7.0)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3 - eslint: 9.39.1(jiti@2.7.0) + eslint: 9.39.4(jiti@2.7.0) get-tsconfig: 4.13.7 is-bun-module: 2.0.0 stable-hash: 0.0.5 tinyglobby: 0.2.16 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.58.1(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.7.0)) - transitivePeerDependencies: - - supports-color - - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.58.1(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.7.0)): - dependencies: - debug: 3.2.7 - optionalDependencies: - '@typescript-eslint/parser': 8.58.1(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3) - eslint: 9.39.1(jiti@2.7.0) - eslint-import-resolver-node: 0.3.10 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.7.0)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)): dependencies: debug: 3.2.7 optionalDependencies: + '@typescript-eslint/parser': 8.58.1(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3) eslint: 9.39.4(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.1(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.7.0)): + eslint-module-utils@2.12.1(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@9.39.1(jiti@2.7.0)))(eslint@9.39.1(jiti@2.7.0)))(eslint@9.39.1(jiti@2.7.0)): dependencies: - '@rtsao/scc': 1.1.0 - array-includes: 3.1.9 - array.prototype.findlastindex: 1.2.6 - array.prototype.flat: 1.3.3 - array.prototype.flatmap: 1.3.3 debug: 3.2.7 - doctrine: 2.1.0 + optionalDependencies: eslint: 9.39.1(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.58.1(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.7.0)) - hasown: 2.0.2 - is-core-module: 2.16.1 - is-glob: 4.0.3 - minimatch: 3.1.5 - object.fromentries: 2.0.8 - object.groupby: 1.0.3 - object.values: 1.2.1 - semver: 6.3.1 - string.prototype.trimend: 1.0.9 - tsconfig-paths: 3.15.0 - optionalDependencies: - '@typescript-eslint/parser': 8.58.1(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(eslint@9.39.1(jiti@2.7.0)))(eslint@9.39.1(jiti@2.7.0)) transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.32.0(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -14392,7 +14358,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.4(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.12.1(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -14403,12 +14369,14 @@ snapshots: semver: 6.3.1 string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.58.1(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.32.0(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.7.0)): + eslint-plugin-import@2.32.0(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@9.39.1(jiti@2.7.0)))(eslint@9.39.1(jiti@2.7.0)))(eslint@9.39.1(jiti@2.7.0)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -14419,7 +14387,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.1(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.58.1(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.7.0)) + eslint-module-utils: 2.12.1(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@9.39.1(jiti@2.7.0)))(eslint@9.39.1(jiti@2.7.0)))(eslint@9.39.1(jiti@2.7.0)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -15108,7 +15076,7 @@ snapshots: fsevents@2.3.3: optional: true - fumadocs-core@16.9.3(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.21.0(react@19.2.7))(next@16.2.7(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3): + fumadocs-core@16.9.3(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.21.0(react@19.2.7))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@3.25.76): dependencies: '@orama/orama': 3.1.18 estree-util-value-to-estree: 3.5.0 @@ -15134,21 +15102,21 @@ snapshots: '@types/mdast': 4.0.4 '@types/react': 19.2.17 lucide-react: 1.21.0(react@19.2.7) - next: 16.2.7(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + next: 16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) - zod: 4.4.3 + zod: 3.25.76 transitivePeerDependencies: - supports-color - fumadocs-mdx@15.0.11(@types/mdast@4.0.4)(@types/mdx@2.0.14)(@types/react@19.2.17)(fumadocs-core@16.9.3(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.21.0(react@19.2.7))(next@16.2.7(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3))(next@16.2.7(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)(vite@7.3.2(@types/node@25.9.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.16.9)(tsx@4.21.0)(yaml@2.8.3)): + fumadocs-mdx@15.0.11(@types/mdast@4.0.4)(@types/mdx@2.0.14)(@types/react@19.2.17)(fumadocs-core@16.9.3(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.21.0(react@19.2.7))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@3.25.76))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)(vite@7.3.2(@types/node@25.9.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.16.9)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@mdx-js/mdx': 3.1.1 '@standard-schema/spec': 1.1.0 chokidar: 5.0.0 esbuild: 0.28.1 estree-util-value-to-estree: 3.5.0 - fumadocs-core: 16.9.3(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.21.0(react@19.2.7))(next@16.2.7(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3) + fumadocs-core: 16.9.3(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.21.0(react@19.2.7))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@3.25.76) js-yaml: 4.2.0 mdast-util-mdx: 3.0.0 picocolors: 1.1.1 @@ -15164,13 +15132,13 @@ snapshots: '@types/mdast': 4.0.4 '@types/mdx': 2.0.14 '@types/react': 19.2.17 - next: 16.2.7(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + next: 16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) react: 19.2.7 vite: 7.3.2(@types/node@25.9.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.16.9)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - supports-color - fumadocs-ui@16.9.3(@tailwindcss/oxide@4.3.1)(@types/mdx@2.0.14)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(fumadocs-core@16.9.3(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.21.0(react@19.2.7))(next@16.2.7(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3))(next@16.2.7(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(tailwindcss@4.3.1): + fumadocs-ui@16.9.3(@tailwindcss/oxide@4.3.1)(@types/mdx@2.0.14)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(fumadocs-core@16.9.3(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.21.0(react@19.2.7))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@3.25.76))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(tailwindcss@4.3.1): dependencies: '@fumadocs/tailwind': 0.0.5(@tailwindcss/oxide@4.3.1)(tailwindcss@4.3.1) '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -15184,7 +15152,7 @@ snapshots: '@radix-ui/react-slot': 1.2.4(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) class-variance-authority: 0.7.1 - fumadocs-core: 16.9.3(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.21.0(react@19.2.7))(next@16.2.7(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3) + fumadocs-core: 16.9.3(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.21.0(react@19.2.7))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@3.25.76) lucide-react: 1.21.0(react@19.2.7) motion: 12.40.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) next-themes: 0.4.6(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -15199,7 +15167,7 @@ snapshots: optionalDependencies: '@types/mdx': 2.0.14 '@types/react': 19.2.17 - next: 16.2.7(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + next: 16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) transitivePeerDependencies: - '@emotion/is-prop-valid' - '@tailwindcss/oxide' @@ -16894,7 +16862,7 @@ snapshots: react: 19.2.7 react-dom: 19.2.7(react@19.2.7) - next@15.5.14(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + next@15.5.14(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: '@next/env': 15.5.14 '@swc/helpers': 0.5.15 @@ -16918,7 +16886,7 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@16.2.7(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: '@next/env': 16.2.7 '@swc/helpers': 0.5.15 @@ -17404,7 +17372,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 25.9.4 + '@types/node': 20.19.39 long: 5.3.2 proxy-addr@2.0.7: