diff --git a/content/en/ai/assistant.md b/content/en/ai/assistant.md new file mode 100644 index 000000000..e99baf68c --- /dev/null +++ b/content/en/ai/assistant.md @@ -0,0 +1,520 @@ +# Assistant + +> Add AI-powered chat to your docs that answers questions, cites sources, and generates code examples. + +## About the Agent + +The assistant answers questions about your documentation through natural language queries. It is embedded directly in your documentation site, so users can find answers quickly and succeed with your product. + +When users ask questions, the assistant: + +- **Searches and retrieves** relevant content from your documentation using an [MCP server](/en/ai/mcp). +- **Cites sources** with navigable links to take users directly to referenced pages. +- **Generates copyable code examples** to help users implement solutions from your documentation. + + + +The assistant requires an [AI Gateway](https://vercel.com/docs/ai-gateway) API key to function. + + + +## How It Works + +The assistant uses a multi-agent architecture: + +1. **Main Agent** - Receives user questions and decides when to search documentation +2. **Search Agent** - Uses [MCP server](/en/ai/mcp) tools to find relevant content +3. **Response Generation** - Synthesizes information into helpful, conversational answers + +By default, the assistant connects to your documentation's built-in MCP server at `/mcp`, giving it access to all your pages without additional configuration. You can also connect to an external MCP server if needed. + +## Quick Start + +### 1. Get an API Key + +Get an API key from [Vercel AI Gateway](https://vercel.com/~/ai/api-keys). AI Gateway works with multiple AI providers (OpenAI, Anthropic, Google, and more) through a unified API. + +### 2. Set Environment Variable + +Add your API key to your environment: + +```bash [.env] +AI_GATEWAY_API_KEY=your-api-key +``` + +### 3. Deploy + +That's it! The assistant is automatically enabled when an API key is detected. Deploy your documentation and the assistant will be available to your users. + +## Using the Assistant + +Users can interact with the assistant in multiple ways: + +### Floating Input + +On documentation pages, a floating input appears at the bottom of the screen. Users can type their questions directly and press Enter to get answers. + + + +Use the keyboard shortcut + + + + + + + + + + + + to focus the floating input. + + + +### Explain with AI + +Each documentation page includes an **Explain with AI** button in the table of contents sidebar. Clicking this button opens the assistant with the current page as context, asking it to explain the content. + +### Slideover Chat + +When a conversation starts, a slideover panel opens on the right side of the screen. This panel displays the conversation history and allows users to continue asking questions. + +## Configuration + +Configure the assistant UI through `app.config.ts`: + +```ts [app.config.ts] +export default defineAppConfig({ + assistant: { + // Show the floating input on documentation pages + floatingInput: true, + + // Show the "Explain with AI" button in the sidebar + explainWithAi: true, + + // FAQ questions to display when chat is empty + faqQuestions: [], + + // Keyboard shortcuts + shortcuts: { + focusInput: 'meta_i' + }, + + // Custom icons + icons: { + trigger: 'i-lucide-sparkles', + explain: 'i-lucide-brain' + } + } +}) +``` + +### FAQ Questions + +Display suggested questions when the chat is empty. This helps users discover what they can ask. + +#### Simple Format + +```ts [app.config.ts] +export default defineAppConfig({ + assistant: { + faqQuestions: [ + 'How do I install Docus?', + 'How do I customize the theme?', + 'How do I add components to my pages?' + ] + } +}) +``` + +#### Category Format + +Organize questions into categories: + +```ts [app.config.ts] +export default defineAppConfig({ + assistant: { + faqQuestions: [ + { + category: 'Getting Started', + items: [ + 'How do I install Docus?', + 'What is the project structure?' + ] + }, + { + category: 'Customization', + items: [ + 'How do I change the theme colors?', + 'How do I add a custom logo?' + ] + } + ] + } +}) +``` + +#### Localized Format + +For multi-language documentation, provide FAQ questions per locale: + +```ts [app.config.ts] +export default defineAppConfig({ + assistant: { + faqQuestions: { + en: [ + { category: 'Getting Started', items: ['How do I install?'] } + ], + fr: [ + { category: 'Démarrage', items: ['Comment installer ?'] } + ] + } + } +}) +``` + +## Keyboard Shortcuts + +Configure the keyboard shortcut for focusing the floating input: + +```ts [app.config.ts] +export default defineAppConfig({ + assistant: { + shortcuts: { + // Default: 'meta_i' (Cmd+I on Mac, Ctrl+I on Windows) + focusInput: 'meta_k' // Change to Cmd/Ctrl+K + } + } +}) +``` + +The shortcut format uses underscores to separate keys. Common examples: + +- `meta_i` - Cmd+I (Mac) / Ctrl+I (Windows) +- `meta_k` - Cmd+K (Mac) / Ctrl+K (Windows) +- `ctrl_shift_p` - Ctrl+Shift+P + +## Custom Icons + +Customize the icons used by the assistant: + +```ts [app.config.ts] +export default defineAppConfig({ + assistant: { + icons: { + // Icon for the trigger button and slideover header + trigger: 'i-lucide-bot', + + // Icon for the "Explain with AI" button + explain: 'i-lucide-lightbulb' + } + } +}) +``` + +Icons use the [Iconify](https://iconify.design/) format (e.g., `i-lucide-sparkles`, `i-heroicons-sparkles`). + +## Internationalization + +All UI texts are automatically translated based on the user's locale. Docus includes built-in translations for English and French. + +The following texts are translated: + +- Slideover title and placeholder +- Tooltip texts +- Button labels ("Clear chat", "Close", "Explain with AI") +- Status messages ("Thinking...", "Chat is cleared on refresh") + +## Disable Features + +### Disable the Floating Input + +Hide the floating input at the bottom of documentation pages: + +```ts [app.config.ts] +export default defineAppConfig({ + assistant: { + floatingInput: false + } +}) +``` + +### Disable "Explain with AI" + +Hide the "Explain with AI" button in the documentation sidebar: + +```ts [app.config.ts] +export default defineAppConfig({ + assistant: { + explainWithAi: false + } +}) +``` + +### Disable the Assistant Entirely + +The assistant is automatically enabled when an API key is detected. To explicitly disable it, you can either remove the environment variable or disable it in your `nuxt.config.ts`: + + + +```bash [.env] +# Comment out or remove the API key +# AI_GATEWAY_API_KEY=your-api-key +``` + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + docus: { + agent: { + chat: { + enabled: false + } + } + } +}) +``` + + + +## Advanced Configuration + +Configure advanced options in `nuxt.config.ts` using the `docus.agent` key: + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + docus: { + agent: { + // AI model (uses AI SDK Gateway format) + model: 'google/gemini-3-flash', + + // MCP server (path or URL) + mcpServer: '/mcp', + + chat: { + // API endpoint path + apiPath: '/__docus__/assistant' + } + } + } +}) +``` + +### MCP Server Configuration + +The assistant uses an MCP server to access your documentation. You have two options: + +#### Use the Built-in MCP Server (Default) + +By default, the assistant uses Docus's built-in MCP server at `/mcp`: + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + docus: { + agent: { + mcpServer: '/mcp' + } + } +}) +``` + + + +Make sure the MCP server is enabled in your configuration. If you've customized the MCP path, update `mcpServer` accordingly. + + + +#### Use an External MCP Server + +Connect to any external MCP server by providing a full URL: + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + docus: { + agent: { + mcpServer: 'https://other-docs.example.com/mcp' + } + } +}) +``` + +This is useful when you want the assistant to answer questions from a different documentation source, or when connecting to a centralized knowledge base. + +### Custom AI Model + +The assistant uses `google/gemini-3-flash` by default. You can change this to any model supported by the AI SDK Gateway: + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + docus: { + agent: { + model: 'anthropic/claude-opus-4.5' + } + } +}) +``` + +### Site Name in Responses + +The assistant automatically uses your site name in its responses. Configure the site name in `nuxt.config.ts`: + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + site: { + name: 'My Documentation' + } +}) +``` + +This makes the assistant respond as "the My Documentation assistant" and speak with authority about your specific product. + +## Programmatic Access + +Use the `useAssistant` composable to control the assistant programmatically: + +```vue + + + +``` + +### Composable API + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Property + + Type + + Description +
+ + isEnabled + + + + ComputedRef + + + Whether the assistant is enabled (API key present) +
+ + isOpen + + + + Ref + + + Whether the slideover is open +
+ + open(message?, clearPrevious?) + + + + Function + + + Open the assistant, optionally with a message +
+ + close() + + + + Function + + + Close the assistant slideover +
+ + toggle() + + + + Function + + + Toggle the assistant open/closed +
+ + clearMessages() + + + + Function + + + Clear the conversation history +
diff --git a/content/en/ai/review-agent.md b/content/en/ai/review-agent.md new file mode 100644 index 000000000..47b5fea2b --- /dev/null +++ b/content/en/ai/review-agent.md @@ -0,0 +1,121 @@ +# Review Agent + +> Keep your documentation in sync with code changes using the Docus AI review agent. + +The Review Agent is an AI-powered tool that monitors your GitHub repository for pull requests. When a PR is opened or updated, the agent analyzes the changes and automatically suggests documentation updates to keep your docs in sync with your code. + +## About the Review Agent + +The agent acts as a virtual documentation maintainer. It uses the [built-in MCP server](/en/ai/mcp) to understand your project's documentation structure and content, then compares it with the code changes in a pull request. + +Depending on your configuration, the agent can: + +- **Analyze code diffs** to identify new features or breaking changes that require documentation. +- **Suggest updates** to existing documentation pages. +- **Create new pages** for major features that aren't yet documented. +- **Maintain consistency** in tone and style across your documentation. + +## Setup + +### 1. Get an AI Gateway API Key + +The review agent requires an [AI Gateway](https://vercel.com/docs/ai-gateway) API key. If you already set this up for the [Assistant](/en/ai/assistant), you can skip this step. + +```bash [.env] +AI_GATEWAY_API_KEY=your-api-key +``` + +### 2. Create a GitHub App + +To use the Review Agent, you need to create and install a GitHub App: + +1. Go to your **GitHub Settings > Developer settings > GitHub Apps** and click **New GitHub App**. +2. Set a **Name** and **Homepage URL** (your documentation site URL). +3. Under **Webhook**, set the **Webhook URL** to `https://your-docs-site.com/__docus__/webhook/github`. +4. Create a **Webhook secret** and save it for later. +5. Under **Permissions**, grant the following: + - **Pull requests**: Read & write + - **Contents**: Read & write (required for `commit` mode) + - **Metadata**: Read-only +6. Under **Events**, subscribe to **Pull request**. +7. After creating the app, **Generate a private key** and download the `.pem` file. +8. **Install** the app on your repository. + +### 3. Set Environment Variables + +Add the GitHub App credentials to your environment: + +```bash [.env] +# The App ID found in your GitHub App settings +GITHUB_APP_ID=123456 + +# The content of the .pem private key file +# For CI/CD, use a single line with \n for newlines +GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n..." + +# The Webhook secret you created +GITHUB_WEBHOOK_SECRET=your-webhook-secret +``` + +### 4. Enable the Agent + +Enable the review agent in your `nuxt.config.ts`: + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + docus: { + agent: { + review: { + enabled: true + } + } + } +}) +``` + +## Configuration + +Configure the agent's behavior in `nuxt.config.ts`: + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + docus: { + agent: { + review: { + enabled: true, + + // feedback mode: + // 'comment' (default): Posts suggested changes as a PR comment + // 'commit': Directly commits changes to the PR branch + mode: 'comment', + + // Target repository (owner/repo) + // Auto-detected from environment variables or .git folder + githubRepo: 'nuxt-content/docus' + } + } + } +}) +``` + +### Repository Auto-detection + +Docus automatically detects the GitHub repository when deployed on popular platforms (Vercel, Netlify, GitHub Actions, GitLab CI) or running in local development. If auto-detection fails, the agent will log a warning and you must specify `githubRepo` manually. + +## Advanced Configuration + +The review agent shares the same AI model and MCP server configuration as the [Assistant](/en/ai/assistant): + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + docus: { + agent: { + // AI model used for review and chat + model: 'google/gemini-3-flash', + + // MCP server used to read documentation + mcpServer: '/mcp' + } + } +}) +``` diff --git a/doc-agent-tasks.md b/doc-agent-tasks.md new file mode 100644 index 000000000..4e22a76c5 --- /dev/null +++ b/doc-agent-tasks.md @@ -0,0 +1,305 @@ +# Specs + +# Doc-Agent Integration — Task Breakdown + +## Context + +Docus has an existing `assistant` module at `layer/modules/assistant/`. This feature extends it with a durable AI agent triggered by GitHub webhooks on PRs: it detects missing or outdated docs, generates/updates MDC pages, and commits the result back to the PR branch. + +The MCP server already exposes `list-pages` and `get-page`. We only need to add `commit-update`. + +## References + +* [Docus repository]() +* [Docus documentation]() +* [Workflow AI documentation]() +* [Workflow Nuxt getting started]() + +--- + +## File Structure (all new files) + +```text +layer/modules/assistant/runtime/server/ +├── utils/ +│ └── github-auth.ts # JWT → installation token +├── mcp/ +│ ├── tools/ +│ │ └── commit-update.ts # new MCP tool +│ └── prompts/ +│ └── review-pr-docs.ts # new MCP prompt +├── workflows/ +│ └── doc-agent.ts # durable workflow +└── api/webhook/ + └── github.post.ts # webhook endpoint +``` + +## Files to modify + +```text +layer/package.json — add workflow, @workflow/ai +layer/nuxt.config.ts — add workflow/nuxt module + runtimeConfig keys +layer/modules/assistant/index.ts — add docAgent options to module schema +layer/app/app.config.ts — add docus.ai.docAgent config block +``` + +--- + +## Tasks + +### Task 1 — Foundation: config, dependencies, module options + +**Files:** `layer/package.json`, `layer/nuxt.config.ts`, `layer/modules/assistant/index.ts`, `layer/app/app.config.ts` + +**References:** + +* [Workflow Nuxt getting started]() + +Add dependencies: + +```json +"workflow": "latest", +"@workflow/ai": "latest" +``` + +Add to `nuxt.config.ts` modules array (alongside existing modules): + +```ts +'workflow/nuxt' +``` + +Add to `nuxt.config.ts` runtimeConfig (match existing `assistant` block pattern): + +```ts +runtimeConfig: { + githubAppId: '', // GITHUB_APP_ID + githubAppPrivateKey: '', // GITHUB_APP_PRIVATE_KEY (PEM) + webhookSecret: '', // GITHUB_WEBHOOK_SECRET +} +``` + +Add `docAgent` to the assistant module options schema in `index.ts`: + +```ts +docAgent?: { + enabled?: boolean + mode?: 'comment' | 'commit' + githubRepo?: string +} +``` + +Extend `docus.ai` in `app.config.ts`: + +```ts +docus: { + ai: { + // ...existing options unchanged... + docAgent: { + enabled: false, + mode: 'comment', // 'comment' | 'commit' + githubRepo: 'org/repo', + } + } +} +``` + +GitHub App permissions required: `contents: write`, `pull_requests: write`, `metadata: read` + +--- + +### Task 2 — GitHub Auth Utility + +**File:** `layer/modules/assistant/runtime/server/utils/github-auth.ts` + +**References:** + +* [Docus repository]() + +Signs a JWT with the GitHub App private key, exchanges it for a short-lived installation access token using `installation.id` from the webhook payload. + +* Uses `runtimeConfig.githubAppId` and `runtimeConfig.githubAppPrivateKey` +* Signs a JWT valid for 10 minutes (GitHub App auth requirement) +* POSTs to `https://api.github.com/app/installations/{installationId}/access_tokens` +* Returns the short-lived `token` string +* Export: `getInstallationToken(installationId: number): Promise` + +--- + +### Task 3 — `commit-update` MCP Tool + +**File:** `layer/modules/assistant/runtime/server/mcp/tools/commit-update.ts` + +**References:** + +* [Docus repository]() +* [Docus documentation]() + +Uses `defineMcpTool` from `@nuxtjs/mcp-toolkit/server` (match pattern of existing tools in the module). + +Input schema (Zod): + +```ts +{ + owner: string, + repo: string, + branch: string, + path: string, // e.g. content/2.guide/my-feature.md + content: string, // full MDC file content + token: string, // installation access token +} +``` + +Execute function — marked `"use step"` (for workflow retries): + +1. Try GET `/repos/{owner}/{repo}/contents/{path}?ref={branch}` to fetch current SHA +2. If file exists → PUT with `{ message, content: base64, sha, branch }` +3. If file doesn't exist → PUT with `{ message, content: base64, branch }` (no sha) +4. Commit message: `docs: update {path} via doc-agent` + +--- + +### Task 4 — `review-pr-docs` MCP Prompt + +**File:** `layer/modules/assistant/runtime/server/mcp/prompts/review-pr-docs.ts` + +**References:** + +* [Docus repository]() +* [Docus documentation]() + +Uses `defineMcpPrompt` from `@nuxtjs/mcp-toolkit/server`. + +Arguments: + +```ts +{ + owner: string, + repo: string, + branch: string, + diff: string, // filtered PR diff (already truncated) + token: string, +} +``` + +The prompt instructs Claude to: + +1. Call `list-pages` to discover existing documentation +2. Call `get-page` on pages that may be related to the diff +3. Decide what to create (new page) or update (existing section) +4. Call `commit-update` for each file changed + +MDC output rules: + +* Use Docus components: `::note`, `::callout`, `::code-group` +* File paths follow content structure: `content/2.guide/my-feature.md` +* Never delete existing sections, only append or update +* Skip docs if the diff is only types, tests, or config files with no user-facing changes + +--- + +### Task 5 — Durable Workflow + +**File:** `layer/modules/assistant/runtime/server/workflows/doc-agent.ts` + +**References:** + +* [Workflow AI documentation]() +* [Workflow Nuxt getting started]() + +Marked `"use workflow"` at the top of the file. + +Input: `{ branch: string, filteredDiff: string }` + +Uses `DurableAgent` from `@workflow/ai/agent` with tools via `experimental_createMCPClient` pointed at `/mcp` (same MCP server path already used by `search.ts`). + +The agent: + +1. Receives the `review-pr-docs` prompt with branch + diff +2. Calls MCP tools (`list-pages`, `get-page`, `commit-update`) until done +3. `commit-update` execute is `"use step"` → automatically retried on failure + +Export: `docAgentWorkflow` (named export used by the webhook handler) + +--- + +### Task 6 — GitHub Webhook Handler + +**File:** `layer/modules/assistant/runtime/server/api/webhook/github.post.ts` + +**References:** + +* [Docus repository]() +* [Workflow Nuxt getting started]() + +Uses `defineEventHandler` (match pattern from `search.ts`). + +Handles `pull_request` events with actions `opened` and `synchronize`. + +Processing order: + +1. Read raw body — verify `x-hub-signature-256` using `runtimeConfig.webhookSecret` (HMAC-SHA256). Return `401` if invalid. +2. Parse payload — extract `installation.id`, `pull_request.head.ref` (branch), `pull_request.base.sha`, `pull_request.head.sha`, `repository.owner.login`, `repository.name` +3. Call `getInstallationToken(installation.id)` → short-lived token +4. Fetch diff: `GET /repos/{owner}/{repo}/compare/{base.sha}...{head.sha}` with `Accept: application/vnd.github.v3.diff` +5. Filter diff: + * Keep only `.ts`, `.vue`, `.js` files + * Drop deleted files, lockfiles (`package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`), generated files (`*.d.ts`, `dist/`) +6. Truncate to 12,000 token hard limit — log file names of any skipped files +7. Call `start(docAgentWorkflow, [{ branch, filteredDiff }])` — **do not await** +8. Return `200` immediately + +> Enable Fluid Compute on the Vercel project for efficient workflow suspension/resumption. + +--- + +## Dependency handling + +Some tasks are blocked by others, while others are only related and can move in parallel. + +**Blocking chain** + +* Task 1 blocks all implementation tasks +* Task 2 blocks Task 6 +* Task 3 blocks Task 4 and Task 5 +* Task 4 blocks Task 5 +* Task 5 and Task 6 both block the final end-to-end wiring and validation + +**Related but parallelizable work** + +* Task 2 and Task 3 can run in parallel after Task 1 +* Task 6 can start after Task 2, even before Task 5 is finished, as long as the workflow start can be stubbed or logged first +* Task 4 and Task 6 are related through the end-to-end flow, but Task 6 does not need to wait for Task 4 to begin + +**Execution rule** + +* Mark issues as `blocked by` only when another task must land first +* Mark issues as `related to` when the work should stay aligned but can proceed independently +* For testing-first delivery, prefer shipping the webhook path with a temporary stub/logging milestone before the agent side is fully connected + +--- + +## Split suggestion + +| Order | Task | Owner | Depends on | Relationship notes | +| -- | -- | -- | -- | -- | +| 1 | Task 1 — Foundation | — | — | Blocks all implementation tasks | +| 2 | Task 2 — GitHub Auth | — | Task 1 | Blocks Task 6 | +| 3 | Task 3 — `commit-update` tool | — | Task 1 | Blocks Tasks 4 and 5 | +| 4 | Task 6 — Webhook handler | — | Task 1, Task 2 | Related to Tasks 4 and 5; can start before they finish | +| 5 | Task 4 — `review-pr-docs` prompt | — | Task 3 | Blocks Task 5 | +| 6 | Task 5 — Durable workflow | — | Task 3, Task 4 | Blocks final end-to-end wiring | +| 7 | Wire webhook to workflow | — | Task 5, Task 6 | Final integration and validation step | + +**Testing-first split for two people:** + +* **Person A**: Tasks 1 + 2 + 6 — foundation, auth, and webhook path first so PR events can be received and the diff filtering can be tested early +* **Person B**: Tasks 3 + 4 + 5 — MCP tool, prompt, and workflow in parallel once the foundation is in place + +Task 1 is still the initial blocker. After that, split the work into two tracks: + +* **Track A:** Task 2 → Task 6 +* **Track B:** Task 3 → Task 4 → Task 5 + +These tracks should stay related to each other, but only converge as blocking work at the final wiring step. + +A practical milestone is to get the webhook returning `200`, verifying signatures, fetching diffs, and logging the workflow input before connecting it to the full agent execution. \ No newline at end of file diff --git a/docs/content/en/4.ai/1.assistant.md b/docs/content/en/4.ai/1.assistant.md index d39b9edc7..467392bf7 100644 --- a/docs/content/en/4.ai/1.assistant.md +++ b/docs/content/en/4.ai/1.assistant.md @@ -1,329 +1,221 @@ --- -title: Assistant -description: Add AI-powered chat to your docs that answers questions, cites sources, and generates code examples. -navigation: - icon: i-lucide-sparkles +title: Agent +description: Docus Agent provides AI-powered features for your documentation, including a chat assistant and automated PR documentation reviews. --- -## About the Assistant +# Agent -The assistant answers questions about your documentation through natural language queries. It is embedded directly in your documentation site, so users can find answers quickly and succeed with your product. +> Docus Agent provides AI-powered features for your documentation, including a chat assistant and automated PR documentation reviews. -When users ask questions, the assistant: +## About Docus Agent -- **Searches and retrieves** relevant content from your documentation using an [MCP server](/en/ai/mcp). -- **Cites sources** with navigable links to take users directly to referenced pages. -- **Generates copyable code examples** to help users implement solutions from your documentation. +The agent enhances your documentation workflow and user experience through two main features: + +- **Chat Assistant**: Answers user questions directly on your documentation site using natural language and source citations. +- **Review Agent**: Automatically reviews Pull Requests and suggests or commits documentation updates to keep your docs in sync with code changes. ::note -The assistant requires an [AI Gateway](https://vercel.com/docs/ai-gateway) API key to function. +The agent requires an [AI Gateway](https://vercel.com/docs/ai-gateway) API key to function. :: -## How It Works - -The assistant uses a multi-agent architecture: - -1. **Main Agent** - Receives user questions and decides when to search documentation -2. **Search Agent** - Uses [MCP server](/en/ai/mcp) tools to find relevant content -3. **Response Generation** - Synthesizes information into helpful, conversational answers - -By default, the assistant connects to your documentation's built-in MCP server at `/mcp`, giving it access to all your pages without additional configuration. You can also connect to an external MCP server if needed. - -## Quick Start - -### 1. Get an API Key - -Get an API key from [Vercel AI Gateway](https://vercel.com/~/ai/api-keys). AI Gateway works with multiple AI providers (OpenAI, Anthropic, Google, and more) through a unified API. +## Chat Assistant -### 2. Set Environment Variable +The chat assistant answers questions about your documentation through natural language queries. It is embedded directly in your documentation site, so users can find answers quickly. -Add your API key to your environment: - -```bash [.env] -AI_GATEWAY_API_KEY=your-api-key -``` - -### 3. Deploy +When users ask questions, the assistant: -That's it! The assistant is automatically enabled when an API key is detected. Deploy your documentation and the assistant will be available to your users. +- **Searches and retrieves** relevant content from your documentation using an [MCP server](/en/ai/mcp). +- **Cites sources** with navigable links to take users directly to referenced pages. +- **Generates copyable code examples** to help users implement solutions from your documentation. -## Using the Assistant +### Using the Assistant Users can interact with the assistant in multiple ways: -### Floating Input +#### Floating Input On documentation pages, a floating input appears at the bottom of the screen. Users can type their questions directly and press Enter to get answers. ::tip -Use the keyboard shortcut :kbd{value="meta"} :kbd{value="I"} to focus the floating input. +Use the keyboard shortcut to focus the floating input. :: -### Explain with AI +#### Explain with AI Each documentation page includes an **Explain with AI** button in the table of contents sidebar. Clicking this button opens the assistant with the current page as context, asking it to explain the content. -### Slideover Chat +#### Slideover Chat When a conversation starts, a slideover panel opens on the right side of the screen. This panel displays the conversation history and allows users to continue asking questions. -## Configuration +## Review Agent -Configure the assistant through `app.config.ts`: +The Review Agent is a specialized documentation agent that keeps your docs in sync with your code. Triggered by GitHub webhooks, it analyzes Pull Request diffs and identifies necessary documentation updates. -```ts [app.config.ts] -export default defineAppConfig({ - assistant: { - // Show the floating input on documentation pages - floatingInput: true, +- **Automated Reviews**: Automatically scans every PR for changes that impact documentation. +- **Content Discovery**: Uses the built-in MCP server to find existing pages related to the changed code. +- **Direct Commits**: Can commit updated documentation directly to your PR branch or post suggestions as comments. - // Show the "Explain with AI" button in the sidebar - explainWithAi: true, +### Setup for Review Agent - // FAQ questions to display when chat is empty - faqQuestions: [], - - // Keyboard shortcuts - shortcuts: { - focusInput: 'meta_i' - }, +To enable the Review Agent, you need to provide GitHub App credentials: - // Custom icons - icons: { - trigger: 'i-lucide-sparkles', - explain: 'i-lucide-brain' - } - } -}) -``` +::steps{level="4"} -### FAQ Questions +#### Create a GitHub App +Create a new GitHub App with **Read & Write** permissions for `Pull Requests` and `Repository Contents`. -Display suggested questions when the chat is empty. This helps users discover what they can ask. +#### Generate Credentials +Generate a private key and a webhook secret for your app. -#### Simple Format +#### Set Environment Variables +Add the following to your environment: -```ts [app.config.ts] -export default defineAppConfig({ - assistant: { - faqQuestions: [ - 'How do I install Docus?', - 'How do I customize the theme?', - 'How do I add components to my pages?' - ] - } -}) +```bash [.env] +GITHUB_APP_ID=your-app-id +GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n..." +GITHUB_WEBHOOK_SECRET=your-webhook-secret ``` -#### Category Format - -Organize questions into categories: +#### Enable in Configuration +Enable the review feature in your `nuxt.config.ts`: -```ts [app.config.ts] -export default defineAppConfig({ - assistant: { - faqQuestions: [ - { - category: 'Getting Started', - items: [ - 'How do I install Docus?', - 'What is the project structure?' - ] - }, - { - category: 'Customization', - items: [ - 'How do I change the theme colors?', - 'How do I add a custom logo?' - ] +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + docus: { + agent: { + review: { + enabled: true, + mode: 'commit' // or 'comment' } - ] - } -}) -``` - -#### Localized Format - -For multi-language documentation, provide FAQ questions per locale: - -```ts [app.config.ts] -export default defineAppConfig({ - assistant: { - faqQuestions: { - en: [ - { category: 'Getting Started', items: ['How do I install?'] } - ], - fr: [ - { category: 'Démarrage', items: ['Comment installer ?'] } - ] } } }) ``` +:: -## Keyboard Shortcuts +## Quick Start -Configure the keyboard shortcut for focusing the floating input: +### 1. Set Environment Variables -```ts [app.config.ts] -export default defineAppConfig({ - assistant: { - shortcuts: { - // Default: 'meta_i' (Cmd+I on Mac, Ctrl+I on Windows) - focusInput: 'meta_k' // Change to Cmd/Ctrl+K - } - } -}) -``` +Add your API key and GitHub credentials to your environment: -The shortcut format uses underscores to separate keys. Common examples: -- `meta_i` - Cmd+I (Mac) / Ctrl+I (Windows) -- `meta_k` - Cmd+K (Mac) / Ctrl+K (Windows) -- `ctrl_shift_p` - Ctrl+Shift+P +```bash [.env] +AI_GATEWAY_API_KEY=your-api-key -## Custom Icons +# For Review Agent +GITHUB_APP_ID=... +GITHUB_APP_PRIVATE_KEY=... +GITHUB_WEBHOOK_SECRET=... +``` -Customize the icons used by the assistant: +### 2. Configure Nuxt -```ts [app.config.ts] -export default defineAppConfig({ - assistant: { - icons: { - // Icon for the trigger button and slideover header - trigger: 'i-lucide-bot', +The agent features are automatically detected based on environment variables, but you can explicitly configure them: - // Icon for the "Explain with AI" button - explain: 'i-lucide-lightbulb' +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + docus: { + agent: { + chat: { + enabled: true + }, + review: { + enabled: true + } } } }) ``` -Icons use the [Iconify](https://iconify.design/) format (e.g., `i-lucide-sparkles`, `i-heroicons-sparkles`). - -## Internationalization - -All UI texts are automatically translated based on the user's locale. Docus includes built-in translations for English and French. - -The following texts are translated: -- Slideover title and placeholder -- Tooltip texts -- Button labels ("Clear chat", "Close", "Explain with AI") -- Status messages ("Thinking...", "Chat is cleared on refresh") - -## Disable Features +## Configuration -### Disable the Floating Input +### Nuxt Configuration -Hide the floating input at the bottom of documentation pages: +Configure advanced options in `nuxt.config.ts`: -```ts [app.config.ts] -export default defineAppConfig({ - assistant: { - floatingInput: false - } -}) -``` +::field-group + ::field{name="model" type="string"} + AI model to use (uses AI SDK Gateway format). Default: `google/gemini-3-flash` + :: + ::field{name="mcpServer" type="string"} + MCP server path or URL. Default: `/mcp` + :: + ::field{name="chat.enabled" type="boolean"} + Enable the chat assistant. Default: `true` (if API key present) + :: + ::field{name="chat.apiPath" type="string"} + API endpoint for the chat assistant. Default: `/__docus__/assistant` + :: + ::field{name="review.enabled" type="boolean"} + Enable the PR documentation review agent. Default: `false` + :: + ::field{name="review.mode" type="string"} + Mode for the review agent: `commit` (direct commits) or `comment` (PR comments). Default: `comment` + :: + ::field{name="review.githubRepo" type="string"} + Target GitHub repository (e.g., `owner/repo`). Auto-detected in CI. + :: +:: -### Disable "Explain with AI" +### App Configuration -Hide the "Explain with AI" button in the documentation sidebar: +The Chat Assistant UI can be customized through `app.config.ts`: ```ts [app.config.ts] export default defineAppConfig({ assistant: { - explainWithAi: false - } -}) -``` - -### Disable the Assistant Entirely - -The assistant is automatically disabled when no API key is set. To explicitly disable it, simply remove the environment variable: - -```bash [.env] -# Comment out or remove the API key -# AI_GATEWAY_API_KEY=your-api-key -``` - -## Advanced Configuration - -Configure advanced options in `nuxt.config.ts`: - -```ts [nuxt.config.ts] -export default defineNuxtConfig({ - assistant: { - // AI model (uses AI SDK Gateway format) - model: 'google/gemini-3-flash', - - // MCP server (path or URL) - mcpServer: '/mcp', - - // API endpoint path - apiPath: '/__docus__/assistant' - } -}) -``` - -### MCP Server Configuration + // Show the floating input on documentation pages + floatingInput: true, -The assistant uses an MCP server to access your documentation. You have two options: + // Show the "Explain with AI" button in the sidebar + explainWithAi: true, -#### Use the Built-in MCP Server (Default) + // FAQ questions to display when chat is empty + faqQuestions: [], -By default, the assistant uses Docus's built-in MCP server at `/mcp`: + // Keyboard shortcuts + shortcuts: { + focusInput: 'meta_i' + }, -```ts [nuxt.config.ts] -export default defineNuxtConfig({ - assistant: { - mcpServer: '/mcp' + // Custom icons + icons: { + trigger: 'i-lucide-sparkles', + explain: 'i-lucide-brain' + } } }) ``` -::warning -Make sure the MCP server is enabled in your configuration. If you've customized the MCP path, update `mcpServer` accordingly. -:: - -#### Use an External MCP Server +#### FAQ Questions -Connect to any external MCP server by providing a full URL: +Display suggested questions when the chat is empty. -```ts [nuxt.config.ts] -export default defineNuxtConfig({ +::code-group +```ts [Simple] +export default defineAppConfig({ assistant: { - mcpServer: 'https://other-docs.example.com/mcp' + faqQuestions: [ + 'How do I install Docus?', + 'How do I customize the theme?' + ] } }) ``` - -This is useful when you want the assistant to answer questions from a different documentation source, or when connecting to a centralized knowledge base. - -### Custom AI Model - -The assistant uses `google/gemini-3-flash` by default. You can change this to any model supported by the AI SDK Gateway: - -```ts [nuxt.config.ts] -export default defineNuxtConfig({ +```ts [Categorized] +export default defineAppConfig({ assistant: { - model: 'anthropic/claude-opus-4.5' - } -}) -``` - -### Site Name in Responses - -The assistant automatically uses your site name in its responses. Configure the site name in `nuxt.config.ts`: - -```ts [nuxt.config.ts] -export default defineNuxtConfig({ - site: { - name: 'My Documentation' + faqQuestions: [ + { + category: 'Getting Started', + items: ['How do I install Docus?', 'What is the project structure?'] + } + ] } }) ``` - -This makes the assistant respond as "the My Documentation assistant" and speak with authority about your specific product. +:: ## Programmatic Access @@ -338,21 +230,25 @@ function askQuestion() { open('How do I configure the theme?', true) } - - ``` -### Composable API - -| Property | Type | Description | -|----------|------|-------------| -| `isEnabled` | `ComputedRef` | Whether the assistant is enabled (API key present) | -| `isOpen` | `Ref` | Whether the slideover is open | -| `open(message?, clearPrevious?)` | `Function` | Open the assistant, optionally with a message | -| `close()` | `Function` | Close the assistant slideover | -| `toggle()` | `Function` | Toggle the assistant open/closed | -| `clearMessages()` | `Function` | Clear the conversation history | +::field-group + ::field{name="isEnabled" type="ComputedRef"} + Whether the agent's chat feature is enabled. + :: + ::field{name="isOpen" type="Ref"} + Whether the slideover is open. + :: + ::field{name="open" type="Function"} + Open the assistant: `open(message?: string, clearPrevious?: boolean)` + :: + ::field{name="close" type="Function"} + Close the assistant slideover. + :: + ::field{name="toggle" type="Function"} + Toggle the assistant open/closed. + :: + ::field{name="clearMessages" type="Function"} + Clear the conversation history. + :: +:: diff --git a/docs/content/en/4.ai/2.mcp.md b/docs/content/en/4.ai/2.mcp.md index b8b5c4f05..412e4f36d 100644 --- a/docs/content/en/4.ai/2.mcp.md +++ b/docs/content/en/4.ai/2.mcp.md @@ -1,9 +1,6 @@ ---- -title: MCP Server -description: Connect your documentation to AI tools with a native MCP server. -navigation: - icon: i-lucide-cpu ---- +# MCP Server + +> Connect your documentation to AI tools with a native MCP server. ## About MCP Servers @@ -25,9 +22,11 @@ For example, if a user asks a coding question and the LLM determines that your d Your MCP server is automatically available at the `/mcp` path of your documentation URL. -::note + + For example, if your documentation is hosted at `https://docs.example.com`, your MCP server URL is `https://docs.example.com/mcp`. -:: + + ## Disable the MCP Server @@ -43,23 +42,116 @@ export default defineNuxtConfig({ ## Built-in Tools -Docus provides two tools out of the box that allow any LLM to discover and read your documentation: +Docus provides tools out of the box that allow any LLM to discover and read your documentation: ### `list-pages` -Lists all documentation pages with their titles, paths, and descriptions. AI assistants should call this first to discover available content. - -| Parameter | Type | Description | -| --------- | ----------------- | ---------------------- | -| `locale` | string (optional) | Filter pages by locale | +Lists all documentation pages with their titles, paths, descriptions, and repository file paths. AI assistants should call this first to discover available content. + + + + + + + + + + + + + + + + + + + + + +
+ Parameter + + Type + + Description +
+ + locale + + + string (optional) + + Filter pages by locale +
### `get-page` -Retrieves the full markdown content of a specific documentation page. - -| Parameter | Type | Description | -| --------- | ----------------- | -------------------------------------------------------- | -| `path` | string (required) | The page path (e.g., `/en/getting-started/installation`) | +Retrieves the full markdown content of a specific documentation page, including its physical file path in the repository. + + + + + + + + + + + + + + + + + + + + + +
+ Parameter + + Type + + Description +
+ + path + + + string (required) + + The page path (e.g., + /en/getting-started/installation + + + ) +
+ +### `commit-files` + +Commits one or more documentation files to a GitHub branch in a single atomic commit using the Git Trees API. + +::field-group +::field{name="owner" type="string" required} +GitHub repository owner. +:: +::field{name="repo" type="string" required} +GitHub repository name. +:: +::field{name="branch" type="string" required} +Branch to commit to (must be the PR head branch). +:: +::field{name="token" type="string" required} +GitHub installation access token. +:: +::field{name="files" type="array" required} +Array of `{ path, content }` objects to commit. +:: +::field{name="message" type="string" required} +Commit message (conventional commits format recommended). +:: +:: ## Setup @@ -75,7 +167,11 @@ claude mcp add --transport http my-docs https://docs.example.com/mcp ### Cursor -:install-button{ide="cursor" label="Install in Cursor" url="https://docs.example.com/mcp"} + + + + + Or manually create/update `.cursor/mcp.json` in your project root: @@ -94,7 +190,11 @@ Or manually create/update `.cursor/mcp.json` in your project root: Ensure you have GitHub Copilot and GitHub Copilot Chat extensions installed. -:install-button{ide="vscode" label="Install in VS Code" url="https://docs.example.com/mcp"} + + + + + Or manually create/update the `.vscode/mcp.json` file: @@ -112,7 +212,7 @@ Or manually create/update the `.vscode/mcp.json` file: ### Windsurf 1. Open Windsurf and navigate to **Settings** > **Windsurf Settings** > **Cascade** -2. Click the **Manage MCPs** button, then select the **View raw config** option +2. Click the **Manage MCPs**, then select the **View raw config** option 3. Add the following configuration: ```json [.codeium/windsurf/mcp_config.json] @@ -268,6 +368,8 @@ export default defineMcpTool({ }) ``` -::tip{to="https://mcp-toolkit.nuxt.dev/"} + + Check the MCP Toolkit documentation for more information about tools, resources, prompts, handlers and advanced configuration. -:: + + diff --git a/docs/content/fr/4.ai/1.assistant.md b/docs/content/fr/4.ai/1.assistant.md index 0d4f03649..35ba5f341 100644 --- a/docs/content/fr/4.ai/1.assistant.md +++ b/docs/content/fr/4.ai/1.assistant.md @@ -1,334 +1,146 @@ ---- -title: Assistant -description: Ajoutez un chat propulsé par l'IA à votre documentation qui répond aux questions, cite les sources et génère des exemples de code. -navigation: - icon: i-lucide-sparkles ---- +# AI Agent -## À propos de l'Assistant +> Ajoutez un chat propulsé par l'IA et des revues automatiques de documentation pour vos pull requests. -L'assistant répond aux questions sur votre documentation via des requêtes en langage naturel. Il est intégré directement dans votre site de documentation, permettant aux utilisateurs de trouver rapidement des réponses. +## Aperçu -Lorsque les utilisateurs posent des questions, l'assistant : +L'Agent IA de Docus apporte des capacités d'intelligence artificielle avancées à votre documentation, aidant à la fois vos utilisateurs et vos développeurs. -- **Recherche et récupère** le contenu pertinent de votre documentation en utilisant un [serveur MCP](/fr/ai/mcp). -- **Cite les sources** avec des liens navigables vers les pages référencées. -- **Génère des exemples de code** copiables pour aider les utilisateurs à implémenter les solutions. +1. **Assistant IA (Chat)** - Répond aux questions des utilisateurs directement sur votre site de documentation, cite les sources et génère des exemples de code. +2. **Agent Review** - Surveille automatiquement les pull requests pour s'assurer que la documentation reste synchronisée avec les changements de code, en suggérant ou en effectuant les mises à jour nécessaires. ::note -L'assistant nécessite une clé API [AI Gateway](https://vercel.com/docs/ai-gateway) pour fonctionner. +Ces deux fonctionnalités nécessitent une clé API [AI Gateway](https://vercel.com/docs/ai-gateway) pour fonctionner. :: -## Comment ça fonctionne - -L'assistant utilise une architecture multi-agents : +## Assistant (Chat) -1. **Agent principal** - Reçoit les questions des utilisateurs et décide quand rechercher dans la documentation -2. **Agent de recherche** - Utilise les outils du [serveur MCP](/fr/ai/mcp) pour trouver le contenu pertinent -3. **Génération de réponse** - Synthétise les informations en réponses utiles et conversationnelles +L'assistant répond aux questions sur votre documentation via des requêtes en langage naturel. Il est intégré directement dans votre site, permettant aux utilisateurs de trouver rapidement des réponses. -Par défaut, l'assistant se connecte au serveur MCP intégré de votre documentation à `/mcp`, lui donnant accès à toutes vos pages sans configuration supplémentaire. Vous pouvez également vous connecter à un serveur MCP externe si nécessaire. +### Comment ça fonctionne -## Démarrage rapide - -### 1. Obtenir une clé API - -Obtenez une clé API depuis [Vercel AI Gateway](https://vercel.com/~/ai/api-keys). AI Gateway fonctionne avec plusieurs fournisseurs d'IA (OpenAI, Anthropic, Google, et plus) via une API unifiée. +Lorsque les utilisateurs posent des questions, l'assistant : -### 2. Configurer la variable d'environnement +- **Recherche et récupère** le contenu pertinent de votre documentation en utilisant un [serveur MCP](/fr/ai/mcp). +- **Cite les sources** avec des liens navigables vers les pages référencées. +- **Génère des exemples de code** copiables pour aider les utilisateurs à implémenter les solutions. -Ajoutez votre clé API à votre environnement : +### Démarrage rapide +1. **Obtenir une clé API** - Obtenez une clé API depuis [Vercel AI Gateway](https://vercel.com/~/ai/api-keys). +2. **Configurer la variable d'environnement** - Ajoutez votre clé API à votre environnement : ```bash [.env] AI_GATEWAY_API_KEY=votre-cle-api ``` +3. **Déployer** - L'assistant est automatiquement activé lorsqu'une clé API est détectée. -### 3. Déployer - -C'est tout ! L'assistant est automatiquement activé lorsqu'une clé API est détectée. Déployez votre documentation et l'assistant sera disponible pour vos utilisateurs. - -## Utiliser l'Assistant - -Les utilisateurs peuvent interagir avec l'assistant de plusieurs façons : - -### Input flottant - -Sur les pages de documentation, un champ de saisie flottant apparaît en bas de l'écran. Les utilisateurs peuvent taper leurs questions directement et appuyer sur Entrée pour obtenir des réponses. - -::tip -Utilisez le raccourci clavier :kbd{value="meta"} :kbd{value="I"} pour activer l'input flottant. -:: - -### Expliquer avec l'IA +### Utiliser l'Assistant -Chaque page de documentation inclut un bouton **Explain with AI** dans la barre latérale de la table des matières. Cliquer sur ce bouton ouvre l'assistant avec la page actuelle comme contexte. +- **Input flottant** : Un champ de saisie apparaît en bas des pages. Appuyez sur I pour lui donner le focus. +- **Expliquer avec l'IA** : Un bouton dans la barre latérale qui demande à l'assistant d'expliquer la page actuelle. +- **Slideover Chat** : Un panneau latéral qui affiche l'historique de la conversation. -### Chat en panneau latéral +## Agent Review -Lorsqu'une conversation commence, un panneau coulissant s'ouvre sur le côté droit de l'écran. Ce panneau affiche l'historique de la conversation et permet aux utilisateurs de continuer à poser des questions. +L'agent de revue surveille les pull requests sur votre dépôt GitHub. Lorsqu'une PR est ouverte ou mise à jour, l'agent analyse les changements et utilise les [outils MCP intégrés](/fr/ai/mcp#outils-int%C3%A9gr%C3%A9s) pour trouver les pages de documentation concernées. Il suggère ou valide ensuite les mises à jour directement sur la branche de la PR. -## Configuration +### Démarrage rapide -Configurez l'assistant via `app.config.ts` : - -```ts [app.config.ts] -export default defineAppConfig({ - assistant: { - // Afficher l'input flottant sur les pages de documentation - floatingInput: true, - - // Afficher le bouton "Expliquer avec l'IA" dans la barre latérale - explainWithAi: true, - - // Questions FAQ à afficher quand le chat est vide - faqQuestions: [], - - // Raccourcis clavier - shortcuts: { - focusInput: 'meta_i' - }, - - // Icônes personnalisées - icons: { - trigger: 'i-lucide-sparkles', - explain: 'i-lucide-brain' - } - } -}) -``` - -### Questions FAQ - -Affichez des questions suggérées quand le chat est vide. Cela aide les utilisateurs à découvrir ce qu'ils peuvent demander. - -#### Format simple - -```ts [app.config.ts] -export default defineAppConfig({ - assistant: { - faqQuestions: [ - 'Comment installer Docus ?', - 'Comment personnaliser le thème ?', - 'Comment ajouter des composants à mes pages ?' - ] - } -}) +1. **Créer une GitHub App** - Dans les paramètres de votre organisation GitHub, créez une nouvelle application avec : + - **Repository permissions** : `Content (Read & Write)`, `Pull Requests (Read & Write)` + - **Events** : `Pull request` +2. **Configurer les variables d'environnement** - Définissez les variables suivantes dans votre environnement de production : +```bash [.env] +GITHUB_APP_ID=votre-app-id +GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----" +GITHUB_WEBHOOK_SECRET=votre-secret-webhook ``` - -#### Format avec catégories - -Organisez les questions en catégories : - -```ts [app.config.ts] -export default defineAppConfig({ - assistant: { - faqQuestions: [ - { - category: 'Démarrage', - items: [ - 'Comment installer Docus ?', - 'Quelle est la structure du projet ?' - ] - }, - { - category: 'Personnalisation', - items: [ - 'Comment changer les couleurs du thème ?', - 'Comment ajouter un logo personnalisé ?' - ] +3. **Activer l'Agent Review** - Mettez à jour votre `nuxt.config.ts` : +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + docus: { + agent: { + review: { + enabled: true } - ] - } -}) -``` - -#### Format multilingue - -Pour une documentation multilingue, fournissez les questions FAQ par locale : - -```ts [app.config.ts] -export default defineAppConfig({ - assistant: { - faqQuestions: { - en: [ - { category: 'Getting Started', items: ['How do I install?'] } - ], - fr: [ - { category: 'Démarrage', items: ['Comment installer ?'] } - ] } } }) ``` +4. **Enregistrer le Webhook** - Déployez votre site et définissez l'URL de webhook de l'application GitHub sur `https://votre-site.com/__docus__/webhook/github`. -## Raccourcis clavier - -Configurez le raccourci clavier pour activer l'input flottant : +### Options de configuration -```ts [app.config.ts] -export default defineAppConfig({ - assistant: { - shortcuts: { - // Par défaut : 'meta_i' (Cmd+I sur Mac, Ctrl+I sur Windows) - focusInput: 'meta_k' // Changer pour Cmd/Ctrl+K - } - } -}) -``` - -Le format de raccourci utilise des underscores pour séparer les touches. Exemples courants : -- `meta_i` - Cmd+I (Mac) / Ctrl+I (Windows) -- `meta_k` - Cmd+K (Mac) / Ctrl+K (Windows) -- `ctrl_shift_p` - Ctrl+Shift+P - -## Icônes personnalisées - -Personnalisez les icônes utilisées par l'assistant : - -```ts [app.config.ts] -export default defineAppConfig({ - assistant: { - icons: { - // Icône pour le bouton déclencheur et l'en-tête du panneau - trigger: 'i-lucide-bot', - - // Icône pour le bouton "Expliquer avec l'IA" - explain: 'i-lucide-lightbulb' - } - } -}) -``` - -Les icônes utilisent le format [Iconify](https://iconify.design/) (ex: `i-lucide-sparkles`, `i-heroicons-sparkles`). - -## Internationalisation - -Tous les textes de l'interface sont automatiquement traduits selon la locale de l'utilisateur. Docus inclut des traductions intégrées pour l'anglais et le français. - -Les textes suivants sont traduits : -- Titre et placeholder du panneau -- Textes des infobulles -- Libellés des boutons ("Effacer le chat", "Fermer", "Expliquer avec l'IA") -- Messages de statut ("Réflexion...", "Le chat est effacé au rechargement") - -::tip -Si aucun `title` ou `placeholder` personnalisé n'est défini dans `app.config.ts`, les valeurs traduites des fichiers i18n seront utilisées automatiquement. +::field-group +::field{name="enabled" type="boolean" default="false"} +Active le gestionnaire de webhook et le workflow de revue. +:: +::field{name="mode" type="'comment' | 'commit'" default="'comment'"} +Définit si l'agent doit poster un commentaire sur la PR ou committer directement les changements. +:: +::field{name="githubRepo" type="string" default="auto"} +Dépôt cible au format `owner/repo`. Auto-détecté sur Vercel, Netlify et CI. +:: :: - -## Désactiver des fonctionnalités - -### Désactiver l'input flottant - -Masquez l'input flottant en bas des pages de documentation : - -```ts [app.config.ts] -export default defineAppConfig({ - assistant: { - floatingInput: false - } -}) -``` - -### Désactiver "Explain with AI" - -Masquez le bouton "Explain with AI" dans la barre latérale de documentation : - -```ts [app.config.ts] -export default defineAppConfig({ - assistant: { - explainWithAi: false - } -}) -``` - -### Désactiver l'assistant entièrement - -L'assistant est automatiquement désactivé quand aucune clé API n'est configurée. Pour le désactiver explicitement, supprimez simplement la variable d'environnement : - -```bash [.env] -# Commentez ou supprimez la clé API -# AI_GATEWAY_API_KEY=votre-cle-api -``` ## Configuration avancée -Configurez les options avancées dans `nuxt.config.ts` : +Configurez l'agent IA dans votre `nuxt.config.ts` : ```ts [nuxt.config.ts] export default defineNuxtConfig({ - assistant: { - // Modèle IA (utilise le format AI SDK Gateway) - model: 'google/gemini-3-flash', + docus: { + agent: { + // Modèle IA (utilise le format AI SDK Gateway) + model: 'google/gemini-3-flash', - // Serveur MCP (chemin ou URL) - mcpServer: '/mcp', + // Serveur MCP (chemin ou URL) + mcpServer: '/mcp', - // Chemin de l'endpoint API - apiPath: '/__docus__/assistant' - } -}) -``` - -### Configuration du serveur MCP - -L'assistant utilise un serveur MCP pour accéder à votre documentation. Vous avez deux options : - -#### Utiliser le serveur MCP intégré (par défaut) - -Par défaut, l'assistant utilise le serveur MCP intégré de Docus à `/mcp` : - -```ts [nuxt.config.ts] -export default defineNuxtConfig({ - assistant: { - mcpServer: '/mcp' - } -}) -``` - -::warning -Assurez-vous que le serveur MCP est activé dans votre configuration. Si vous avez personnalisé le chemin MCP, mettez à jour `mcpServer` en conséquence. -:: - -#### Utiliser un serveur MCP externe - -Connectez-vous à n'importe quel serveur MCP externe en fournissant une URL complète : + chat: { + // Chemin de l'endpoint API pour l'assistant + apiPath: '/__docus__/assistant' + }, -```ts [nuxt.config.ts] -export default defineNuxtConfig({ - assistant: { - mcpServer: 'https://autre-docs.exemple.com/mcp' + review: { + // Activer les revues automatiques de documentation + enabled: true, + // Mode de retour de l'agent + mode: 'comment' + } + } } }) ``` -C'est utile lorsque vous voulez que l'assistant réponde aux questions d'une autre source de documentation, ou lors de la connexion à une base de connaissances centralisée. +### Modèle IA -### Modèle IA personnalisé - -L'assistant utilise `google/gemini-3-flash` par défaut. Vous pouvez le changer pour n'importe quel modèle supporté par AI SDK Gateway : +L'agent utilise `google/gemini-3-flash` par défaut. Vous pouvez le changer pour n'importe quel modèle supporté par AI SDK Gateway : ```ts [nuxt.config.ts] export default defineNuxtConfig({ - assistant: { - model: 'anthropic/claude-opus-4.5' + docus: { + agent: { + model: 'anthropic/claude-3-5-sonnet' + } } }) ``` -### Nom du site dans les réponses +### Serveur MCP -L'assistant utilise automatiquement le nom de votre site dans ses réponses. Configurez le nom du site dans `nuxt.config.ts` : +L'agent utilise un serveur MCP pour accéder à votre documentation. Par défaut, il utilise le serveur MCP intégré de Docus à `/mcp`. ```ts [nuxt.config.ts] export default defineNuxtConfig({ - site: { - name: 'Ma Documentation' + docus: { + agent: { + mcpServer: 'https://autre-docs.exemple.com/mcp' + } } }) ``` -Cela permet à l'assistant de répondre en tant qu'"assistant de Ma Documentation" et de parler avec autorité sur votre produit spécifique. - ## Accès programmatique Utilisez le composable `useAssistant` pour contrôler l'assistant programmatiquement : @@ -336,27 +148,20 @@ Utilisez le composable `useAssistant` pour contrôler l'assistant programmatique ```vue - - ``` -### API du composable +`isEnabled` retourne `true` si l'assistant de chat est activé (clé API présente). -| Propriété | Type | Description | -|-----------|------|-------------| -| `isEnabled` | `ComputedRef` | Si l'assistant est activé (clé API présente) | -| `isOpen` | `Ref` | Si le panneau est ouvert | -| `open(message?, clearPrevious?)` | `Function` | Ouvrir l'assistant, optionnellement avec un message | -| `close()` | `Function` | Fermer le panneau de l'assistant | -| `toggle()` | `Function` | Basculer l'assistant ouvert/fermé | -| `clearMessages()` | `Function` | Effacer l'historique de conversation | +```ts [app.config.ts] +export default defineAppConfig({ + assistant: { + // Les FAQ, raccourcis et icônes sont toujours configurés dans app.config.ts + faqQuestions: ['Comment installer Docus ?'] + } +}) +``` + +::tip{to="/fr/ai/mcp"} +En savoir plus sur le serveur MCP qui alimente l'Agent IA. +:: diff --git a/docs/content/fr/4.ai/2.mcp.md b/docs/content/fr/4.ai/2.mcp.md index 14995bfc0..e02f981ab 100644 --- a/docs/content/fr/4.ai/2.mcp.md +++ b/docs/content/fr/4.ai/2.mcp.md @@ -1,9 +1,6 @@ ---- -title: Serveur MCP -description: Connectez votre documentation aux outils IA avec un serveur MCP natif. -navigation: - icon: i-lucide-cpu ---- +# Serveur MCP + +> Connectez votre documentation aux outils IA avec un serveur MCP natif. ## À Propos des Serveurs MCP @@ -25,9 +22,11 @@ Par exemple, si un utilisateur pose une question de code et que le LLM détermin Votre serveur MCP est automatiquement disponible au chemin `/mcp` de l'URL de votre documentation. -::note + + Par exemple, si votre documentation est hébergée à `https://docs.example.com`, l'URL de votre serveur MCP est `https://docs.example.com/mcp`. -:: + + ## Désactiver le Serveur MCP @@ -43,23 +42,116 @@ export default defineNuxtConfig({ ## Outils Intégrés -Docus fournit deux outils par défaut qui permettent à n'importe quel LLM de découvrir et lire votre documentation : +Docus fournit des outils par défaut qui permettent à n'importe quel LLM de découvrir et lire votre documentation : ### `list-pages` -Liste toutes les pages de documentation avec leurs titres, chemins et descriptions. Les assistants IA doivent appeler cet outil en premier pour découvrir le contenu disponible. - -| Paramètre | Type | Description | -| --------- | ------------------ | ---------------------------- | -| `locale` | string (optionnel) | Filtrer les pages par locale | +Liste toutes les pages de documentation avec leurs titres, chemins, descriptions et chemins de fichiers dans le dépôt. Les assistants IA doivent appeler cet outil en premier pour découvrir le contenu disponible. + + + + + + + + + + + + + + + + + + + + + +
+ Paramètre + + Type + + Description +
+ + locale + + + string (optionnel) + + Filtrer les pages par locale +
### `get-page` -Récupère le contenu markdown complet d'une page de documentation spécifique. - -| Paramètre | Type | Description | -| --------- | --------------- | ------------------------------------------------------------- | -| `path` | string (requis) | Le chemin de la page (ex: `/fr/getting-started/installation`) | +Récupère le contenu markdown complet d'une page de documentation spécifique, incluant son chemin de fichier physique dans le dépôt. + + + + + + + + + + + + + + + + + + + + + +
+ Paramètre + + Type + + Description +
+ + path + + + string (requis) + + Le chemin de la page (ex: + /fr/getting-started/installation + + + ) +
+ +### `commit-files` + +Commit un ou plusieurs fichiers de documentation sur une branche GitHub en un seul commit atomique via l'API Git Trees. + +::field-group +::field{name="owner" type="string" required} +Propriétaire du dépôt GitHub. +:: +::field{name="repo" type="string" required} +Nom du dépôt GitHub. +:: +::field{name="branch" type="string" required} +Branche sur laquelle committer (doit être la branche de la PR). +:: +::field{name="token" type="string" required} +Jeton d'accès d'installation GitHub. +:: +::field{name="files" type="array" required} +Tableau d'objets `{ path, content }` à committer. +:: +::field{name="message" type="string" required} +Message de commit (format conventional commits recommandé). +:: +:: ## Configuration @@ -75,7 +167,11 @@ claude mcp add --transport http my-docs https://docs.example.com/mcp ### Cursor -:install-button{ide="cursor" label="Installer dans Cursor" url="https://docs.example.com/mcp"} + + + + + Ou créez/modifiez manuellement `.cursor/mcp.json` à la racine de votre projet : @@ -94,7 +190,11 @@ Ou créez/modifiez manuellement `.cursor/mcp.json` à la racine de votre projet Assurez-vous d'avoir les extensions GitHub Copilot et GitHub Copilot Chat installées. -:install-button{ide="vscode" label="Installer dans VS Code" url="https://docs.example.com/mcp"} + + + + + Ou créez/modifiez manuellement le fichier `.vscode/mcp.json` : diff --git a/docs/nuxt.config.ts b/docs/nuxt.config.ts index 5c548acd9..add1d6d55 100644 --- a/docs/nuxt.config.ts +++ b/docs/nuxt.config.ts @@ -16,6 +16,13 @@ export default defineNuxtConfig({ sourcemap: false, }, }, + docus: { + agent: { + review: { + enabled: true, + }, + }, + }, i18n: { defaultLocale: 'en', locales: [{ diff --git a/layer/modules/assistant/index.ts b/layer/modules/assistant/index.ts index c41b9a0ee..9f9d5a5b7 100644 --- a/layer/modules/assistant/index.ts +++ b/layer/modules/assistant/index.ts @@ -1,45 +1,96 @@ import { addComponent, addImports, addServerHandler, createResolver, defineNuxtModule, logger } from '@nuxt/kit' +import { getGitEnv, getLocalGitInfo } from '../../utils/git' -export interface AssistantModuleOptions { - /** - * API endpoint path for the assistant - * @default '/__docus__/assistant' - */ - apiPath?: string - /** - * MCP server URL or path. - * - Use a path like '/mcp' to use the built-in Docus MCP server - * - Use a full URL like 'https://docs.example.com/mcp' for external MCP servers - * @default '/mcp' - */ - mcpServer?: string - /** - * AI model to use via AI SDK Gateway - * @default 'google/gemini-3-flash' - */ - model?: string +export interface DocusModuleOptions { + agent?: { + /** + * AI model to use via AI SDK Gateway + * @default 'google/gemini-3-flash' + */ + model?: string + /** + * MCP server URL or path. + * - Use a path like '/mcp' to use the built-in Docus MCP server + * - Use a full URL like 'https://docs.example.com/mcp' for external MCP servers + * @default '/mcp' + */ + mcpServer?: string + /** + * AI chat assistant embedded in the docs site. + */ + chat?: { + /** + * Enable the chat assistant (auto-detected from AI_GATEWAY_API_KEY) + * @default true + */ + enabled?: boolean + /** + * API endpoint path for the chat assistant + * @default '/__docus__/assistant' + */ + apiPath?: string + } + /** + * PR documentation review agent triggered by GitHub webhooks. + */ + review?: { + /** + * Enable the webhook handler + * @default false + */ + enabled?: boolean + /** + * Whether the agent posts a PR comment ('comment') or directly commits ('commit') + * @default 'comment' + */ + mode?: 'comment' | 'commit' + /** + * Target GitHub repository in 'org/repo' format. + * Auto-detected from Vercel, Netlify, and GitHub Actions CI environment variables. + */ + githubRepo?: string + } + } } const log = logger.withTag('Docus') -export default defineNuxtModule({ +async function detectGithubRepo(rootDir: string): Promise { + // Local: read origin remote from .git/config + const local = await getLocalGitInfo(rootDir) + if (local?.owner && local?.name) + return `${local.owner}/${local.name}` + + // CI: Vercel, Netlify, GitHub Actions, GitLab CI + const env = getGitEnv() + if (env.owner && env.name) + return `${env.owner}/${env.name}` +} + +export default defineNuxtModule({ meta: { - name: 'assistant', - configKey: 'assistant', + name: 'docus:agent', + configKey: 'docus', }, defaults: { - apiPath: '/__docus__/assistant', - mcpServer: '/mcp', - model: 'google/gemini-3-flash', + agent: { + model: 'google/gemini-3-flash', + mcpServer: '/mcp', + chat: { + apiPath: '/__docus__/assistant', + }, + }, }, - setup(options, nuxt) { + async setup(options, nuxt) { + const agent = options.agent! const hasApiKey = !!process.env.AI_GATEWAY_API_KEY + const chatEnabled = hasApiKey && (agent.chat?.enabled !== false) const { resolve } = createResolver(import.meta.url) - nuxt.options.runtimeConfig.public.assistant = { - enabled: hasApiKey, - apiPath: options.apiPath!, + nuxt.options.runtimeConfig.public.agent = { + chatEnabled, + chatApiPath: agent.chat?.apiPath!, } addImports([ @@ -60,23 +111,48 @@ export default defineNuxtModule({ components.forEach(name => addComponent({ name, - filePath: hasApiKey + filePath: chatEnabled ? resolve(`./runtime/components/${name}.vue`) : resolve('./runtime/components/AssistantChatDisabled.vue'), }), ) + if (process.env.GITHUB_APP_ID) + nuxt.options.runtimeConfig.githubAppId = process.env.GITHUB_APP_ID + if (process.env.GITHUB_APP_PRIVATE_KEY) + nuxt.options.runtimeConfig.githubAppPrivateKey = process.env.GITHUB_APP_PRIVATE_KEY + if (process.env.GITHUB_WEBHOOK_SECRET) + nuxt.options.runtimeConfig.webhookSecret = process.env.GITHUB_WEBHOOK_SECRET + + if (agent.review?.enabled) { + const githubRepo = agent.review.githubRepo || await detectGithubRepo(nuxt.options.rootDir) + if (githubRepo) + nuxt.options.runtimeConfig.agentGithubRepo = githubRepo + else + log.warn('Review agent enabled but no GitHub repository detected — set docus.agent.review.githubRepo or deploy on Vercel, Netlify, or GitHub Actions') + + addServerHandler({ + route: '/__docus__/webhook/github', + handler: resolve('./runtime/server/api/webhook/github.post'), + }) + + const webhookPath = '/__docus__/webhook/github' + const siteUrl = process.env.NUXT_PUBLIC_SITE_URL?.replace(/\/$/, '') + || nuxt.options.devServer?.url?.replace(/\/$/, '') + log.info(`GitHub webhook registered — set Webhook URL to: ${siteUrl ? `${siteUrl}${webhookPath}` : webhookPath}`) + } + if (!hasApiKey) { - log.warn('AI assistant disabled: AI_GATEWAY_API_KEY not found') + log.warn('AI agent disabled: AI_GATEWAY_API_KEY not found') return } - nuxt.options.runtimeConfig.assistant = { - mcpServer: options.mcpServer!, - model: options.model!, + nuxt.options.runtimeConfig.agent = { + mcpServer: agent.mcpServer!, + model: agent.model!, } - const routePath = options.apiPath!.replace(/^\//, '') + const routePath = agent.chat?.apiPath!.replace(/^\//, '') addServerHandler({ route: `/${routePath}`, handler: resolve('./runtime/server/api/search'), @@ -86,13 +162,17 @@ export default defineNuxtModule({ declare module 'nuxt/schema' { interface PublicRuntimeConfig { - assistant: { - enabled: boolean - apiPath: string + agent: { + chatEnabled: boolean + chatApiPath: string } } interface RuntimeConfig { - assistant: { + githubAppId: string + githubAppPrivateKey: string + webhookSecret: string + agentGithubRepo: string + agent: { mcpServer: string model: string } diff --git a/layer/modules/assistant/runtime/composables/useAssistant.ts b/layer/modules/assistant/runtime/composables/useAssistant.ts index bdcf419de..1c6ba528c 100644 --- a/layer/modules/assistant/runtime/composables/useAssistant.ts +++ b/layer/modules/assistant/runtime/composables/useAssistant.ts @@ -25,10 +25,10 @@ const PANEL_WIDTH_EXPANDED = 520 export function useAssistant() { const config = useRuntimeConfig() const appConfig = useAppConfig() - const assistantRuntimeConfig = config.public.assistant as { enabled?: boolean } | undefined + const assistantRuntimeConfig = config.public.agent as { chatEnabled?: boolean } | undefined const assistantConfig = appConfig.assistant as { faqQuestions?: FaqQuestions | LocalizedFaqQuestions } | undefined const docusRuntimeConfig = appConfig.docus as { locale?: string } | undefined - const isEnabled = computed(() => assistantRuntimeConfig?.enabled ?? false) + const isEnabled = computed(() => assistantRuntimeConfig?.chatEnabled ?? false) const isOpen = useState('assistant-open', () => false) const isExpanded = useState('assistant-expanded', () => false) diff --git a/layer/modules/assistant/runtime/server/api/search.ts b/layer/modules/assistant/runtime/server/api/search.ts index 279508057..4160bd53a 100644 --- a/layer/modules/assistant/runtime/server/api/search.ts +++ b/layer/modules/assistant/runtime/server/api/search.ts @@ -58,7 +58,7 @@ export default defineEventHandler(async (event) => { const siteName = siteConfig.name || 'Documentation' - const mcpServer = config.assistant.mcpServer + const mcpServer = config.agent.mcpServer const isExternalUrl = mcpServer.startsWith('http://') || mcpServer.startsWith('https://') const baseURL = config.app?.baseURL?.replace(/\/$/, '') || '' const mcpUrl = isExternalUrl @@ -76,7 +76,7 @@ export default defineEventHandler(async (event) => { execute: async ({ writer }: { writer: UIMessageStreamWriter }) => { const modelMessages = await convertToModelMessages(messages) const result = streamText({ - model: config.assistant.model, + model: config.agent.model, maxOutputTokens: 4000, maxRetries: 2, stopWhen: stopWhenResponseComplete, diff --git a/layer/modules/assistant/runtime/server/api/webhook/github.post.ts b/layer/modules/assistant/runtime/server/api/webhook/github.post.ts new file mode 100644 index 000000000..71fedf51f --- /dev/null +++ b/layer/modules/assistant/runtime/server/api/webhook/github.post.ts @@ -0,0 +1,69 @@ +// TODO(workflow): import { start } from 'workflow/api' +import { agentReviewWorkflow } from '../../workflows/agent-review' +import { getInstallationToken } from '../../utils/github-auth' +import { fetchPrDiff, filterDiff, logDiff } from '../../utils/github-diff' +import { verifyWebhookSignature } from '../../utils/github-webhook' + +export default defineEventHandler(async (event) => { + const githubEvent = getHeader(event, 'x-github-event') + if (githubEvent !== 'pull_request') return { ok: true } + + const config = useRuntimeConfig() + + const rawBody = await readRawBody(event, false) + if (!rawBody) return sendError(event, createError({ statusCode: 400 })) + + const signature = getHeader(event, 'x-hub-signature-256') ?? '' + if (!verifyWebhookSignature(config.webhookSecret, rawBody, signature)) + return sendError(event, createError({ statusCode: 401, message: 'Invalid signature' })) + + const payload = JSON.parse(rawBody.toString('utf8')) + + if (!['opened', 'reopened', 'synchronize'].includes(payload.action)) return { ok: true } + + // Skip events triggered by bots (e.g. the agent itself committing back to the PR branch) + if (payload.sender?.type === 'Bot') { + console.log(`[agent-review] Skipping bot-triggered event (sender: ${payload.sender.login})`) + return { ok: true } + } + + const { installation, pull_request, repository } = payload + + if (pull_request.base.ref !== repository.default_branch) return { ok: true } + const owner: string = repository.owner.login + const repo: string = repository.name + const branch: string = pull_request.head.ref + const baseSha: string = pull_request.base.sha + const headSha: string = pull_request.head.sha + + const token = await getInstallationToken(installation.id) + + const rawDiff = await fetchPrDiff(owner, repo, baseSha, headSha, token) + + const filteredDiff = filterDiff(rawDiff) + + if (!filteredDiff) { + console.log('[agent-review] No relevant files in diff, skipping') + return { ok: true } + } + + logDiff(filteredDiff) + + const mcpPath = config.agent.mcpServer + const isExternalUrl = mcpPath.startsWith('http://') || mcpPath.startsWith('https://') + const baseURL = (config.app?.baseURL as string | undefined)?.replace(/\/$/, '') || '' + const mcpUrl = isExternalUrl + ? mcpPath + : import.meta.dev + ? `http://localhost:3000${baseURL}${mcpPath}` + : `${getRequestURL(event).origin}${baseURL}${mcpPath}` + + // Return immediately so GitHub doesn't retry the delivery — agent runs in the background + // TODO(workflow): replace with start(agentReviewWorkflow, [...]) for durable execution + event.waitUntil( + agentReviewWorkflow({ owner, repo, branch, filteredDiff, model: config.agent.model, mcpUrl, token, webhookSecret: config.webhookSecret }) + .catch(err => console.error('[agent-review] Workflow failed:', err)), + ) + + return { ok: true } +}) diff --git a/layer/modules/assistant/runtime/server/utils/github-auth.ts b/layer/modules/assistant/runtime/server/utils/github-auth.ts new file mode 100644 index 000000000..52123ac26 --- /dev/null +++ b/layer/modules/assistant/runtime/server/utils/github-auth.ts @@ -0,0 +1,34 @@ +import { createSign } from 'node:crypto' + +function base64url(value: string): string { + return Buffer.from(value).toString('base64url') +} + +function buildJwt(appId: string, privateKey: string): string { + const now = Math.floor(Date.now() / 1000) + const header = base64url(JSON.stringify({ alg: 'RS256', typ: 'JWT' })) + const payload = base64url(JSON.stringify({ iat: now - 60, exp: now + 600, iss: appId })) + const data = `${header}.${payload}` + const sign = createSign('RSA-SHA256') + sign.update(data) + return `${data}.${sign.sign(privateKey, 'base64url')}` +} + +export async function getInstallationToken(installationId: number): Promise { + const config = useRuntimeConfig() + const jwt = buildJwt(config.githubAppId, config.githubAppPrivateKey) + + const response = await $fetch<{ token: string }>( + `https://api.github.com/app/installations/${installationId}/access_tokens`, + { + method: 'POST', + headers: { + 'Authorization': `Bearer ${jwt}`, + 'Accept': 'application/vnd.github.v3+json', + 'User-Agent': 'docus-doc-agent', + }, + }, + ) + + return response.token +} diff --git a/layer/modules/assistant/runtime/server/utils/github-diff.ts b/layer/modules/assistant/runtime/server/utils/github-diff.ts new file mode 100644 index 000000000..a9865467c --- /dev/null +++ b/layer/modules/assistant/runtime/server/utils/github-diff.ts @@ -0,0 +1,70 @@ +// ~12 000 tokens at 4 chars/token +const TOKEN_CHAR_LIMIT = 48_000 + +const KEEP_EXTENSIONS = /\.(?:ts|vue|js)$/ +const DROP_PATTERNS = /package-lock\.json|pnpm-lock\.yaml|yarn\.lock|\.d\.ts$|\/dist\// + +export function filterDiff(rawDiff: string): string { + const chunks = rawDiff.split(/(?=^diff --git )/m) + const kept: string[] = [] + const skipped: string[] = [] + let totalChars = 0 + + for (const chunk of chunks) { + if (!chunk.startsWith('diff --git ')) continue + + const pathMatch = chunk.match(/^diff --git a\/.+ b\/(.+)$/m) + const filePath = pathMatch?.[1] ?? '' + + if (!KEEP_EXTENSIONS.test(filePath) || DROP_PATTERNS.test(filePath) || /^deleted file mode/m.test(chunk)) { + skipped.push(filePath) + continue + } + + if (totalChars + chunk.length > TOKEN_CHAR_LIMIT) { + skipped.push(filePath) + continue + } + + kept.push(chunk) + totalChars += chunk.length + } + + if (skipped.length > 0) + console.warn(`[doc-agent] Skipped files: ${skipped.filter(Boolean).join(', ')}`) + + return kept.join('') +} + +export function logDiff(filteredDiff: string): void { + const chunks = filteredDiff.split(/(?=^diff --git )/m).filter(c => c.startsWith('diff --git ')) + + const files = chunks.map((chunk) => { + const pathMatch = chunk.match(/^diff --git a\/.+ b\/(.+)$/m) + const filePath = pathMatch?.[1] ?? '?' + const added = (chunk.match(/^\+(?!\+\+)/mg) ?? []).length + const removed = (chunk.match(/^-(?!--)/mg) ?? []).length + return { filePath, added, removed } + }) + + const totalAdded = files.reduce((s, f) => s + f.added, 0) + const totalRemoved = files.reduce((s, f) => s + f.removed, 0) + + console.log(`[doc-agent] Diff: ${files.length} file(s) +${totalAdded} -${totalRemoved}`) + for (const { filePath, added, removed } of files) + console.log(` ${filePath} +${added} -${removed}`) +} + +export async function fetchPrDiff(owner: string, repo: string, baseSha: string, headSha: string, token: string): Promise { + return $fetch( + `https://api.github.com/repos/${owner}/${repo}/compare/${baseSha}...${headSha}`, + { + responseType: 'text', + headers: { + 'Authorization': `Bearer ${token}`, + 'Accept': 'application/vnd.github.v3.diff', + 'User-Agent': 'docus-doc-agent', + }, + }, + ) +} diff --git a/layer/modules/assistant/runtime/server/utils/github-webhook.ts b/layer/modules/assistant/runtime/server/utils/github-webhook.ts new file mode 100644 index 000000000..a2a2f77be --- /dev/null +++ b/layer/modules/assistant/runtime/server/utils/github-webhook.ts @@ -0,0 +1,19 @@ +import { createHmac, timingSafeEqual } from 'node:crypto' + +export function verifyWebhookSignature(secret: string, body: Buffer, signature: string): boolean { + const trimmedSecret = secret.trim() + + // Try both buffer and utf8-string HMAC — some proxies re-encode the body + const hmacBuf = createHmac('sha256', trimmedSecret) + hmacBuf.update(body) + const expectedFromBuffer = `sha256=${hmacBuf.digest('hex')}` + const hmacStr = createHmac('sha256', trimmedSecret) + hmacStr.update(body.toString('utf8')) + const received = signature.trim() + const expected = Buffer.from(expectedFromBuffer) + const receivedBuf = Buffer.from(received) + + if (expected.length !== receivedBuf.length) return false + + return timingSafeEqual(expected, receivedBuf) +} diff --git a/layer/modules/assistant/runtime/server/workflows/agent-review.ts b/layer/modules/assistant/runtime/server/workflows/agent-review.ts new file mode 100644 index 000000000..05d5850ad --- /dev/null +++ b/layer/modules/assistant/runtime/server/workflows/agent-review.ts @@ -0,0 +1,195 @@ +import { generateText } from 'ai' +import { gateway } from '@ai-sdk/gateway' +import { createMCPClient } from '@ai-sdk/mcp' + +interface AgentReviewInput { + owner: string + repo: string + branch: string + filteredDiff: string + model: string + mcpUrl: string + token: string + webhookSecret: string +} + +export async function agentReviewWorkflow({ owner, repo, branch, filteredDiff, model, mcpUrl, token, webhookSecret }: AgentReviewInput) { + // TODO(workflow): 'use workflow' + console.log(`[agent-review] Starting review for ${owner}/${repo} branch: ${branch}, model: ${model}`) + console.log(`[agent-review] MCP server: ${mcpUrl}`) + + const mcpClient = await createMCPClient({ + transport: { type: 'http', url: mcpUrl, headers: { 'x-agent-review': webhookSecret } }, + }) + + try { + console.log(`[agent-review] Fetching MCP tools`) + const tools = await mcpClient.tools() + const toolNames = Object.keys(tools) + console.log(`[agent-review] Tools available: ${toolNames.join(', ')}`) + + const prompt = buildReviewPrompt({ owner, repo, branch, diff: filteredDiff, token }) + + console.log(`[agent-review] Running agent (diff: ${filteredDiff.length} chars)`) + const { text, steps } = await generateText({ + model: gateway(model), + stopWhen: ({ steps }) => steps.length >= 20, + messages: [{ role: 'user', content: prompt }], + tools, + }) + + const toolCallCount = steps.reduce((n, s) => n + (s.toolCalls?.length ?? 0), 0) + console.log(`[agent-review] Done — ${steps.length} step(s), ${toolCallCount} tool call(s)`) + if (text) console.log(`[agent-review] Agent message:\n${text}`) + } + finally { + await mcpClient.close() + } +} + +function buildReviewPrompt({ owner, repo, branch, diff, token }: { owner: string, repo: string, branch: string, diff: string, token: string }): string { + return `You are a documentation agent for a Docus documentation site. Your job is to keep the docs in sync with code changes. + +You have been triggered by a pull request on ${owner}/${repo} (branch: ${branch}). + +## Your workflow + +1. Call \`list-pages\` to discover the existing documentation structure — it returns a \`filePath\` field for each page (the actual path in the repository to use with \`commit-files\`) +2. Call \`get-page\` on pages that appear related to the diff (by topic, file name, or module name) — it also returns \`filePath\` +3. Decide what documentation action to take for each relevant file: + - **Existing page that is now outdated** → update the relevant section(s) in place — **this is always the preferred action** + - **Brand-new major feature with absolutely no existing page** → only then create a new MDC page + - **Only internal refactor / types / tests / config** → skip, no docs needed + - When in doubt, update an existing page rather than creating a new one +4. Call \`commit-files\` **ONCE** with ALL files to create or update — do not call it multiple times + +## Commit parameters + +Always pass these exact values to \`commit-files\`: +- owner: "${owner}" +- repo: "${repo}" +- branch: "${branch}" +- token: "${token}" +- files: array of every \`{ path, content }\` you want to write, all in one call — use the \`filePath\` value from \`list-pages\`/\`get-page\` for existing files; for new files, follow the exact same directory pattern +- message: a conventional commit message describing what changed and why (e.g. \`docs: document new \\\`useHead\\\` options added in this PR\`) + +## MDC syntax rules + +**CRITICAL — violations will break the site:** + +- **Never write HTML tags.** Use MDC components instead (e.g. \`::note\` not \`
\`). +- **Never delete or modify the frontmatter block** (\`---\` ... \`---\` at the top of the file). You may only append new keys. +- **Preserve all existing MDC component syntax.** If a page already uses \`::callout\`, keep it exactly as-is unless updating its content. +- Frontmatter must include at minimum: \`title\` and \`description\`. + +### Block components (use \`::\` delimiters) + +\`\`\`mdc +::note +Informational note. +:: + +::tip +Helpful suggestion. +:: + +::warning +Important warning. +:: + +::caution +Destructive / irreversible action warning. +:: + +::callout{icon="i-lucide-info" color="blue"} +Custom callout with icon and color. +:: + +::code-group +\`\`\`bash [npm] +npm install foo +\`\`\` +\`\`\`bash [pnpm] +pnpm add foo +\`\`\` +:: + +::card{title="My card" icon="i-lucide-star" to="/some-link"} +Card body text. +:: + +:::card-group +::card{title="A" icon="i-lucide-box"} +First card. +:: +::card{title="B" icon="i-lucide-box"} +Second card. +:: +::: + +::field-group +::field{name="myOption" type="string"} +Description of the option. +:: +:: + +::steps{level="4"} +#### Step one title +Step one body. +#### Step two title +Step two body. +:: +\`\`\` + +### Inline components (use \`:\` prefix) + +\`\`\`mdc +:icon{name="i-lucide-check"} +:badge[v1.2.0] +\`\`\` + +### Nesting: add one \`:\` per nesting level + +\`\`\`mdc +::tabs +:::tabs-item{label="Preview" icon="i-lucide-eye"} +Content here. +::: +:::tabs-item{label="Code" icon="i-lucide-code"} +\`\`\`ts +const x = 1 +\`\`\` +::: +:: +\`\`\` + +### Component props: inline vs YAML + +\`\`\`mdc +// Inline (short props) +::card{title="Hello" icon="i-lucide-star"} + +// YAML block (many or long props) +::card +--- +title: Hello +icon: i-lucide-star +to: /some-page +--- +Card body. +:: +\`\`\` + +## Tone and existing content + +- **Match the tone of the existing docs exactly.** Read related pages with \`get-page\` first and mirror their sentence structure, vocabulary, and level of detail. +- You may only append new content or update a specific outdated value (e.g. a renamed option, a changed default). If a section is no longer accurate you can delete it if you really think it's not needed anymore. Be careful with deletion. +- **Do not rewrite for style.** If the existing text is correct, leave it unchanged even if you would phrase it differently. +- **Always prefer updating an existing page over creating a new one.** Only create a new page for a brand-new major feature that has no related existing page at all. + +## PR diff + +\`\`\`diff +${diff} +\`\`\`` +} diff --git a/layer/nuxt.config.ts b/layer/nuxt.config.ts index 92537b823..88c4be0ad 100644 --- a/layer/nuxt.config.ts +++ b/layer/nuxt.config.ts @@ -105,6 +105,13 @@ export default defineNuxtConfig({ nitroConfig.prerender.routes.push('/sitemap.xml') }, }, + docus: { + agent: { + review: { + enabled: true, + }, + }, + }, icon: { customCollections: [ { diff --git a/layer/package.json b/layer/package.json index 5709b47e5..3a90a8666 100644 --- a/layer/package.json +++ b/layer/package.json @@ -33,7 +33,7 @@ "@nuxt/kit": "^4.4.2", "@nuxt/ui": "^4.5.1", "@nuxtjs/i18n": "^10.2.3", - "@nuxtjs/mcp-toolkit": "^0.11.0", + "@nuxtjs/mcp-toolkit": "^0.12.0", "@nuxtjs/mdc": "^0.20.2", "@nuxtjs/robots": "^5.7.1", "@shikijs/core": "^4.0.2", diff --git a/layer/server/mcp/prompts/review-pr-docs.ts b/layer/server/mcp/prompts/review-pr-docs.ts new file mode 100644 index 000000000..6cefc10c1 --- /dev/null +++ b/layer/server/mcp/prompts/review-pr-docs.ts @@ -0,0 +1,163 @@ +import { z } from 'zod' + +export default defineMcpPrompt({ + enabled: (event: import('h3').H3Event) => { + const config = useRuntimeConfig(event) + return getHeader(event, 'x-agent-review') === config.webhookSecret + }, + name: 'review-pr-docs', + description: 'Reviews a PR diff and updates the Docus documentation site accordingly. Discovers existing pages, identifies gaps, then creates or updates MDC files directly on the PR branch.', + inputSchema: { + owner: z.string().describe('GitHub repository owner'), + repo: z.string().describe('GitHub repository name'), + branch: z.string().describe('PR head branch to commit documentation updates to'), + diff: z.string().describe('Filtered PR diff (only .ts/.vue/.js files, already truncated)'), + token: z.string().describe('GitHub installation access token for committing changes'), + }, + handler: ({ owner, repo, branch, diff, token }: { owner: string, repo: string, branch: string, diff: string, token: string }) => { + return `You are a documentation agent for a Docus documentation site. Your job is to keep the docs in sync with code changes. + +You have been triggered by a pull request on ${owner}/${repo} (branch: ${branch}). + +## Your workflow + +1. Call \`list-pages\` to discover the existing documentation structure — it returns a \`filePath\` field for each page (the actual path in the repository to use with \`commit-files\`) +2. Call \`get-page\` on pages that appear related to the diff (by topic, file name, or module name) — it also returns \`filePath\` +3. Decide what documentation action to take for each relevant file: + - **Existing page that is now outdated** → update the relevant section(s) in place — **this is always the preferred action** + - **Brand-new major feature with absolutely no existing page** → only then create a new MDC page + - **Only internal refactor / types / tests / config** → skip, no docs needed + - When in doubt, update an existing page rather than creating a new one +4. Call \`commit-files\` **ONCE** with ALL files to create or update — do not call it multiple times + +## Commit parameters + +Always pass these exact values to \`commit-files\`: +- owner: "${owner}" +- repo: "${repo}" +- branch: "${branch}" +- token: "${token}" +- files: array of every \`{ path, content }\` you want to write, all in one call — use the \`filePath\` value from \`list-pages\`/\`get-page\` for existing files; for new files, follow the exact same directory pattern +- message: a conventional commit message describing what changed and why (e.g. \`docs: document new \\\`useHead\\\` options added in this PR\`) + +## MDC syntax rules + +**CRITICAL — violations will break the site:** + +- **Never write HTML tags.** Use MDC components instead (e.g. \`::note\` not \`
\`). +- **Never delete or modify the frontmatter block** (\`---\` ... \`---\` at the top of the file). You may only append new keys. +- **Preserve all existing MDC component syntax.** If a page already uses \`::callout\`, keep it exactly as-is unless updating its content. +- Frontmatter must include at minimum: \`title\` and \`description\`. + +### Block components (use \`::\` delimiters) + +\`\`\`mdc +::note +Informational note. +:: + +::tip +Helpful suggestion. +:: + +::warning +Important warning. +:: + +::caution +Destructive / irreversible action warning. +:: + +::callout{icon="i-lucide-info" color="blue"} +Custom callout with icon and color. +:: + +::code-group +\`\`\`bash [npm] +npm install foo +\`\`\` +\`\`\`bash [pnpm] +pnpm add foo +\`\`\` +:: + +::card{title="My card" icon="i-lucide-star" to="/some-link"} +Card body text. +:: + +:::card-group +::card{title="A" icon="i-lucide-box"} +First card. +:: +::card{title="B" icon="i-lucide-box"} +Second card. +:: +::: + +::field-group +::field{name="myOption" type="string"} +Description of the option. +:: +:: + +::steps{level="4"} +#### Step one title +Step one body. +#### Step two title +Step two body. +:: +\`\`\` + +### Inline components (use \`:\` prefix) + +\`\`\`mdc +:icon{name="i-lucide-check"} +:badge[v1.2.0] +\`\`\` + +### Nesting: add one \`:\` per nesting level + +\`\`\`mdc +::tabs +:::tabs-item{label="Preview" icon="i-lucide-eye"} +Content here. +::: +:::tabs-item{label="Code" icon="i-lucide-code"} +\`\`\`ts +const x = 1 +\`\`\` +::: +:: +\`\`\` + +### Component props: inline vs YAML + +\`\`\`mdc +// Inline (short props) +::card{title="Hello" icon="i-lucide-star"} + +// YAML block (many or long props) +::card +--- +title: Hello +icon: i-lucide-star +to: /some-page +--- +Card body. +:: +\`\`\` + +## Tone and existing content + +- **Match the tone of the existing docs exactly.** Read related pages with \`get-page\` first and mirror their sentence structure, vocabulary, and level of detail. +- **Never remove or shorten existing sections.** You may only append new content or update a specific outdated value (e.g. a renamed option, a changed default). If a section is no longer accurate, add a note below it — do not delete it. +- **Do not rewrite for style.** If the existing text is correct, leave it unchanged even if you would phrase it differently. +- **Always prefer updating an existing page over creating a new one.** Only create a new page for a brand-new major feature that has no related existing page at all. + +## PR diff + +\`\`\`diff +${diff} +\`\`\`` + }, +}) diff --git a/layer/server/mcp/tools/commit-files.ts b/layer/server/mcp/tools/commit-files.ts new file mode 100644 index 000000000..e27d0c4f8 --- /dev/null +++ b/layer/server/mcp/tools/commit-files.ts @@ -0,0 +1,205 @@ +import { z } from 'zod' + +const ALLOWED_EXTENSIONS = /\.(?:md|mdx|mdc)$/ +const MAX_CONTENT_SIZE = 500 * 1024 // 500 KB per file +const PROTECTED_BRANCH_PATTERNS = [ + /^main$/, + /^master$/, + /^release\//, + /^hotfix\//, +] + +export default defineMcpTool({ + description: 'Commits one or more documentation files to a GitHub branch in a single atomic commit using the Git Trees API.', + enabled: (event) => { + const config = useRuntimeConfig(event) + return getHeader(event, 'x-agent-review') === config.webhookSecret + }, + inputSchema: { + owner: z.string().describe('GitHub repository owner'), + repo: z.string().describe('GitHub repository name'), + branch: z.string().describe('Branch to commit to (must be the PR head branch, never the default branch)'), + token: z.string().describe('GitHub installation access token'), + files: z.array(z.object({ + path: z.string().describe('File path relative to repo root (e.g. content/2.guide/my-feature.md)'), + content: z.string().describe('Full file content to write'), + })).min(1).describe('Array of files to create or update in a single commit'), + message: z.string().describe('Commit message. Use conventional commits format: "docs: " — be specific about what was added or updated (e.g. "docs: document new `useHead` options added in PR #42")'), + }, + handler: async ({ owner, repo, branch, token, files, message }: { + owner: string + repo: string + branch: string + token: string + files: Array<{ path: string, content: string }> + message: string + }) => { + const appConfig = useAppConfig() as { github?: { rootDir?: string } } + const contentRepoBase = appConfig.github?.rootDir + ? `${appConfig.github.rootDir}/content` + : 'content' + + console.log(`[commit-files] Preparing commit: ${owner}/${repo} branch=${branch} files=${files.length} contentRepoBase=${contentRepoBase}`) + + // --- Security guards --- + + // 1. Reject protected branch patterns + for (const pattern of PROTECTED_BRANCH_PATTERNS) { + if (pattern.test(branch)) { + console.warn(`[commit-files] Rejected: branch "${branch}" matches protected pattern ${pattern}`) + return errorResult(`Refusing to commit to protected branch: ${branch}`) + } + } + + const githubHeaders = { + 'Authorization': `token ${token}`, + 'Accept': 'application/vnd.github.v3+json', + 'Content-Type': 'application/json', + } + + // 2. Verify branch exists and is not the default branch + let defaultBranch: string + let branchSha: string + try { + const repoInfo = await $fetch<{ default_branch: string }>(`https://api.github.com/repos/${owner}/${repo}`, { + headers: githubHeaders, + }) + defaultBranch = repoInfo.default_branch + console.log(`[commit-files] Default branch: ${defaultBranch}`) + + if (branch === defaultBranch) { + console.warn(`[commit-files] Rejected: branch "${branch}" is the default branch`) + return errorResult(`Refusing to commit directly to the default branch: ${branch}`) + } + + const branchInfo = await $fetch<{ commit: { sha: string } }>(`https://api.github.com/repos/${owner}/${repo}/branches/${branch}`, { + headers: githubHeaders, + }) + branchSha = branchInfo.commit.sha + console.log(`[commit-files] Branch HEAD: ${branchSha}`) + } + catch (err: unknown) { + const msg = err instanceof Error ? err.message : String(err) + console.error(`[commit-files] Failed to fetch repo/branch info: ${msg}`) + return errorResult(`Failed to fetch repo or branch info: ${msg}`) + } + + // 3. Validate each file + for (const file of files) { + // Path traversal + if (file.path.includes('..') || file.path.startsWith('/')) { + console.warn(`[commit-files] Rejected: invalid path "${file.path}"`) + return errorResult(`Invalid file path: ${file.path}`) + } + + // Must live under the content directory + if (!file.path.startsWith(`${contentRepoBase}/`)) { + console.warn(`[commit-files] Rejected: path outside ${contentRepoBase}/: "${file.path}"`) + return errorResult(`File path must start with "${contentRepoBase}/": ${file.path}`) + } + + // Markdown-only + if (!ALLOWED_EXTENSIONS.test(file.path)) { + console.warn(`[commit-files] Rejected: non-markdown extension "${file.path}"`) + return errorResult(`Only .md, .mdx, and .mdc files are allowed: ${file.path}`) + } + + // Size limit + const byteSize = Buffer.byteLength(file.content, 'utf8') + if (byteSize > MAX_CONTENT_SIZE) { + console.warn(`[commit-files] Rejected: file "${file.path}" exceeds size limit (${byteSize} bytes)`) + return errorResult(`File exceeds 500 KB limit: ${file.path} (${byteSize} bytes)`) + } + + console.log(`[commit-files] Validated: ${file.path} (${byteSize} bytes)`) + } + + // --- Git Trees API --- + + // 4. Get the current tree SHA + let baseTreeSha: string + try { + const commitInfo = await $fetch<{ tree: { sha: string } }>(`https://api.github.com/repos/${owner}/${repo}/git/commits/${branchSha}`, { + headers: githubHeaders, + }) + baseTreeSha = commitInfo.tree.sha + console.log(`[commit-files] Base tree SHA: ${baseTreeSha}`) + } + catch (err: unknown) { + const msg = err instanceof Error ? err.message : String(err) + console.error(`[commit-files] Failed to fetch commit tree: ${msg}`) + return errorResult(`Failed to fetch commit tree: ${msg}`) + } + + // 5. Create a new tree with all files + let newTreeSha: string + try { + const treePayload = { + base_tree: baseTreeSha, + tree: files.map(file => ({ + path: file.path, + mode: '100644', + type: 'blob', + content: file.content, + })), + } + const newTree = await $fetch<{ sha: string }>(`https://api.github.com/repos/${owner}/${repo}/git/trees`, { + method: 'POST', + headers: githubHeaders, + body: JSON.stringify(treePayload), + }) + newTreeSha = newTree.sha + console.log(`[commit-files] New tree SHA: ${newTreeSha}`) + } + catch (err: unknown) { + const msg = err instanceof Error ? err.message : String(err) + console.error(`[commit-files] Failed to create tree: ${msg}`) + return errorResult(`Failed to create Git tree: ${msg}`) + } + + // 6. Create the commit + let newCommitSha: string + try { + const newCommit = await $fetch<{ sha: string }>(`https://api.github.com/repos/${owner}/${repo}/git/commits`, { + method: 'POST', + headers: githubHeaders, + body: JSON.stringify({ + message, + tree: newTreeSha, + parents: [branchSha], + }), + }) + newCommitSha = newCommit.sha + console.log(`[commit-files] New commit SHA: ${newCommitSha}`) + } + catch (err: unknown) { + const msg = err instanceof Error ? err.message : String(err) + console.error(`[commit-files] Failed to create commit: ${msg}`) + return errorResult(`Failed to create commit: ${msg}`) + } + + // 7. Update the branch ref + try { + await $fetch(`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`, { + method: 'PATCH', + headers: githubHeaders, + body: JSON.stringify({ sha: newCommitSha }), + }) + console.log(`[commit-files] Branch "${branch}" updated to ${newCommitSha}`) + } + catch (err: unknown) { + const msg = err instanceof Error ? err.message : String(err) + console.error(`[commit-files] Failed to update ref: ${msg}`) + return errorResult(`Failed to update branch ref: ${msg}`) + } + + const paths = files.map(f => f.path).join(', ') + console.log(`[commit-files] Done — committed ${files.length} file(s) to ${branch}: ${paths}`) + + return jsonResult({ + sha: newCommitSha, + branch, + files: files.map(f => f.path), + }) + }, +}) diff --git a/layer/server/mcp/tools/get-page.ts b/layer/server/mcp/tools/get-page.ts index 1f2bec3e3..aabe17b98 100644 --- a/layer/server/mcp/tools/get-page.ts +++ b/layer/server/mcp/tools/get-page.ts @@ -24,35 +24,49 @@ WORKFLOW: This tool returns the complete page content including title, descripti handler: async ({ path }) => { const event = useEvent() const config = useRuntimeConfig(event).public + const appConfig = useAppConfig() as { github?: { rootDir?: string } } + const contentRepoBase = appConfig.github?.rootDir + ? `${appConfig.github.rootDir}/content` + : 'content' const siteUrl = getRequestURL(event).origin || inferSiteURL() - const availableLocales = getAvailableLocales(config) + const availableLocales = getAvailableLocales(config as unknown as Parameters[0]) const collectionName = config.i18n?.locales ? getCollectionFromPath(path, availableLocales) : 'docs' + let page: { title: string, path: string, description: string, stem: string, extension: string, body: { value: unknown[] | null } | null } | null try { - const page = await queryCollection(event, collectionName as keyof Collections) + page = await queryCollection(event, collectionName as keyof Collections) .where('path', '=', path) - .select('title', 'path', 'description') - .first() - - if (!page) { - return errorResult('Page not found') - } - - const content = await event.$fetch(`/raw${path}.md`) - - return jsonResult({ - title: page.title, - path: page.path, - description: page.description, - content, - url: `${siteUrl}${page.path}`, - }) + .select('title', 'path', 'description', 'stem', 'extension', 'body') + .first() as typeof page } catch { - return errorResult('Failed to get page') + return errorResult('Failed to query page') + } + + if (!page) { + return errorResult('Page not found') } + + let content: string | undefined + if (page.body?.value) { + try { + content = await event.$fetch(`/raw${path}.md`) + } + catch { + // Raw fetch failed — return page metadata without content + } + } + + return jsonResult({ + title: page.title, + path: page.path, + description: page.description, + filePath: `${contentRepoBase}/${page.stem}.${page.extension}`, + content, + url: `${siteUrl}${page.path}`, + }) }, }) diff --git a/layer/server/mcp/tools/list-pages.ts b/layer/server/mcp/tools/list-pages.ts index 66df7da26..a770155a9 100644 --- a/layer/server/mcp/tools/list-pages.ts +++ b/layer/server/mcp/tools/list-pages.ts @@ -27,25 +27,30 @@ OUTPUT: Returns a structured list with: locale: z.string().optional().describe('The locale to filter pages by'), }, cache: '1h', - handler: async ({ locale }) => { + handler: async ({ locale }: { locale?: string }) => { const event = useEvent() const config = useRuntimeConfig(event).public + const appConfig = useAppConfig() as { github?: { rootDir?: string } } + const contentRepoBase = appConfig.github?.rootDir + ? `${appConfig.github.rootDir}/content` + : 'content' const siteUrl = getRequestURL(event).origin || inferSiteURL() - const availableLocales = getAvailableLocales(config) + const availableLocales = getAvailableLocales(config as unknown as Parameters[0]) const collections = getCollectionsToQuery(locale, availableLocales) try { const allPages = await Promise.all( collections.map(async (collectionName) => { const pages = await queryCollection(event, collectionName as keyof Collections) - .select('title', 'path', 'description') + .select('title', 'path', 'description', 'stem', 'extension') .all() return pages.map(page => ({ title: page.title, path: page.path, description: page.description, + filePath: `${contentRepoBase}/${page.stem}.${page.extension}`, locale: collectionName.replace('docs_', ''), url: `${siteUrl}${page.path}`, })) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8f2c9a32c..847e4f8c6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,7 +28,7 @@ importers: version: 10.0.3(jiti@2.6.1) nuxt: specifier: 4.4.2 - version: 4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vue/compiler-sfc@3.5.30)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.2) + version: 4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(@vue/compiler-sfc@3.5.30)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.2) release-it: specifier: ^19.2.4 version: 19.2.4(@types/node@25.5.0)(magicast@0.5.2) @@ -71,7 +71,7 @@ importers: version: 25.5.0 tsup: specifier: ^8.5.1 - version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + version: 8.5.1(@swc/core@1.15.3(@swc/helpers@0.5.19))(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) tsx: specifier: ^4.21.0 version: 4.21.0 @@ -80,25 +80,25 @@ importers: dependencies: '@nuxt/ui': specifier: ^4.5.1 - version: 4.5.1(@nuxt/content@3.12.0(better-sqlite3@12.8.0)(magicast@0.5.2))(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30))(change-case@5.4.4)(db0@0.3.4(better-sqlite3@12.8.0))(embla-carousel@8.6.0)(ioredis@5.10.1)(magicast@0.5.2)(tailwindcss@4.2.2)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-router@4.6.4(vue@3.5.30(typescript@5.9.3)))(vue@3.5.30(typescript@5.9.3))(yjs@13.6.30)(zod@4.3.6) + version: 4.5.1(@nuxt/content@3.12.0(better-sqlite3@12.8.0)(magicast@0.5.2))(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30))(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(change-case@5.4.4)(db0@0.3.4(better-sqlite3@12.8.0))(embla-carousel@8.6.0)(ioredis@5.10.1)(magicast@0.5.2)(tailwindcss@4.2.2)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-router@4.6.4(vue@3.5.30(typescript@5.9.3)))(vue@3.5.30(typescript@5.9.3))(yjs@13.6.30)(zod@4.3.6) '@nuxtjs/i18n': specifier: ^10.2.3 - version: 10.2.3(@vue/compiler-dom@3.5.30)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(magicast@0.5.2)(rollup@4.59.0)(vue@3.5.30(typescript@5.9.3)) + version: 10.2.3(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(@vue/compiler-dom@3.5.30)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(magicast@0.5.2)(rollup@4.59.0)(vue@3.5.30(typescript@5.9.3)) '@vercel/analytics': specifier: ^2.0.1 - version: 2.0.1(nuxt@4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vue/compiler-sfc@3.5.30)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.2))(vue-router@4.6.4(vue@3.5.30(typescript@5.9.3)))(vue@3.5.30(typescript@5.9.3)) + version: 2.0.1(nuxt@4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(@vue/compiler-sfc@3.5.30)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.2))(vue-router@4.6.4(vue@3.5.30(typescript@5.9.3)))(vue@3.5.30(typescript@5.9.3)) '@vercel/speed-insights': specifier: ^2.0.0 - version: 2.0.0(nuxt@4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vue/compiler-sfc@3.5.30)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.2))(vue-router@4.6.4(vue@3.5.30(typescript@5.9.3)))(vue@3.5.30(typescript@5.9.3)) + version: 2.0.0(nuxt@4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(@vue/compiler-sfc@3.5.30)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.2))(vue-router@4.6.4(vue@3.5.30(typescript@5.9.3)))(vue@3.5.30(typescript@5.9.3)) docus: specifier: workspace:* version: link:../layer nuxt: specifier: 4.4.2 - version: 4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vue/compiler-sfc@3.5.30)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.2) + version: 4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(@vue/compiler-sfc@3.5.30)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.2) nuxt-studio: specifier: ^1.5.1 - version: 1.5.1(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1)(magicast@0.5.2)(vue@3.5.30(typescript@5.9.3)) + version: 1.5.1(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1)(magicast@0.5.2)(vue@3.5.30(typescript@5.9.3)) tailwindcss: specifier: ^4.2.2 version: 4.2.2 @@ -128,19 +128,19 @@ importers: version: 3.12.0(better-sqlite3@12.8.0)(magicast@0.5.2) '@nuxt/image': specifier: ^2.0.0 - version: 2.0.0(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1)(magicast@0.5.2) + version: 2.0.0(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1)(magicast@0.5.2) '@nuxt/kit': specifier: ^4.4.2 version: 4.4.2(magicast@0.5.2) '@nuxt/ui': specifier: ^4.5.1 - version: 4.5.1(@nuxt/content@3.12.0(better-sqlite3@12.8.0)(magicast@0.5.2))(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30))(change-case@5.4.4)(db0@0.3.4(better-sqlite3@12.8.0))(embla-carousel@8.6.0)(ioredis@5.10.1)(magicast@0.5.2)(tailwindcss@4.2.2)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-router@4.6.4(vue@3.5.30(typescript@5.9.3)))(vue@3.5.30(typescript@5.9.3))(yjs@13.6.30)(zod@4.3.6) + version: 4.5.1(@nuxt/content@3.12.0(better-sqlite3@12.8.0)(magicast@0.5.2))(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30))(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(change-case@5.4.4)(db0@0.3.4(better-sqlite3@12.8.0))(embla-carousel@8.6.0)(ioredis@5.10.1)(magicast@0.5.2)(tailwindcss@4.2.2)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-router@4.6.4(vue@3.5.30(typescript@5.9.3)))(vue@3.5.30(typescript@5.9.3))(yjs@13.6.30)(zod@4.3.6) '@nuxtjs/i18n': specifier: ^10.2.3 - version: 10.2.3(@vue/compiler-dom@3.5.30)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(magicast@0.5.2)(rollup@4.59.0)(vue@3.5.30(typescript@5.9.3)) + version: 10.2.3(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(@vue/compiler-dom@3.5.30)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(magicast@0.5.2)(rollup@4.59.0)(vue@3.5.30(typescript@5.9.3)) '@nuxtjs/mcp-toolkit': - specifier: ^0.11.0 - version: 0.11.0(h3@1.15.9)(magicast@0.5.2)(zod@4.3.6) + specifier: ^0.12.0 + version: 0.12.0(h3@1.15.9)(magicast@0.5.2)(zod@4.3.6) '@nuxtjs/mdc': specifier: ^0.20.2 version: 0.20.2(magicast@0.5.2) @@ -185,13 +185,13 @@ importers: version: 2.0.1(@vueuse/core@14.2.1(vue@3.5.30(typescript@5.9.3)))(vue@3.5.30(typescript@5.9.3)) nuxt: specifier: 4.x - version: 4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vue/compiler-sfc@3.5.30)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.2) + version: 4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(@vue/compiler-sfc@3.5.30)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.2) nuxt-llms: specifier: ^0.2.0 version: 0.2.0(magicast@0.5.2) nuxt-og-image: specifier: ^6.0.7 - version: 6.0.7(@resvg/resvg-js@2.6.2)(@resvg/resvg-wasm@2.6.2)(@takumi-rs/core@0.73.1)(@unhead/vue@2.1.12(vue@3.5.30(typescript@5.9.3)))(fontless@0.2.1(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)))(playwright-core@1.58.2)(sharp@0.34.5)(tailwindcss@4.2.2)(unifont@0.7.4)(unstorage@1.17.4(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1))(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.30(typescript@5.9.3)) + version: 6.0.7(@resvg/resvg-js@2.6.2)(@resvg/resvg-wasm@2.6.2)(@takumi-rs/core@0.73.1)(@unhead/vue@2.1.12(vue@3.5.30(typescript@5.9.3)))(fontless@0.2.1(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)))(playwright-core@1.58.2)(sharp@0.34.5)(tailwindcss@4.2.2)(unifont@0.7.4)(unstorage@1.17.4(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1))(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.30(typescript@5.9.3)) pkg-types: specifier: ^2.3.0 version: 2.3.0 @@ -261,6 +261,83 @@ packages: peerDependencies: '@types/json-schema': ^7.0.15 + '@aws-crypto/sha256-browser@5.2.0': + resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} + + '@aws-crypto/sha256-js@5.2.0': + resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/supports-web-crypto@5.2.0': + resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} + + '@aws-crypto/util@5.2.0': + resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} + + '@aws-sdk/core@3.973.25': + resolution: {integrity: sha512-TNrx7eq6nKNOO62HWPqoBqPLXEkW6nLZQGwjL6lq1jZtigWYbK1NbCnT7mKDzbLMHZfuOECUt3n6CzxjUW9HWQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-web-identity@3.972.13': + resolution: {integrity: sha512-a6iFMh1pgUH0TdcouBppLJUfPM7Yd3R9S1xFodPtCRoLqCz2RQFA3qjA8x4112PVYXEd4/pHX2eihapq39w0rA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-host-header@3.972.8': + resolution: {integrity: sha512-wAr2REfKsqoKQ+OkNqvOShnBoh+nkPurDKW7uAeVSu6kUECnWlSJiPvnoqxGlfousEY/v9LfS9sNc46hjSYDIQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-logger@3.972.8': + resolution: {integrity: sha512-CWl5UCM57WUFaFi5kB7IBY1UmOeLvNZAZ2/OZ5l20ldiJ3TiIz1pC65gYj8X0BCPWkeR1E32mpsCk1L1I4n+lA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-recursion-detection@3.972.9': + resolution: {integrity: sha512-/Wt5+CT8dpTFQxEJ9iGy/UGrXr7p2wlIOEHvIr/YcHYByzoLjrqkYqXdJjd9UIgWjv7eqV2HnFJen93UTuwfTQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-user-agent@3.972.26': + resolution: {integrity: sha512-AilFIh4rI/2hKyyGN6XrB0yN96W2o7e7wyrPWCM6QjZM1mcC/pVkW3IWWRvuBWMpVP8Fg+rMpbzeLQ6dTM4gig==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/nested-clients@3.996.15': + resolution: {integrity: sha512-k6WAVNkub5DrU46iPQvH1m0xc1n+0dX79+i287tYJzf5g1yU2rX3uf4xNeL5JvK1NtYgfwMnsxHqhOXFBn367A==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/region-config-resolver@3.972.10': + resolution: {integrity: sha512-1dq9ToC6e070QvnVhhbAs3bb5r6cQ10gTVc6cyRV5uvQe7P138TV2uG2i6+Yok4bAkVAcx5AqkTEBUvWEtBlsQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/types@3.973.6': + resolution: {integrity: sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-endpoints@3.996.5': + resolution: {integrity: sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-locate-window@3.965.5': + resolution: {integrity: sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-user-agent-browser@3.972.8': + resolution: {integrity: sha512-B3KGXJviV2u6Cdw2SDY2aDhoJkVfY/Q/Trwk2CMSkikE1Oi6gRzxhvhIfiRpHfmIsAhV4EA54TVEX8K6CbHbkA==} + + '@aws-sdk/util-user-agent-node@3.973.12': + resolution: {integrity: sha512-8phW0TS8ntENJgDcFewYT/Q8dOmarpvSxEjATu2GUBAutiHr++oEGCiBUwxslCMNvwW2cAPZNT53S/ym8zm/gg==} + engines: {node: '>=20.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + + '@aws-sdk/xml-builder@3.972.16': + resolution: {integrity: sha512-iu2pyvaqmeatIJLURLqx9D+4jKAdTH20ntzB6BFwjyN7V960r4jK32mx0Zf7YbtOYAbmbtQfDNuL60ONinyw7A==} + engines: {node: '>=20.0.0'} + + '@aws/lambda-invoke-store@0.2.4': + resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==} + engines: {node: '>=18.0.0'} + '@babel/code-frame@7.29.0': resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} @@ -1499,15 +1576,18 @@ packages: resolution: {integrity: sha512-nRAQJbWjbiBvW6XRsG3Q6olYw2EKz7V1J6cDCHLCPbF1EyNhrAH/9aCNQaR5PYcoXPeRLpF86DIPBEnamghyHw==} engines: {node: '>=20.11.1'} - '@nuxtjs/mcp-toolkit@0.11.0': - resolution: {integrity: sha512-Fgj8GyhML4/sv2kltk1VNbRFEwp4YzP3V5GnUNLzzFQ95OelPpCUrNtf/wkN+8wXpjccJtTSX98TE+r3WLNLSw==} + '@nuxtjs/mcp-toolkit@0.12.0': + resolution: {integrity: sha512-OdfzEgM6ZtI/g/WbfKrrmJdATJFPvEOLTGteXT9uPEFPPmdjIO/V3qFFjD+Itvzij282uX/Nu/Tn95yJZl/Y7w==} peerDependencies: agents: '>=0.7.6' h3: ^1.15.6 + secure-exec: '>=0.1.0' zod: ^4.1.13 peerDependenciesMeta: agents: optional: true + secure-exec: + optional: true '@nuxtjs/mdc@0.20.2': resolution: {integrity: sha512-afAJKnXKdvDtoNOGARQMpZoGprL1T3OGnj+K9edJjX+WdhCwvVabBijhi8BAlpx+YzA/DpcZx8bDFZk/aoSJmA==} @@ -2804,6 +2884,178 @@ packages: resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} + '@smithy/abort-controller@4.2.12': + resolution: {integrity: sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q==} + engines: {node: '>=18.0.0'} + + '@smithy/config-resolver@4.4.13': + resolution: {integrity: sha512-iIzMC5NmOUP6WL6o8iPBjFhUhBZ9pPjpUpQYWMUFQqKyXXzOftbfK8zcQCz/jFV1Psmf05BK5ypx4K2r4Tnwdg==} + engines: {node: '>=18.0.0'} + + '@smithy/core@3.23.12': + resolution: {integrity: sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w==} + engines: {node: '>=18.0.0'} + + '@smithy/credential-provider-imds@4.2.12': + resolution: {integrity: sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg==} + engines: {node: '>=18.0.0'} + + '@smithy/fetch-http-handler@5.3.15': + resolution: {integrity: sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-node@4.2.12': + resolution: {integrity: sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w==} + engines: {node: '>=18.0.0'} + + '@smithy/invalid-dependency@4.2.12': + resolution: {integrity: sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g==} + engines: {node: '>=18.0.0'} + + '@smithy/is-array-buffer@2.2.0': + resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} + engines: {node: '>=14.0.0'} + + '@smithy/is-array-buffer@4.2.2': + resolution: {integrity: sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-content-length@4.2.12': + resolution: {integrity: sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-endpoint@4.4.27': + resolution: {integrity: sha512-T3TFfUgXQlpcg+UdzcAISdZpj4Z+XECZ/cefgA6wLBd6V4lRi0svN2hBouN/be9dXQ31X4sLWz3fAQDf+nt6BA==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-retry@4.4.44': + resolution: {integrity: sha512-Y1Rav7m5CFRPQyM4CI0koD/bXjyjJu3EQxZZhtLGD88WIrBrQ7kqXM96ncd6rYnojwOo/u9MXu57JrEvu/nLrA==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-serde@4.2.15': + resolution: {integrity: sha512-ExYhcltZSli0pgAKOpQQe1DLFBLryeZ22605y/YS+mQpdNWekum9Ujb/jMKfJKgjtz1AZldtwA/wCYuKJgjjlg==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-stack@4.2.12': + resolution: {integrity: sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw==} + engines: {node: '>=18.0.0'} + + '@smithy/node-config-provider@4.3.12': + resolution: {integrity: sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw==} + engines: {node: '>=18.0.0'} + + '@smithy/node-http-handler@4.5.0': + resolution: {integrity: sha512-Rnq9vQWiR1+/I6NZZMNzJHV6pZYyEHt2ZnuV3MG8z2NNenC4i/8Kzttz7CjZiHSmsN5frhXhg17z3Zqjjhmz1A==} + engines: {node: '>=18.0.0'} + + '@smithy/property-provider@4.2.12': + resolution: {integrity: sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A==} + engines: {node: '>=18.0.0'} + + '@smithy/protocol-http@5.3.12': + resolution: {integrity: sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==} + engines: {node: '>=18.0.0'} + + '@smithy/querystring-builder@4.2.12': + resolution: {integrity: sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==} + engines: {node: '>=18.0.0'} + + '@smithy/querystring-parser@4.2.12': + resolution: {integrity: sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw==} + engines: {node: '>=18.0.0'} + + '@smithy/service-error-classification@4.2.12': + resolution: {integrity: sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ==} + engines: {node: '>=18.0.0'} + + '@smithy/shared-ini-file-loader@4.4.7': + resolution: {integrity: sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw==} + engines: {node: '>=18.0.0'} + + '@smithy/signature-v4@5.3.12': + resolution: {integrity: sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw==} + engines: {node: '>=18.0.0'} + + '@smithy/smithy-client@4.12.7': + resolution: {integrity: sha512-q3gqnwml60G44FECaEEsdQMplYhDMZYCtYhMCzadCnRnnHIobZJjegmdoUo6ieLQlPUzvrMdIJUpx6DoPmzANQ==} + engines: {node: '>=18.0.0'} + + '@smithy/types@4.13.1': + resolution: {integrity: sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==} + engines: {node: '>=18.0.0'} + + '@smithy/url-parser@4.2.12': + resolution: {integrity: sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-base64@4.3.2': + resolution: {integrity: sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-browser@4.2.2': + resolution: {integrity: sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-node@4.2.3': + resolution: {integrity: sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==} + engines: {node: '>=18.0.0'} + + '@smithy/util-buffer-from@2.2.0': + resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} + engines: {node: '>=14.0.0'} + + '@smithy/util-buffer-from@4.2.2': + resolution: {integrity: sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==} + engines: {node: '>=18.0.0'} + + '@smithy/util-config-provider@4.2.2': + resolution: {integrity: sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-defaults-mode-browser@4.3.43': + resolution: {integrity: sha512-Qd/0wCKMaXxev/z00TvNzGCH2jlKKKxXP1aDxB6oKwSQthe3Og2dMhSayGCnsma1bK/kQX1+X7SMP99t6FgiiQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-defaults-mode-node@4.2.47': + resolution: {integrity: sha512-qSRbYp1EQ7th+sPFuVcVO05AE0QH635hycdEXlpzIahqHHf2Fyd/Zl+8v0XYMJ3cgDVPa0lkMefU7oNUjAP+DQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-endpoints@3.3.3': + resolution: {integrity: sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig==} + engines: {node: '>=18.0.0'} + + '@smithy/util-hex-encoding@4.2.2': + resolution: {integrity: sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-middleware@4.2.12': + resolution: {integrity: sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-retry@4.2.12': + resolution: {integrity: sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-stream@4.5.20': + resolution: {integrity: sha512-4yXLm5n/B5SRBR2p8cZ90Sbv4zL4NKsgxdzCzp/83cXw2KxLEumt5p+GAVyRNZgQOSrzXn9ARpO0lUe8XSlSDw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-uri-escape@4.2.2': + resolution: {integrity: sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-utf8@2.3.0': + resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} + engines: {node: '>=14.0.0'} + + '@smithy/util-utf8@4.2.2': + resolution: {integrity: sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==} + engines: {node: '>=18.0.0'} + + '@smithy/uuid@1.1.2': + resolution: {integrity: sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==} + engines: {node: '>=18.0.0'} + '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} @@ -2823,9 +3075,88 @@ packages: peerDependencies: eslint: ^9.0.0 || ^10.0.0 + '@swc/core-darwin-arm64@1.15.3': + resolution: {integrity: sha512-AXfeQn0CvcQ4cndlIshETx6jrAM45oeUrK8YeEY6oUZU/qzz0Id0CyvlEywxkWVC81Ajpd8TQQ1fW5yx6zQWkQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + + '@swc/core-darwin-x64@1.15.3': + resolution: {integrity: sha512-p68OeCz1ui+MZYG4wmfJGvcsAcFYb6Sl25H9TxWl+GkBgmNimIiRdnypK9nBGlqMZAcxngNPtnG3kEMNnvoJ2A==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + + '@swc/core-linux-arm-gnueabihf@1.15.3': + resolution: {integrity: sha512-Nuj5iF4JteFgwrai97mUX+xUOl+rQRHqTvnvHMATL/l9xE6/TJfPBpd3hk/PVpClMXG3Uvk1MxUFOEzM1JrMYg==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + + '@swc/core-linux-arm64-gnu@1.15.3': + resolution: {integrity: sha512-2Nc/s8jE6mW2EjXWxO/lyQuLKShcmTrym2LRf5Ayp3ICEMX6HwFqB1EzDhwoMa2DcUgmnZIalesq2lG3krrUNw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@swc/core-linux-arm64-musl@1.15.3': + resolution: {integrity: sha512-j4SJniZ/qaZ5g8op+p1G9K1z22s/EYGg1UXIb3+Cg4nsxEpF5uSIGEE4mHUfA70L0BR9wKT2QF/zv3vkhfpX4g==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@swc/core-linux-x64-gnu@1.15.3': + resolution: {integrity: sha512-aKttAZnz8YB1VJwPQZtyU8Uk0BfMP63iDMkvjhJzRZVgySmqt/apWSdnoIcZlUoGheBrcqbMC17GGUmur7OT5A==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@swc/core-linux-x64-musl@1.15.3': + resolution: {integrity: sha512-oe8FctPu1gnUsdtGJRO2rvOUIkkIIaHqsO9xxN0bTR7dFTlPTGi2Fhk1tnvXeyAvCPxLIcwD8phzKg6wLv9yug==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@swc/core-win32-arm64-msvc@1.15.3': + resolution: {integrity: sha512-L9AjzP2ZQ/Xh58e0lTRMLvEDrcJpR7GwZqAtIeNLcTK7JVE+QineSyHp0kLkO1rttCHyCy0U74kDTj0dRz6raA==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@swc/core-win32-ia32-msvc@1.15.3': + resolution: {integrity: sha512-B8UtogMzErUPDWUoKONSVBdsgKYd58rRyv2sHJWKOIMCHfZ22FVXICR4O/VwIYtlnZ7ahERcjayBHDlBZpR0aw==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + + '@swc/core-win32-x64-msvc@1.15.3': + resolution: {integrity: sha512-SpZKMR9QBTecHeqpzJdYEfgw30Oo8b/Xl6rjSzBt1g0ZsXyy60KLXrp6IagQyfTYqNYE/caDvwtF2FPn7pomog==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@swc/core@1.15.3': + resolution: {integrity: sha512-Qd8eBPkUFL4eAONgGjycZXj1jFCBW8Fd+xF0PzdTlBCWQIV1xnUT7B93wUANtW3KGjl3TRcOyxwSx/u/jyKw/Q==} + engines: {node: '>=10'} + peerDependencies: + '@swc/helpers': '>=0.5.17' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + '@swc/helpers@0.5.19': resolution: {integrity: sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==} + '@swc/types@0.1.26': + resolution: {integrity: sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==} + '@tailwindcss/node@4.2.2': resolution: {integrity: sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==} @@ -3480,6 +3811,15 @@ packages: vue-router: optional: true + '@vercel/functions@3.4.3': + resolution: {integrity: sha512-kA14KIUVgAY6VXbhZ5jjY+s0883cV3cZqIU3WhrSRxuJ9KvxatMjtmzl0K23HK59oOUjYl7HaE/eYMmhmqpZzw==} + engines: {node: '>= 20'} + peerDependencies: + '@aws-sdk/credential-provider-web-identity': '*' + peerDependenciesMeta: + '@aws-sdk/credential-provider-web-identity': + optional: true + '@vercel/nft@1.4.0': resolution: {integrity: sha512-rr7JVnI7YGjA4lngucrWjZ7eCOJZZQaDHB+5NRGOuNc+k4PU2Lb9PmYm8uBmW8qichF7WkR2RmwmhXHBhx6wzw==} engines: {node: '>=20'} @@ -3489,6 +3829,10 @@ packages: resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==} engines: {node: '>= 20'} + '@vercel/oidc@3.2.0': + resolution: {integrity: sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==} + engines: {node: '>= 20'} + '@vercel/speed-insights@2.0.0': resolution: {integrity: sha512-jwkNcrTeafWxjmWq4AHBaptSqZiJkYU5adLC9QBSqeim0GcqDMgN5Ievh8OG1rJ6W3A4l1oiP7qr9CWxGuzu3w==} peerDependencies: @@ -3911,6 +4255,9 @@ packages: boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + bowser@2.14.1: + resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} + brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} @@ -4830,6 +5177,13 @@ packages: fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fast-xml-builder@1.1.4: + resolution: {integrity: sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==} + + fast-xml-parser@5.5.8: + resolution: {integrity: sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ==} + hasBin: true + fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} @@ -6313,6 +6667,10 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} + path-expression-matcher@1.2.0: + resolution: {integrity: sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ==} + engines: {node: '>=14.0.0'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -7190,6 +7548,9 @@ packages: strip-literal@3.1.0: resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + strnum@2.2.2: + resolution: {integrity: sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA==} + structured-clone-es@2.0.0: resolution: {integrity: sha512-5UuAHmBLXYPCl22xWJrFuGmIhBKQzxISPVz6E7nmTmTcAOpUzlbjKJsRrCE4vADmMQ0dzeCnlWn9XufnAGf76Q==} @@ -8097,6 +8458,203 @@ snapshots: '@types/json-schema': 7.0.15 js-yaml: 4.1.1 + '@aws-crypto/sha256-browser@5.2.0': + dependencies: + '@aws-crypto/sha256-js': 5.2.0 + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-locate-window': 3.965.5 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + optional: true + + '@aws-crypto/sha256-js@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.6 + tslib: 2.8.1 + optional: true + + '@aws-crypto/supports-web-crypto@5.2.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@aws-crypto/util@5.2.0': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + optional: true + + '@aws-sdk/core@3.973.25': + dependencies: + '@aws-sdk/types': 3.973.6 + '@aws-sdk/xml-builder': 3.972.16 + '@smithy/core': 3.23.12 + '@smithy/node-config-provider': 4.3.12 + '@smithy/property-provider': 4.2.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/signature-v4': 5.3.12 + '@smithy/smithy-client': 4.12.7 + '@smithy/types': 4.13.1 + '@smithy/util-base64': 4.3.2 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + optional: true + + '@aws-sdk/credential-provider-web-identity@3.972.13': + dependencies: + '@aws-sdk/core': 3.973.25 + '@aws-sdk/nested-clients': 3.996.15 + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + optional: true + + '@aws-sdk/middleware-host-header@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + optional: true + + '@aws-sdk/middleware-logger@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + optional: true + + '@aws-sdk/middleware-recursion-detection@3.972.9': + dependencies: + '@aws-sdk/types': 3.973.6 + '@aws/lambda-invoke-store': 0.2.4 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + optional: true + + '@aws-sdk/middleware-user-agent@3.972.26': + dependencies: + '@aws-sdk/core': 3.973.25 + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-endpoints': 3.996.5 + '@smithy/core': 3.23.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-retry': 4.2.12 + tslib: 2.8.1 + optional: true + + '@aws-sdk/nested-clients@3.996.15': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.25 + '@aws-sdk/middleware-host-header': 3.972.8 + '@aws-sdk/middleware-logger': 3.972.8 + '@aws-sdk/middleware-recursion-detection': 3.972.9 + '@aws-sdk/middleware-user-agent': 3.972.26 + '@aws-sdk/region-config-resolver': 3.972.10 + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-endpoints': 3.996.5 + '@aws-sdk/util-user-agent-browser': 3.972.8 + '@aws-sdk/util-user-agent-node': 3.973.12 + '@smithy/config-resolver': 4.4.13 + '@smithy/core': 3.23.12 + '@smithy/fetch-http-handler': 5.3.15 + '@smithy/hash-node': 4.2.12 + '@smithy/invalid-dependency': 4.2.12 + '@smithy/middleware-content-length': 4.2.12 + '@smithy/middleware-endpoint': 4.4.27 + '@smithy/middleware-retry': 4.4.44 + '@smithy/middleware-serde': 4.2.15 + '@smithy/middleware-stack': 4.2.12 + '@smithy/node-config-provider': 4.3.12 + '@smithy/node-http-handler': 4.5.0 + '@smithy/protocol-http': 5.3.12 + '@smithy/smithy-client': 4.12.7 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.43 + '@smithy/util-defaults-mode-node': 4.2.47 + '@smithy/util-endpoints': 3.3.3 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-retry': 4.2.12 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + optional: true + + '@aws-sdk/region-config-resolver@3.972.10': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/config-resolver': 4.4.13 + '@smithy/node-config-provider': 4.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + optional: true + + '@aws-sdk/types@3.973.6': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + optional: true + + '@aws-sdk/util-endpoints@3.996.5': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + '@smithy/util-endpoints': 3.3.3 + tslib: 2.8.1 + optional: true + + '@aws-sdk/util-locate-window@3.965.5': + dependencies: + tslib: 2.8.1 + optional: true + + '@aws-sdk/util-user-agent-browser@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/types': 4.13.1 + bowser: 2.14.1 + tslib: 2.8.1 + optional: true + + '@aws-sdk/util-user-agent-node@3.973.12': + dependencies: + '@aws-sdk/middleware-user-agent': 3.972.26 + '@aws-sdk/types': 3.973.6 + '@smithy/node-config-provider': 4.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-config-provider': 4.2.2 + tslib: 2.8.1 + optional: true + + '@aws-sdk/xml-builder@3.972.16': + dependencies: + '@smithy/types': 4.13.1 + fast-xml-parser: 5.5.8 + tslib: 2.8.1 + optional: true + + '@aws/lambda-invoke-store@0.2.4': + optional: true + '@babel/code-frame@7.29.0': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -9264,13 +9822,13 @@ snapshots: - utf-8-validate - vite - '@nuxt/fonts@0.14.0(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1)(magicast@0.5.2)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))': + '@nuxt/fonts@0.14.0(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1)(magicast@0.5.2)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@nuxt/devtools-kit': 3.2.4(magicast@0.5.2)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) '@nuxt/kit': 4.4.2(magicast@0.5.2) consola: 3.4.2 defu: 6.1.4 - fontless: 0.2.1(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) + fontless: 0.2.1(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) h3: 1.15.9 magic-regexp: 0.10.0 ofetch: 1.5.1 @@ -9280,7 +9838,7 @@ snapshots: ufo: 1.6.3 unifont: 0.7.4 unplugin: 3.0.0 - unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1) + unstorage: 1.17.4(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -9325,7 +9883,7 @@ snapshots: - vite - vue - '@nuxt/image@2.0.0(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1)(magicast@0.5.2)': + '@nuxt/image@2.0.0(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1)(magicast@0.5.2)': dependencies: '@nuxt/kit': 4.4.2(magicast@0.5.2) consola: 3.4.2 @@ -9338,7 +9896,7 @@ snapshots: std-env: 3.10.0 ufo: 1.6.3 optionalDependencies: - ipx: 3.1.1(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1) + ipx: 3.1.1(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -9412,7 +9970,7 @@ snapshots: transitivePeerDependencies: - magicast - '@nuxt/nitro-server@4.4.2(@babel/core@7.29.0)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1)(magicast@0.5.2)(nuxt@4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vue/compiler-sfc@3.5.30)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.2))(typescript@5.9.3)': + '@nuxt/nitro-server@4.4.2(@babel/core@7.29.0)(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1)(magicast@0.5.2)(nuxt@4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(@vue/compiler-sfc@3.5.30)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.2))(typescript@5.9.3)': dependencies: '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) '@nuxt/devalue': 2.0.2 @@ -9430,8 +9988,8 @@ snapshots: impound: 1.1.5 klona: 2.0.6 mocked-exports: 0.1.1 - nitropack: 2.13.1(better-sqlite3@12.8.0) - nuxt: 4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vue/compiler-sfc@3.5.30)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.2) + nitropack: 2.13.1(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(better-sqlite3@12.8.0) + nuxt: 4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(@vue/compiler-sfc@3.5.30)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.2) nypm: 0.6.5 ohash: 2.0.11 pathe: 2.0.3 @@ -9440,7 +9998,7 @@ snapshots: std-env: 4.0.0 ufo: 1.6.3 unctx: 2.5.0 - unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1) + unstorage: 1.17.4(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1) vue: 3.5.30(typescript@5.9.3) vue-bundle-renderer: 2.2.0 vue-devtools-stub: 0.1.0 @@ -9498,13 +10056,13 @@ snapshots: rc9: 3.0.0 std-env: 3.10.0 - '@nuxt/ui@4.5.1(@nuxt/content@3.12.0(better-sqlite3@12.8.0)(magicast@0.5.2))(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30))(change-case@5.4.4)(db0@0.3.4(better-sqlite3@12.8.0))(embla-carousel@8.6.0)(ioredis@5.10.1)(magicast@0.5.2)(tailwindcss@4.2.2)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-router@4.6.4(vue@3.5.30(typescript@5.9.3)))(vue@3.5.30(typescript@5.9.3))(yjs@13.6.30)(zod@4.3.6)': + '@nuxt/ui@4.5.1(@nuxt/content@3.12.0(better-sqlite3@12.8.0)(magicast@0.5.2))(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30))(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(change-case@5.4.4)(db0@0.3.4(better-sqlite3@12.8.0))(embla-carousel@8.6.0)(ioredis@5.10.1)(magicast@0.5.2)(tailwindcss@4.2.2)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-router@4.6.4(vue@3.5.30(typescript@5.9.3)))(vue@3.5.30(typescript@5.9.3))(yjs@13.6.30)(zod@4.3.6)': dependencies: '@floating-ui/dom': 1.7.6 '@iconify/vue': 5.0.0(vue@3.5.30(typescript@5.9.3)) '@internationalized/date': 3.12.0 '@internationalized/number': 3.6.5 - '@nuxt/fonts': 0.14.0(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1)(magicast@0.5.2)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) + '@nuxt/fonts': 0.14.0(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1)(magicast@0.5.2)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) '@nuxt/icon': 2.2.1(magicast@0.5.2)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.30(typescript@5.9.3)) '@nuxt/kit': 4.4.2(magicast@0.5.2) '@nuxt/schema': 4.4.2 @@ -9612,7 +10170,7 @@ snapshots: - vue - yjs - '@nuxt/vite-builder@4.4.2(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@types/node@25.5.0)(eslint@10.0.3(jiti@2.6.1))(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(nuxt@4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vue/compiler-sfc@3.5.30)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.2))(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vue-tsc@3.2.6(typescript@5.9.3))(vue@3.5.30(typescript@5.9.3))(yaml@2.8.2)': + '@nuxt/vite-builder@4.4.2(f52e8cd5b3f246b958ca0cfda5abd4c2)': dependencies: '@nuxt/kit': 4.4.2(magicast@0.5.2) '@rollup/plugin-replace': 6.0.3(rollup@4.59.0) @@ -9630,7 +10188,7 @@ snapshots: magic-string: 0.30.21 mlly: 1.8.2 mocked-exports: 0.1.1 - nuxt: 4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vue/compiler-sfc@3.5.30)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.2) + nuxt: 4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(@vue/compiler-sfc@3.5.30)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.2) nypm: 0.6.5 pathe: 2.0.3 pkg-types: 2.3.0 @@ -9681,7 +10239,7 @@ snapshots: transitivePeerDependencies: - magicast - '@nuxtjs/i18n@10.2.3(@vue/compiler-dom@3.5.30)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(magicast@0.5.2)(rollup@4.59.0)(vue@3.5.30(typescript@5.9.3))': + '@nuxtjs/i18n@10.2.3(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(@vue/compiler-dom@3.5.30)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(magicast@0.5.2)(rollup@4.59.0)(vue@3.5.30(typescript@5.9.3))': dependencies: '@intlify/core': 11.3.0 '@intlify/h3': 0.7.4 @@ -9708,7 +10266,7 @@ snapshots: ufo: 1.6.3 unplugin: 2.3.11 unplugin-vue-router: 0.16.2(@vue/compiler-sfc@3.5.30)(vue-router@4.6.4(vue@3.5.30(typescript@5.9.3)))(vue@3.5.30(typescript@5.9.3)) - unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1) + unstorage: 1.17.4(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1) vue-i18n: 11.3.0(vue@3.5.30(typescript@5.9.3)) vue-router: 4.6.4(vue@3.5.30(typescript@5.9.3)) transitivePeerDependencies: @@ -9739,7 +10297,7 @@ snapshots: - uploadthing - vue - '@nuxtjs/mcp-toolkit@0.11.0(h3@1.15.9)(magicast@0.5.2)(zod@4.3.6)': + '@nuxtjs/mcp-toolkit@0.12.0(h3@1.15.9)(magicast@0.5.2)(zod@4.3.6)': dependencies: '@modelcontextprotocol/sdk': 1.27.1(zod@4.3.6) '@nuxt/kit': 4.4.2(magicast@0.5.2) @@ -10645,6 +11203,324 @@ snapshots: '@sindresorhus/merge-streams@4.0.0': {} + '@smithy/abort-controller@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + optional: true + + '@smithy/config-resolver@4.4.13': + dependencies: + '@smithy/node-config-provider': 4.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-config-provider': 4.2.2 + '@smithy/util-endpoints': 3.3.3 + '@smithy/util-middleware': 4.2.12 + tslib: 2.8.1 + optional: true + + '@smithy/core@3.23.12': + dependencies: + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-stream': 4.5.20 + '@smithy/util-utf8': 4.2.2 + '@smithy/uuid': 1.1.2 + tslib: 2.8.1 + optional: true + + '@smithy/credential-provider-imds@4.2.12': + dependencies: + '@smithy/node-config-provider': 4.3.12 + '@smithy/property-provider': 4.2.12 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + tslib: 2.8.1 + optional: true + + '@smithy/fetch-http-handler@5.3.15': + dependencies: + '@smithy/protocol-http': 5.3.12 + '@smithy/querystring-builder': 4.2.12 + '@smithy/types': 4.13.1 + '@smithy/util-base64': 4.3.2 + tslib: 2.8.1 + optional: true + + '@smithy/hash-node@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + '@smithy/util-buffer-from': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + optional: true + + '@smithy/invalid-dependency@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + optional: true + + '@smithy/is-array-buffer@2.2.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@smithy/is-array-buffer@4.2.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@smithy/middleware-content-length@4.2.12': + dependencies: + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + optional: true + + '@smithy/middleware-endpoint@4.4.27': + dependencies: + '@smithy/core': 3.23.12 + '@smithy/middleware-serde': 4.2.15 + '@smithy/node-config-provider': 4.3.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + '@smithy/util-middleware': 4.2.12 + tslib: 2.8.1 + optional: true + + '@smithy/middleware-retry@4.4.44': + dependencies: + '@smithy/node-config-provider': 4.3.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/service-error-classification': 4.2.12 + '@smithy/smithy-client': 4.12.7 + '@smithy/types': 4.13.1 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-retry': 4.2.12 + '@smithy/uuid': 1.1.2 + tslib: 2.8.1 + optional: true + + '@smithy/middleware-serde@4.2.15': + dependencies: + '@smithy/core': 3.23.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + optional: true + + '@smithy/middleware-stack@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + optional: true + + '@smithy/node-config-provider@4.3.12': + dependencies: + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + optional: true + + '@smithy/node-http-handler@4.5.0': + dependencies: + '@smithy/abort-controller': 4.2.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/querystring-builder': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + optional: true + + '@smithy/property-provider@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + optional: true + + '@smithy/protocol-http@5.3.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + optional: true + + '@smithy/querystring-builder@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + '@smithy/util-uri-escape': 4.2.2 + tslib: 2.8.1 + optional: true + + '@smithy/querystring-parser@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + optional: true + + '@smithy/service-error-classification@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + optional: true + + '@smithy/shared-ini-file-loader@4.4.7': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + optional: true + + '@smithy/signature-v4@5.3.12': + dependencies: + '@smithy/is-array-buffer': 4.2.2 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-hex-encoding': 4.2.2 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-uri-escape': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + optional: true + + '@smithy/smithy-client@4.12.7': + dependencies: + '@smithy/core': 3.23.12 + '@smithy/middleware-endpoint': 4.4.27 + '@smithy/middleware-stack': 4.2.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-stream': 4.5.20 + tslib: 2.8.1 + optional: true + + '@smithy/types@4.13.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@smithy/url-parser@4.2.12': + dependencies: + '@smithy/querystring-parser': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + optional: true + + '@smithy/util-base64@4.3.2': + dependencies: + '@smithy/util-buffer-from': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + optional: true + + '@smithy/util-body-length-browser@4.2.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@smithy/util-body-length-node@4.2.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@smithy/util-buffer-from@2.2.0': + dependencies: + '@smithy/is-array-buffer': 2.2.0 + tslib: 2.8.1 + optional: true + + '@smithy/util-buffer-from@4.2.2': + dependencies: + '@smithy/is-array-buffer': 4.2.2 + tslib: 2.8.1 + optional: true + + '@smithy/util-config-provider@4.2.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@smithy/util-defaults-mode-browser@4.3.43': + dependencies: + '@smithy/property-provider': 4.2.12 + '@smithy/smithy-client': 4.12.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + optional: true + + '@smithy/util-defaults-mode-node@4.2.47': + dependencies: + '@smithy/config-resolver': 4.4.13 + '@smithy/credential-provider-imds': 4.2.12 + '@smithy/node-config-provider': 4.3.12 + '@smithy/property-provider': 4.2.12 + '@smithy/smithy-client': 4.12.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + optional: true + + '@smithy/util-endpoints@3.3.3': + dependencies: + '@smithy/node-config-provider': 4.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + optional: true + + '@smithy/util-hex-encoding@4.2.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@smithy/util-middleware@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + optional: true + + '@smithy/util-retry@4.2.12': + dependencies: + '@smithy/service-error-classification': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + optional: true + + '@smithy/util-stream@4.5.20': + dependencies: + '@smithy/fetch-http-handler': 5.3.15 + '@smithy/node-http-handler': 4.5.0 + '@smithy/types': 4.13.1 + '@smithy/util-base64': 4.3.2 + '@smithy/util-buffer-from': 4.2.2 + '@smithy/util-hex-encoding': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + optional: true + + '@smithy/util-uri-escape@4.2.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@smithy/util-utf8@2.3.0': + dependencies: + '@smithy/util-buffer-from': 2.2.0 + tslib: 2.8.1 + optional: true + + '@smithy/util-utf8@4.2.2': + dependencies: + '@smithy/util-buffer-from': 4.2.2 + tslib: 2.8.1 + optional: true + + '@smithy/uuid@1.1.2': + dependencies: + tslib: 2.8.1 + optional: true + '@socket.io/component-emitter@3.1.2': {} '@speed-highlight/core@1.2.15': {} @@ -10663,10 +11539,66 @@ snapshots: estraverse: 5.3.0 picomatch: 4.0.3 + '@swc/core-darwin-arm64@1.15.3': + optional: true + + '@swc/core-darwin-x64@1.15.3': + optional: true + + '@swc/core-linux-arm-gnueabihf@1.15.3': + optional: true + + '@swc/core-linux-arm64-gnu@1.15.3': + optional: true + + '@swc/core-linux-arm64-musl@1.15.3': + optional: true + + '@swc/core-linux-x64-gnu@1.15.3': + optional: true + + '@swc/core-linux-x64-musl@1.15.3': + optional: true + + '@swc/core-win32-arm64-msvc@1.15.3': + optional: true + + '@swc/core-win32-ia32-msvc@1.15.3': + optional: true + + '@swc/core-win32-x64-msvc@1.15.3': + optional: true + + '@swc/core@1.15.3(@swc/helpers@0.5.19)': + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.26 + optionalDependencies: + '@swc/core-darwin-arm64': 1.15.3 + '@swc/core-darwin-x64': 1.15.3 + '@swc/core-linux-arm-gnueabihf': 1.15.3 + '@swc/core-linux-arm64-gnu': 1.15.3 + '@swc/core-linux-arm64-musl': 1.15.3 + '@swc/core-linux-x64-gnu': 1.15.3 + '@swc/core-linux-x64-musl': 1.15.3 + '@swc/core-win32-arm64-msvc': 1.15.3 + '@swc/core-win32-ia32-msvc': 1.15.3 + '@swc/core-win32-x64-msvc': 1.15.3 + '@swc/helpers': 0.5.19 + optional: true + + '@swc/counter@0.1.3': + optional: true + '@swc/helpers@0.5.19': dependencies: tslib: 2.8.1 + '@swc/types@0.1.26': + dependencies: + '@swc/counter': 0.1.3 + optional: true + '@tailwindcss/node@4.2.2': dependencies: '@jridgewell/remapping': 2.3.5 @@ -11242,12 +12174,19 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true - '@vercel/analytics@2.0.1(nuxt@4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vue/compiler-sfc@3.5.30)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.2))(vue-router@4.6.4(vue@3.5.30(typescript@5.9.3)))(vue@3.5.30(typescript@5.9.3))': + '@vercel/analytics@2.0.1(nuxt@4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(@vue/compiler-sfc@3.5.30)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.2))(vue-router@4.6.4(vue@3.5.30(typescript@5.9.3)))(vue@3.5.30(typescript@5.9.3))': optionalDependencies: - nuxt: 4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vue/compiler-sfc@3.5.30)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.2) + nuxt: 4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(@vue/compiler-sfc@3.5.30)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.2) vue: 3.5.30(typescript@5.9.3) vue-router: 4.6.4(vue@3.5.30(typescript@5.9.3)) + '@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13)': + dependencies: + '@vercel/oidc': 3.2.0 + optionalDependencies: + '@aws-sdk/credential-provider-web-identity': 3.972.13 + optional: true + '@vercel/nft@1.4.0(rollup@4.59.0)': dependencies: '@mapbox/node-pre-gyp': 2.0.3 @@ -11269,9 +12208,12 @@ snapshots: '@vercel/oidc@3.1.0': {} - '@vercel/speed-insights@2.0.0(nuxt@4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vue/compiler-sfc@3.5.30)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.2))(vue-router@4.6.4(vue@3.5.30(typescript@5.9.3)))(vue@3.5.30(typescript@5.9.3))': + '@vercel/oidc@3.2.0': + optional: true + + '@vercel/speed-insights@2.0.0(nuxt@4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(@vue/compiler-sfc@3.5.30)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.2))(vue-router@4.6.4(vue@3.5.30(typescript@5.9.3)))(vue@3.5.30(typescript@5.9.3))': optionalDependencies: - nuxt: 4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vue/compiler-sfc@3.5.30)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.2) + nuxt: 4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(@vue/compiler-sfc@3.5.30)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.2) vue: 3.5.30(typescript@5.9.3) vue-router: 4.6.4(vue@3.5.30(typescript@5.9.3)) @@ -11697,6 +12639,9 @@ snapshots: boolbase@1.0.0: {} + bowser@2.14.1: + optional: true + brace-expansion@2.0.2: dependencies: balanced-match: 1.0.2 @@ -12703,6 +13648,18 @@ snapshots: fast-uri@3.1.0: {} + fast-xml-builder@1.1.4: + dependencies: + path-expression-matcher: 1.2.0 + optional: true + + fast-xml-parser@5.5.8: + dependencies: + fast-xml-builder: 1.1.4 + path-expression-matcher: 1.2.0 + strnum: 2.2.2 + optional: true + fastq@1.20.1: dependencies: reusify: 1.1.0 @@ -12777,7 +13734,7 @@ snapshots: dependencies: tiny-inflate: 1.0.3 - fontless@0.2.1(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)): + fontless@0.2.1(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)): dependencies: consola: 3.4.2 css-tree: 3.2.1 @@ -12791,7 +13748,7 @@ snapshots: pathe: 2.0.3 ufo: 1.6.3 unifont: 0.7.4 - unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1) + unstorage: 1.17.4(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1) optionalDependencies: vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: @@ -13255,7 +14212,7 @@ snapshots: ipaddr.js@1.9.1: {} - ipx@3.1.1(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1): + ipx@3.1.1(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1): dependencies: '@fastify/accept-negotiator': 2.0.1 citty: 0.1.6 @@ -13271,7 +14228,7 @@ snapshots: sharp: 0.34.5 svgo: 4.0.1 ufo: 1.6.3 - unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1) + unstorage: 1.17.4(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1) xss: 1.0.15 transitivePeerDependencies: - '@azure/app-configuration' @@ -14137,7 +15094,7 @@ snapshots: dependencies: type-fest: 2.19.0 - nitropack@2.13.1(better-sqlite3@12.8.0): + nitropack@2.13.1(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(better-sqlite3@12.8.0): dependencies: '@cloudflare/kv-asset-handler': 0.4.2 '@rollup/plugin-alias': 6.0.0(rollup@4.59.0) @@ -14204,7 +15161,7 @@ snapshots: unenv: 2.0.0-rc.24 unimport: 5.6.0 unplugin-utils: 0.3.1 - unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1) + unstorage: 1.17.4(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1) untyped: 2.0.0 unwasm: 0.5.3 youch: 4.1.0 @@ -14313,7 +15270,7 @@ snapshots: transitivePeerDependencies: - magicast - nuxt-og-image@6.0.7(@resvg/resvg-js@2.6.2)(@resvg/resvg-wasm@2.6.2)(@takumi-rs/core@0.73.1)(@unhead/vue@2.1.12(vue@3.5.30(typescript@5.9.3)))(fontless@0.2.1(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)))(playwright-core@1.58.2)(sharp@0.34.5)(tailwindcss@4.2.2)(unifont@0.7.4)(unstorage@1.17.4(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1))(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.30(typescript@5.9.3)): + nuxt-og-image@6.0.7(@resvg/resvg-js@2.6.2)(@resvg/resvg-wasm@2.6.2)(@takumi-rs/core@0.73.1)(@unhead/vue@2.1.12(vue@3.5.30(typescript@5.9.3)))(fontless@0.2.1(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)))(playwright-core@1.58.2)(sharp@0.34.5)(tailwindcss@4.2.2)(unifont@0.7.4)(unstorage@1.17.4(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1))(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.30(typescript@5.9.3)): dependencies: '@clack/prompts': 1.1.0 '@nuxt/devtools-kit': 3.2.4(magicast@0.5.2)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) @@ -14347,12 +15304,12 @@ snapshots: ufo: 1.6.3 ultrahtml: 1.6.0 unplugin: 3.0.0 - unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1) + unstorage: 1.17.4(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1) optionalDependencies: '@resvg/resvg-js': 2.6.2 '@resvg/resvg-wasm': 2.6.2 '@takumi-rs/core': 0.73.1 - fontless: 0.2.1(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) + fontless: 0.2.1(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) playwright-core: 1.58.2 sharp: 0.34.5 tailwindcss: 4.2.2 @@ -14389,7 +15346,7 @@ snapshots: - vite - vue - nuxt-studio@1.5.1(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1)(magicast@0.5.2)(vue@3.5.30(typescript@5.9.3)): + nuxt-studio@1.5.1(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1)(magicast@0.5.2)(vue@3.5.30(typescript@5.9.3)): dependencies: '@ai-sdk/gateway': 3.0.66(zod@4.3.6) '@ai-sdk/vue': 3.0.116(vue@3.5.30(typescript@5.9.3))(zod@4.3.6) @@ -14399,13 +15356,13 @@ snapshots: ai: 6.0.116(zod@4.3.6) defu: 6.1.4 destr: 2.0.5 - ipx: 3.1.1(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1) + ipx: 3.1.1(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1) js-yaml: 4.1.1 minimatch: 10.2.4 nuxt-component-meta: 0.17.2(magicast@0.5.2) remark-mdc: 3.10.0 shiki: 3.23.0 - unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1) + unstorage: 1.17.4(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1) zod: 4.3.6 zod-to-json-schema: 3.25.1(zod@4.3.6) transitivePeerDependencies: @@ -14432,16 +15389,16 @@ snapshots: - uploadthing - vue - nuxt@4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vue/compiler-sfc@3.5.30)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.2): + nuxt@4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(@vue/compiler-sfc@3.5.30)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.2): dependencies: '@dxup/nuxt': 0.4.0(magicast@0.5.2)(typescript@5.9.3) '@nuxt/cli': 3.34.0(@nuxt/schema@4.4.2)(cac@6.7.14)(magicast@0.5.2) '@nuxt/devtools': 3.2.4(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.30(typescript@5.9.3)) '@nuxt/kit': 4.4.2(magicast@0.5.2) - '@nuxt/nitro-server': 4.4.2(@babel/core@7.29.0)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1)(magicast@0.5.2)(nuxt@4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vue/compiler-sfc@3.5.30)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.2))(typescript@5.9.3) + '@nuxt/nitro-server': 4.4.2(@babel/core@7.29.0)(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1)(magicast@0.5.2)(nuxt@4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(@vue/compiler-sfc@3.5.30)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.2))(typescript@5.9.3) '@nuxt/schema': 4.4.2 '@nuxt/telemetry': 2.7.0(@nuxt/kit@4.4.2(magicast@0.5.2)) - '@nuxt/vite-builder': 4.4.2(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@types/node@25.5.0)(eslint@10.0.3(jiti@2.6.1))(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(nuxt@4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vue/compiler-sfc@3.5.30)(better-sqlite3@12.8.0)(db0@0.3.4(better-sqlite3@12.8.0))(eslint@10.0.3(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.2))(optionator@0.9.4)(rollup-plugin-visualizer@6.0.11(rollup@4.59.0))(rollup@4.59.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vue-tsc@3.2.6(typescript@5.9.3))(vue@3.5.30(typescript@5.9.3))(yaml@2.8.2) + '@nuxt/vite-builder': 4.4.2(f52e8cd5b3f246b958ca0cfda5abd4c2) '@unhead/vue': 2.1.12(vue@3.5.30(typescript@5.9.3)) '@vue/shared': 3.5.30 c12: 3.3.3(magicast@0.5.2) @@ -14890,6 +15847,9 @@ snapshots: path-exists@4.0.0: {} + path-expression-matcher@1.2.0: + optional: true + path-key@3.1.1: {} path-key@4.0.0: {} @@ -15975,6 +16935,9 @@ snapshots: dependencies: js-tokens: 9.0.1 + strnum@2.2.2: + optional: true + structured-clone-es@2.0.0: {} stylehacks@7.0.8(postcss@8.5.8): @@ -16143,7 +17106,7 @@ snapshots: tslib@2.8.1: {} - tsup@8.5.1(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2): + tsup@8.5.1(@swc/core@1.15.3(@swc/helpers@0.5.19))(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2): dependencies: bundle-require: 5.1.0(esbuild@0.27.4) cac: 6.7.14 @@ -16163,6 +17126,7 @@ snapshots: tinyglobby: 0.2.15 tree-kill: 1.2.2 optionalDependencies: + '@swc/core': 1.15.3(@swc/helpers@0.5.19) postcss: 8.5.8 typescript: 5.9.3 transitivePeerDependencies: @@ -16416,7 +17380,7 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 - unstorage@1.17.4(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1): + unstorage@1.17.4(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13))(db0@0.3.4(better-sqlite3@12.8.0))(ioredis@5.10.1): dependencies: anymatch: 3.1.3 chokidar: 5.0.0 @@ -16427,6 +17391,7 @@ snapshots: ofetch: 1.5.1 ufo: 1.6.3 optionalDependencies: + '@vercel/functions': 3.4.3(@aws-sdk/credential-provider-web-identity@3.972.13) db0: 0.3.4(better-sqlite3@12.8.0) ioredis: 5.10.1