feat(mcp): add @xen-orchestra/mcp package (#9519)

This PR adds the new @xen-orchestra/mcp package — an MCP (Model Context Protocol) server that enables AI assistants (Claude Desktop, Claude Code, or any MCP-compatible client) to query a Xen Orchestra infrastructure in read-only mode.
This commit is contained in:
Mathieu
2026-02-24 17:10:32 +01:00
committed by GitHub
parent 31e2ff1b02
commit 438a7cc746
11 changed files with 1912 additions and 14 deletions

View File

@@ -0,0 +1 @@
../../scripts/npmignore

View File

@@ -0,0 +1,128 @@
# @xen-orchestra/mcp
[MCP](https://modelcontextprotocol.io/) (Model Context Protocol) server for [Xen Orchestra](https://xen-orchestra.com/). Enables AI assistants to query your XO infrastructure.
## Features
- **Infrastructure queries** — List and inspect pools, hosts, and VMs
- **Dashboard** — Aggregated pool statistics (host status, top consumers, alarms)
- **Documentation** — Search XO docs directly from the assistant
- **Read-only** — Safe by design, no destructive operations
## Quick Start
### Claude Desktop
Add to your Claude Desktop config (`~/.config/claude-desktop/config.json`):
```json
{
"mcpServers": {
"xo": {
"command": "npx",
"args": ["@xen-orchestra/mcp"],
"env": {
"XO_URL": "https://your-xo-server",
"XO_USERNAME": "admin@example.com",
"XO_PASSWORD": "your-password"
}
}
}
}
```
### Claude Code
```bash
claude mcp add xo -- env XO_URL=https://your-xo-server XO_USERNAME=admin@example.com XO_PASSWORD=secret npx @xen-orchestra/mcp
```
### Any MCP Client
```bash
XO_URL=https://your-xo-server XO_USERNAME=admin XO_PASSWORD=secret npx @xen-orchestra/mcp
```
## Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| `XO_URL` | Yes | Xen Orchestra server URL |
| `XO_USERNAME` | Yes | XO username |
| `XO_PASSWORD` | Yes | XO password |
## Available Tools
### `check_connection`
Test the connection to the XO server.
### `list_pools`
List all pools with optional field selection.
| Parameter | Type | Description |
|-----------|------|-------------|
| `fields` | string? | Comma-separated fields to return |
### `get_pool_dashboard`
Get aggregated dashboard for a pool (host status, top consumers, alarms).
| Parameter | Type | Description |
|-----------|------|-------------|
| `pool_id` | string | The pool ID |
### `list_hosts`
List all hosts (hypervisors) with optional filtering.
| Parameter | Type | Description |
|-----------|------|-------------|
| `filter` | string? | Filter expression (e.g., `productBrand:XCP-ng`) |
| `fields` | string? | Comma-separated fields to return |
### `list_vms`
List virtual machines with optional filtering and pagination.
| Parameter | Type | Description |
|-----------|------|-------------|
| `filter` | string? | Filter expression (e.g., `power_state:Running`) |
| `fields` | string? | Comma-separated fields to return |
| `limit` | number? | Maximum number of results |
### `get_vm_details`
Get full details about a specific VM.
| Parameter | Type | Description |
|-----------|------|-------------|
| `vm_id` | string | The VM ID or UUID |
### `get_infrastructure_summary`
Get a high-level summary of the entire infrastructure (pool count, host count, VM counts by state).
### `search_documentation`
Retrieve XO documentation by topic.
| Parameter | Type | Description |
|-----------|------|-------------|
| `topic` | enum | One of: `installation`, `configuration`, `backups`, `restapi`, `manage`, `users`, `architecture`, `troubleshooting`, `releases` |
## Development
```bash
npm install
npm run build
npm test
# Run locally
XO_URL=https://your-xo XO_USERNAME=admin XO_PASSWORD=secret npm start
```
## License
AGPL-3.0-or-later

View File

@@ -0,0 +1,25 @@
import eslint from '@eslint/js'
import tseslint from 'typescript-eslint'
import { fileURLToPath } from 'node:url'
import { dirname } from 'node:path'
const __dirname = dirname(fileURLToPath(import.meta.url))
export default tseslint.config(
eslint.configs.recommended,
tseslint.configs.recommended,
{
languageOptions: {
parserOptions: {
tsconfigRootDir: __dirname,
},
},
rules: {
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
},
},
{
ignores: ['dist/'],
}
)

View File

@@ -0,0 +1,56 @@
{
"private": false,
"name": "@xen-orchestra/mcp",
"version": "0.0.0",
"description": "MCP server for Xen Orchestra — allows AI assistants to query and manage XO infrastructure",
"type": "module",
"main": "./dist/index.mjs",
"bin": {
"xo-mcp": "./dist/index.mjs"
},
"scripts": {
"build": "tsc",
"clean": "rimraf dist/",
"lint": "eslint src/",
"postversion": "npm publish --access public",
"prebuild": "npm run clean",
"prepublishOnly": "npm run build",
"start": "node dist/index.mjs",
"test": "tsc && node --test dist/*.test.mjs"
},
"keywords": [
"xen-orchestra",
"xcp-ng",
"mcp",
"model-context-protocol",
"ai",
"virtualization"
],
"dependencies": {
"@modelcontextprotocol/sdk": "^1.26.0",
"zod": "^4.3.6"
},
"devDependencies": {
"@eslint/js": "^9.19.0",
"@types/node": "^22.3.0",
"@vates/types": "^1.19.0",
"rimraf": "^6.0.0",
"typescript": "~5.6.3",
"typescript-eslint": "^8.23.0"
},
"engines": {
"node": ">=20.0.0"
},
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/mcp",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "@xen-orchestra/mcp",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Vates SAS",
"url": "https://vates.fr"
},
"license": "AGPL-3.0-or-later"
}

View File

@@ -0,0 +1,457 @@
#!/usr/bin/env node
/**
* XO MCP Server
*
* Model Context Protocol server for Xen Orchestra.
* Allows AI assistants to query XO infrastructure and documentation.
*
* Usage:
* XO_URL=http://xo.local XO_USERNAME=admin XO_PASSWORD=*** node dist/index.mjs
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { z } from 'zod'
import { realpathSync } from 'node:fs'
import { XoClient } from './xo-client.mjs'
const DOC_FETCH_TIMEOUT_MS = 30_000
const XO_DOCS_BASE_URL = 'https://docs.xen-orchestra.com'
const DOC_TOPICS = [
'installation',
'configuration',
'backups',
'restapi',
'manage',
'users',
'architecture',
'troubleshooting',
'releases',
] as const
export interface EnvConfig {
url: string
username: string
password: string
}
export function validateEnv(): EnvConfig {
const url = process.env.XO_URL
const username = process.env.XO_USERNAME
const password = process.env.XO_PASSWORD
const missing: string[] = []
if (!url) missing.push('XO_URL')
if (!username) missing.push('XO_USERNAME')
if (!password) missing.push('XO_PASSWORD')
if (missing.length > 0) {
throw new Error(
`Missing required environment variables: ${missing.join(', ')}\n\n` +
'Please set:\n' +
' XO_URL - Xen Orchestra server URL (e.g., http://xo.local:9000)\n' +
' XO_USERNAME - Admin username\n' +
' XO_PASSWORD - Password\n\n' +
'Note: XO currently requires username/password authentication.\n' +
'Token authentication is also supported via xo-cli create-token.'
)
}
return { url: url!, username: username!, password: password! }
}
export async function fetchDocumentation(path: string): Promise<string> {
const url = `${XO_DOCS_BASE_URL}${path}`
let response: Response
try {
response = await fetch(url, {
headers: { Accept: 'text/html' },
signal: AbortSignal.timeout(DOC_FETCH_TIMEOUT_MS),
})
} catch (cause) {
throw new Error(`Cannot reach documentation server: ${cause instanceof Error ? cause.message : 'Unknown error'}`, {
cause,
})
}
if (!response.ok) {
throw new Error(`Failed to fetch documentation: HTTP ${response.status} ${response.statusText}`)
}
const html = await response.text()
let text = html
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
.replace(/<nav[^>]*>[\s\S]*?<\/nav>/gi, '')
.replace(/<header[^>]*>[\s\S]*?<\/header>/gi, '')
.replace(/<footer[^>]*>[\s\S]*?<\/footer>/gi, '')
text = text
.replace(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi, '\n## $1\n')
.replace(/<p[^>]*>([\s\S]*?)<\/p>/gi, '$1\n')
.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, '- $1\n')
.replace(/<br\s*\/?>/gi, '\n')
.replace(/<[^>]+>/g, '')
.replace(/&nbsp;/g, ' ')
.replace(/&amp;/g, '&')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'")
.replace(/&mdash;/g, '\u2014')
.replace(/&ndash;/g, '\u2013')
.replace(/&hellip;/g, '\u2026')
.replace(/\n\s*\n\s*\n/g, '\n\n')
.trim()
return text
}
function formatToolError(error: unknown): string {
return error instanceof Error ? error.message : 'Unknown error occurred'
}
export function createServer(getClient: () => XoClient): McpServer {
const server = new McpServer({
name: 'xo-mcp-server',
version: '1.0.0',
})
// =============================================================================
// TOOL: check_connection
// =============================================================================
server.registerTool(
'check_connection',
{
title: 'Check Connection',
description: 'Test the connection to the Xen Orchestra server. Use this to validate your setup.',
inputSchema: {},
},
async () => {
const client = getClient()
const result = await client.testConnection()
if (result.ok) {
return {
content: [{ type: 'text', text: 'Connection to XO server successful.' }],
}
}
return {
content: [
{
type: 'text',
text: `Connection test failed: ${result.error ?? 'Unknown error'}`,
},
],
isError: true,
}
}
)
// =============================================================================
// TOOL: list_pools
// =============================================================================
server.registerTool(
'list_pools',
{
title: 'List Pools',
description: 'List all pools in Xen Orchestra with their basic information',
inputSchema: {
fields: z
.string()
.optional()
.describe(
'Comma-separated fields to return (default: id,name_label,name_description,auto_poweron,HA_enabled)'
),
},
},
async ({ fields }) => {
try {
const client = getClient()
const pools = await client.listPools(fields)
return {
content: [{ type: 'text', text: JSON.stringify(pools, null, 2) }],
}
} catch (error) {
return {
content: [{ type: 'text', text: `Failed to list pools: ${formatToolError(error)}` }],
isError: true,
}
}
}
)
// =============================================================================
// TOOL: get_pool_dashboard
// =============================================================================
server.registerTool(
'get_pool_dashboard',
{
title: 'Get Pool Dashboard',
description: 'Get aggregated dashboard for a pool including hosts status, top consumers, and alarms',
inputSchema: {
pool_id: z.string().describe('The pool ID'),
},
},
async ({ pool_id }) => {
try {
const client = getClient()
const dashboard = await client.getPoolDashboard(pool_id)
return {
content: [{ type: 'text', text: JSON.stringify(dashboard, null, 2) }],
}
} catch (error) {
return {
content: [{ type: 'text', text: `Failed to get pool dashboard: ${formatToolError(error)}` }],
isError: true,
}
}
}
)
// =============================================================================
// TOOL: list_hosts
// =============================================================================
server.registerTool(
'list_hosts',
{
title: 'List Hosts',
description: 'List all hosts (hypervisors) in Xen Orchestra',
inputSchema: {
filter: z.string().optional().describe('Filter expression (e.g., productBrand:XCP-ng)'),
fields: z.string().optional().describe('Comma-separated fields to return'),
},
},
async ({ filter, fields }) => {
try {
const client = getClient()
const hosts = await client.listHosts({ filter, fields })
return {
content: [{ type: 'text', text: JSON.stringify(hosts, null, 2) }],
}
} catch (error) {
return {
content: [{ type: 'text', text: `Failed to list hosts: ${formatToolError(error)}` }],
isError: true,
}
}
}
)
// =============================================================================
// TOOL: list_vms
// =============================================================================
server.registerTool(
'list_vms',
{
title: 'List Virtual Machines',
description: 'List virtual machines in Xen Orchestra with optional filtering',
inputSchema: {
filter: z.string().optional().describe('Filter expression (e.g., power_state:Running, name_label:web*)'),
fields: z.string().optional().describe('Comma-separated fields to return'),
limit: z.number().optional().describe('Maximum number of VMs to return'),
},
},
async ({ filter, fields, limit }) => {
try {
const client = getClient()
const vms = await client.listVms({ filter, fields, limit })
return {
content: [{ type: 'text', text: JSON.stringify(vms, null, 2) }],
}
} catch (error) {
return {
content: [{ type: 'text', text: `Failed to list VMs: ${formatToolError(error)}` }],
isError: true,
}
}
}
)
// =============================================================================
// TOOL: get_vm_details
// =============================================================================
server.registerTool(
'get_vm_details',
{
title: 'Get VM Details',
description: 'Get detailed information about a specific virtual machine',
inputSchema: {
vm_id: z.string().describe('The VM ID or UUID'),
},
},
async ({ vm_id }) => {
try {
const client = getClient()
const vm = await client.getVm(vm_id)
return {
content: [{ type: 'text', text: JSON.stringify(vm, null, 2) }],
}
} catch (error) {
return {
content: [{ type: 'text', text: `Failed to get VM details: ${formatToolError(error)}` }],
isError: true,
}
}
}
)
// =============================================================================
// TOOL: search_documentation
// =============================================================================
server.registerTool(
'search_documentation',
{
title: 'Search XO Documentation',
description:
'Search and retrieve Xen Orchestra documentation. Use this to help users understand XO features, configuration, and best practices.',
inputSchema: {
topic: z.enum(DOC_TOPICS).describe('Documentation topic to retrieve'),
},
},
async ({ topic }) => {
const path = `/${topic}`
try {
const content = await fetchDocumentation(path)
return {
content: [
{
type: 'text',
text: `# XO Documentation: ${topic}\n\nSource: ${XO_DOCS_BASE_URL}${path}\n\n${content}`,
},
],
}
} catch (error) {
return {
content: [
{
type: 'text',
text: `Failed to fetch documentation for "${topic}": ${formatToolError(error)}`,
},
],
isError: true,
}
}
}
)
// =============================================================================
// TOOL: get_infrastructure_summary
// =============================================================================
server.registerTool(
'get_infrastructure_summary',
{
title: 'Get Infrastructure Summary',
description: 'Get a high-level summary of the entire XO infrastructure (pools, hosts, VMs counts)',
inputSchema: {},
},
async () => {
try {
const client = getClient()
const [pools, hosts, vms] = await Promise.all([
client.listPools('id,name_label'),
client.listHosts({ fields: 'id,name_label,power_state' }),
client.listVms({ fields: 'id,power_state' }),
])
const runningVms = vms.filter(vm => vm.power_state === 'Running').length
const haltedVms = vms.filter(vm => vm.power_state === 'Halted').length
const summary = {
pools: {
total: pools.length,
names: pools.map(p => p.name_label),
},
hosts: {
total: hosts.length,
},
vms: {
total: vms.length,
running: runningVms,
halted: haltedVms,
other: vms.length - runningVms - haltedVms,
},
}
return {
content: [{ type: 'text', text: JSON.stringify(summary, null, 2) }],
}
} catch (error) {
return {
content: [
{
type: 'text',
text: `Failed to get infrastructure summary: ${formatToolError(error)}`,
},
],
isError: true,
}
}
}
)
// =============================================================================
// PROMPT: infrastructure-overview
// =============================================================================
server.prompt(
'infrastructure-overview',
'Generate a natural language overview of the XO infrastructure',
async () => {
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: `Please use the get_infrastructure_summary tool to fetch the current state of the Xen Orchestra infrastructure, then provide a clear, human-readable summary of:
1. How many pools are configured and their names
2. Total number of hosts
3. VM statistics (total, running, halted)
Present this information in a friendly, conversational way.`,
},
},
],
}
}
)
return server
}
// =============================================================================
// Start server
// =============================================================================
async function main() {
const env = validateEnv()
let xoClient: XoClient | null = null
function getClient(): XoClient {
if (!xoClient) {
xoClient = new XoClient({ url: env.url, username: env.username, password: env.password })
}
return xoClient
}
const server = createServer(getClient)
const transport = new StdioServerTransport()
await server.connect(transport)
}
// Only run main when executed directly (not when imported for testing)
// realpathSync handles the case where the binary is invoked via a symlink (e.g., npx bin)
import { fileURLToPath } from 'node:url'
if (realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))) {
// console.error is used intentionally instead of @xen-orchestra/log because
// MCP servers communicate via stdout (JSON-RPC) — only stderr is safe for logging.
main().catch(error => {
console.error('Fatal error:', error instanceof Error ? error.message : error)
process.exit(1)
})
}

View File

@@ -0,0 +1,419 @@
import assert from 'node:assert/strict'
import { describe, it, beforeEach, afterEach } from 'node:test'
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js'
import { createServer, validateEnv, fetchDocumentation } from './index.mjs'
import type { XoClient } from './xo-client.mjs'
// Helper to create a mock XoClient
// Uses `as unknown as XoClient` because @vates/types uses branded string IDs
// that plain string literals don't satisfy at the type level.
function createMockClient(overrides: Record<string, unknown> = {}): XoClient {
return {
testConnection: async () => ({ ok: true }),
listPools: async () => [{ id: 'pool1', name_label: 'Pool 1' }],
listHosts: async () => [{ id: 'host1', name_label: 'Host 1' }],
listVms: async () => [
{ id: 'vm1', name_label: 'VM 1', power_state: 'Running' },
{ id: 'vm2', name_label: 'VM 2', power_state: 'Halted' },
],
getVm: async () => ({ id: 'vm1', name_label: 'VM 1', power_state: 'Running' }),
getPool: async () => ({ id: 'pool1', name_label: 'Pool 1' }),
getPoolDashboard: async () => ({ hostsByStatus: { running: 1 } }),
getHost: async () => ({ id: 'host1', name_label: 'Host 1' }),
getVmStats: async () => ({ endTimestamp: 0, interval: 0, stats: {} }),
...overrides,
} as unknown as XoClient
}
// Helper to set up client + server connected via InMemoryTransport
async function setupTestServer(mockClient?: XoClient) {
const client = mockClient ?? createMockClient()
const server = createServer(() => client)
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair()
const mcpClient = new Client({ name: 'test-client', version: '1.0.0' })
await Promise.all([server.connect(serverTransport), mcpClient.connect(clientTransport)])
return { mcpClient, server }
}
describe('createServer', () => {
describe('tool listing', () => {
it('registers all 8 tools', async () => {
const { mcpClient } = await setupTestServer()
const { tools } = await mcpClient.listTools()
const toolNames = tools.map((t) => t.name).sort()
assert.deepStrictEqual(toolNames, [
'check_connection',
'get_infrastructure_summary',
'get_pool_dashboard',
'get_vm_details',
'list_hosts',
'list_pools',
'list_vms',
'search_documentation',
])
})
})
describe('check_connection tool', () => {
it('returns success message on good connection', async () => {
const { mcpClient } = await setupTestServer()
const result = await mcpClient.callTool({ name: 'check_connection', arguments: {} })
const text = (result.content as Array<{ type: string; text: string }>)[0].text
assert.ok(text.includes('successful'))
})
it('returns error message on failed connection', async () => {
const mockClient = createMockClient({
testConnection: async () => ({ ok: false, error: 'Cannot connect to XO server' }),
})
const { mcpClient } = await setupTestServer(mockClient)
const result = await mcpClient.callTool({ name: 'check_connection', arguments: {} })
const text = (result.content as Array<{ type: string; text: string }>)[0].text
assert.ok(text.includes('failed'))
assert.ok(text.includes('Cannot connect'))
})
})
describe('list_pools tool', () => {
it('returns pools as JSON', async () => {
const { mcpClient } = await setupTestServer()
const result = await mcpClient.callTool({ name: 'list_pools', arguments: {} })
const text = (result.content as Array<{ type: string; text: string }>)[0].text
const parsed = JSON.parse(text)
assert.strictEqual(parsed[0].id, 'pool1')
})
it('passes fields parameter', async () => {
let receivedFields: string | undefined
const mockClient = createMockClient({
listPools: async (fields?: string) => {
receivedFields = fields
return [{ id: 'pool1', name_label: 'Pool 1' }]
},
})
const { mcpClient } = await setupTestServer(mockClient)
await mcpClient.callTool({ name: 'list_pools', arguments: { fields: 'id,name_label' } })
assert.strictEqual(receivedFields, 'id,name_label')
})
it('returns error on failure', async () => {
const mockClient = createMockClient({
listPools: async () => {
throw new Error('Connection refused')
},
})
const { mcpClient } = await setupTestServer(mockClient)
const result = await mcpClient.callTool({ name: 'list_pools', arguments: {} })
const text = (result.content as Array<{ type: string; text: string }>)[0].text
assert.ok(text.includes('Failed to list pools'))
})
})
describe('list_vms tool', () => {
it('returns VMs as JSON', async () => {
const { mcpClient } = await setupTestServer()
const result = await mcpClient.callTool({ name: 'list_vms', arguments: {} })
const text = (result.content as Array<{ type: string; text: string }>)[0].text
const parsed = JSON.parse(text)
assert.strictEqual(parsed.length, 2)
})
it('passes filter, fields, and limit', async () => {
let receivedArgs: { filter?: string; fields?: string; limit?: number } = {}
const mockClient = createMockClient({
listVms: async (options?: { filter?: string; fields?: string; limit?: number }) => {
receivedArgs = { filter: options?.filter, fields: options?.fields, limit: options?.limit }
return []
},
})
const { mcpClient } = await setupTestServer(mockClient)
await mcpClient.callTool({
name: 'list_vms',
arguments: { filter: 'power_state:Running', fields: 'id', limit: 5 },
})
assert.strictEqual(receivedArgs.filter, 'power_state:Running')
assert.strictEqual(receivedArgs.fields, 'id')
assert.strictEqual(receivedArgs.limit, 5)
})
})
describe('get_vm_details tool', () => {
it('returns VM details', async () => {
const { mcpClient } = await setupTestServer()
const result = await mcpClient.callTool({
name: 'get_vm_details',
arguments: { vm_id: 'vm1' },
})
const text = (result.content as Array<{ type: string; text: string }>)[0].text
const parsed = JSON.parse(text)
assert.strictEqual(parsed.id, 'vm1')
})
it('returns error when VM not found', async () => {
const mockClient = createMockClient({
getVm: async () => {
throw new Error('XO API error (404 Not Found): Not found')
},
})
const { mcpClient } = await setupTestServer(mockClient)
const result = await mcpClient.callTool({
name: 'get_vm_details',
arguments: { vm_id: 'nonexistent' },
})
const text = (result.content as Array<{ type: string; text: string }>)[0].text
assert.ok(text.includes('Failed to get VM details'))
})
})
describe('get_infrastructure_summary tool', () => {
it('returns aggregated summary', async () => {
const { mcpClient } = await setupTestServer()
const result = await mcpClient.callTool({
name: 'get_infrastructure_summary',
arguments: {},
})
const text = (result.content as Array<{ type: string; text: string }>)[0].text
const parsed = JSON.parse(text)
assert.strictEqual(parsed.pools.total, 1)
assert.strictEqual(parsed.hosts.total, 1)
assert.strictEqual(parsed.vms.total, 2)
assert.strictEqual(parsed.vms.running, 1)
assert.strictEqual(parsed.vms.halted, 1)
})
})
describe('get_pool_dashboard tool', () => {
it('returns dashboard data', async () => {
const { mcpClient } = await setupTestServer()
const result = await mcpClient.callTool({
name: 'get_pool_dashboard',
arguments: { pool_id: 'pool1' },
})
const text = (result.content as Array<{ type: string; text: string }>)[0].text
const parsed = JSON.parse(text)
assert.deepStrictEqual(parsed.hostsByStatus, { running: 1 })
})
})
describe('list_hosts tool', () => {
it('returns hosts as JSON', async () => {
const { mcpClient } = await setupTestServer()
const result = await mcpClient.callTool({ name: 'list_hosts', arguments: {} })
const text = (result.content as Array<{ type: string; text: string }>)[0].text
const parsed = JSON.parse(text)
assert.strictEqual(parsed[0].id, 'host1')
})
})
describe('search_documentation tool', () => {
let originalFetch: typeof globalThis.fetch
beforeEach(() => {
originalFetch = globalThis.fetch
})
afterEach(() => {
globalThis.fetch = originalFetch
})
it('fetches and returns documentation', async () => {
globalThis.fetch = async (input: RequestInfo | URL) => {
const url = typeof input === 'string' ? input : input.toString()
if (url.includes('docs.xen-orchestra.com')) {
return new Response('<h1>Installation Guide</h1><p>Install XO here.</p>', {
status: 200,
headers: { 'content-type': 'text/html' },
})
}
return originalFetch(input)
}
const { mcpClient } = await setupTestServer()
const result = await mcpClient.callTool({
name: 'search_documentation',
arguments: { topic: 'installation' },
})
const text = (result.content as Array<{ type: string; text: string }>)[0].text
assert.ok(text.includes('Installation Guide'))
assert.ok(text.includes('Install XO here'))
})
it('returns error when documentation fetch fails', async () => {
globalThis.fetch = async (input: RequestInfo | URL) => {
const url = typeof input === 'string' ? input : input.toString()
if (url.includes('docs.xen-orchestra.com')) {
return new Response('Not Found', { status: 404, statusText: 'Not Found' })
}
return originalFetch(input)
}
const { mcpClient } = await setupTestServer()
const result = await mcpClient.callTool({
name: 'search_documentation',
arguments: { topic: 'installation' },
})
const text = (result.content as Array<{ type: string; text: string }>)[0].text
assert.ok(text.includes('Failed to fetch documentation'))
})
})
})
describe('validateEnv', () => {
const originalEnv = { ...process.env }
afterEach(() => {
process.env = { ...originalEnv }
})
it('returns config when all env vars are set', () => {
process.env.XO_URL = 'http://xo.local:9000'
process.env.XO_USERNAME = 'admin'
process.env.XO_PASSWORD = 'pass'
const config = validateEnv()
assert.strictEqual(config.url, 'http://xo.local:9000')
assert.strictEqual(config.username, 'admin')
assert.strictEqual(config.password, 'pass')
})
it('throws when XO_URL is missing', () => {
delete process.env.XO_URL
process.env.XO_USERNAME = 'admin'
process.env.XO_PASSWORD = 'pass'
assert.throws(() => validateEnv(), { message: /XO_URL/ })
})
it('throws when XO_USERNAME is missing', () => {
process.env.XO_URL = 'http://xo.local:9000'
delete process.env.XO_USERNAME
process.env.XO_PASSWORD = 'pass'
assert.throws(() => validateEnv(), { message: /XO_USERNAME/ })
})
it('throws when XO_PASSWORD is missing', () => {
process.env.XO_URL = 'http://xo.local:9000'
process.env.XO_USERNAME = 'admin'
delete process.env.XO_PASSWORD
assert.throws(() => validateEnv(), { message: /XO_PASSWORD/ })
})
it('lists all missing vars in error message', () => {
delete process.env.XO_URL
delete process.env.XO_USERNAME
delete process.env.XO_PASSWORD
assert.throws(() => validateEnv(), (error: Error) => {
assert.ok(error.message.includes('XO_URL'))
assert.ok(error.message.includes('XO_USERNAME'))
assert.ok(error.message.includes('XO_PASSWORD'))
return true
})
})
it('includes help text in error message', () => {
delete process.env.XO_URL
process.env.XO_USERNAME = 'admin'
process.env.XO_PASSWORD = 'pass'
assert.throws(() => validateEnv(), (error: Error) => {
assert.ok(error.message.includes('Please set'))
assert.ok(error.message.includes('XO_URL'))
return true
})
})
})
describe('fetchDocumentation', () => {
let originalFetch: typeof globalThis.fetch
beforeEach(() => {
originalFetch = globalThis.fetch
})
afterEach(() => {
globalThis.fetch = originalFetch
})
it('strips HTML and returns clean text', async () => {
globalThis.fetch = async () => {
return new Response(
'<html><body><h1>Title</h1><p>Content here.</p><script>evil()</script></body></html>',
{ status: 200, headers: { 'content-type': 'text/html' } }
)
}
const text = await fetchDocumentation('/test')
assert.ok(text.includes('## Title'))
assert.ok(text.includes('Content here.'))
assert.ok(!text.includes('evil()'))
assert.ok(!text.includes('<script>'))
})
it('decodes HTML entities', async () => {
globalThis.fetch = async () => {
return new Response('<p>A &amp; B &lt; C &gt; D</p>', {
status: 200,
headers: { 'content-type': 'text/html' },
})
}
const text = await fetchDocumentation('/test')
assert.ok(text.includes('A & B < C > D'))
})
it('decodes additional HTML entities', async () => {
globalThis.fetch = async () => {
return new Response('<p>&quot;quoted&quot; &amp; it&#39;s &mdash; a &ndash; test&hellip;</p>', {
status: 200,
headers: { 'content-type': 'text/html' },
})
}
const text = await fetchDocumentation('/test')
assert.ok(text.includes('"quoted"'))
assert.ok(text.includes("it's"))
assert.ok(text.includes('\u2014'))
assert.ok(text.includes('\u2013'))
assert.ok(text.includes('\u2026'))
})
it('handles multiline HTML tags', async () => {
globalThis.fetch = async () => {
return new Response('<h1>Title\nwith newline</h1><p>Para\nwith newline</p>', {
status: 200,
headers: { 'content-type': 'text/html' },
})
}
const text = await fetchDocumentation('/test')
assert.ok(text.includes('Title'))
assert.ok(text.includes('with newline'))
})
it('throws on HTTP error', async () => {
globalThis.fetch = async () => {
return new Response('Not Found', { status: 404, statusText: 'Not Found' })
}
await assert.rejects(() => fetchDocumentation('/test'), {
message: /404/,
})
})
it('throws on network error', async () => {
globalThis.fetch = async () => {
throw new Error('Network failure')
}
await assert.rejects(() => fetchDocumentation('/test'), {
message: /Cannot reach documentation server/,
})
})
})

View File

@@ -0,0 +1,157 @@
/**
* XO REST API Client
*
* Minimal client using native fetch to interact with Xen Orchestra REST API.
* Authentication is done via Basic Auth or token cookie.
*/
import type { XoPool, XoHost, XoVm } from '@vates/types/xo'
import type { XapiVmStats, XapiStatsGranularity } from '@vates/types/common'
export type { XoPool, XoHost, XoVm, XapiVmStats, XapiStatsGranularity }
const REQUEST_TIMEOUT_MS = 30_000
export interface XoClientConfig {
url: string
username: string
password: string
}
export interface XoPoolDashboard {
hostsByStatus?: Record<string, number>
vmsByStatus?: Record<string, number>
topHostsByRam?: Array<{ id: string; name: string; value: number }>
topHostsByCpu?: Array<{ id: string; name: string; value: number }>
alarms?: Array<{ id: string; name: string; time: number }>
}
export class XoClient {
private readonly baseUrl: string
private readonly authHeader: string
constructor(config: XoClientConfig) {
this.baseUrl = config.url.replace(/\/$/, '')
const credentials = Buffer.from(`${config.username}:${config.password}`).toString('base64')
this.authHeader = `Basic ${credentials}`
}
private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
const url = `${this.baseUrl}/rest/v0${endpoint}`
let response: Response
try {
response = await fetch(url, {
...options,
headers: {
Authorization: this.authHeader,
Accept: 'application/json',
...options.headers,
},
signal: options.signal ?? AbortSignal.timeout(REQUEST_TIMEOUT_MS),
})
} catch (cause) {
const message = cause instanceof Error ? cause.message : String(cause)
if (message.includes('ECONNREFUSED') || message.includes('fetch failed')) {
throw new Error(
`Cannot connect to XO server at ${this.baseUrl}. Verify the server is running and XO_URL is correct.`,
{ cause }
)
}
if (message.includes('TimeoutError') || message.includes('abort')) {
throw new Error(`Request to XO server timed out after ${REQUEST_TIMEOUT_MS}ms.`, { cause })
}
throw new Error(`Network error connecting to XO server: ${message}`, { cause })
}
if (!response.ok) {
if (response.status === 401) {
throw new Error('Authentication failed: check XO_USERNAME and XO_PASSWORD.')
}
const errorText = await response.text().catch(() => response.statusText)
throw new Error(`XO API error (${response.status} ${response.statusText}): ${errorText}`)
}
try {
return (await response.json()) as T
} catch (cause) {
throw new Error(`XO API returned invalid JSON for ${endpoint}`, { cause })
}
}
async testConnection(): Promise<{ ok: boolean; error?: string }> {
try {
await this.request('/pools?limit=1')
return { ok: true }
} catch (error) {
return { ok: false, error: error instanceof Error ? error.message : 'Unknown error' }
}
}
async listPools(fields?: string): Promise<Partial<XoPool>[]> {
const params = new URLSearchParams()
params.set('fields', fields ?? 'id,name_label,name_description,auto_poweron,HA_enabled')
return this.request<Partial<XoPool>[]>(`/pools?${params}`)
}
async getPool(poolId: string): Promise<XoPool> {
return this.request<XoPool>(`/pools/${encodeURIComponent(poolId)}`)
}
async getPoolDashboard(poolId: string): Promise<XoPoolDashboard> {
return this.request<XoPoolDashboard>(
`/pools/${encodeURIComponent(poolId)}/dashboard?ndjson=false`
)
}
async listHosts(options?: { filter?: string; fields?: string }): Promise<Partial<XoHost>[]> {
const params = new URLSearchParams()
params.set(
'fields',
options?.fields ?? 'id,name_label,productBrand,version,power_state'
)
if (options?.filter) {
params.set('filter', options.filter)
}
return this.request<Partial<XoHost>[]>(`/hosts?${params}`)
}
async getHost(hostId: string): Promise<XoHost> {
return this.request<XoHost>(`/hosts/${encodeURIComponent(hostId)}`)
}
async listVms(options?: {
filter?: string
fields?: string
limit?: number
}): Promise<Partial<XoVm>[]> {
const params = new URLSearchParams()
params.set('fields', options?.fields ?? 'id,name_label,power_state,CPUs,memory')
if (options?.filter) {
params.set('filter', options.filter)
}
if (options?.limit !== undefined) {
params.set('limit', String(options.limit))
}
return this.request<Partial<XoVm>[]>(`/vms?${params}`)
}
async getVm(vmId: string): Promise<XoVm> {
return this.request<XoVm>(`/vms/${encodeURIComponent(vmId)}`)
}
async getVmStats(
vmId: string,
granularity: XapiStatsGranularity = 'hours'
): Promise<XapiVmStats> {
const params = new URLSearchParams()
params.set('granularity', granularity)
return this.request<XapiVmStats>(
`/vms/${encodeURIComponent(vmId)}/stats?${params}`
)
}
}

View File

@@ -0,0 +1,305 @@
import assert from 'node:assert/strict'
import { describe, it, beforeEach, afterEach } from 'node:test'
import { XoClient } from './xo-client.mjs'
// Helper to create a mock Response
function mockResponse(body: unknown, init: ResponseInit = {}): Response {
return new Response(JSON.stringify(body), {
status: 200,
headers: { 'content-type': 'application/json' },
...init,
})
}
describe('XoClient', () => {
let originalFetch: typeof globalThis.fetch
beforeEach(() => {
originalFetch = globalThis.fetch
})
afterEach(() => {
globalThis.fetch = originalFetch
})
describe('constructor', () => {
it('strips trailing slash from URL', async () => {
const client = new XoClient({ url: 'http://xo.local:9000/', username: 'admin', password: 'pass' })
globalThis.fetch = async (input: RequestInfo | URL) => {
const url = typeof input === 'string' ? input : input.toString()
assert.ok(!url.includes('9000//'), 'URL should not have double slashes')
assert.ok(url.startsWith('http://xo.local:9000/rest/v0/'), 'URL should start with base URL + /rest/v0/')
return mockResponse([])
}
await client.listPools()
})
it('creates correct Basic Auth header', async () => {
const client = new XoClient({ url: 'http://xo.local:9000', username: 'admin', password: 'secret' })
const expected = `Basic ${Buffer.from('admin:secret').toString('base64')}`
globalThis.fetch = async (_input: RequestInfo | URL, init?: RequestInit) => {
const headers = init?.headers as Record<string, string>
assert.strictEqual(headers?.Authorization, expected)
return mockResponse([])
}
await client.listPools()
})
})
describe('request', () => {
it('prepends /rest/v0 to endpoints', async () => {
const client = new XoClient({ url: 'http://xo.local:9000', username: 'admin', password: 'pass' })
globalThis.fetch = async (input: RequestInfo | URL) => {
const url = typeof input === 'string' ? input : input.toString()
assert.ok(url.includes('/rest/v0/pools'), `URL should contain /rest/v0/pools, got: ${url}`)
return mockResponse([])
}
await client.listPools()
})
it('sends Accept: application/json header', async () => {
const client = new XoClient({ url: 'http://xo.local:9000', username: 'admin', password: 'pass' })
globalThis.fetch = async (_input: RequestInfo | URL, init?: RequestInit) => {
const headers = init?.headers as Record<string, string>
assert.strictEqual(headers?.Accept, 'application/json')
return mockResponse([])
}
await client.listPools()
})
it('throws descriptive error on connection refused', async () => {
const client = new XoClient({ url: 'http://xo.local:9000', username: 'admin', password: 'pass' })
globalThis.fetch = async () => {
throw new TypeError('fetch failed: ECONNREFUSED')
}
await assert.rejects(() => client.listPools(), {
message: /Cannot connect to XO server/,
})
})
it('preserves original error as cause', async () => {
const client = new XoClient({ url: 'http://xo.local:9000', username: 'admin', password: 'pass' })
const originalError = new TypeError('fetch failed: ECONNREFUSED')
globalThis.fetch = async () => {
throw originalError
}
try {
await client.listPools()
assert.fail('Should have thrown')
} catch (error) {
assert.ok(error instanceof Error)
assert.strictEqual(error.cause, originalError)
}
})
it('throws descriptive error on 401', async () => {
const client = new XoClient({ url: 'http://xo.local:9000', username: 'admin', password: 'wrong' })
globalThis.fetch = async () => {
return new Response('Unauthorized', { status: 401, statusText: 'Unauthorized' })
}
await assert.rejects(() => client.listPools(), {
message: /Authentication failed/,
})
})
it('throws error with status code on other HTTP errors', async () => {
const client = new XoClient({ url: 'http://xo.local:9000', username: 'admin', password: 'pass' })
globalThis.fetch = async () => {
return new Response('Not Found', { status: 404, statusText: 'Not Found' })
}
await assert.rejects(() => client.listPools(), {
message: /404/,
})
})
it('throws network error with message', async () => {
const client = new XoClient({ url: 'http://xo.local:9000', username: 'admin', password: 'pass' })
globalThis.fetch = async () => {
throw new Error('DNS resolution failed')
}
await assert.rejects(() => client.listPools(), {
message: /Network error.*DNS resolution failed/,
})
})
it('throws on invalid JSON response with cause', async () => {
const client = new XoClient({ url: 'http://xo.local:9000', username: 'admin', password: 'pass' })
globalThis.fetch = async () => {
return new Response('not-json', { status: 200, headers: { 'content-type': 'text/plain' } })
}
try {
await client.listPools()
assert.fail('Should have thrown')
} catch (error) {
assert.ok(error instanceof Error)
assert.ok(error.message.includes('invalid JSON'))
assert.ok(error.cause !== undefined, 'Should preserve JSON parse error as cause')
}
})
it('sets AbortSignal timeout on requests', async () => {
const client = new XoClient({ url: 'http://xo.local:9000', username: 'admin', password: 'pass' })
globalThis.fetch = async (_input: RequestInfo | URL, init?: RequestInit) => {
assert.ok(init?.signal, 'Request should have an abort signal')
return mockResponse([])
}
await client.listPools()
})
})
describe('testConnection', () => {
it('returns { ok: true } on successful connection', async () => {
const client = new XoClient({ url: 'http://xo.local:9000', username: 'admin', password: 'pass' })
globalThis.fetch = async () => mockResponse([{ id: 'pool1' }])
const result = await client.testConnection()
assert.deepStrictEqual(result, { ok: true })
})
it('returns { ok: false, error } on connection failure', async () => {
const client = new XoClient({ url: 'http://xo.local:9000', username: 'admin', password: 'pass' })
globalThis.fetch = async () => {
throw new Error('ECONNREFUSED')
}
const result = await client.testConnection()
assert.strictEqual(result.ok, false)
assert.ok(result.error)
assert.ok(result.error!.includes('Cannot connect'))
})
})
describe('listPools', () => {
it('returns pools with default fields', async () => {
const pools = [{ id: 'pool1', name_label: 'Pool 1' }]
const client = new XoClient({ url: 'http://xo.local:9000', username: 'admin', password: 'pass' })
globalThis.fetch = async (input: RequestInfo | URL) => {
const url = typeof input === 'string' ? input : input.toString()
assert.ok(url.includes('fields=id%2Cname_label%2Cname_description%2Cauto_poweron%2CHA_enabled'))
return mockResponse(pools)
}
const result = await client.listPools()
assert.deepStrictEqual(result, pools)
})
it('passes custom fields', async () => {
const client = new XoClient({ url: 'http://xo.local:9000', username: 'admin', password: 'pass' })
globalThis.fetch = async (input: RequestInfo | URL) => {
const url = typeof input === 'string' ? input : input.toString()
assert.ok(url.includes('fields=id%2Cname_label'))
return mockResponse([])
}
await client.listPools('id,name_label')
})
})
describe('listHosts', () => {
it('returns hosts with default fields', async () => {
const hosts = [{ id: 'host1', name_label: 'Host 1' }]
const client = new XoClient({ url: 'http://xo.local:9000', username: 'admin', password: 'pass' })
globalThis.fetch = async () => mockResponse(hosts)
const result = await client.listHosts()
assert.deepStrictEqual(result, hosts)
})
it('passes filter parameter via object', async () => {
const client = new XoClient({ url: 'http://xo.local:9000', username: 'admin', password: 'pass' })
globalThis.fetch = async (input: RequestInfo | URL) => {
const url = typeof input === 'string' ? input : input.toString()
assert.ok(url.includes('filter=productBrand'))
return mockResponse([])
}
await client.listHosts({ filter: 'productBrand:XCP-ng' })
})
})
describe('listVms', () => {
it('returns VMs with default fields', async () => {
const vms = [{ id: 'vm1', name_label: 'VM 1', power_state: 'Running' }]
const client = new XoClient({ url: 'http://xo.local:9000', username: 'admin', password: 'pass' })
globalThis.fetch = async () => mockResponse(vms)
const result = await client.listVms()
assert.deepStrictEqual(result, vms)
})
it('passes filter, fields, and limit via object', async () => {
const client = new XoClient({ url: 'http://xo.local:9000', username: 'admin', password: 'pass' })
globalThis.fetch = async (input: RequestInfo | URL) => {
const url = typeof input === 'string' ? input : input.toString()
assert.ok(url.includes('filter=power_state'))
assert.ok(url.includes('limit=10'))
return mockResponse([])
}
await client.listVms({ filter: 'power_state:Running', fields: 'id,name_label', limit: 10 })
})
it('passes limit=0 correctly', async () => {
const client = new XoClient({ url: 'http://xo.local:9000', username: 'admin', password: 'pass' })
globalThis.fetch = async (input: RequestInfo | URL) => {
const url = typeof input === 'string' ? input : input.toString()
assert.ok(url.includes('limit=0'), 'limit=0 should be included in URL')
return mockResponse([])
}
await client.listVms({ limit: 0 })
})
})
describe('getVm', () => {
it('returns VM details', async () => {
const vm = { id: 'vm1', name_label: 'VM 1', power_state: 'Running' }
const client = new XoClient({ url: 'http://xo.local:9000', username: 'admin', password: 'pass' })
globalThis.fetch = async (input: RequestInfo | URL) => {
const url = typeof input === 'string' ? input : input.toString()
assert.ok(url.includes('/vms/vm1'))
return mockResponse(vm)
}
const result = await client.getVm('vm1')
assert.deepStrictEqual(result, vm)
})
it('encodes special characters in VM ID', async () => {
const client = new XoClient({ url: 'http://xo.local:9000', username: 'admin', password: 'pass' })
globalThis.fetch = async (input: RequestInfo | URL) => {
const url = typeof input === 'string' ? input : input.toString()
assert.ok(url.includes('/vms/vm%2Fspecial'), `URL should encode slash: ${url}`)
return mockResponse({ id: 'vm/special', name_label: 'Special', power_state: 'Running' })
}
await client.getVm('vm/special')
})
})
describe('getPoolDashboard', () => {
it('returns dashboard data', async () => {
const dashboard = { hostsByStatus: { running: 2 }, vmsByStatus: { running: 10 } }
const client = new XoClient({ url: 'http://xo.local:9000', username: 'admin', password: 'pass' })
globalThis.fetch = async (input: RequestInfo | URL) => {
const url = typeof input === 'string' ? input : input.toString()
assert.ok(url.includes('/pools/pool1/dashboard'))
assert.ok(url.includes('ndjson=false'))
return mockResponse(dashboard)
}
const result = await client.getPoolDashboard('pool1')
assert.deepStrictEqual(result, dashboard)
})
})
})

View File

@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"rootDir": "src",
"outDir": "dist",
"strict": true,
"declaration": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"types": ["node"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -13,6 +13,8 @@
- [Search Engine] Implement first version of the Query Builder on Pools, Hosts, VMs, networks and Storage tables (PR [#9488](https://github.com/vatesfr/xen-orchestra/pull/9488))
- [REST API] Added POST `/srs/{id}/actions/scan` rest routes (PR [#9514](https://github.com/vatesfr/xen-orchestra/pull/9514))
- [MCP] Initial release of `@xen-orchestra/mcp`: MCP server enabling AI assistants to query XO infrastructure (pools, hosts, VMs, dashboard, documentation) (PR [#9519](https://github.com/vatesfr/xen-orchestra/pull/9519))
- **XO 5:**
- [Network] Ability to switch management PIF (PRs [#9369](https://github.com/vatesfr/xen-orchestra/pull/9369) [#9510](https://github.com/vatesfr/xen-orchestra/pull/9510))
- [Licenses] Display bundle name next to license name (PR [#9512](https://github.com/vatesfr/xen-orchestra/pull/9512))
@@ -41,6 +43,7 @@
<!--packages-start-->
- @xen-orchestra/backups patch
- @xen-orchestra/mcp major
- @xen-orchestra/rest-api minor
- @xen-orchestra/web minor
- @xen-orchestra/web-core minor

359
yarn.lock
View File

@@ -2444,6 +2444,11 @@
"@hapi/bourne" "^3.0.0"
"@hapi/hoek" "^11.0.2"
"@hono/node-server@^1.19.9":
version "1.19.9"
resolved "https://registry.yarnpkg.com/@hono/node-server/-/node-server-1.19.9.tgz#8f37119b1acf283fd3f6035f3d1356fdb97a09ac"
integrity sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==
"@humanwhocodes/config-array@^0.13.0":
version "0.13.0"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748"
@@ -2772,6 +2777,29 @@
slash "^3.0.0"
tsconfig-paths "^4"
"@modelcontextprotocol/sdk@^1.26.0":
version "1.26.0"
resolved "https://registry.yarnpkg.com/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz#5b35d73062125f126cc70b0be83cbab53bcdde74"
integrity sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==
dependencies:
"@hono/node-server" "^1.19.9"
ajv "^8.17.1"
ajv-formats "^3.0.1"
content-type "^1.0.5"
cors "^2.8.5"
cross-spawn "^7.0.5"
eventsource "^3.0.2"
eventsource-parser "^3.0.0"
express "^5.2.1"
express-rate-limit "^8.2.1"
hono "^4.11.4"
jose "^6.1.3"
json-schema-typed "^8.0.2"
pkce-challenge "^5.0.0"
raw-body "^3.0.0"
zod "^3.25 || ^4.0"
zod-to-json-schema "^3.25.1"
"@napi-rs/wasm-runtime@^0.2.11":
version "0.2.12"
resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz#3e78a8b96e6c33a6c517e1894efbd5385a7cb6f2"
@@ -5642,6 +5670,14 @@ accepts@^1.3.5, accepts@~1.3.8:
mime-types "~2.1.34"
negotiator "0.6.3"
accepts@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895"
integrity sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==
dependencies:
mime-types "^3.0.0"
negotiator "^1.0.0"
acme-client@^5.0.0:
version "5.4.0"
resolved "https://registry.yarnpkg.com/acme-client/-/acme-client-5.4.0.tgz#989e0625c3ff916d16fb7d7377035e80565aa635"
@@ -5724,6 +5760,13 @@ aggregate-error@^3.0.0:
clean-stack "^2.0.0"
indent-string "^4.0.0"
ajv-formats@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-3.0.1.tgz#3d5dc762bca17679c3c2ea7e90ad6b7532309578"
integrity sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==
dependencies:
ajv "^8.0.0"
ajv@^6.12.4:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
@@ -5734,6 +5777,16 @@ ajv@^6.12.4:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
ajv@^8.0.0, ajv@^8.17.1:
version "8.18.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.18.0.tgz#8864186b6738d003eb3a933172bb3833e10cefbc"
integrity sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==
dependencies:
fast-deep-equal "^3.1.3"
fast-uri "^3.0.1"
json-schema-traverse "^1.0.0"
require-from-string "^2.0.2"
ajv@^8.0.3, ajv@^8.11.0:
version "8.17.1"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6"
@@ -6487,6 +6540,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
balanced-match@^4.0.2:
version "4.0.3"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.3.tgz#6337a2f23e0604a30481423432f99eac603599f9"
integrity sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==
bare-events@^2.7.0:
version "2.8.2"
resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.8.2.tgz#7b3e10bd8e1fc80daf38bb516921678f566ab89f"
@@ -6655,6 +6713,21 @@ body-parser@^1.20.0, body-parser@~1.20.3:
type-is "~1.6.18"
unpipe "~1.0.0"
body-parser@^2.2.1:
version "2.2.2"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.2.2.tgz#1a32cdb966beaf68de50a9dfbe5b58f83cb8890c"
integrity sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==
dependencies:
bytes "^3.1.2"
content-type "^1.0.5"
debug "^4.4.3"
http-errors "^2.0.0"
iconv-lite "^0.7.0"
on-finished "^2.4.1"
qs "^6.14.1"
raw-body "^3.0.1"
type-is "^2.0.1"
body-parser@~1.14.0:
version "1.14.2"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.14.2.tgz#1015cb1fe2c443858259581db53332f8d0cf50f9"
@@ -6709,6 +6782,13 @@ brace-expansion@^2.0.1:
dependencies:
balanced-match "^1.0.0"
brace-expansion@^5.0.2:
version "5.0.2"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.2.tgz#b6c16d0791087af6c2bc463f52a8142046c06b6f"
integrity sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==
dependencies:
balanced-match "^4.0.2"
braces@^1.8.2:
version "1.8.5"
resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7"
@@ -7749,6 +7829,11 @@ constants-browserify@~1.0.0:
resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
integrity sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==
content-disposition@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-1.0.1.tgz#a8b7bbeb2904befdfb6787e5c0c086959f605f9b"
integrity sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==
content-disposition@~0.5.2, content-disposition@~0.5.4:
version "0.5.4"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
@@ -7823,12 +7908,17 @@ cookie-signature@1.0.6:
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==
cookie-signature@^1.2.1:
version "1.2.2"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793"
integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==
cookie-signature@~1.0.6, cookie-signature@~1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.7.tgz#ab5dd7ab757c54e60f37ef6550f481c426d10454"
integrity sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==
cookie@0.7.2, cookie@^0.7.0, cookie@^0.7.2, cookie@~0.7.1, cookie@~0.7.2:
cookie@0.7.2, cookie@^0.7.0, cookie@^0.7.1, cookie@^0.7.2, cookie@~0.7.1, cookie@~0.7.2:
version "0.7.2"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7"
integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==
@@ -7895,6 +7985,14 @@ core-util-is@~1.0.0:
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
cors@^2.8.5:
version "2.8.6"
resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.6.tgz#ff5dd69bd95e547503820d29aba4f8faf8dfec96"
integrity sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==
dependencies:
object-assign "^4"
vary "^1"
cosmiconfig-typescript-loader@^6.1.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.2.0.tgz#26399fa92e9569052062846afd038c94628f0f69"
@@ -7977,7 +8075,7 @@ cross-spawn@^6.0.5:
shebang-command "^1.2.0"
which "^1.2.9"
cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3, cross-spawn@^7.0.6:
cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3, cross-spawn@^7.0.5, cross-spawn@^7.0.6:
version "7.0.6"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
@@ -9026,7 +9124,7 @@ encodeurl@^1.0.2:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
encodeurl@~2.0.0:
encodeurl@^2.0.0, encodeurl@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
@@ -9670,7 +9768,7 @@ esutils@^2.0.2:
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
etag@~1.8.1:
etag@^1.8.1, etag@~1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
@@ -9771,6 +9869,18 @@ events@^2.0.0:
resolved "https://registry.yarnpkg.com/events/-/events-2.1.0.tgz#2a9a1e18e6106e0e812aa9ebd4a819b3c29c0ba5"
integrity sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg==
eventsource-parser@^3.0.0, eventsource-parser@^3.0.1:
version "3.0.6"
resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-3.0.6.tgz#292e165e34cacbc936c3c92719ef326d4aeb4e90"
integrity sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==
eventsource@^3.0.2:
version "3.0.7"
resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-3.0.7.tgz#1157622e2f5377bb6aef2114372728ba0c156989"
integrity sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==
dependencies:
eventsource-parser "^3.0.1"
evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02"
@@ -9890,6 +10000,13 @@ exponential-backoff@^3.1.1:
resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.3.tgz#51cf92c1c0493c766053f9d3abee4434c244d2f6"
integrity sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==
express-rate-limit@^8.2.1:
version "8.2.1"
resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-8.2.1.tgz#ec75fdfe280ecddd762b8da8784c61bae47d7f7f"
integrity sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==
dependencies:
ip-address "10.0.1"
express-session@^1.15.6:
version "1.19.0"
resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.19.0.tgz#682139f6eec6c69db9febbe2362e8a85dd84af2f"
@@ -9941,6 +10058,40 @@ express@^4.16.2, express@^4.21.2:
utils-merge "1.0.1"
vary "~1.1.2"
express@^5.2.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/express/-/express-5.2.1.tgz#8f21d15b6d327f92b4794ecf8cb08a72f956ac04"
integrity sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==
dependencies:
accepts "^2.0.0"
body-parser "^2.2.1"
content-disposition "^1.0.0"
content-type "^1.0.5"
cookie "^0.7.1"
cookie-signature "^1.2.1"
debug "^4.4.0"
depd "^2.0.0"
encodeurl "^2.0.0"
escape-html "^1.0.3"
etag "^1.8.1"
finalhandler "^2.1.0"
fresh "^2.0.0"
http-errors "^2.0.0"
merge-descriptors "^2.0.0"
mime-types "^3.0.0"
on-finished "^2.4.1"
once "^1.4.0"
parseurl "^1.3.3"
proxy-addr "^2.0.7"
qs "^6.14.0"
range-parser "^1.2.1"
router "^2.2.0"
send "^1.1.0"
serve-static "^2.2.0"
statuses "^2.0.1"
type-is "^2.0.1"
vary "^1.1.2"
exsolve@^1.0.7:
version "1.0.8"
resolved "https://registry.yarnpkg.com/exsolve/-/exsolve-1.0.8.tgz#7f5e34da61cd1116deda5136e62292c096f50613"
@@ -10219,6 +10370,18 @@ fill-range@^7.1.1:
dependencies:
to-regex-range "^5.0.1"
finalhandler@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-2.1.1.tgz#a2c517a6559852bcdb06d1f8bd7f51b68fad8099"
integrity sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==
dependencies:
debug "^4.4.0"
encodeurl "^2.0.0"
escape-html "^1.0.3"
on-finished "^2.4.1"
parseurl "^1.3.3"
statuses "^2.0.1"
finalhandler@~1.3.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.2.tgz#1ebc2228fc7673aac4a472c310cc05b77d852b88"
@@ -10442,6 +10605,11 @@ fragment-cache@^0.2.1:
dependencies:
map-cache "^0.2.2"
fresh@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4"
integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==
fresh@~0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
@@ -10815,6 +10983,15 @@ glob@^13.0.0:
minipass "^7.1.2"
path-scurry "^2.0.0"
glob@^13.0.3:
version "13.0.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-13.0.6.tgz#078666566a425147ccacfbd2e332deb66a2be71d"
integrity sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==
dependencies:
minimatch "^10.2.2"
minipass "^7.1.3"
path-scurry "^2.0.2"
glob@^7.1.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.2.0:
version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
@@ -11385,6 +11562,11 @@ homedir-polyfill@^1.0.1:
dependencies:
parse-passwd "^1.0.0"
hono@^4.11.4:
version "4.12.0"
resolved "https://registry.yarnpkg.com/hono/-/hono-4.12.0.tgz#18f96a2962274398efc57a1a79e22b3daad2ca1c"
integrity sha512-NekXntS5M94pUfiVZ8oXXK/kkri+5WpX2/Ik+LVsl+uvw+soj4roXIsPqO+XsWrAw20mOzaXOZf3Q7PfB9A/IA==
hookable@^5.5.3:
version "5.5.3"
resolved "https://registry.yarnpkg.com/hookable/-/hookable-5.5.3.tgz#6cfc358984a1ef991e2518cb9ed4a778bbd3215d"
@@ -11491,7 +11673,7 @@ http-errors@^1.6.3, http-errors@^1.8.1, http-errors@~1.8.0, http-errors@~1.8.1:
statuses ">= 1.5.0 < 2"
toidentifier "1.0.1"
http-errors@^2.0.0, http-errors@~2.0.0, http-errors@~2.0.1:
http-errors@^2.0.0, http-errors@^2.0.1, http-errors@~2.0.0, http-errors@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.1.tgz#36d2f65bc909c8790018dd36fb4d93da6caae06b"
integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==
@@ -11613,7 +11795,7 @@ iconv-lite@^0.6.2:
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
iconv-lite@^0.7.0:
iconv-lite@^0.7.0, iconv-lite@~0.7.0:
version "0.7.2"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.7.2.tgz#d0bdeac3f12b4835b7359c2ad89c422a4d1cc72e"
integrity sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==
@@ -11880,6 +12062,11 @@ invert-kv@^1.0.0:
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
integrity sha512-xgs2NH9AE66ucSq4cNG1nhSFghr5l6tdL15Pk+jl46bmmBapgoaY/AacXyaDznAqmGL99TiLSQgO/XazFSKYeQ==
ip-address@10.0.1:
version "10.0.1"
resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-10.0.1.tgz#a8180b783ce7788777d796286d61bce4276818ed"
integrity sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==
ip-address@^10.0.1:
version "10.1.0"
resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-10.1.0.tgz#d8dcffb34d0e02eb241427444a6e23f5b0595aa4"
@@ -12285,6 +12472,11 @@ is-promise@^2.0.0, is-promise@^2.2.2:
resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1"
integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==
is-promise@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3"
integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==
is-regex@^1.0.3, is-regex@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22"
@@ -12556,6 +12748,11 @@ jiti@^2.6.1:
resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.6.1.tgz#178ef2fc9a1a594248c20627cd820187a4d78d92"
integrity sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==
jose@^6.1.3:
version "6.1.3"
resolved "https://registry.yarnpkg.com/jose/-/jose-6.1.3.tgz#8453d7be88af7bb7d64a0481d6a35a0145ba3ea5"
integrity sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==
"jquery@1.9.1 - 3":
version "3.7.1"
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.7.1.tgz#083ef98927c9a6a74d05a6af02806566d16274de"
@@ -12665,6 +12862,11 @@ json-schema-traverse@^1.0.0:
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
json-schema-typed@^8.0.2:
version "8.0.2"
resolved "https://registry.yarnpkg.com/json-schema-typed/-/json-schema-typed-8.0.2.tgz#e98ee7b1899ff4a184534d1f167c288c66bbeff4"
integrity sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==
json-stable-stringify-without-jsonify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
@@ -13627,6 +13829,11 @@ media-typer@0.3.0:
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
media-typer@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561"
integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==
memoizee@0.4.X:
version "0.4.17"
resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.17.tgz#942a5f8acee281fa6fb9c620bddc57e3b7382949"
@@ -13676,6 +13883,11 @@ merge-descriptors@1.0.3:
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5"
integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==
merge-descriptors@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz#ea922f660635a2249ee565e0449f951e6b603808"
integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==
merge-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
@@ -13750,7 +13962,7 @@ mime-db@1.52.0:
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
"mime-db@>= 1.43.0 < 2", mime-db@^1.52.0:
"mime-db@>= 1.43.0 < 2", mime-db@^1.52.0, mime-db@^1.54.0:
version "1.54.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5"
integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==
@@ -13762,6 +13974,13 @@ mime-types@^2.1.12, mime-types@^2.1.18, mime-types@~2.1.24, mime-types@~2.1.34:
dependencies:
mime-db "1.52.0"
mime-types@^3.0.0, mime-types@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.2.tgz#39002d4182575d5af036ffa118100f2524b2e2ab"
integrity sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==
dependencies:
mime-db "^1.54.0"
mime@1.6.0, mime@^1.4.1:
version "1.6.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
@@ -13830,6 +14049,13 @@ minimatch@^10.1.2:
dependencies:
"@isaacs/brace-expansion" "^5.0.1"
minimatch@^10.2.2:
version "10.2.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.2.tgz#361603ee323cfb83496fea2ae17cc44ea4e1f99f"
integrity sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==
dependencies:
brace-expansion "^5.0.2"
minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
@@ -13920,6 +14146,11 @@ minipass@^5.0.0:
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
minipass@^7.1.3:
version "7.1.3"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.3.tgz#79389b4eb1bb2d003a9bba87d492f2bd37bdc65b"
integrity sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==
minizlib@^2.1.1, minizlib@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
@@ -14533,6 +14764,11 @@ negotiator@^0.6.3, negotiator@~0.6.4:
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7"
integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==
negotiator@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a"
integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==
neo-async@^2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
@@ -14873,7 +15109,7 @@ oauth@0.10.x:
resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.10.2.tgz#fd7139b0ce1a1037bd11fa4e236afc588132418c"
integrity sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q==
object-assign@4.X, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
object-assign@4.X, object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
@@ -15024,7 +15260,7 @@ object.values@^1.1.6, object.values@^1.2.1:
define-properties "^1.2.1"
es-object-atoms "^1.0.0"
on-finished@^2.3.0, on-finished@~2.4.1:
on-finished@^2.3.0, on-finished@^2.4.1, on-finished@~2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
@@ -15412,7 +15648,7 @@ parse5@^7.0.0, parse5@^7.1.2:
dependencies:
entities "^6.0.0"
parseurl@^1.3.2, parseurl@~1.3.0, parseurl@~1.3.3:
parseurl@^1.3.2, parseurl@^1.3.3, parseurl@~1.3.0, parseurl@~1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
@@ -15593,12 +15829,20 @@ path-scurry@^2.0.0:
lru-cache "^11.0.0"
minipass "^7.1.2"
path-scurry@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.2.tgz#6be0d0ee02a10d9e0de7a98bae65e182c9061f85"
integrity sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==
dependencies:
lru-cache "^11.0.0"
minipass "^7.1.2"
path-to-regexp@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.3.0.tgz#2b6a26a337737a8e1416f9272ed0766b1c0389f4"
integrity sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==
path-to-regexp@^8.1.0:
path-to-regexp@^8.0.0, path-to-regexp@^8.1.0:
version "8.3.0"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.3.0.tgz#aa818a6981f99321003a08987d3cec9c3474cd1f"
integrity sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==
@@ -15737,6 +15981,11 @@ pirates@^4.0.5, pirates@^4.0.6:
resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22"
integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==
pkce-challenge@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/pkce-challenge/-/pkce-challenge-5.0.1.tgz#3b4446865b17b1745e9ace2016a31f48ddf6230d"
integrity sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==
pkg-dir@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3"
@@ -16458,6 +16707,13 @@ qs@^6.12.3, qs@~6.14.0:
dependencies:
side-channel "^1.1.0"
qs@^6.14.0, qs@^6.14.1:
version "6.15.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.15.0.tgz#db8fd5d1b1d2d6b5b33adaf87429805f1909e7b3"
integrity sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==
dependencies:
side-channel "^1.1.0"
qs@~2.2.3:
version "2.2.5"
resolved "https://registry.yarnpkg.com/qs/-/qs-2.2.5.tgz#1088abaf9dcc0ae5ae45b709e6c6b5888b23923c"
@@ -16525,11 +16781,21 @@ randomfill@^1.0.4:
randombytes "^2.0.5"
safe-buffer "^5.1.0"
range-parser@~1.2.1:
range-parser@^1.2.1, range-parser@~1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
raw-body@^3.0.0, raw-body@^3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-3.0.2.tgz#3e3ada5ae5568f9095d84376fd3a49b8fb000a51"
integrity sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==
dependencies:
bytes "~3.1.2"
http-errors "~2.0.1"
iconv-lite "~0.7.0"
unpipe "~1.0.0"
raw-body@~2.1.5:
version "2.1.7"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.1.7.tgz#adfeace2e4fb3098058014d08c072dcc59758774"
@@ -17380,6 +17646,14 @@ rimraf@^5.0.1, rimraf@^5.0.5:
dependencies:
glob "^10.3.7"
rimraf@^6.0.0:
version "6.1.3"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-6.1.3.tgz#afbee236b3bd2be331d4e7ce4493bac1718981af"
integrity sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==
dependencies:
glob "^13.0.3"
package-json-from-dist "^1.0.1"
rimraf@^6.0.1:
version "6.1.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-6.1.2.tgz#9a0f3cea2ab853e81291127422116ecf2a86ae89"
@@ -17430,6 +17704,17 @@ rollup@^4.34.9:
"@rollup/rollup-win32-x64-msvc" "4.57.1"
fsevents "~2.3.2"
router@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/router/-/router-2.2.0.tgz#019be620b711c87641167cc79b99090f00b146ef"
integrity sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==
dependencies:
debug "^4.4.0"
depd "^2.0.0"
is-promise "^4.0.0"
parseurl "^1.3.3"
path-to-regexp "^8.0.0"
run-async@^2.4.0:
version "2.4.1"
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455"
@@ -17597,6 +17882,23 @@ semver@^7.0.0, semver@^7.1.1, semver@^7.3.2, semver@^7.3.5, semver@^7.3.6, semve
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a"
integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==
send@^1.1.0, send@^1.2.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/send/-/send-1.2.1.tgz#9eab743b874f3550f40a26867bf286ad60d3f3ed"
integrity sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==
dependencies:
debug "^4.4.3"
encodeurl "^2.0.0"
escape-html "^1.0.3"
etag "^1.8.1"
fresh "^2.0.0"
http-errors "^2.0.1"
mime-types "^3.0.2"
ms "^2.1.3"
on-finished "^2.4.1"
range-parser "^1.2.1"
statuses "^2.0.2"
send@~0.19.0, send@~0.19.1:
version "0.19.2"
resolved "https://registry.yarnpkg.com/send/-/send-0.19.2.tgz#59bc0da1b4ea7ad42736fd642b1c4294e114ff29"
@@ -17626,6 +17928,16 @@ serve-static@^1.13.1, serve-static@~1.16.2:
parseurl "~1.3.3"
send "~0.19.1"
serve-static@^2.2.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-2.2.1.tgz#7f186a4a4e5f5b663ad7a4294ff1bf37cf0e98a9"
integrity sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==
dependencies:
encodeurl "^2.0.0"
escape-html "^1.0.3"
parseurl "^1.3.3"
send "^1.2.0"
set-blocking@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
@@ -18145,7 +18457,7 @@ statuses@1, "statuses@>= 1.5.0 < 2", statuses@^1.5.0:
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==
statuses@~2.0.1, statuses@~2.0.2:
statuses@^2.0.1, statuses@^2.0.2, statuses@~2.0.1, statuses@~2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382"
integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==
@@ -19161,6 +19473,15 @@ type-is@^1.6.16, type-is@~1.6.10, type-is@~1.6.18:
media-typer "0.3.0"
mime-types "~2.1.24"
type-is@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-2.0.1.tgz#64f6cf03f92fce4015c2b224793f6bdd4b068c97"
integrity sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==
dependencies:
content-type "^1.0.5"
media-typer "^1.1.0"
mime-types "^3.0.0"
type@^2.7.2:
version "2.7.3"
resolved "https://registry.yarnpkg.com/type/-/type-2.7.3.tgz#436981652129285cc3ba94f392886c2637ea0486"
@@ -19712,7 +20033,7 @@ varint@^5.0.0, varint@~5.0.0:
resolved "https://registry.yarnpkg.com/varint/-/varint-5.0.2.tgz#5b47f8a947eb668b848e034dcfa87d0ff8a7f7a4"
integrity sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==
vary@^1.1.2, vary@~1.1.2:
vary@^1, vary@^1.1.2, vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
@@ -20519,6 +20840,16 @@ yoga-wasm-web@~0.3.3:
resolved "https://registry.yarnpkg.com/yoga-wasm-web/-/yoga-wasm-web-0.3.3.tgz#eb8e9fcb18e5e651994732f19a220cb885d932ba"
integrity sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==
zod-to-json-schema@^3.25.1:
version "3.25.1"
resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz#7f24962101a439ddade2bf1aeab3c3bfec7d84ba"
integrity sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==
"zod@^3.25 || ^4.0", zod@^4.3.6:
version "4.3.6"
resolved "https://registry.yarnpkg.com/zod/-/zod-4.3.6.tgz#89c56e0aa7d2b05107d894412227087885ab112a"
integrity sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==
zrender@5.6.1:
version "5.6.1"
resolved "https://registry.yarnpkg.com/zrender/-/zrender-5.6.1.tgz#e08d57ecf4acac708c4fcb7481eb201df7f10a6b"