From ed4be200b4c2a044c3cabb2c366b1bd08e26f5b9 Mon Sep 17 00:00:00 2001 From: Bleyle Date: Mon, 1 Jun 2026 07:10:32 +0300 Subject: [PATCH 1/3] Implement MCP HTTP transport for TypeScript and Python clients (v0.1.0). Port the shared KeeperHub MCP kernel from the plugins monorepo: session bootstrap, tools/call, 401/404 re-init, API key helpers, and unit tests. Bump both packages to 0.1.0 and update docs for first npm/PyPI release. Co-authored-by: Cursor --- README.md | 2 +- packages/mcp-client/LICENSE | 19 + packages/mcp-client/README.md | 38 + packages/mcp-client/package.json | 14 +- .../mcp-client/src/__tests__/client.test.ts | 217 +++ packages/mcp-client/src/client.ts | 229 +++ packages/mcp-client/src/index.ts | 33 +- packages/mcp-client/src/keys.ts | 110 ++ packages/mcp-client/vitest.config.ts | 8 + pnpm-lock.yaml | 1586 +++++++++++++++++ python/README.md | 23 +- python/keeperhub_mcp_client/__init__.py | 52 +- python/keeperhub_mcp_client/client.py | 220 ++- python/keeperhub_mcp_client/keys.py | 97 + python/pyproject.toml | 13 +- python/tests/test_client.py | 100 ++ 16 files changed, 2731 insertions(+), 30 deletions(-) create mode 100644 packages/mcp-client/LICENSE create mode 100644 packages/mcp-client/README.md create mode 100644 packages/mcp-client/src/__tests__/client.test.ts create mode 100644 packages/mcp-client/src/client.ts create mode 100644 packages/mcp-client/src/keys.ts create mode 100644 packages/mcp-client/vitest.config.ts create mode 100644 pnpm-lock.yaml create mode 100644 python/keeperhub_mcp_client/keys.py create mode 100644 python/tests/test_client.py diff --git a/README.md b/README.md index a1eef4e..39fe143 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Both clients implement the same kernel: ## Status -Early development. Neither package is published yet. The surface is still stabilizing. +**v0.1.0** — MCP HTTP transport implemented (session bootstrap, `tools/call`, 401/404 re-init, API key helpers). Ready for first publish to npm and PyPI. See [`.github/workflows`](.github/workflows) for release tags (`npm-v*`, `py-v*`). ## Where the plugins live diff --git a/packages/mcp-client/LICENSE b/packages/mcp-client/LICENSE new file mode 100644 index 0000000..a5a111c --- /dev/null +++ b/packages/mcp-client/LICENSE @@ -0,0 +1,19 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + Copyright 2026 KeeperHub + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + The full Apache License 2.0 text is available at the URL above. diff --git a/packages/mcp-client/README.md b/packages/mcp-client/README.md new file mode 100644 index 0000000..f29649f --- /dev/null +++ b/packages/mcp-client/README.md @@ -0,0 +1,38 @@ +# @keeperhub/mcp-client + +Official TypeScript client for the KeeperHub MCP HTTP endpoint (`https://app.keeperhub.com/mcp`). + +## Features + +- Lazy MCP session (`initialize` + `mcp-session-id`) +- `tools/call` with JSON parsing of `content[0].text` +- Automatic re-init on **401** and **404** session expiry +- API key resolution (`KH_API_KEY`, `KEEPERHUB_API_KEY`) +- Key types: `kh_` (organization — MCP/REST) vs `wfb_` (webhooks only) + +## Usage + +```ts +import { getClient, resolveApiKey } from "@keeperhub/mcp-client"; + +const apiKey = resolveApiKey({ env: process.env }); +if (!apiKey) throw new Error("KH_API_KEY not set"); + +const client = getClient(apiKey, { + clientInfo: { name: "my-plugin", version: "1.0.0" }, +}); + +const workflows = await client.callTool("list_workflows", {}); +``` + +## Develop + +```bash +pnpm install +pnpm --filter @keeperhub/mcp-client build +pnpm --filter @keeperhub/mcp-client test +``` + +## License + +Apache-2.0 diff --git a/packages/mcp-client/package.json b/packages/mcp-client/package.json index 57d1a19..f4fa901 100644 --- a/packages/mcp-client/package.json +++ b/packages/mcp-client/package.json @@ -1,6 +1,6 @@ { "name": "@keeperhub/mcp-client", - "version": "0.0.0", + "version": "0.1.0", "description": "Shared MCP client foundation for KeeperHub agent-framework adapters.", "license": "Apache-2.0", "type": "module", @@ -17,11 +17,7 @@ "require": "./dist/index.cjs" } }, - "files": [ - "dist", - "README.md", - "LICENSE" - ], + "files": ["dist", "README.md", "LICENSE"], "publishConfig": { "access": "public" }, @@ -38,10 +34,8 @@ "build": "tsup", "test": "vitest run", "test:watch": "vitest", - "type-check": "tsc --noEmit" - }, - "dependencies": { - "@modelcontextprotocol/sdk": "1.29.0" + "type-check": "tsc --noEmit", + "prepublishOnly": "npm run build" }, "devDependencies": { "@types/node": "24.1.0", diff --git a/packages/mcp-client/src/__tests__/client.test.ts b/packages/mcp-client/src/__tests__/client.test.ts new file mode 100644 index 0000000..6b0c081 --- /dev/null +++ b/packages/mcp-client/src/__tests__/client.test.ts @@ -0,0 +1,217 @@ +import { afterEach, beforeEach, describe, expect, it } from "vitest"; + +import { + __resetClientForTests, + getClient, + KeeperHubMcpClient, +} from "../client"; +import { + classifyApiKey, + getApiKeyKindWarning, + maskApiKey, + resolveApiKey, + validateApiKeyForMcp, +} from "../keys"; + +type FetchInit = RequestInit & { headers?: Record }; + +function jsonResponse( + body: unknown, + init: { status?: number; sessionId?: string } = {}, +): Response { + const headers = new Headers({ "content-type": "application/json" }); + if (init.sessionId) headers.set("mcp-session-id", init.sessionId); + return new Response(JSON.stringify(body), { + status: init.status ?? 200, + headers, + }); +} + +function textResponse( + body: string, + init: { status?: number; sessionId?: string } = {}, +): Response { + const headers = new Headers({ "content-type": "text/plain" }); + if (init.sessionId) headers.set("mcp-session-id", init.sessionId); + return new Response(body, { + status: init.status ?? 200, + headers, + }); +} + +function captureFetch(responses: Array<() => Response>) { + const calls: Array<{ url: string; init: FetchInit; body: unknown }> = []; + let i = 0; + const fn = (async (url: RequestInfo | URL, init?: RequestInit) => { + const next = responses[i++]; + if (!next) { + throw new Error(`Unexpected fetch call #${i} to ${String(url)}`); + } + let parsedBody: unknown; + if (init?.body && typeof init.body === "string") { + try { + parsedBody = JSON.parse(init.body); + } catch { + parsedBody = init.body; + } + } + calls.push({ url: String(url), init: (init ?? {}) as FetchInit, body: parsedBody }); + return next(); + }) as typeof fetch; + return { fn, calls }; +} + +describe("keys", () => { + it("classifies kh_ and wfb_ prefixes", () => { + expect(classifyApiKey("kh_abc")).toBe("org"); + expect(classifyApiKey("wfb_abc")).toBe("webhook"); + expect(classifyApiKey("other")).toBe("unknown"); + }); + + it("rejects wfb_ keys for MCP", () => { + expect(getApiKeyKindWarning("wfb_test")).toContain("wfb_"); + expect(() => validateApiKeyForMcp("wfb_test")).toThrow(/not interchangeable/); + }); + + it("resolves pluginConfig before env", () => { + expect( + resolveApiKey({ + pluginConfig: { apiKey: "kh_from_config" }, + env: { KH_API_KEY: "kh_from_env" }, + }), + ).toBe("kh_from_config"); + }); + + it("masks api keys for display", () => { + expect(maskApiKey("kh_supersecret_value_123")).toBe("kh_s…_123"); + }); +}); + +describe("KeeperHubMcpClient", () => { + const silentLogger = { + warn: () => undefined, + info: () => undefined, + error: () => undefined, + debug: () => undefined, + }; + + beforeEach(() => __resetClientForTests()); + afterEach(() => __resetClientForTests()); + + it("opens a session via initialize and uses the session id on tools/call", async () => { + const { fn, calls } = captureFetch([ + () => + jsonResponse({ result: { protocolVersion: "2024-11-05" } }, { sessionId: "sess-1" }), + () => + jsonResponse({ + result: { + content: [{ type: "text", text: JSON.stringify([{ id: "wf-1", name: "Demo" }]) }], + }, + }), + ]); + + const client = new KeeperHubMcpClient({ + apiKey: "kh_test", + logger: silentLogger, + fetchFn: fn, + }); + const result = await client.callTool("list_workflows", {}); + + expect(result).toEqual([{ id: "wf-1", name: "Demo" }]); + expect(calls).toHaveLength(2); + expect((calls[0]!.body as { method: string }).method).toBe("initialize"); + expect((calls[1]!.body as { method: string }).method).toBe("tools/call"); + }); + + it("re-initializes on 401", async () => { + const { fn, calls } = captureFetch([ + () => jsonResponse({ result: {} }, { sessionId: "sess-old" }), + () => jsonResponse({ error: "unauthorized" }, { status: 401 }), + () => jsonResponse({ result: {} }, { sessionId: "sess-new" }), + () => + jsonResponse({ + result: { content: [{ type: "text", text: '"ok"' }] }, + }), + ]); + + const client = new KeeperHubMcpClient({ + apiKey: "kh_test", + logger: silentLogger, + fetchFn: fn, + }); + expect(await client.callTool("list_workflows", {})).toBe("ok"); + expect(calls).toHaveLength(4); + }); + + it("re-initializes on 404 when the body mentions session", async () => { + const { fn } = captureFetch([ + () => jsonResponse({ result: {} }, { sessionId: "sess-old" }), + () => textResponse("session expired", { status: 404 }), + () => jsonResponse({ result: {} }, { sessionId: "sess-new" }), + () => + jsonResponse({ + result: { content: [{ type: "text", text: '{"status":"queued"}' }] }, + }), + ]); + + const client = new KeeperHubMcpClient({ + apiKey: "kh_test", + logger: silentLogger, + fetchFn: fn, + }); + expect(await client.callTool("execute_workflow", { workflowId: "wf-1" })).toEqual({ + status: "queued", + }); + }); + + it("does not re-initialize on unrelated 404", async () => { + const { fn } = captureFetch([ + () => jsonResponse({ result: {} }, { sessionId: "sess-old" }), + () => textResponse("workflow not found", { status: 404 }), + ]); + + const client = new KeeperHubMcpClient({ + apiKey: "kh_test", + logger: silentLogger, + fetchFn: fn, + }); + await expect( + client.callTool("get_workflow", { workflowId: "missing" }), + ).rejects.toThrow(/404/); + }); + + it("rejects empty api key", () => { + expect( + () => + new KeeperHubMcpClient({ + apiKey: "", + logger: silentLogger, + }), + ).toThrow(/non-empty apiKey/); + }); + + it("rejects wfb_ keys at construction", () => { + expect( + () => + new KeeperHubMcpClient({ + apiKey: "wfb_webhook_only", + logger: silentLogger, + }), + ).toThrow(/wfb_/); + }); +}); + +describe("getClient singleton", () => { + beforeEach(() => __resetClientForTests()); + afterEach(() => __resetClientForTests()); + + it("returns the same instance for the same api key", () => { + expect(getClient("kh_one")).toBe(getClient("kh_one")); + }); + + it("replaces the instance when the api key changes", () => { + const a = getClient("kh_one"); + const b = getClient("kh_two"); + expect(a).not.toBe(b); + }); +}); diff --git a/packages/mcp-client/src/client.ts b/packages/mcp-client/src/client.ts new file mode 100644 index 0000000..d146b79 --- /dev/null +++ b/packages/mcp-client/src/client.ts @@ -0,0 +1,229 @@ +/** + * KeeperHub MCP HTTP transport client. + * + * Maintains a single MCP session per client instance. Lazily opens the session + * on the first tool call. Re-initializes on 401 or 404 with a session-related body. + */ + +import { classifyApiKey, validateApiKeyForMcp, type ApiKeyKind } from "./keys"; + +export const DEFAULT_MCP_URL = "https://app.keeperhub.com/mcp"; +export const MCP_PROTOCOL_VERSION = "2024-11-05"; + +export interface KeeperHubOrgContext { + orgId: string | null; + workflowCount: number; +} + +export interface CallToolResult { + content?: Array<{ type: string; text: string }>; + isError?: boolean; +} + +export interface ClientLogger { + debug?(...args: unknown[]): void; + info?(...args: unknown[]): void; + warn?(...args: unknown[]): void; + error?(...args: unknown[]): void; +} + +export interface KeeperHubMcpClientOptions { + apiKey: string; + baseUrl?: string; + clientInfo?: { name: string; version: string }; + logger?: ClientLogger; + /** Inject for tests; defaults to global `fetch`. */ + fetchFn?: typeof fetch; +} + +export class KeeperHubMcpClient { + readonly apiKey: string; + readonly apiKeyKind: ApiKeyKind; + + private readonly baseUrl: string; + private readonly clientInfo: { name: string; version: string }; + private readonly logger: ClientLogger; + private readonly fetchFn: typeof fetch; + + private sessionId: string | null = null; + private requestId = 0; + orgContext: KeeperHubOrgContext = { orgId: null, workflowCount: 0 }; + + constructor(options: KeeperHubMcpClientOptions) { + const apiKey = options.apiKey?.trim(); + if (!apiKey) { + throw new Error("KeeperHubMcpClient requires a non-empty apiKey"); + } + validateApiKeyForMcp(apiKey); + this.apiKey = apiKey; + this.apiKeyKind = classifyApiKey(apiKey); + this.baseUrl = options.baseUrl ?? DEFAULT_MCP_URL; + this.clientInfo = options.clientInfo ?? { + name: "@keeperhub/mcp-client", + version: "0.1.0", + }; + this.logger = options.logger ?? console; + this.fetchFn = options.fetchFn ?? fetch; + } + + async callTool(name: string, args: Record = {}): Promise { + await this.ensureSession(); + const result = (await this.postMcp({ + jsonrpc: "2.0", + id: ++this.requestId, + method: "tools/call", + params: { name, arguments: args }, + })) as CallToolResult | undefined; + + if (result?.isError) { + const msg = result.content?.[0]?.text ?? "Unknown KeeperHub error"; + throw new Error(`KeeperHub tool error (${name}): ${msg}`); + } + + const text = result?.content?.[0]?.text; + if (typeof text === "string") { + try { + return JSON.parse(text) as unknown; + } catch { + return text; + } + } + + return result; + } + + async refreshOrgContext(): Promise { + try { + const workflows = await this.callTool("list_workflows", {}); + const list = Array.isArray(workflows) + ? (workflows as Array>) + : []; + const orgId = + list.length > 0 + ? ((list[0]?.organizationId as string | undefined) ?? null) + : null; + this.orgContext = { orgId, workflowCount: list.length }; + } catch { + // Non-fatal + } + return this.orgContext; + } + + resetSession(): void { + this.sessionId = null; + } + + private async ensureSession(): Promise { + if (this.sessionId) return; + + const body = { + jsonrpc: "2.0", + id: ++this.requestId, + method: "initialize", + params: { + protocolVersion: MCP_PROTOCOL_VERSION, + capabilities: {}, + clientInfo: this.clientInfo, + }, + }; + + const res = await this.fetchFn(this.baseUrl, { + method: "POST", + headers: this.headers(), + body: JSON.stringify(body), + }); + + if (!res.ok) { + const text = await res.text().catch(() => ""); + throw new Error(`KeeperHub initialize failed (${res.status}): ${text}`); + } + + const sid = res.headers.get("mcp-session-id"); + if (!sid) throw new Error("KeeperHub did not return mcp-session-id"); + this.sessionId = sid; + this.logger.info?.("[KeeperHub] MCP session established"); + } + + private async postMcp(body: object): Promise { + if (!this.sessionId) throw new Error("No active MCP session"); + + const res = await this.fetchFn(this.baseUrl, { + method: "POST", + headers: { ...this.headers(), "mcp-session-id": this.sessionId }, + body: JSON.stringify(body), + }); + + if (res.status === 401) { + this.logger.warn?.("[KeeperHub] Session unauthorized, re-initializing"); + this.sessionId = null; + await this.ensureSession(); + return this.postMcp(body); + } + + if (res.status === 404) { + const text = await res.text().catch(() => ""); + if (text.toLowerCase().includes("session")) { + this.logger.warn?.("[KeeperHub] Session expired, re-initializing"); + this.sessionId = null; + await this.ensureSession(); + return this.postMcp(body); + } + throw new Error(`KeeperHub MCP error (404): ${text}`); + } + + if (!res.ok) { + const text = await res.text().catch(() => ""); + throw new Error(`KeeperHub MCP error (${res.status}): ${text}`); + } + + const json = (await res.json()) as { + result?: unknown; + error?: { message: string }; + }; + if (json.error) throw new Error(`KeeperHub RPC error: ${json.error.message}`); + return json.result; + } + + private headers(): Record { + return { + "Content-Type": "application/json", + Accept: "application/json, text/event-stream", + Authorization: `Bearer ${this.apiKey}`, + }; + } +} + +/** @deprecated Alias for {@link KeeperHubMcpClient}. */ +export const KeeperHubClient = KeeperHubMcpClient; + +export interface GetClientOptions { + logger?: ClientLogger; + clientInfo?: { name: string; version: string }; + baseUrl?: string; + fetchFn?: typeof fetch; +} + +let cachedClient: KeeperHubMcpClient | null = null; +let cachedKey: string | null = null; + +export function getClient( + apiKey: string, + options: GetClientOptions = {}, +): KeeperHubMcpClient { + if (!cachedClient || cachedKey !== apiKey) { + cachedClient = new KeeperHubMcpClient({ + apiKey, + logger: options.logger, + clientInfo: options.clientInfo, + baseUrl: options.baseUrl, + fetchFn: options.fetchFn, + }); + cachedKey = apiKey; + } + return cachedClient; +} + +export function __resetClientForTests(): void { + cachedClient = null; + cachedKey = null; +} diff --git a/packages/mcp-client/src/index.ts b/packages/mcp-client/src/index.ts index 31aad94..03df615 100644 --- a/packages/mcp-client/src/index.ts +++ b/packages/mcp-client/src/index.ts @@ -1,4 +1,31 @@ -// @keeperhub/mcp-client - shared MCP client foundation for KeeperHub framework adapters. -// Scaffold only. The session bootstrap, key disambiguation, and poll-to-terminal kernel land here. +export { + DEFAULT_MCP_URL, + MCP_PROTOCOL_VERSION, + KeeperHubMcpClient, + KeeperHubClient, + getClient, + __resetClientForTests, + type CallToolResult, + type ClientLogger, + type GetClientOptions, + type KeeperHubMcpClientOptions, + type KeeperHubOrgContext, +} from "./client"; -export const VERSION = "0.0.0"; +export { + ENV_VAR_NAMES, + ORG_KEY_HINT, + SUPPORTED_ENV_VARS, + WFB_KEY_NOT_FOR_MCP_MESSAGE, + classifyApiKey, + getApiKeyKindWarning, + isLikelyValidApiKey, + isLikelyValidOrgApiKey, + isOrgApiKey, + maskApiKey, + resolveApiKey, + validateApiKeyForMcp, + type ApiKeyKind, + type ApiKeySources, + type SupportedEnvVar, +} from "./keys"; diff --git a/packages/mcp-client/src/keys.ts b/packages/mcp-client/src/keys.ts new file mode 100644 index 0000000..dfe1231 --- /dev/null +++ b/packages/mcp-client/src/keys.ts @@ -0,0 +1,110 @@ +/** + * KeeperHub API key resolution and classification. + * + * @see https://docs.keeperhub.com — API Keys + * + * - `kh_` — organization keys (`/api/keys`). REST API, MCP server, agent plugins. + * - `wfb_` — user keys (`/api/api-keys`). Webhook triggers only; not valid for MCP. + */ + +export const ENV_VAR_NAMES = ["KH_API_KEY", "KEEPERHUB_API_KEY"] as const; + +export type SupportedEnvVar = (typeof ENV_VAR_NAMES)[number]; + +export const SUPPORTED_ENV_VARS: ReadonlyArray = ENV_VAR_NAMES; + +/** Known KeeperHub key prefixes (not interchangeable). */ +export type ApiKeyKind = "org" | "webhook" | "unknown"; + +export interface ApiKeySources { + /** e.g. OpenClaw `api.pluginConfig` */ + pluginConfig?: Record; + /** e.g. Eliza `runtime.getSetting(...)` values in precedence order */ + settings?: Array; + /** Host process environment */ + env?: Record; +} + +/** Shown when a `wfb_` key is used against MCP / REST. */ +export const WFB_KEY_NOT_FOR_MCP_MESSAGE = + "This key starts with wfb_ (user webhook key from /api/api-keys). MCP and the REST API require an organization key (kh_) from Settings → API Keys → Organisation (/api/keys). The two key types are not interchangeable."; + +export const ORG_KEY_HINT = + "Use an organization API key (prefix kh_) from Settings → API Keys → Organisation."; + +function firstString(...values: Array): string | null { + for (const value of values) { + if (typeof value === "string") { + const trimmed = value.trim(); + if (trimmed.length > 0) return trimmed; + } + } + return null; +} + +/** + * Resolve the active KeeperHub API key. + * + * Precedence: + * 1. `pluginConfig.apiKey` + * 2. each entry in `settings` (in order) + * 3. `KH_API_KEY`, `KEEPERHUB_API_KEY` from `env` + */ +export function resolveApiKey(sources: ApiKeySources = {}): string | null { + const fromConfig = firstString(sources.pluginConfig?.apiKey); + if (fromConfig) return fromConfig; + + if (sources.settings) { + for (const value of sources.settings) { + const resolved = firstString(value); + if (resolved) return resolved; + } + } + + const env = sources.env ?? (typeof process !== "undefined" ? process.env : {}); + for (const name of ENV_VAR_NAMES) { + const value = firstString(env[name]); + if (value) return value; + } + + return null; +} + +export function classifyApiKey(apiKey: string): ApiKeyKind { + if (apiKey.startsWith("kh_")) return "org"; + if (apiKey.startsWith("wfb_")) return "webhook"; + return "unknown"; +} + +export function isOrgApiKey(apiKey: string): boolean { + return classifyApiKey(apiKey) === "org"; +} + +/** @deprecated Use {@link isOrgApiKey}. */ +export function isLikelyValidOrgApiKey(apiKey: string): boolean { + return isOrgApiKey(apiKey); +} + +/** @deprecated Use {@link isOrgApiKey}. */ +export function isLikelyValidApiKey(apiKey: string): boolean { + return isOrgApiKey(apiKey); +} + +export function getApiKeyKindWarning(apiKey: string): string | null { + const kind = classifyApiKey(apiKey); + if (kind === "org") return null; + if (kind === "webhook") return WFB_KEY_NOT_FOR_MCP_MESSAGE; + return `Key does not start with kh_. ${ORG_KEY_HINT}`; +} + +export function validateApiKeyForMcp(apiKey: string): void { + const warning = getApiKeyKindWarning(apiKey); + if (warning) { + throw new Error(`KeeperHub API key: ${warning}`); + } +} + +export function maskApiKey(apiKey: string): string { + if (apiKey.length <= 8) return "••••"; + return `${apiKey.slice(0, 4)}…${apiKey.slice(-4)}`; +} diff --git a/packages/mcp-client/vitest.config.ts b/packages/mcp-client/vitest.config.ts new file mode 100644 index 0000000..cfbd4c3 --- /dev/null +++ b/packages/mcp-client/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + include: ["src/**/*.test.ts"], + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..8b7e938 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1586 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: {} + + packages/mcp-client: + devDependencies: + '@types/node': + specifier: 24.1.0 + version: 24.1.0 + tsup: + specifier: 8.5.1 + version: 8.5.1(postcss@8.5.15)(typescript@5.9.2) + typescript: + specifier: 5.9.2 + version: 5.9.2 + vitest: + specifier: 4.1.2 + version: 4.1.2(@types/node@24.1.0)(vite@8.0.14(@types/node@24.1.0)(esbuild@0.27.7)) + +packages: + + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@napi-rs/wasm-runtime@1.1.4': + resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@oxc-project/types@0.132.0': + resolution: {integrity: sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==} + + '@rolldown/binding-android-arm64@1.0.2': + resolution: {integrity: sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.2': + resolution: {integrity: sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.2': + resolution: {integrity: sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.2': + resolution: {integrity: sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.2': + resolution: {integrity: sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.2': + resolution: {integrity: sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-arm64-musl@1.0.2': + resolution: {integrity: sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-ppc64-gnu@1.0.2': + resolution: {integrity: sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@rolldown/binding-linux-s390x-gnu@1.0.2': + resolution: {integrity: sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + + '@rolldown/binding-linux-x64-gnu@1.0.2': + resolution: {integrity: sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-linux-x64-musl@1.0.2': + resolution: {integrity: sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-openharmony-arm64@1.0.2': + resolution: {integrity: sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.2': + resolution: {integrity: sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.2': + resolution: {integrity: sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.2': + resolution: {integrity: sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@rollup/rollup-android-arm-eabi@4.60.4': + resolution: {integrity: sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.60.4': + resolution: {integrity: sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.60.4': + resolution: {integrity: sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.60.4': + resolution: {integrity: sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.60.4': + resolution: {integrity: sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.60.4': + resolution: {integrity: sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.60.4': + resolution: {integrity: sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.60.4': + resolution: {integrity: sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.60.4': + resolution: {integrity: sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.60.4': + resolution: {integrity: sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.60.4': + resolution: {integrity: sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.60.4': + resolution: {integrity: sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.60.4': + resolution: {integrity: sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.60.4': + resolution: {integrity: sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.60.4': + resolution: {integrity: sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.60.4': + resolution: {integrity: sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.60.4': + resolution: {integrity: sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.60.4': + resolution: {integrity: sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.60.4': + resolution: {integrity: sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.60.4': + resolution: {integrity: sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.60.4': + resolution: {integrity: sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.60.4': + resolution: {integrity: sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.60.4': + resolution: {integrity: sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.60.4': + resolution: {integrity: sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.60.4': + resolution: {integrity: sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==} + cpu: [x64] + os: [win32] + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@tybys/wasm-util@0.10.2': + resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/node@24.1.0': + resolution: {integrity: sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==} + + '@vitest/expect@4.1.2': + resolution: {integrity: sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ==} + + '@vitest/mocker@4.1.2': + resolution: {integrity: sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.2': + resolution: {integrity: sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA==} + + '@vitest/runner@4.1.2': + resolution: {integrity: sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ==} + + '@vitest/snapshot@4.1.2': + resolution: {integrity: sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A==} + + '@vitest/spy@4.1.2': + resolution: {integrity: sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA==} + + '@vitest/utils@4.1.2': + resolution: {integrity: sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==} + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + bundle-require@5.1.0: + resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.18' + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + es-module-lexer@2.1.0: + resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fix-dts-default-cjs-exports@1.0.1: + resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + mlly@1.8.2: + resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + engines: {node: ^10 || ^12 || >=14} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + rolldown@1.0.2: + resolution: {integrity: sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + rollup@4.60.4: + resolution: {integrity: sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@4.1.0: + resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyexec@1.2.4: + resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsup@8.5.1: + resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + + typescript@5.9.2: + resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.4: + resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} + + undici-types@7.8.0: + resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} + + vite@8.0.14: + resolution: {integrity: sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.18 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.1.2: + resolution: {integrity: sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.2 + '@vitest/browser-preview': 4.1.2 + '@vitest/browser-webdriverio': 4.1.2 + '@vitest/ui': 4.1.2 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + +snapshots: + + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.2 + optional: true + + '@oxc-project/types@0.132.0': {} + + '@rolldown/binding-android-arm64@1.0.2': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.2': + optional: true + + '@rolldown/binding-darwin-x64@1.0.2': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.2': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.2': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.2': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.2': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.2': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.2': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.2': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.2': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.2': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.2': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.2': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.2': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@rollup/rollup-android-arm-eabi@4.60.4': + optional: true + + '@rollup/rollup-android-arm64@4.60.4': + optional: true + + '@rollup/rollup-darwin-arm64@4.60.4': + optional: true + + '@rollup/rollup-darwin-x64@4.60.4': + optional: true + + '@rollup/rollup-freebsd-arm64@4.60.4': + optional: true + + '@rollup/rollup-freebsd-x64@4.60.4': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.60.4': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.60.4': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.60.4': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.60.4': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.60.4': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.60.4': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-x64-musl@4.60.4': + optional: true + + '@rollup/rollup-openbsd-x64@4.60.4': + optional: true + + '@rollup/rollup-openharmony-arm64@4.60.4': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.60.4': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.60.4': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.60.4': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.60.4': + optional: true + + '@standard-schema/spec@1.1.0': {} + + '@tybys/wasm-util@0.10.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.8': {} + + '@types/estree@1.0.9': {} + + '@types/node@24.1.0': + dependencies: + undici-types: 7.8.0 + + '@vitest/expect@4.1.2': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.2 + '@vitest/utils': 4.1.2 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.2(vite@8.0.14(@types/node@24.1.0)(esbuild@0.27.7))': + dependencies: + '@vitest/spy': 4.1.2 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.0.14(@types/node@24.1.0)(esbuild@0.27.7) + + '@vitest/pretty-format@4.1.2': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.2': + dependencies: + '@vitest/utils': 4.1.2 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.2': + dependencies: + '@vitest/pretty-format': 4.1.2 + '@vitest/utils': 4.1.2 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.2': {} + + '@vitest/utils@4.1.2': + dependencies: + '@vitest/pretty-format': 4.1.2 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + + acorn@8.16.0: {} + + any-promise@1.3.0: {} + + assertion-error@2.0.1: {} + + bundle-require@5.1.0(esbuild@0.27.7): + dependencies: + esbuild: 0.27.7 + load-tsconfig: 0.2.5 + + cac@6.7.14: {} + + chai@6.2.2: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + commander@4.1.1: {} + + confbox@0.1.8: {} + + consola@3.4.2: {} + + convert-source-map@2.0.0: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + detect-libc@2.1.2: {} + + es-module-lexer@2.1.0: {} + + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + expect-type@1.3.0: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + fix-dts-default-cjs-exports@1.0.1: + dependencies: + magic-string: 0.30.21 + mlly: 1.8.2 + rollup: 4.60.4 + + fsevents@2.3.3: + optional: true + + joycon@3.1.1: {} + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + load-tsconfig@0.2.5: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + mlly@1.8.2: + dependencies: + acorn: 8.16.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.4 + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.12: {} + + object-assign@4.1.1: {} + + obug@2.1.1: {} + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@4.0.4: {} + + pirates@4.0.7: {} + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.2 + pathe: 2.0.3 + + postcss-load-config@6.0.1(postcss@8.5.15): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + postcss: 8.5.15 + + postcss@8.5.15: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + readdirp@4.1.2: {} + + resolve-from@5.0.0: {} + + rolldown@1.0.2: + dependencies: + '@oxc-project/types': 0.132.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.2 + '@rolldown/binding-darwin-arm64': 1.0.2 + '@rolldown/binding-darwin-x64': 1.0.2 + '@rolldown/binding-freebsd-x64': 1.0.2 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.2 + '@rolldown/binding-linux-arm64-gnu': 1.0.2 + '@rolldown/binding-linux-arm64-musl': 1.0.2 + '@rolldown/binding-linux-ppc64-gnu': 1.0.2 + '@rolldown/binding-linux-s390x-gnu': 1.0.2 + '@rolldown/binding-linux-x64-gnu': 1.0.2 + '@rolldown/binding-linux-x64-musl': 1.0.2 + '@rolldown/binding-openharmony-arm64': 1.0.2 + '@rolldown/binding-wasm32-wasi': 1.0.2 + '@rolldown/binding-win32-arm64-msvc': 1.0.2 + '@rolldown/binding-win32-x64-msvc': 1.0.2 + + rollup@4.60.4: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.60.4 + '@rollup/rollup-android-arm64': 4.60.4 + '@rollup/rollup-darwin-arm64': 4.60.4 + '@rollup/rollup-darwin-x64': 4.60.4 + '@rollup/rollup-freebsd-arm64': 4.60.4 + '@rollup/rollup-freebsd-x64': 4.60.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.4 + '@rollup/rollup-linux-arm-musleabihf': 4.60.4 + '@rollup/rollup-linux-arm64-gnu': 4.60.4 + '@rollup/rollup-linux-arm64-musl': 4.60.4 + '@rollup/rollup-linux-loong64-gnu': 4.60.4 + '@rollup/rollup-linux-loong64-musl': 4.60.4 + '@rollup/rollup-linux-ppc64-gnu': 4.60.4 + '@rollup/rollup-linux-ppc64-musl': 4.60.4 + '@rollup/rollup-linux-riscv64-gnu': 4.60.4 + '@rollup/rollup-linux-riscv64-musl': 4.60.4 + '@rollup/rollup-linux-s390x-gnu': 4.60.4 + '@rollup/rollup-linux-x64-gnu': 4.60.4 + '@rollup/rollup-linux-x64-musl': 4.60.4 + '@rollup/rollup-openbsd-x64': 4.60.4 + '@rollup/rollup-openharmony-arm64': 4.60.4 + '@rollup/rollup-win32-arm64-msvc': 4.60.4 + '@rollup/rollup-win32-ia32-msvc': 4.60.4 + '@rollup/rollup-win32-x64-gnu': 4.60.4 + '@rollup/rollup-win32-x64-msvc': 4.60.4 + fsevents: 2.3.3 + + siginfo@2.0.0: {} + + source-map-js@1.2.1: {} + + source-map@0.7.6: {} + + stackback@0.0.2: {} + + std-env@4.1.0: {} + + sucrase@3.35.1: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.17 + ts-interface-checker: 0.1.13 + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyexec@1.2.4: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tinyrainbow@3.1.0: {} + + tree-kill@1.2.2: {} + + ts-interface-checker@0.1.13: {} + + tslib@2.8.1: + optional: true + + tsup@8.5.1(postcss@8.5.15)(typescript@5.9.2): + dependencies: + bundle-require: 5.1.0(esbuild@0.27.7) + cac: 6.7.14 + chokidar: 4.0.3 + consola: 3.4.2 + debug: 4.4.3 + esbuild: 0.27.7 + fix-dts-default-cjs-exports: 1.0.1 + joycon: 3.1.1 + picocolors: 1.1.1 + postcss-load-config: 6.0.1(postcss@8.5.15) + resolve-from: 5.0.0 + rollup: 4.60.4 + source-map: 0.7.6 + sucrase: 3.35.1 + tinyexec: 0.3.2 + tinyglobby: 0.2.17 + tree-kill: 1.2.2 + optionalDependencies: + postcss: 8.5.15 + typescript: 5.9.2 + transitivePeerDependencies: + - jiti + - supports-color + - tsx + - yaml + + typescript@5.9.2: {} + + ufo@1.6.4: {} + + undici-types@7.8.0: {} + + vite@8.0.14(@types/node@24.1.0)(esbuild@0.27.7): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.15 + rolldown: 1.0.2 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 24.1.0 + esbuild: 0.27.7 + fsevents: 2.3.3 + + vitest@4.1.2(@types/node@24.1.0)(vite@8.0.14(@types/node@24.1.0)(esbuild@0.27.7)): + dependencies: + '@vitest/expect': 4.1.2 + '@vitest/mocker': 4.1.2(vite@8.0.14(@types/node@24.1.0)(esbuild@0.27.7)) + '@vitest/pretty-format': 4.1.2 + '@vitest/runner': 4.1.2 + '@vitest/snapshot': 4.1.2 + '@vitest/spy': 4.1.2 + '@vitest/utils': 4.1.2 + es-module-lexer: 2.1.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.1.0 + tinybench: 2.9.0 + tinyexec: 1.2.4 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.0 + vite: 8.0.14(@types/node@24.1.0)(esbuild@0.27.7) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.1.0 + transitivePeerDependencies: + - msw + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 diff --git a/python/README.md b/python/README.md index 732ba1e..3513ea7 100644 --- a/python/README.md +++ b/python/README.md @@ -2,11 +2,28 @@ Python MCP client foundation for connecting agent frameworks (Hermes and others) to [KeeperHub](https://keeperhub.com). -The Python counterpart to [`@keeperhub/mcp-client`](https://github.com/KeeperHub/mcp-client). Implements the same kernel: MCP session bootstrap + re-init on `401`/`404`, `kh_` vs `wfb_` key disambiguation, poll-to-terminal, and single JSON-result unwrap. +The Python counterpart to [`@keeperhub/mcp-client`](https://www.npmjs.com/package/@keeperhub/mcp-client). Implements the same kernel: MCP session bootstrap + re-init on `401`/`404`, `kh_` vs `wfb_` key disambiguation, and single JSON-result unwrap. -## Status +## Usage -Early development. Not yet published to PyPI. +```python +from keeperhub_mcp_client import get_client, resolve_api_key + +api_key = resolve_api_key() +if not api_key: + raise RuntimeError("KH_API_KEY not set") + +client = get_client(api_key, client_name="my-plugin", client_version="1.0.0") +workflows = client.call_tool("list_workflows", {}) +``` + +## Develop + +```bash +cd python +pip install -e ".[dev]" +pytest +``` ## License diff --git a/python/keeperhub_mcp_client/__init__.py b/python/keeperhub_mcp_client/__init__.py index b678f28..73ae3e8 100644 --- a/python/keeperhub_mcp_client/__init__.py +++ b/python/keeperhub_mcp_client/__init__.py @@ -1,7 +1,49 @@ -"""keeperhub-mcp-client: shared MCP client foundation for KeeperHub framework adapters. +"""KeeperHub MCP HTTP client for Python integrators.""" -Scaffold only. The session bootstrap, key disambiguation, and poll-to-terminal -kernel land in client.py. -""" +from keeperhub_mcp_client.client import ( + DEFAULT_MCP_URL, + MCP_PROTOCOL_VERSION, + KeeperHubMcpClient, + get_client, + reset_client_for_tests, +) +from keeperhub_mcp_client.keys import ( + ENV_VAR_NAMES, + ORG_KEY_HINT, + SUPPORTED_ENV_VARS, + WFB_KEY_NOT_FOR_MCP_MESSAGE, + classify_api_key, + get_api_key_kind_warning, + is_likely_valid_api_key, + is_likely_valid_org_api_key, + is_org_api_key, + mask_api_key, + resolve_api_key, + validate_api_key_for_mcp, +) -__version__ = "0.0.0" +KeeperHubClient = KeeperHubMcpClient + +__version__ = "0.1.0" + +__all__ = [ + "DEFAULT_MCP_URL", + "ENV_VAR_NAMES", + "KeeperHubClient", + "KeeperHubMcpClient", + "MCP_PROTOCOL_VERSION", + "SUPPORTED_ENV_VARS", + "ORG_KEY_HINT", + "WFB_KEY_NOT_FOR_MCP_MESSAGE", + "__version__", + "classify_api_key", + "get_api_key_kind_warning", + "get_client", + "is_likely_valid_api_key", + "is_likely_valid_org_api_key", + "is_org_api_key", + "mask_api_key", + "reset_client_for_tests", + "resolve_api_key", + "validate_api_key_for_mcp", +] diff --git a/python/keeperhub_mcp_client/client.py b/python/keeperhub_mcp_client/client.py index 95d91a1..d75583d 100644 --- a/python/keeperhub_mcp_client/client.py +++ b/python/keeperhub_mcp_client/client.py @@ -1,6 +1,216 @@ -"""KeeperHub MCP client. +"""KeeperHub MCP HTTP client (sync). Mirrors @keeperhub/mcp-client.""" -Scaffold placeholder. Implements the same kernel as the TypeScript client: -session bootstrap + re-init on 401/404, kh_ vs wfb_ key disambiguation, -poll-to-terminal, and single JSON-result unwrap. -""" +from __future__ import annotations + +import json +import logging +import threading +from typing import Any + +import httpx + +from keeperhub_mcp_client.keys import classify_api_key, validate_api_key_for_mcp + +logger = logging.getLogger(__name__) + +DEFAULT_MCP_URL = "https://app.keeperhub.com/mcp" +MCP_PROTOCOL_VERSION = "2024-11-05" +DEFAULT_CLIENT_NAME = "keeperhub-mcp-client" +DEFAULT_CLIENT_VERSION = "0.1.0" + + +class KeeperHubMcpClient: + """One MCP session per instance; lazy init; re-init on 401 / session 404.""" + + __slots__ = ( + "api_key", + "api_key_kind", + "base_url", + "client_name", + "client_version", + "_http", + "_owns_http", + "_lock", + "session_id", + "request_id", + "org_context", + ) + + def __init__( + self, + api_key: str, + *, + base_url: str = DEFAULT_MCP_URL, + client_name: str = DEFAULT_CLIENT_NAME, + client_version: str = DEFAULT_CLIENT_VERSION, + timeout: float = 120.0, + http_client: httpx.Client | None = None, + ) -> None: + key = (api_key or "").strip() + if not key: + raise ValueError("KeeperHubMcpClient requires a non-empty apiKey") + validate_api_key_for_mcp(key) + self.api_key = key + self.api_key_kind = classify_api_key(key) + self.base_url = base_url + self.client_name = client_name + self.client_version = client_version + self._http = http_client or httpx.Client(timeout=timeout) + self._owns_http = http_client is None + self._lock = threading.RLock() + self.session_id: str | None = None + self.request_id = 0 + self.org_context: dict[str, Any] = {"orgId": None, "workflowCount": 0} + + def close(self) -> None: + if self._owns_http: + self._http.close() + + def reset_session(self) -> None: + self.session_id = None + + def headers_base(self) -> dict[str, str]: + return { + "Content-Type": "application/json", + "Accept": "application/json, text/event-stream", + "Authorization": f"Bearer {self.api_key}", + } + + def _ensure_session_unlocked(self) -> None: + if self.session_id: + return + self.request_id += 1 + body = { + "jsonrpc": "2.0", + "id": self.request_id, + "method": "initialize", + "params": { + "protocolVersion": MCP_PROTOCOL_VERSION, + "capabilities": {}, + "clientInfo": {"name": self.client_name, "version": self.client_version}, + }, + } + res = self._http.post(self.base_url, headers=self.headers_base(), json=body) + if not res.is_success: + raise RuntimeError(f"KeeperHub initialize failed ({res.status_code}): {res.text}") + sid = res.headers.get("mcp-session-id") + if not sid: + raise RuntimeError("KeeperHub did not return mcp-session-id") + self.session_id = sid + logger.info("[KeeperHub] MCP session established") + + def _post_mcp_unlocked(self, body: dict[str, Any]) -> Any: + if not self.session_id: + raise RuntimeError("No active MCP session") + hdrs = {**self.headers_base(), "mcp-session-id": self.session_id} + res = self._http.post(self.base_url, headers=hdrs, json=body) + + if res.status_code == 401: + logger.warning("[KeeperHub] Session unauthorized, re-initializing") + self.session_id = None + self._ensure_session_unlocked() + return self._post_mcp_unlocked(body) + + if res.status_code == 404: + text = res.text or "" + if "session" in text.lower(): + logger.warning("[KeeperHub] Session expired, re-initializing") + self.session_id = None + self._ensure_session_unlocked() + return self._post_mcp_unlocked(body) + raise RuntimeError(f"KeeperHub MCP error (404): {text}") + + if not res.is_success: + raise RuntimeError(f"KeeperHub MCP error ({res.status_code}): {res.text}") + + payload = res.json() + if payload.get("error"): + msg = payload["error"].get("message", str(payload["error"])) + raise RuntimeError(f"KeeperHub RPC error: {msg}") + return payload.get("result") + + def call_tool(self, name: str, arguments: dict[str, Any] | None = None) -> Any: + args = arguments if arguments is not None else {} + with self._lock: + self._ensure_session_unlocked() + self.request_id += 1 + body = { + "jsonrpc": "2.0", + "id": self.request_id, + "method": "tools/call", + "params": {"name": name, "arguments": args}, + } + result = self._post_mcp_unlocked(body) + + typed = result if isinstance(result, dict) else {} + if typed.get("isError"): + msg = "Unknown KeeperHub error" + content = typed.get("content") + if isinstance(content, list) and content: + block = content[0] + if isinstance(block, dict) and isinstance(block.get("text"), str): + msg = block["text"] + raise RuntimeError(f"KeeperHub tool error ({name}): {msg}") + + content = typed.get("content") if isinstance(typed, dict) else None + if isinstance(content, list) and content: + block = content[0] + if isinstance(block, dict): + txt = block.get("text") + if isinstance(txt, str): + try: + return json.loads(txt) + except json.JSONDecodeError: + return txt + return result + + def refresh_org_context(self) -> dict[str, Any]: + try: + workflows = self.call_tool("list_workflows", {}) + lst = workflows if isinstance(workflows, list) else [] + org_id = None + if lst and isinstance(lst[0], dict): + org_id = lst[0].get("organizationId") + self.org_context = {"orgId": org_id, "workflowCount": len(lst)} + except Exception: + pass + return self.org_context + + +KeeperHubClient = KeeperHubMcpClient + +_cached_client: KeeperHubMcpClient | None = None +_cached_key: str | None = None + + +def get_client( + api_key: str, + *, + client_name: str = DEFAULT_CLIENT_NAME, + client_version: str = DEFAULT_CLIENT_VERSION, +) -> KeeperHubMcpClient: + global _cached_client, _cached_key + if _cached_client is None or _cached_key != api_key: + if _cached_client is not None: + try: + _cached_client.close() + except Exception: + pass + _cached_client = KeeperHubMcpClient( + api_key, + client_name=client_name, + client_version=client_version, + ) + _cached_key = api_key + return _cached_client + + +def reset_client_for_tests() -> None: + global _cached_client, _cached_key + if _cached_client is not None: + try: + _cached_client.close() + except Exception: + pass + _cached_client = None + _cached_key = None diff --git a/python/keeperhub_mcp_client/keys.py b/python/keeperhub_mcp_client/keys.py new file mode 100644 index 0000000..b663bfe --- /dev/null +++ b/python/keeperhub_mcp_client/keys.py @@ -0,0 +1,97 @@ +"""KeeperHub API key resolution and classification. + +See KeeperHub docs: API Keys — kh_ (organization, MCP/REST) vs wfb_ (user, webhooks only). +""" + +from __future__ import annotations + +import os +from typing import Literal + +ApiKeyKind = Literal["org", "webhook", "unknown"] + +ENV_VAR_NAMES = ("KH_API_KEY", "KEEPERHUB_API_KEY") +SUPPORTED_ENV_VARS: tuple[str, ...] = ENV_VAR_NAMES + +WFB_KEY_NOT_FOR_MCP_MESSAGE = ( + "This key starts with wfb_ (user webhook key from /api/api-keys). " + "MCP and the REST API require an organization key (kh_) from " + "Settings → API Keys → Organisation (/api/keys). " + "The two key types are not interchangeable." +) + +ORG_KEY_HINT = ( + "Use an organization API key (prefix kh_) from Settings → API Keys → Organisation." +) + + +def first_string(*values: object) -> str | None: + for value in values: + if isinstance(value, str): + t = value.strip() + if t: + return t + return None + + +def resolve_api_key( + *, + plugin_config: dict[str, object] | None = None, + settings: tuple[str | None, ...] | None = None, +) -> str | None: + """Resolve API key: plugin config apiKey, then settings, then env vars.""" + if plugin_config: + v = first_string(plugin_config.get("apiKey")) + if v: + return v + if settings: + for item in settings: + v = first_string(item) + if v: + return v + for name in ENV_VAR_NAMES: + v = first_string(os.environ.get(name, "")) + if v: + return v + return None + + +def classify_api_key(api_key: str) -> ApiKeyKind: + if api_key.startswith("kh_"): + return "org" + if api_key.startswith("wfb_"): + return "webhook" + return "unknown" + + +def is_org_api_key(api_key: str) -> bool: + return classify_api_key(api_key) == "org" + + +def is_likely_valid_org_api_key(api_key: str) -> bool: + return is_org_api_key(api_key) + + +def is_likely_valid_api_key(api_key: str) -> bool: + return is_org_api_key(api_key) + + +def get_api_key_kind_warning(api_key: str) -> str | None: + kind = classify_api_key(api_key) + if kind == "org": + return None + if kind == "webhook": + return WFB_KEY_NOT_FOR_MCP_MESSAGE + return f"Key does not start with kh_. {ORG_KEY_HINT}" + + +def validate_api_key_for_mcp(api_key: str) -> None: + warning = get_api_key_kind_warning(api_key) + if warning: + raise ValueError(f"KeeperHub API key: {warning}") + + +def mask_api_key(api_key: str) -> str: + if len(api_key) <= 8: + return "••••" + return f"{api_key[:4]}…{api_key[-4:]}" diff --git a/python/pyproject.toml b/python/pyproject.toml index b794066..8e5e6aa 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -4,13 +4,16 @@ build-backend = "hatchling.build" [project] name = "keeperhub-mcp-client" -version = "0.0.0" +version = "0.1.0" description = "Shared MCP client foundation for KeeperHub agent-framework adapters." readme = "README.md" license = "Apache-2.0" -requires-python = ">=3.10" +requires-python = ">=3.9" authors = [{ name = "KeeperHub" }] -dependencies = ["mcp>=1.9.0"] +dependencies = ["httpx>=0.27"] + +[project.optional-dependencies] +dev = ["pytest>=8", "httpx>=0.27"] [project.urls] Homepage = "https://keeperhub.com" @@ -20,3 +23,7 @@ Issues = "https://github.com/KeeperHub/mcp-client/issues" [tool.hatch.build.targets.wheel] packages = ["keeperhub_mcp_client"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +pythonpath = ["."] diff --git a/python/tests/test_client.py b/python/tests/test_client.py new file mode 100644 index 0000000..69902b5 --- /dev/null +++ b/python/tests/test_client.py @@ -0,0 +1,100 @@ +"""KeeperHubMcpClient MCP transport with mocked HTTP.""" + +from __future__ import annotations + +import json + +import httpx +import pytest + +from keeperhub_mcp_client import ( + KeeperHubMcpClient, + classify_api_key, + reset_client_for_tests, +) + + +def test_classify_api_key(): + assert classify_api_key("kh_x") == "org" + assert classify_api_key("wfb_x") == "webhook" + assert classify_api_key("x") == "unknown" + + +def test_wfb_rejected_for_mcp(): + with pytest.raises(ValueError, match="wfb_"): + KeeperHubMcpClient("wfb_webhook_key") + + +def test_initialize_and_tools_call_roundtrip(): + calls = {"n": 0} + + def handler(request: httpx.Request) -> httpx.Response: + body = json.loads(request.content.decode()) + calls["n"] += 1 + rid = body["id"] + if body["method"] == "initialize": + return httpx.Response( + 200, + headers={"mcp-session-id": "test-session"}, + json={"jsonrpc": "2.0", "id": rid, "result": {}}, + ) + if body["method"] == "tools/call": + assert request.headers.get("mcp-session-id") == "test-session" + return httpx.Response( + 200, + json={ + "jsonrpc": "2.0", + "id": rid, + "result": { + "content": [{"type": "text", "text": json.dumps({"answer": 42})}], + }, + }, + ) + return httpx.Response(400, json={"error": "unknown method"}) + + transport = httpx.MockTransport(handler) + client = KeeperHubMcpClient( + "kh_test", http_client=httpx.Client(transport=transport, timeout=10.0) + ) + try: + assert client.call_tool("demo_tool", {"x": 1}) == {"answer": 42} + assert calls["n"] == 2 + finally: + client.close() + reset_client_for_tests() + + +def test_401_triggers_reinitialize(): + phase = {"initialize": 0, "tools": 0} + + def handler(request: httpx.Request) -> httpx.Response: + body = json.loads(request.content.decode()) + rid = body["id"] + if body["method"] == "initialize": + phase["initialize"] += 1 + return httpx.Response( + 200, + headers={"mcp-session-id": f"sess-{phase['initialize']}"}, + json={"jsonrpc": "2.0", "id": rid, "result": {}}, + ) + if body["method"] == "tools/call": + phase["tools"] += 1 + if phase["tools"] == 1: + return httpx.Response(401, text="unauthorized") + return httpx.Response( + 200, + json={ + "jsonrpc": "2.0", + "id": rid, + "result": {"content": [{"type": "text", "text": '"ok"'}]}, + }, + ) + return httpx.Response(400) + + transport = httpx.MockTransport(handler) + c = KeeperHubMcpClient("kh_test", http_client=httpx.Client(transport=transport, timeout=10.0)) + try: + assert c.call_tool("t", {}) == "ok" + assert phase["initialize"] == 2 + finally: + c.close() From 8674e321af2f943fadaebee35539890c4b33bf5e Mon Sep 17 00:00:00 2001 From: Bleyle Date: Mon, 1 Jun 2026 07:16:45 +0300 Subject: [PATCH 2/3] Add CI workflow and improve publish metadata. Run TS build/test/type-check and Python pytest on PRs and main. Add npm keywords and a root test:python script for local verification. Co-authored-by: Cursor --- .github/workflows/ci.yml | 43 ++++++++++++++++++++++++++++++++ package.json | 3 ++- packages/mcp-client/package.json | 2 ++ 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d6d714b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,43 @@ +name: ci + +on: + push: + branches: [main] + pull_request: + branches: [main] + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + typescript: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm --filter @keeperhub/mcp-client build + - run: pnpm --filter @keeperhub/mcp-client test + - run: pnpm --filter @keeperhub/mcp-client type-check + + python: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.9", "3.12"] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install and test + working-directory: python + run: | + python -m pip install --upgrade pip + pip install -e ".[dev]" + pytest -q diff --git a/package.json b/package.json index 08d4c29..4ec3f09 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ }, "scripts": { "build": "pnpm -r build", - "test": "pnpm -r test", + "test": "pnpm -r test && pnpm run test:python", + "test:python": "cd python && python -m pip install -q -e \".[dev]\" && python -m pytest -q", "type-check": "pnpm -r type-check" }, "engines": { diff --git a/packages/mcp-client/package.json b/packages/mcp-client/package.json index f4fa901..29daa2e 100644 --- a/packages/mcp-client/package.json +++ b/packages/mcp-client/package.json @@ -2,6 +2,8 @@ "name": "@keeperhub/mcp-client", "version": "0.1.0", "description": "Shared MCP client foundation for KeeperHub agent-framework adapters.", + "author": "KeeperHub", + "keywords": ["keeperhub", "mcp", "model-context-protocol", "workflow", "web3"], "license": "Apache-2.0", "type": "module", "engines": { From 01997c183e4b3d04b37e8724d7a908e57afc4b41 Mon Sep 17 00:00:00 2001 From: Bleyle Date: Mon, 1 Jun 2026 07:18:14 +0300 Subject: [PATCH 3/3] Fix CI type-check by excluding test files from tsc scope. Co-authored-by: Cursor --- packages/mcp-client/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mcp-client/tsconfig.json b/packages/mcp-client/tsconfig.json index 456cf18..f288b83 100644 --- a/packages/mcp-client/tsconfig.json +++ b/packages/mcp-client/tsconfig.json @@ -15,5 +15,5 @@ "rootDir": "src" }, "include": ["src"], - "exclude": ["node_modules", "dist"] + "exclude": ["node_modules", "dist", "src/**/__tests__/**"] }