diff --git a/.dockerignore b/.dockerignore index 9885e51..f475ab5 100644 --- a/.dockerignore +++ b/.dockerignore @@ -52,3 +52,8 @@ apps/backend/storage/ Dockerfile* docker-compose*.yml .dockerignore + +# ---------- Non-runtime workspaces ---------- +# The MCP server is a local development companion; nothing in the image depends +# on it, and excluding it keeps its edits out of the build context cache key. +apps/mcp diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..08fec27 --- /dev/null +++ b/.mcp.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "wordyme": { + "command": "pnpm", + "args": ["--filter", "@repo/mcp", "--silent", "start"] + } + } +} diff --git a/MCP.md b/MCP.md new file mode 100644 index 0000000..fd88792 --- /dev/null +++ b/MCP.md @@ -0,0 +1,116 @@ +# Connect Claude to your wiki + +WordyMe ships an [MCP](https://modelcontextprotocol.io) server. Point Claude Code or +Claude Desktop at your wiki and Claude can search it, read any page, write new notes and +revise existing ones — in plain Markdown, as your own account, with every change recorded +as a restorable revision. + +```text +You: Read my DOCKERHUB document and create a note called "MCP Test" + with a summary table and a mermaid diagram of the release flow. + +Claude: Created "MCP Test" in Color Workspace — open it in WordyMe: + the table renders, the diagram draws, Revisions History says "via Claude". +``` + +## Why this one is different + +**It speaks the editor's own language.** Claude writes Markdown; WordyMe's editor converts +it with the exact transformers it uses when _you_ type Markdown. Headings, lists, tables, +code blocks and even Mermaid diagram fences become real rich-text nodes — not an +approximation. Reading goes the other way through the same transformers, so Claude sees +your pages as faithful Markdown. + +**It is you, not a second user.** WordyMe is deliberately single-user. The server signs in +with your credentials and acts on your behalf — the same delegation model every +connected app uses — so there is no "AI account" to manage and no sharing model to bolt +on. + +**Nothing is ever lost.** Every content write — creating a page or revising one — is saved +as a new revision named **"via Claude"**. Earlier revisions stay in Revisions History and +can be restored with one click. Moving a page only changes where it sits in the tree. The +server never deletes anything. + +**Local by design.** It runs on your machine, next to your clone of the repository. +Credentials live in a git-ignored file you control. Nothing about the WordyMe app or its +Docker image changes. + +## What Claude can do + +| Tool | What it does | +| ------------------ | -------------------------------------------------------------- | +| `list_spaces` | List your Spaces | +| `list_documents` | List the documents and folders of one Space | +| `search_documents` | Full-text search across the wiki, with snippets | +| `read_document` | Return a page as Markdown | +| `create_note` | Create a note from Markdown, optionally inside a folder | +| `update_document` | Replace a page body with new Markdown, saved as a new revision | +| `move_document` | Move a page or folder into a folder, or back to the Space root | + +Things people ask once it is connected: + +- _"Search my wiki for everything about backups and summarise it."_ +- _"Read my meeting notes from this week and draft the follow-up email."_ +- _"Turn this rough list into a properly structured page with a table."_ +- _"Add a Mermaid diagram of the deployment flow to the Docker page."_ + +## Setup in three steps + +Requires a running WordyMe and a clone of this repository. + +1. **Credentials** — copy the example and fill in your login: + + ```bash + cp apps/mcp/.env.example apps/mcp/.env + ``` + + `WORDYME_URL` is where the API answers: `http://localhost:3000` for `pnpm dev`, + `http://localhost:8080` for Docker, or your instance URL. + +2. **Claude Code** (desktop app or CLI) — nothing to install. The repository carries a + project-level `.mcp.json`; open the repository in Claude Code and approve the + `wordyme` server when prompted. + +3. **Claude Desktop** — add to `claude_desktop_config.json`: + + ```json + { + "mcpServers": { + "wordyme": { + "command": "pnpm", + "args": ["--dir", "/path/to/WordyMe", "--filter", "@repo/mcp", "--silent", "start"] + } + } + } + ``` + +Then ask Claude to list your Spaces. + +## How it works + +- **Conversion** runs a headless copy of the WordyMe editor in Node — the same node list + and the same Markdown transformers the browser uses — so output is what the editor + itself would have produced. +- **Authentication** uses the backend's existing Better Auth bearer-token support: one + sign-in at first use, an expired session re-signs transparently. +- **Writes** go through the normal document and revision APIs; MCP-created notes are + structurally identical to notes created in the editor. + +The implementation lives in [`apps/mcp`](apps/mcp/README.md). + +## Honest limits + +- Fidelity is bounded by Markdown. Standard constructs and the editor's fenced + extensions (` ```mermaid `) round-trip; exotic nodes such as sketches, scores and + stickies degrade to plain text when read. +- Nested list items need 4-space indentation. +- Claude acts with your full account; the server is meant for your own machine. + +## What's next + +- **Built into the image.** An opt-in `/mcp` endpoint inside the WordyMe container, so + self-hosters connect Claude with a URL and no local setup. +- **Proper OAuth.** WordyMe as the identity provider — Claude signs in through WordyMe's + own consent screen, with revocable tokens and read-only scopes. +- **claude.ai.** With the two above, publicly reachable instances connect from the web app + as well. diff --git a/README.md b/README.md index 97637ae..dd08b5e 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,7 @@ This project uses [Turborepo](https://turborepo.com) for managing a monorepo wit - User profiles with avatar and cover images - Editor settings and preferences - Real-time updates via WebSocket +- MCP server for Claude Code and Claude Desktop — see [MCP.md](MCP.md) ### Performance @@ -185,6 +186,13 @@ frontend URL. The API reference is at . Already running an older version? Ports and origins changed; see [Upgrading](DOCKER.md#upgrading-from-a-version-before-the-all-in-one-image). +## Connect Claude (MCP) + +WordyMe™ ships an MCP server, so Claude Code and Claude Desktop can search your wiki, read +any page and write notes — in Markdown, as your own account, with every content change +saved as a restorable revision. See [MCP.md](MCP.md) for the three-step setup and what it +can do. + ## Development Workflow ### Prerequisites diff --git a/apps/mcp/.env.example b/apps/mcp/.env.example new file mode 100644 index 0000000..7d9b19c --- /dev/null +++ b/apps/mcp/.env.example @@ -0,0 +1,6 @@ +# Copy to .env (git-ignored). Variables already set in the environment take precedence. +# The origin the WordyMe API answers on: http://localhost:3000 for `pnpm dev`, +# http://localhost:8080 for Docker, or your instance URL. +WORDYME_URL=http://localhost:3000 +WORDYME_EMAIL=you@example.com +WORDYME_PASSWORD=your-password diff --git a/apps/mcp/README.md b/apps/mcp/README.md new file mode 100644 index 0000000..5faab2e --- /dev/null +++ b/apps/mcp/README.md @@ -0,0 +1,74 @@ +# @repo/mcp + +A local MCP (Model Context Protocol) server that exposes a WordyMe instance to AI +clients such as Claude Code and Claude Desktop. Markdown in, Markdown out — content is +converted with the editor's own transformers, so what the AI writes is exactly what the +editor would produce if you typed the same Markdown yourself. + +The server acts **as your account** (delegation — WordyMe stays single-user). Every content +change it makes is saved as a new revision named **"via Claude"**, restorable from +Revisions History; moving a document only changes its place in the tree. + +## Tools + +| Tool | What it does | +| ------------------ | ------------------------------------------------------------- | +| `list_spaces` | List the Spaces in the wiki | +| `list_documents` | List one Space's documents and folders | +| `search_documents` | Full-text search with snippets | +| `read_document` | Return a document as Markdown | +| `create_note` | Create a note from Markdown | +| `update_document` | Replace a document body with new Markdown (new revision) | +| `move_document` | Move a document or folder into a folder, or to the Space root | + +## Setup + +Requires a running WordyMe instance and its owner credentials. `WORDYME_URL` is the +origin the API answers on: + +| How WordyMe runs | `WORDYME_URL` | +| ----------------------------- | ---------------------------------------------- | +| `pnpm dev` (backend directly) | `http://localhost:3000` | +| Docker / self-hosted | `http://localhost:8080` (or your instance URL) | + +### 1. Credentials + +```bash +cp apps/mcp/.env.example apps/mcp/.env +``` + +Fill in `apps/mcp/.env`. It is git-ignored and read by the server at startup; variables +already present in the environment take precedence over the file. + +### 2. Claude Code (desktop app or CLI) + +Nothing to install: the repository ships a project-level `.mcp.json` that starts the +server with `pnpm --filter @repo/mcp --silent start`. Open the repository in Claude Code +and approve the project MCP server when prompted — new sessions then have the `wordyme` +tools. Check status with `/mcp` inside a session. + +### Claude Desktop + +`claude_desktop_config.json`: + +```json +{ + "mcpServers": { + "wordyme": { + "command": "pnpm", + "args": ["--dir", "/path/to/WordyMe", "--filter", "@repo/mcp", "--silent", "start"] + } + } +} +``` + +Credentials never live in MCP client configuration or in this repository — only in +`apps/mcp/.env` (or the process environment). + +## Notes + +- `pnpm smoke` runs the Markdown round-trip self-test (no WordyMe instance needed). +- Content fidelity is bounded by Markdown: standard constructs plus the editor's fenced + extensions (```mermaid diagrams) round-trip; exotic nodes (sketches, scores, stickies) + degrade to plain text. Nested list items need 4-space indentation. +- The server never deletes anything and every write is a new revision. diff --git a/apps/mcp/eslint.config.js b/apps/mcp/eslint.config.js new file mode 100644 index 0000000..992f558 --- /dev/null +++ b/apps/mcp/eslint.config.js @@ -0,0 +1,3 @@ +import { config } from '@repo/eslint-config/base'; + +export default config; diff --git a/apps/mcp/package.json b/apps/mcp/package.json new file mode 100644 index 0000000..ee2a6fa --- /dev/null +++ b/apps/mcp/package.json @@ -0,0 +1,36 @@ +{ + "name": "@repo/mcp", + "version": "0.0.0", + "private": true, + "type": "module", + "engines": { + "node": ">=22.9.0" + }, + "scripts": { + "build": "esbuild src/index.ts --bundle --platform=node --target=node20 --format=esm --banner:js=\"import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);\" --outfile=dist/index.js --loader:.css=empty --loader:.svg=empty --loader:.png=empty --loader:.jpg=empty --loader:.woff=empty --loader:.woff2=empty --log-level=warning", + "start": "pnpm build && node --env-file-if-exists=.env dist/index.js", + "smoke": "esbuild scripts/smoke-markdown.ts --bundle --platform=node --target=node20 --format=esm --banner:js=\"import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);\" --outfile=dist/smoke.js --loader:.css=empty --loader:.svg=empty --loader:.png=empty --loader:.jpg=empty --loader:.woff=empty --loader:.woff2=empty --log-level=warning && node dist/smoke.js", + "lint": "eslint .", + "check-types": "tsc --noEmit" + }, + "dependencies": { + "@happy-dom/global-registrator": "^20.11.2", + "@lexical/markdown": "^0.41.0", + "@modelcontextprotocol/sdk": "^1.30.0", + "@repo/backend": "workspace:*", + "@repo/editor": "workspace:*", + "@repo/lib": "workspace:*", + "@repo/sdk": "workspace:*", + "axios": "^1.19.0", + "lexical": "^0.41.0", + "zod": "^3.25.76" + }, + "devDependencies": { + "@repo/eslint-config": "workspace:*", + "@repo/typescript-config": "workspace:*", + "@types/node": "^26.2.0", + "esbuild": "^0.25.12", + "eslint": "^10.8.0", + "typescript": "^6.0.3" + } +} diff --git a/apps/mcp/scripts/smoke-markdown.ts b/apps/mcp/scripts/smoke-markdown.ts new file mode 100644 index 0000000..8a28608 --- /dev/null +++ b/apps/mcp/scripts/smoke-markdown.ts @@ -0,0 +1,49 @@ +/** + * SPDX-FileCopyrightText: 2026 TeamCoderz Ltd + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { fromMarkdown, toMarkdown } from '../src/markdown.js'; + +const sample = `# Smoke Test + +Some **bold** and *italic* text with a [link](https://wordy.me). + +## A list + +- one +- two + - nested + +## A table + +| Col A | Col B | +| ----- | ----- | +| 1 | 2 | + +## Code + +\`\`\`ts +const x: number = 1; +\`\`\` + +\`\`\`mermaid +graph TD; A-->B; +\`\`\` +`; + +const state = fromMarkdown(sample); +const roundTripped = toMarkdown(state); + +const rootTypes = state.root.children.map((node) => node.type); +if (rootTypes[0] !== 'page-setup' || rootTypes[1] !== 'page') { + throw new Error(`Expected the editor's page scaffold, got root children: ${rootTypes}`); +} +if (!roundTripped.includes('## A table') || !roundTripped.includes('```mermaid')) { + throw new Error('Round-trip lost block structure'); +} + +console.log('--- root structure ---'); +console.log(rootTypes.join(' > ')); +console.log('--- markdown round-trip ---'); +console.log(roundTripped); diff --git a/apps/mcp/src/auth.ts b/apps/mcp/src/auth.ts new file mode 100644 index 0000000..af6c3f3 --- /dev/null +++ b/apps/mcp/src/auth.ts @@ -0,0 +1,73 @@ +/** + * SPDX-FileCopyrightText: 2026 TeamCoderz Ltd + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import axios from 'axios'; +import { client } from '@repo/sdk/client.ts'; + +const url = process.env.WORDYME_URL?.replace(/\/$/, ''); +const email = process.env.WORDYME_EMAIL; +const password = process.env.WORDYME_PASSWORD; + +export function assertConfig(): void { + const missing = [ + !url && 'WORDYME_URL', + !email && 'WORDYME_EMAIL', + !password && 'WORDYME_PASSWORD', + ].filter(Boolean); + if (missing.length > 0) { + throw new Error(`Missing environment variables: ${missing.join(', ')}`); + } +} + +let loginPromise: Promise | null = null; + +async function login(): Promise { + // Plain Node HTTP on purpose: the global fetch adds Sec-Fetch-* headers, which + // make Better Auth treat the call as a browser submission and demand an Origin. + const response = await axios.post<{ message?: string }>( + `${url}/api/auth/sign-in/email`, + { email, password }, + { adapter: 'http', validateStatus: () => true }, + ); + if (response.status !== 200) { + const detail = response.data?.message ? `: ${response.data.message}` : ''; + throw new Error( + `WordyMe sign-in failed (HTTP ${response.status}${detail}) — check WORDYME_EMAIL and WORDYME_PASSWORD`, + ); + } + // Provided by the backend's Better Auth bearer() plugin. + const token = response.headers['set-auth-token']; + if (typeof token !== 'string' || !token) { + throw new Error('WordyMe sign-in returned no set-auth-token header'); + } + client.defaults.headers.common.Authorization = `Bearer ${token}`; +} + +export function ensureAuth(): Promise { + loginPromise ??= login().catch((error: unknown) => { + loginPromise = null; + throw error; + }); + return loginPromise; +} + +export function configureClient(): void { + client.defaults.baseURL = `${url}/api`; + client.defaults.adapter = 'http'; + client.interceptors.response.use(undefined, async (error) => { + const config = error?.config; + if (error?.response?.status === 401 && config && !config.__retriedAfterLogin) { + config.__retriedAfterLogin = true; + loginPromise = null; + await ensureAuth(); + config.headers = { + ...config.headers, + Authorization: client.defaults.headers.common.Authorization, + }; + return client.request(config); + } + throw error; + }); +} diff --git a/apps/mcp/src/dom-shim.ts b/apps/mcp/src/dom-shim.ts new file mode 100644 index 0000000..6fa539c --- /dev/null +++ b/apps/mcp/src/dom-shim.ts @@ -0,0 +1,35 @@ +/** + * SPDX-FileCopyrightText: 2026 TeamCoderz Ltd + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { GlobalRegistrator } from '@happy-dom/global-registrator'; + +// Several editor nodes (MathLive, and the DOM-rendering decorators) expect +// browser globals at module load. Register a headless DOM before any +// @repo/editor module is imported — this file must stay the first import. +const nodeFetch = globalThis.fetch; + +if (typeof globalThis.window === 'undefined') { + GlobalRegistrator.register(); + // happy-dom also installs browser-style fetch/XMLHttpRequest that enforce the + // same-origin policy. Networking must stay Node's own. + globalThis.fetch = nodeFetch; + delete (globalThis as { XMLHttpRequest?: unknown }).XMLHttpRequest; +} + +// MathLive skips its window registration outside a real browser, but the +// editor's MathNode module reads these globals at import time. +const win = globalThis.window as unknown as Record; +win.MathfieldElement ??= class {}; +win.mathVirtualKeyboard ??= { + normalizedLayouts: Array.from({ length: 4 }, () => ({ + layers: [ + { + rows: Array.from({ length: 8 }, () => + Array.from({ length: 16 }, () => ({ variants: [] as unknown[] })), + ), + }, + ], + })), +}; diff --git a/apps/mcp/src/env.d.ts b/apps/mcp/src/env.d.ts new file mode 100644 index 0000000..6ebd879 --- /dev/null +++ b/apps/mcp/src/env.d.ts @@ -0,0 +1,12 @@ +/** + * SPDX-FileCopyrightText: 2026 TeamCoderz Ltd + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +// The editor and sdk packages are authored for Vite; these ambient +// declarations stand in for Vite's when their sources are type-checked here. +declare module '*.css'; + +interface ImportMeta { + readonly env?: Record; +} diff --git a/apps/mcp/src/index.ts b/apps/mcp/src/index.ts new file mode 100644 index 0000000..2475d85 --- /dev/null +++ b/apps/mcp/src/index.ts @@ -0,0 +1,277 @@ +/** + * SPDX-FileCopyrightText: 2026 TeamCoderz Ltd + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import './dom-shim.js'; +import { randomUUID } from 'node:crypto'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { z } from 'zod'; +import { + createDocumentWithRevision, + getDocumentByHandle, + getDocumentById, + getUserDocuments, + searchDocuments, + updateDocument, +} from '@repo/sdk/documents.ts'; +import { createRevision, getRevisionById } from '@repo/sdk/revisions.ts'; +import { generatePositionKeyBetween, getSiblings, sortByPosition } from '@repo/lib/utils/position'; +import { computeChecksum } from '@repo/editor/utils/computeChecksum'; +import { generateText } from '@repo/editor/utils/generateText'; +import { assertConfig, configureClient, ensureAuth } from './auth.js'; +import { fromMarkdown, toMarkdown } from './markdown.js'; + +const REVISION_NAME = 'via Claude'; + +type ToolResult = { content: Array<{ type: 'text'; text: string }>; isError?: boolean }; + +function ok(text: string): ToolResult { + return { content: [{ type: 'text', text }] }; +} + +function fail(message: string): ToolResult { + return { content: [{ type: 'text', text: message }], isError: true }; +} + +async function run(handler: () => Promise): Promise { + try { + await ensureAuth(); + return await handler(); + } catch (error) { + return fail(error instanceof Error ? error.message : String(error)); + } +} + +async function resolveDocument(input: { id?: string; handle?: string }) { + if (input.id) { + const { data, error } = await getDocumentById(input.id); + if (error || !data) throw new Error(`Document not found for id "${input.id}"`); + return data; + } + if (input.handle) { + const { data, error } = await getDocumentByHandle(input.handle); + if (error || !data) throw new Error(`Document not found for handle "${input.handle}"`); + return data; + } + throw new Error('Provide either "id" or "handle"'); +} + +async function markdownToRevisionFields(markdown: string) { + const state = fromMarkdown(markdown); + return { + content: JSON.stringify(state), + checksum: computeChecksum(state), + text: generateText(state), + }; +} + +const server = new McpServer({ name: 'wordyme', version: '0.1.0' }); + +server.registerTool( + 'list_spaces', + { + description: + 'List all Spaces (top-level workspaces) in the WordyMe wiki, including folder-type container spaces.', + inputSchema: {}, + }, + async () => + run(async () => { + const { data, error } = await getUserDocuments({ documentType: 'space' }); + if (error) throw new Error(error.message); + const spaces = (data ?? []).map((s) => ({ + id: s.id, + name: s.name, + parentId: s.parentId, + isFolder: s.isContainer, + })); + return ok(JSON.stringify(spaces, null, 2)); + }), +); + +server.registerTool( + 'list_documents', + { + description: + 'List the documents and folders inside one Space as a flat list; reconstruct the tree via parentId.', + inputSchema: { space_id: z.string().describe('The Space id, from list_spaces') }, + }, + async ({ space_id }) => + run(async () => { + const { data, error } = await getUserDocuments({ spaceId: space_id }); + if (error) throw new Error(error.message); + const documents = (data ?? []).map((d) => ({ + id: d.id, + name: d.name, + handle: d.handle, + parentId: d.parentId, + isFolder: d.isContainer, + updatedAt: d.updatedAt, + })); + return ok(JSON.stringify(documents, null, 2)); + }), +); + +server.registerTool( + 'search_documents', + { + description: 'Full-text search across the wiki. Returns matching documents with snippets.', + inputSchema: { + query: z.string().describe('Search terms'), + space_id: z.string().optional().describe('Restrict the search to one Space'), + limit: z.number().int().min(1).max(50).optional(), + }, + }, + async ({ query, space_id, limit }) => + run(async () => { + const { data, error } = await searchDocuments(query, limit ?? 20, space_id); + if (error) throw new Error(error.message); + return ok(JSON.stringify(data ?? [], null, 2)); + }), +); + +server.registerTool( + 'read_document', + { + description: 'Read one document and return its content as Markdown.', + inputSchema: { + id: z.string().optional().describe('Document id'), + handle: z.string().optional().describe('Document handle (URL slug), alternative to id'), + }, + }, + async (input) => + run(async () => { + const document = await resolveDocument(input); + if (!document.currentRevisionId) { + return ok(`# ${document.name}\n\n(This document has no content yet.)`); + } + const { data: revision, error } = await getRevisionById(document.currentRevisionId); + if (error || !revision) throw new Error('Could not load the document content'); + const markdown = toMarkdown(JSON.parse(revision.content)); + return ok(markdown); + }), +); + +server.registerTool( + 'create_note', + { + description: + 'Create a new note in a Space from Markdown content. Returns the new document id and handle.', + inputSchema: { + space_id: z.string().describe('The Space id, from list_spaces'), + name: z.string().describe('The note title'), + markdown: z.string().describe('The note body as Markdown'), + parent_id: z + .string() + .optional() + .describe('Optional folder (container document) id to create the note inside'), + }, + }, + async ({ space_id, name, markdown, parent_id }) => + run(async () => { + const { data: documents, error: listError } = await getUserDocuments({ spaceId: space_id }); + if (listError) throw new Error(listError.message); + const siblings = sortByPosition(getSiblings(documents ?? [], parent_id ?? null)); + const last = siblings.at(-1); + const position = last ? generatePositionKeyBetween(last.position || 'a0', null) : 'a0'; + + const revision = await markdownToRevisionFields(markdown); + const { data, error } = await createDocumentWithRevision({ + name, + icon: 'file', + parentId: parent_id ?? null, + position, + spaceId: space_id, + isContainer: false, + clientId: randomUUID(), + documentType: 'note', + revision: { ...revision, revisionName: REVISION_NAME, makeCurrentRevision: true }, + }); + if (error || !data) throw new Error(error?.message ?? 'Failed to create the note'); + return ok(JSON.stringify({ id: data.id, handle: data.handle, name: data.name }, null, 2)); + }), +); + +server.registerTool( + 'update_document', + { + description: + 'Replace a document body with new Markdown content. Saved as a new revision named "via Claude"; earlier revisions stay restorable from Revisions History.', + inputSchema: { + id: z.string().optional().describe('Document id'), + handle: z.string().optional().describe('Document handle (URL slug), alternative to id'), + markdown: z.string().describe('The full new document body as Markdown'), + }, + }, + async ({ markdown, ...input }) => + run(async () => { + const document = await resolveDocument(input); + const revision = await markdownToRevisionFields(markdown); + const { data, error } = await createRevision({ + documentId: document.id, + ...revision, + revisionName: REVISION_NAME, + makeCurrentRevision: true, + }); + if (error || !data) throw new Error(error?.message ?? 'Failed to update the document'); + return ok( + JSON.stringify({ id: document.id, handle: document.handle, revisionId: data.id }, null, 2), + ); + }), +); + +server.registerTool( + 'move_document', + { + description: + 'Move a document or folder into another folder of the same Space, or to the Space root. It is placed last among its new siblings.', + inputSchema: { + id: z.string().optional().describe('Document id'), + handle: z.string().optional().describe('Document handle (URL slug), alternative to id'), + parent_id: z + .string() + .nullable() + .describe('Target folder id (from list_documents), or null for the Space root'), + }, + }, + async ({ parent_id, ...input }) => + run(async () => { + const document = await resolveDocument(input); + if (!document.spaceId) throw new Error('Only documents inside a Space can be moved'); + const { data: documents, error: listError } = await getUserDocuments({ + spaceId: document.spaceId, + }); + if (listError) throw new Error(listError.message); + const all = documents ?? []; + + if (parent_id) { + const target = all.find((d) => d.id === parent_id); + if (!target) throw new Error(`Folder "${parent_id}" not found in this Space`); + if (!target.isContainer) throw new Error(`"${target.name}" is a note, not a folder`); + for (let cursor = target; cursor;) { + if (cursor.id === document.id) { + throw new Error('Cannot move a folder into itself or one of its subfolders'); + } + const next = cursor.parentId ? all.find((d) => d.id === cursor.parentId) : undefined; + if (!next) break; + cursor = next; + } + } + + const siblings = sortByPosition(getSiblings(all, parent_id)).filter( + (d) => d.id !== document.id, + ); + const last = siblings.at(-1); + const position = last ? generatePositionKeyBetween(last.position || 'a0', null) : 'a0'; + const { data, error } = await updateDocument(document.id, { parentId: parent_id, position }); + if (error || !data) throw new Error(error?.message ?? 'Failed to move the document'); + return ok( + JSON.stringify({ id: data.id, handle: data.handle, parentId: data.parentId }, null, 2), + ); + }), +); + +assertConfig(); +configureClient(); +await server.connect(new StdioServerTransport()); diff --git a/apps/mcp/src/markdown.ts b/apps/mcp/src/markdown.ts new file mode 100644 index 0000000..74e5d15 --- /dev/null +++ b/apps/mcp/src/markdown.ts @@ -0,0 +1,63 @@ +/** + * SPDX-FileCopyrightText: 2026 TeamCoderz Ltd + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import './dom-shim.js'; +import { + $getRoot, + $isElementNode, + createEditor, + type ElementNode, + type LexicalNode, + type SerializedEditorState, +} from 'lexical'; +import { $convertFromMarkdownString, $convertToMarkdownString } from '@lexical/markdown'; +import { editorConfig } from '@repo/editor/config'; +import { createTransformers } from '@repo/editor/plugins/MarkdownPlugin'; +import { serializeEditorState } from '@repo/editor/utils/editorState'; +import { getInitialEditorState } from '@repo/editor/utils/getInitialEditorState'; + +// Same headless pattern the app itself uses in @repo/editor/utils/generateText: +// a bare editor with the full node list, never attached to a DOM. +const editor = createEditor({ ...editorConfig }); +const transformers = createTransformers(editor); + +// Documents are wrapped in page nodes (page-setup, page → header/content/footer). +// Markdown lives inside the page-content nodes; the wrapper has no transformer. +function $findPageContents(): ElementNode[] { + const found: ElementNode[] = []; + const visit = (node: LexicalNode) => { + if (!$isElementNode(node)) return; + if (node.getType() === 'page-content') { + found.push(node); + return; + } + node.getChildren().forEach(visit); + }; + visit($getRoot()); + return found; +} + +export function toMarkdown(state: SerializedEditorState): string { + editor.setEditorState(editor.parseEditorState(state)); + return editor.read(() => { + const contents = $findPageContents(); + if (contents.length === 0) return $convertToMarkdownString(transformers); + return contents.map((content) => $convertToMarkdownString(transformers, content)).join('\n\n'); + }); +} + +export function fromMarkdown(markdown: string): SerializedEditorState { + // Start from the app's own new-document scaffold so the result is + // structurally identical to a note created in the editor. + editor.setEditorState(editor.parseEditorState(getInitialEditorState(''))); + editor.update( + () => { + const [content] = $findPageContents(); + $convertFromMarkdownString(markdown, transformers, content); + }, + { discrete: true }, + ); + return serializeEditorState(editor.getEditorState()) as SerializedEditorState; +} diff --git a/apps/mcp/tsconfig.json b/apps/mcp/tsconfig.json new file mode 100644 index 0000000..1a1d420 --- /dev/null +++ b/apps/mcp/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "@repo/typescript-config/node.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": ".", + "jsx": "react-jsx", + "module": "esnext", + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "noEmit": true + }, + "include": ["./src/**/*", "./scripts/**/*"] +} diff --git a/packages/sdk/src/app/client.ts b/packages/sdk/src/app/client.ts index 4e7a3e3..ff6c8e2 100644 --- a/packages/sdk/src/app/client.ts +++ b/packages/sdk/src/app/client.ts @@ -8,7 +8,9 @@ import axios, { AxiosError } from 'axios'; import { applyBackendMessageToAxiosError } from './axios-backend-message.js'; export const client = axios.create({ - baseURL: import.meta.env.VITE_BACKEND_URL + '/api', + // Optional chaining: import.meta.env only exists under Vite; Node consumers + // (the MCP server) override baseURL at startup. + baseURL: (import.meta.env?.VITE_BACKEND_URL ?? '') + '/api', withCredentials: true, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 171ab86..e84b448 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -42,7 +42,7 @@ importers: devDependencies: '@release-it/conventional-changelog': specifier: ^10.0.6 - version: 10.0.6(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)(release-it@19.2.4(@types/node@25.9.5)) + version: 10.0.6(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)(release-it@19.2.4(@types/node@26.2.0)) lefthook: specifier: ^2.1.4 version: 2.1.10 @@ -57,7 +57,7 @@ importers: version: 3.9.6 release-it: specifier: ^19.2.4 - version: 19.2.4(@types/node@25.9.5) + version: 19.2.4(@types/node@26.2.0) turbo: specifier: ^2.9.3 version: 2.10.8 @@ -81,7 +81,7 @@ importers: version: 0.9.20 better-auth: specifier: ^1.6.25 - version: 1.6.26(@opentelemetry/api@1.9.1)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@libsql/client@0.17.4)(@opentelemetry/api@1.9.1)(kysely@0.28.17))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.9.5)(jsdom@28.1.0(@noble/hashes@2.3.0))(vite@8.2.0(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.9))) + version: 1.6.26(@opentelemetry/api@1.9.1)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@libsql/client@0.17.4)(@opentelemetry/api@1.9.1)(kysely@0.28.17))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.9.5)(happy-dom@20.11.2)(jsdom@28.1.0(@noble/hashes@2.3.0))(vite@8.2.0(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.9))) cors: specifier: ^2.8.6 version: 2.8.6 @@ -171,6 +171,58 @@ importers: specifier: ^6.0.2 version: 6.0.3 + apps/mcp: + dependencies: + '@happy-dom/global-registrator': + specifier: ^20.11.2 + version: 20.11.2 + '@lexical/markdown': + specifier: ^0.41.0 + version: 0.41.0 + '@modelcontextprotocol/sdk': + specifier: ^1.30.0 + version: 1.30.0(zod@3.25.76) + '@repo/backend': + specifier: workspace:* + version: link:../backend + '@repo/editor': + specifier: workspace:* + version: link:../../packages/editor + '@repo/lib': + specifier: workspace:* + version: link:../../packages/lib + '@repo/sdk': + specifier: workspace:* + version: link:../../packages/sdk + axios: + specifier: ^1.19.0 + version: 1.19.0 + lexical: + specifier: ^0.41.0 + version: 0.41.0 + zod: + specifier: ^3.25.76 + version: 3.25.76 + devDependencies: + '@repo/eslint-config': + specifier: workspace:* + version: link:../../packages/eslint-config + '@repo/typescript-config': + specifier: workspace:* + version: link:../../packages/typescript-config + '@types/node': + specifier: ^26.2.0 + version: 26.2.0 + esbuild: + specifier: ^0.25.0 + version: 0.25.12 + eslint: + specifier: ^10.8.0 + version: 10.8.0(jiti@2.7.0) + typescript: + specifier: ^6.0.3 + version: 6.0.3 + apps/web: dependencies: '@dnd-kit/core': @@ -356,7 +408,7 @@ importers: version: 1.3.0(@types/babel__core@7.20.5)(vite@8.2.0(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.9)) vitest: specifier: ^4.0.18 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.9.5)(jsdom@28.1.0(@noble/hashes@2.3.0))(vite@8.2.0(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.9)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.9.5)(happy-dom@20.11.2)(jsdom@28.1.0(@noble/hashes@2.3.0))(vite@8.2.0(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.9)) web-vitals: specifier: ^5.1.0 version: 5.3.0 @@ -704,7 +756,7 @@ importers: version: 1.19.0 better-auth: specifier: ^1.6.25 - version: 1.6.26(@opentelemetry/api@1.9.1)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@libsql/client@0.17.4)(@opentelemetry/api@1.9.1)(kysely@0.28.17))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.9.5)(jsdom@28.1.0(@noble/hashes@2.3.0))(vite@8.2.0(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.9))) + version: 1.6.26(@opentelemetry/api@1.9.1)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@libsql/client@0.17.4)(@opentelemetry/api@1.9.1)(kysely@0.28.17))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.2.0)(happy-dom@20.11.2)(jsdom@28.1.0(@noble/hashes@2.3.0))(vite@8.2.0(@types/node@26.2.0)(esbuild@0.25.12)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.9))) socket.io-client: specifier: ^4.8.3 version: 4.8.3 @@ -2273,6 +2325,10 @@ packages: '@fontsource/roboto@5.3.0': resolution: {integrity: sha512-BapRJOWYP+LZ21zp+wBQjfpPYKRoxc4LspJ/RLuI+HSMBD5u/X4O+ESDrSvEqDSy0rAl7GwBJ+09mdc16cVQ1Q==} + '@happy-dom/global-registrator@20.11.2': + resolution: {integrity: sha512-7fkpoXZWzyxaBkzRtlD0wRU5ckxbNT4j6BywzpSYh+SFPsGxEVmNR9KHaMwM2xkHB0pADQLl4Z8DSrztECJ7Ww==} + engines: {node: '>=20.0.0'} + '@headless-tree/core@1.7.0': resolution: {integrity: sha512-LxcX7LNepwfOPrZcs4PNfDwCzbi326uCAX5jYBfw94jI+pZQA7ANaE/No3LW0XFgcBcQtK/G2bInbVEhLF1C/Q==} @@ -2283,6 +2339,12 @@ packages: react: '*' react-dom: '*' + '@hono/node-server@2.1.1': + resolution: {integrity: sha512-ELuehkj5VCBdgEw9zs+ivkKwyzzUCSQuE96YmiPvn1ECBoZCczbFXJLeEGMTYjphP6gydh4pHMqEYPVMYUVgQg==} + engines: {node: '>=20'} + peerDependencies: + hono: ^4 + '@hookform/resolvers@5.7.1': resolution: {integrity: sha512-8wS/P4UDr5sQDe4nFaV51TVyfDPrWgNIXweqG0Bs9Z5LSuzKLb+RQNPvkN2oHM5SRrJyWrVH/F+LOUcFjUyvwQ==} peerDependencies: @@ -2680,6 +2742,16 @@ packages: '@mermaid-js/parser@0.6.3': resolution: {integrity: sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==} + '@modelcontextprotocol/sdk@1.30.0': + resolution: {integrity: sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + '@napi-rs/lzma-linux-x64-gnu@1.5.1': resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==} engines: {node: ^22.20 || ^24.12 || >=25} @@ -4577,6 +4649,9 @@ packages: '@types/node@25.9.5': resolution: {integrity: sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg==} + '@types/node@26.2.0': + resolution: {integrity: sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==} + '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -4616,6 +4691,9 @@ packages: '@types/vexflow@1.2.42': resolution: {integrity: sha512-bj3If1sm1FYJN0tJMV2BJNrwoeQ6xfrTt+rbmFvUytyxUhklLlW79MR4uwQ+upzrNJLVy012TnC7oZqSpEglSw==} + '@types/whatwg-mimetype@3.0.2': + resolution: {integrity: sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==} + '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} @@ -4788,6 +4866,9 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + ajv@6.15.0: resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} @@ -5081,6 +5162,10 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + buffer-image-size@0.6.4: + resolution: {integrity: sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==} + engines: {node: '>=4.0'} + buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} @@ -6067,6 +6152,14 @@ packages: eventemitter3@5.0.4: resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + eventsource-parser@3.1.1: + resolution: {integrity: sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + execa@8.0.1: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} @@ -6082,6 +6175,12 @@ packages: exponential-backoff@3.1.3: resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} + express-rate-limit@8.6.2: + resolution: {integrity: sha512-YH4ru+eOJxQABscKFfRCy9R7x9QFGdezclVMwwgFFndzS2Xnm0uo6B0ABZsLhcpeptGv2qvuJVWlQr9gQZoC3A==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + express@5.2.1: resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} engines: {node: '>= 18'} @@ -6370,6 +6469,10 @@ packages: engines: {node: '>=0.4.7'} hasBin: true + happy-dom@20.11.2: + resolution: {integrity: sha512-7MB+bJLkxu3SowAfBJbjW+c55kNz5tkR45gu2qzrxznezhLeN5YIlJbwUgSzlGc+qWoZ8Ykg71H5ezz69xixrw==} + engines: {node: '>=20.0.0'} + has-bigints@1.1.0: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} @@ -6399,6 +6502,10 @@ packages: hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + hono@4.13.2: + resolution: {integrity: sha512-JydRilDRkYBQMt9qR9U92mXxmbGqsqSn/IKOrh4e7/gEbn+0zSr8igTu0obwJoNGN4sez28DIql7FBHWydoJpA==} + engines: {node: '>=16.9.0'} + hosted-git-info@8.1.0: resolution: {integrity: sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==} engines: {node: ^18.17.0 || >=20.5.0} @@ -6793,6 +6900,9 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -7739,6 +7849,10 @@ packages: resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} engines: {node: '>=12'} + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + pkg-types@2.3.1: resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==} @@ -8609,6 +8723,9 @@ packages: undici-types@7.24.6: resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + undici-types@8.3.0: + resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} + undici@6.28.0: resolution: {integrity: sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==} engines: {node: '>=18.17'} @@ -8906,6 +9023,10 @@ packages: webworkify@1.5.0: resolution: {integrity: sha512-AMcUeyXAhbACL8S2hqqdqOLqvJ8ylmIbNwUIqQujRSouf4+eUFaXbG6F1Rbu+srlJMmxQWsiU7mOJi0nMBfM1g==} + whatwg-mimetype@3.0.0: + resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} + engines: {node: '>=12'} + whatwg-mimetype@5.0.0: resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} engines: {node: '>=20'} @@ -9098,12 +9219,20 @@ packages: peerDependencies: zod: ^3.25.74 || ^4.0.0 + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + peerDependencies: + zod: ^3.25.28 || ^4 + zod-validation-error@4.0.2: resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} engines: {node: '>=18.0.0'} peerDependencies: zod: ^3.25.0 || ^4.0.0 + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.4.3: resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} @@ -10517,6 +10646,14 @@ snapshots: '@fontsource/roboto@5.3.0': {} + '@happy-dom/global-registrator@20.11.2': + dependencies: + '@types/node': 25.9.5 + happy-dom: 20.11.2 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + '@headless-tree/core@1.7.0': {} '@headless-tree/react@1.7.0(@headless-tree/core@1.7.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': @@ -10525,6 +10662,10 @@ snapshots: react: 19.2.8 react-dom: 19.2.8(react@19.2.8) + '@hono/node-server@2.1.1(hono@4.13.2)': + dependencies: + hono: 4.13.2 + '@hookform/resolvers@5.7.1(@standard-schema/spec@1.1.0)(ajv@8.20.0)(react-hook-form@7.84.0(react@19.2.8))(zod@4.4.3)': dependencies: '@standard-schema/utils': 0.3.0 @@ -10564,6 +10705,16 @@ snapshots: optionalDependencies: '@types/node': 25.9.5 + '@inquirer/checkbox@4.3.2(@types/node@26.2.0)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@26.2.0) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@26.2.0) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 26.2.0 + '@inquirer/confirm@5.1.21(@types/node@25.9.5)': dependencies: '@inquirer/core': 10.3.2(@types/node@25.9.5) @@ -10571,6 +10722,13 @@ snapshots: optionalDependencies: '@types/node': 25.9.5 + '@inquirer/confirm@5.1.21(@types/node@26.2.0)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@26.2.0) + '@inquirer/type': 3.0.10(@types/node@26.2.0) + optionalDependencies: + '@types/node': 26.2.0 + '@inquirer/core@10.3.2(@types/node@25.9.5)': dependencies: '@inquirer/ansi': 1.0.2 @@ -10584,6 +10742,19 @@ snapshots: optionalDependencies: '@types/node': 25.9.5 + '@inquirer/core@10.3.2(@types/node@26.2.0)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@26.2.0) + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 26.2.0 + '@inquirer/editor@4.2.23(@types/node@25.9.5)': dependencies: '@inquirer/core': 10.3.2(@types/node@25.9.5) @@ -10592,6 +10763,14 @@ snapshots: optionalDependencies: '@types/node': 25.9.5 + '@inquirer/editor@4.2.23(@types/node@26.2.0)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@26.2.0) + '@inquirer/external-editor': 1.0.3(@types/node@26.2.0) + '@inquirer/type': 3.0.10(@types/node@26.2.0) + optionalDependencies: + '@types/node': 26.2.0 + '@inquirer/expand@4.0.23(@types/node@25.9.5)': dependencies: '@inquirer/core': 10.3.2(@types/node@25.9.5) @@ -10600,6 +10779,14 @@ snapshots: optionalDependencies: '@types/node': 25.9.5 + '@inquirer/expand@4.0.23(@types/node@26.2.0)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@26.2.0) + '@inquirer/type': 3.0.10(@types/node@26.2.0) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 26.2.0 + '@inquirer/external-editor@1.0.3(@types/node@25.9.5)': dependencies: chardet: 2.2.0 @@ -10607,6 +10794,13 @@ snapshots: optionalDependencies: '@types/node': 25.9.5 + '@inquirer/external-editor@1.0.3(@types/node@26.2.0)': + dependencies: + chardet: 2.2.0 + iconv-lite: 0.7.3 + optionalDependencies: + '@types/node': 26.2.0 + '@inquirer/figures@1.0.15': {} '@inquirer/input@4.3.1(@types/node@25.9.5)': @@ -10616,6 +10810,13 @@ snapshots: optionalDependencies: '@types/node': 25.9.5 + '@inquirer/input@4.3.1(@types/node@26.2.0)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@26.2.0) + '@inquirer/type': 3.0.10(@types/node@26.2.0) + optionalDependencies: + '@types/node': 26.2.0 + '@inquirer/number@3.0.23(@types/node@25.9.5)': dependencies: '@inquirer/core': 10.3.2(@types/node@25.9.5) @@ -10623,6 +10824,13 @@ snapshots: optionalDependencies: '@types/node': 25.9.5 + '@inquirer/number@3.0.23(@types/node@26.2.0)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@26.2.0) + '@inquirer/type': 3.0.10(@types/node@26.2.0) + optionalDependencies: + '@types/node': 26.2.0 + '@inquirer/password@4.0.23(@types/node@25.9.5)': dependencies: '@inquirer/ansi': 1.0.2 @@ -10631,6 +10839,14 @@ snapshots: optionalDependencies: '@types/node': 25.9.5 + '@inquirer/password@4.0.23(@types/node@26.2.0)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@26.2.0) + '@inquirer/type': 3.0.10(@types/node@26.2.0) + optionalDependencies: + '@types/node': 26.2.0 + '@inquirer/prompts@7.10.1(@types/node@25.9.5)': dependencies: '@inquirer/checkbox': 4.3.2(@types/node@25.9.5) @@ -10646,6 +10862,21 @@ snapshots: optionalDependencies: '@types/node': 25.9.5 + '@inquirer/prompts@7.10.1(@types/node@26.2.0)': + dependencies: + '@inquirer/checkbox': 4.3.2(@types/node@26.2.0) + '@inquirer/confirm': 5.1.21(@types/node@26.2.0) + '@inquirer/editor': 4.2.23(@types/node@26.2.0) + '@inquirer/expand': 4.0.23(@types/node@26.2.0) + '@inquirer/input': 4.3.1(@types/node@26.2.0) + '@inquirer/number': 3.0.23(@types/node@26.2.0) + '@inquirer/password': 4.0.23(@types/node@26.2.0) + '@inquirer/rawlist': 4.1.11(@types/node@26.2.0) + '@inquirer/search': 3.2.2(@types/node@26.2.0) + '@inquirer/select': 4.4.2(@types/node@26.2.0) + optionalDependencies: + '@types/node': 26.2.0 + '@inquirer/rawlist@4.1.11(@types/node@25.9.5)': dependencies: '@inquirer/core': 10.3.2(@types/node@25.9.5) @@ -10654,6 +10885,14 @@ snapshots: optionalDependencies: '@types/node': 25.9.5 + '@inquirer/rawlist@4.1.11(@types/node@26.2.0)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@26.2.0) + '@inquirer/type': 3.0.10(@types/node@26.2.0) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 26.2.0 + '@inquirer/search@3.2.2(@types/node@25.9.5)': dependencies: '@inquirer/core': 10.3.2(@types/node@25.9.5) @@ -10663,6 +10902,15 @@ snapshots: optionalDependencies: '@types/node': 25.9.5 + '@inquirer/search@3.2.2(@types/node@26.2.0)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@26.2.0) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@26.2.0) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 26.2.0 + '@inquirer/select@4.4.2(@types/node@25.9.5)': dependencies: '@inquirer/ansi': 1.0.2 @@ -10673,10 +10921,24 @@ snapshots: optionalDependencies: '@types/node': 25.9.5 + '@inquirer/select@4.4.2(@types/node@26.2.0)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@26.2.0) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@26.2.0) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 26.2.0 + '@inquirer/type@3.0.10(@types/node@25.9.5)': optionalDependencies: '@types/node': 25.9.5 + '@inquirer/type@3.0.10(@types/node@26.2.0)': + optionalDependencies: + '@types/node': 26.2.0 + '@isaacs/cliui@9.0.0': {} '@isaacs/fs-minipass@4.0.1': @@ -10928,6 +11190,28 @@ snapshots: dependencies: langium: 3.3.1 + '@modelcontextprotocol/sdk@1.30.0(zod@3.25.76)': + dependencies: + '@hono/node-server': 2.1.1(hono@4.13.2) + ajv: 8.20.0 + ajv-formats: 3.0.1 + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.1.1 + express: 5.2.1 + express-rate-limit: 8.6.2(express@5.2.1) + hono: 4.13.2 + jose: 6.2.8 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 3.25.76 + zod-to-json-schema: 3.25.2(zod@3.25.76) + transitivePeerDependencies: + - supports-color + '@napi-rs/lzma-linux-x64-gnu@1.5.1': optional: true @@ -12127,7 +12411,7 @@ snapshots: '@radix-ui/rect@1.1.3': {} - '@release-it/conventional-changelog@10.0.6(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)(release-it@19.2.4(@types/node@25.9.5))': + '@release-it/conventional-changelog@10.0.6(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)(release-it@19.2.4(@types/node@26.2.0))': dependencies: '@conventional-changelog/git-client': 2.7.0(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0) concat-stream: 2.0.0 @@ -12135,7 +12419,7 @@ snapshots: conventional-changelog-angular: 8.3.1 conventional-changelog-conventionalcommits: 9.3.1 conventional-recommended-bump: 11.2.0 - release-it: 19.2.4(@types/node@25.9.5) + release-it: 19.2.4(@types/node@26.2.0) semver: 7.8.5 transitivePeerDependencies: - conventional-commits-filter @@ -12739,6 +13023,10 @@ snapshots: dependencies: undici-types: 7.24.6 + '@types/node@26.2.0': + dependencies: + undici-types: 8.3.0 + '@types/normalize-package-data@2.4.4': {} '@types/parse-path@7.1.0': @@ -12774,6 +13062,8 @@ snapshots: '@types/vexflow@1.2.42': {} + '@types/whatwg-mimetype@3.0.2': {} + '@types/ws@8.18.1': dependencies: '@types/node': 25.9.5 @@ -12944,6 +13234,15 @@ snapshots: optionalDependencies: vite: 8.2.0(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.9) + '@vitest/mocker@4.1.10(vite@8.2.0(@types/node@26.2.0)(esbuild@0.25.12)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.9))': + dependencies: + '@vitest/spy': 4.1.10 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.2.0(@types/node@26.2.0)(esbuild@0.25.12)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.9) + optional: true + '@vitest/pretty-format@4.1.10': dependencies: tinyrainbow: 3.1.1 @@ -12995,6 +13294,10 @@ snapshots: agent-base@7.1.4: {} + ajv-formats@3.0.1: + dependencies: + ajv: 8.20.0 + ajv@6.15.0: dependencies: fast-deep-equal: 3.1.3 @@ -13191,7 +13494,7 @@ snapshots: before-after-hook@4.0.0: {} - better-auth@1.6.26(@opentelemetry/api@1.9.1)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@libsql/client@0.17.4)(@opentelemetry/api@1.9.1)(kysely@0.28.17))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.9.5)(jsdom@28.1.0(@noble/hashes@2.3.0))(vite@8.2.0(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.9))): + better-auth@1.6.26(@opentelemetry/api@1.9.1)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@libsql/client@0.17.4)(@opentelemetry/api@1.9.1)(kysely@0.28.17))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.9.5)(happy-dom@20.11.2)(jsdom@28.1.0(@noble/hashes@2.3.0))(vite@8.2.0(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.9))): dependencies: '@better-auth/core': 1.6.26(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2) '@better-auth/drizzle-adapter': 1.6.26(@better-auth/core@1.6.26(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(drizzle-orm@0.45.2(@libsql/client@0.17.4)(@opentelemetry/api@1.9.1)(kysely@0.28.17)) @@ -13215,7 +13518,36 @@ snapshots: drizzle-orm: 0.45.2(@libsql/client@0.17.4)(@opentelemetry/api@1.9.1)(kysely@0.28.17) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) - vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.9.5)(jsdom@28.1.0(@noble/hashes@2.3.0))(vite@8.2.0(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.9)) + vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.9.5)(happy-dom@20.11.2)(jsdom@28.1.0(@noble/hashes@2.3.0))(vite@8.2.0(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.9)) + transitivePeerDependencies: + - '@cloudflare/workers-types' + - '@opentelemetry/api' + + better-auth@1.6.26(@opentelemetry/api@1.9.1)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@libsql/client@0.17.4)(@opentelemetry/api@1.9.1)(kysely@0.28.17))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.2.0)(happy-dom@20.11.2)(jsdom@28.1.0(@noble/hashes@2.3.0))(vite@8.2.0(@types/node@26.2.0)(esbuild@0.25.12)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.9))): + dependencies: + '@better-auth/core': 1.6.26(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2) + '@better-auth/drizzle-adapter': 1.6.26(@better-auth/core@1.6.26(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(drizzle-orm@0.45.2(@libsql/client@0.17.4)(@opentelemetry/api@1.9.1)(kysely@0.28.17)) + '@better-auth/kysely-adapter': 1.6.26(@better-auth/core@1.6.26(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(kysely@0.28.17) + '@better-auth/memory-adapter': 1.6.26(@better-auth/core@1.6.26(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2) + '@better-auth/mongo-adapter': 1.6.26(@better-auth/core@1.6.26(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2) + '@better-auth/prisma-adapter': 1.6.26(@better-auth/core@1.6.26(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2) + '@better-auth/telemetry': 1.6.26(@better-auth/core@1.6.26(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1) + '@better-auth/utils': 0.4.2 + '@better-fetch/fetch': 1.3.1 + '@noble/ciphers': 2.2.0 + '@noble/hashes': 2.3.0 + better-call: 1.3.7(zod@4.4.3) + defu: 6.1.7 + jose: 6.2.8 + kysely: 0.28.17 + nanostores: 1.4.2 + zod: 4.4.3 + optionalDependencies: + drizzle-kit: 0.31.10 + drizzle-orm: 0.45.2(@libsql/client@0.17.4)(@opentelemetry/api@1.9.1)(kysely@0.28.17) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.2.0)(happy-dom@20.11.2)(jsdom@28.1.0(@noble/hashes@2.3.0))(vite@8.2.0(@types/node@26.2.0)(esbuild@0.25.12)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.9)) transitivePeerDependencies: - '@cloudflare/workers-types' - '@opentelemetry/api' @@ -13297,6 +13629,10 @@ snapshots: buffer-from@1.1.2: {} + buffer-image-size@0.6.4: + dependencies: + '@types/node': 25.9.5 + buffer@5.7.1: dependencies: base64-js: 1.5.1 @@ -14365,6 +14701,12 @@ snapshots: eventemitter3@5.0.4: {} + eventsource-parser@3.1.1: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.1.1 + execa@8.0.1: dependencies: cross-spawn: 7.0.6 @@ -14385,6 +14727,14 @@ snapshots: exponential-backoff@3.1.3: optional: true + express-rate-limit@8.6.2(express@5.2.1): + dependencies: + debug: 4.4.3 + express: 5.2.1 + ip-address: 10.4.0 + transitivePeerDependencies: + - supports-color + express@5.2.1: dependencies: accepts: 2.0.0 @@ -14742,6 +15092,19 @@ snapshots: optionalDependencies: uglify-js: 3.19.3 + happy-dom@20.11.2: + dependencies: + '@types/node': 25.9.5 + '@types/whatwg-mimetype': 3.0.2 + '@types/ws': 8.18.1 + buffer-image-size: 0.6.4 + entities: 7.0.1 + whatwg-mimetype: 3.0.0 + ws: 8.21.2 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + has-bigints@1.1.0: {} has-property-descriptors@1.0.2: @@ -14768,6 +15131,8 @@ snapshots: dependencies: hermes-estree: 0.25.1 + hono@4.13.2: {} + hosted-git-info@8.1.0: dependencies: lru-cache: 10.4.3 @@ -14855,17 +15220,17 @@ snapshots: ini@1.3.8: optional: true - inquirer@12.11.1(@types/node@25.9.5): + inquirer@12.11.1(@types/node@26.2.0): dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@25.9.5) - '@inquirer/prompts': 7.10.1(@types/node@25.9.5) - '@inquirer/type': 3.0.10(@types/node@25.9.5) + '@inquirer/core': 10.3.2(@types/node@26.2.0) + '@inquirer/prompts': 7.10.1(@types/node@26.2.0) + '@inquirer/type': 3.0.10(@types/node@26.2.0) mute-stream: 2.0.0 run-async: 4.0.6 rxjs: 7.8.2 optionalDependencies: - '@types/node': 25.9.5 + '@types/node': 26.2.0 internal-slot@1.1.0: dependencies: @@ -15150,6 +15515,8 @@ snapshots: json-schema-traverse@1.0.0: {} + json-schema-typed@8.0.2: {} + json-stable-stringify-without-jsonify@1.0.1: {} json-with-bigint@3.5.10: {} @@ -16260,6 +16627,8 @@ snapshots: picomatch@4.0.5: {} + pkce-challenge@5.0.1: {} + pkg-types@2.3.1: dependencies: confbox: 0.2.4 @@ -16631,7 +17000,7 @@ snapshots: dependencies: jsesc: 3.1.0 - release-it@19.2.4(@types/node@25.9.5): + release-it@19.2.4(@types/node@26.2.0): dependencies: '@nodeutils/defaults-deep': 1.1.0 '@octokit/rest': 22.0.1 @@ -16641,7 +17010,7 @@ snapshots: ci-info: 4.4.0 eta: 4.5.0 git-url-parse: 16.1.0 - inquirer: 12.11.1(@types/node@25.9.5) + inquirer: 12.11.1(@types/node@26.2.0) issue-parser: 7.0.1 lodash.merge: 4.6.2 mime-types: 3.0.2 @@ -17373,6 +17742,8 @@ snapshots: undici-types@7.24.6: {} + undici-types@8.3.0: {} + undici@6.28.0: {} unicode-canonical-property-names-ecmascript@2.0.1: {} @@ -17510,7 +17881,23 @@ snapshots: terser: 5.49.2 tsx: 4.23.9 - vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.9.5)(jsdom@28.1.0(@noble/hashes@2.3.0))(vite@8.2.0(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.9)): + vite@8.2.0(@types/node@26.2.0)(esbuild@0.25.12)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.9): + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.5 + postcss: 8.5.26 + rolldown: 1.2.3 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 26.2.0 + esbuild: 0.25.12 + fsevents: 2.3.3 + jiti: 2.7.0 + terser: 5.49.2 + tsx: 4.23.9 + optional: true + + vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.9.5)(happy-dom@20.11.2)(jsdom@28.1.0(@noble/hashes@2.3.0))(vite@8.2.0(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.9)): dependencies: '@vitest/expect': 4.1.10 '@vitest/mocker': 4.1.10(vite@8.2.0(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.9)) @@ -17535,9 +17922,41 @@ snapshots: optionalDependencies: '@opentelemetry/api': 1.9.1 '@types/node': 25.9.5 + happy-dom: 20.11.2 + jsdom: 28.1.0(@noble/hashes@2.3.0) + transitivePeerDependencies: + - msw + + vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.2.0)(happy-dom@20.11.2)(jsdom@28.1.0(@noble/hashes@2.3.0))(vite@8.2.0(@types/node@26.2.0)(esbuild@0.25.12)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.9)): + dependencies: + '@vitest/expect': 4.1.10 + '@vitest/mocker': 4.1.10(vite@8.2.0(@types/node@26.2.0)(esbuild@0.25.12)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.9)) + '@vitest/pretty-format': 4.1.10 + '@vitest/runner': 4.1.10 + '@vitest/snapshot': 4.1.10 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + es-module-lexer: 2.3.1 + expect-type: 1.4.0 + magic-string: 0.30.21 + obug: 2.1.4 + pathe: 2.0.3 + picomatch: 4.0.5 + std-env: 4.2.0 + tinybench: 2.9.0 + tinyexec: 1.3.0 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.1 + vite: 8.2.0(@types/node@26.2.0)(esbuild@0.25.12)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.9) + why-is-node-running: 2.3.0 + optionalDependencies: + '@opentelemetry/api': 1.9.1 + '@types/node': 26.2.0 + happy-dom: 20.11.2 jsdom: 28.1.0(@noble/hashes@2.3.0) transitivePeerDependencies: - msw + optional: true vscode-jsonrpc@8.2.0: {} @@ -17572,6 +17991,8 @@ snapshots: webworkify@1.5.0: {} + whatwg-mimetype@3.0.0: {} + whatwg-mimetype@5.0.0: {} whatwg-url@16.0.1(@noble/hashes@2.3.0): @@ -17836,10 +18257,16 @@ snapshots: dependencies: zod: 4.4.3 + zod-to-json-schema@3.25.2(zod@3.25.76): + dependencies: + zod: 3.25.76 + zod-validation-error@4.0.2(zod@4.4.3): dependencies: zod: 4.4.3 + zod@3.25.76: {} + zod@4.4.3: {} zustand@4.5.7(@types/react@19.2.18)(react@19.2.8):