From aa8d9a470ee904cd5d7ef09a3775b6acee2c18c2 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 21:17:38 +0800 Subject: [PATCH] feat(cli): add `devmap mcp` read-only MCP server (Phase 1) Run DevMap as an MCP server over stdio so AI agents can query the project map with tool calls instead of reading dumped context files. Tools (all read-only, no AI, no disk writes): - devmap_overview framework, stats, entry points, critical files, features, flows - devmap_map project / feature / file dependency map - devmap_flow trace a feature or request end to end - devmap_routes page/API routes, filter by kind, prefix, method - devmap_impact dependents, impacts and imports of a file - devmap_find keyword search over features, files, routes Resources: devmap://snapshot, devmap://devmap-md, devmap://feature/{id}. Design: - every result carries snapshot { generatedAt, stale } plus a hint when stale - missing/corrupt snapshot -> structured error with the `devmap analyze` hint - responses are size-capped with an explicit `truncated` flag - stdout is reserved for JSON-RPC; console.log/info/debug go to stderr - snapshot provider caches by snapshot.json mtime and memoizes the staleness scan - feature resource ids are validated (no path traversal) Tests: 13 in-memory + stdio tests (mcp-server.test.ts) and an MCP smoke test in the packed-package e2e. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01AgTNRXcqugxRyJfMKL4AJ2 --- packages/cli/package.json | 4 +- packages/cli/src/commands/mcp.ts | 37 ++ packages/cli/src/index.ts | 7 + packages/cli/src/mcp/queries.ts | 286 +++++++++ packages/cli/src/mcp/server.ts | 321 ++++++++++ packages/cli/src/mcp/snapshotProvider.ts | 64 ++ packages/cli/test/mcp-server.test.ts | 386 ++++++++++++ packages/cli/test/package-e2e.mjs | 40 +- pnpm-lock.yaml | 739 +++++++++++++++++++++++ 9 files changed, 1882 insertions(+), 2 deletions(-) create mode 100644 packages/cli/src/commands/mcp.ts create mode 100644 packages/cli/src/mcp/queries.ts create mode 100644 packages/cli/src/mcp/server.ts create mode 100644 packages/cli/src/mcp/snapshotProvider.ts create mode 100644 packages/cli/test/mcp-server.test.ts diff --git a/packages/cli/package.json b/packages/cli/package.json index 61da82f..3f3483d 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -60,10 +60,12 @@ }, "type": "module", "dependencies": { + "@modelcontextprotocol/sdk": "^1.32.2", "commander": "^15.0.0", "ignore": "^7.0.12", "p-limit": "^7.3.3", - "ts-morph": "^28.0.0" + "ts-morph": "^28.0.0", + "zod": "^4.6.7" }, "devDependencies": { "@types/node": "^22.20.5", diff --git a/packages/cli/src/commands/mcp.ts b/packages/cli/src/commands/mcp.ts new file mode 100644 index 0000000..cade359 --- /dev/null +++ b/packages/cli/src/commands/mcp.ts @@ -0,0 +1,37 @@ +import { resolve } from "node:path"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { createDevmapServer } from "../mcp/server.js"; + +export type McpOptions = { + root?: string; +}; + +/** + * Run DevMap as an MCP server over stdio. + * + * stdout carries the JSON-RPC stream, so anything else that would print there + * must go to stderr instead. Every console method that writes to stdout is + * redirected before the server starts. + */ +export async function mcpCommand(options: McpOptions = {}): Promise { + const redirect = (...values: unknown[]): void => { + console.error(...values); + }; + console.log = redirect; + console.info = redirect; + console.debug = redirect; + + const projectRoot = resolve(options.root ?? "."); + const { server } = createDevmapServer({ projectRoot }); + const transport = new StdioServerTransport(); + + const shutdown = (): void => { + void server.close().finally(() => process.exit(0)); + }; + process.once("SIGINT", shutdown); + process.once("SIGTERM", shutdown); + process.stdin.once("end", shutdown); + + await server.connect(transport); + console.error(`DevMap MCP server ready (root: ${projectRoot})`); +} diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index adeaed1..97bae85 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -7,6 +7,7 @@ import { explainCommand } from "./commands/explain.js"; import { flowCommand } from "./commands/flow.js"; import { initCommand } from "./commands/init.js"; import { mapCommand } from "./commands/map.js"; +import { mcpCommand } from "./commands/mcp.js"; import { onboardingCommand } from "./commands/onboarding.js"; import { printHelp } from "./utils/help.js"; import { printWelcome } from "./utils/welcome.js"; @@ -96,6 +97,12 @@ configCommand local: options.local })); +program + .command("mcp") + .description("Run DevMap as an MCP server (stdio) so AI agents can query the project map") + .option("--root ", "project root containing .devmap/snapshot.json", ".") + .action((options) => mcpCommand({ root: options.root })); + program .command("doctor") .description("Diagnose DevMap setup") diff --git a/packages/cli/src/mcp/queries.ts b/packages/cli/src/mcp/queries.ts new file mode 100644 index 0000000..6672c89 --- /dev/null +++ b/packages/cli/src/mcp/queries.ts @@ -0,0 +1,286 @@ +import type { ProjectMap } from "../analyzers/pipeline/index.js"; +import type { RouteInfo } from "../analyzers/detectors/routeDetector.js"; +import { DevmapError } from "../utils/errors.js"; + +/** + * Pure, read-only queries over a ProjectMap snapshot. No I/O, no printing, + * so they are cheap to test and safe to call from any front-end. + */ + +export type SnapshotMeta = { generatedAt: string; stale: boolean }; + +export const DEFAULT_LIMIT = 20; +export const MAX_LIMIT = 100; + +export function clampLimit(limit: number | undefined, fallback = DEFAULT_LIMIT): number { + if (limit === undefined || !Number.isFinite(limit)) return fallback; + return Math.max(1, Math.min(MAX_LIMIT, Math.floor(limit))); +} + +// ── overview ──────────────────────────────────────────────────────────────── + +export type OverviewResult = { + project: ProjectMap["project"]; + stats: ProjectMap["stats"]; + entryPoints: string[]; + criticalFiles: Array<{ path: string; referencedBy: number; reasons: string[] }>; + features: Array<{ + name: string; + purpose: string; + confidence: string; + entryPoint?: string; + fileCount: number; + }>; + flows: Array<{ name: string; type: string; purpose: string }>; + routeCounts: { pages: number; api: number }; + database?: ProjectMap["database"]; + externalServices: string[]; + recommendedPath: string[]; + nextSteps: string[]; +}; + +export function buildOverview(snapshot: ProjectMap, limit = 10): OverviewResult { + return { + project: snapshot.project, + stats: snapshot.stats, + entryPoints: snapshot.entryPoints.slice(0, limit), + criticalFiles: snapshot.criticalFiles.slice(0, limit).map((file) => ({ + path: file.path, + referencedBy: file.referencedBy, + reasons: file.reasons + })), + features: snapshot.features.map((feature) => ({ + name: feature.name, + purpose: feature.purpose, + confidence: feature.confidence, + ...(feature.entryPoint ? { entryPoint: feature.entryPoint } : {}), + fileCount: feature.files.length + })), + flows: snapshot.flows.map((flow) => ({ + name: flow.name, + type: flow.type, + purpose: flow.purpose + })), + routeCounts: { + pages: snapshot.routes.filter((route) => route.kind === "page").length, + api: snapshot.routes.filter((route) => route.kind === "api").length + }, + ...(snapshot.database ? { database: snapshot.database } : {}), + externalServices: snapshot.externalServices, + recommendedPath: snapshot.onboarding.recommendedPath, + nextSteps: [ + "devmap_map with a feature name to see its files and dependencies", + "devmap_flow with a flow name to trace it end to end", + "devmap_impact with a file path before changing it", + "devmap_find to locate files, features or routes by keyword" + ] + }; +} + +// ── routes ────────────────────────────────────────────────────────────────── + +export type RoutesQuery = { + kind?: "page" | "api" | "all"; + prefix?: string; + method?: string; + limit?: number; +}; + +export type RoutesResult = { + total: number; + returned: number; + truncated: boolean; + routes: RouteInfo[]; +}; + +export function queryRoutes(snapshot: ProjectMap, query: RoutesQuery = {}): RoutesResult { + const kind = query.kind ?? "all"; + const prefix = query.prefix?.trim(); + const method = query.method?.trim().toUpperCase(); + const limit = clampLimit(query.limit, 50); + + const matches = snapshot.routes.filter((route) => { + if (kind !== "all" && route.kind !== kind) return false; + if (prefix && !route.path.startsWith(prefix)) return false; + if (method && !(route.methods ?? []).some((candidate) => candidate.toUpperCase() === method)) { + return false; + } + return true; + }); + + return { + total: matches.length, + returned: Math.min(matches.length, limit), + truncated: matches.length > limit, + routes: matches.slice(0, limit) + }; +} + +// ── impact ────────────────────────────────────────────────────────────────── + +export function resolveProjectFile(snapshot: ProjectMap, input: string): string { + const normalized = input.trim().replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, ""); + if (normalized === "") { + throw new DevmapError("A file path is required.", "Pass a path relative to the project root."); + } + + const paths = Object.keys(snapshot.fileIndex); + if (snapshot.fileIndex[normalized]) return normalized; + + const lower = normalized.toLowerCase(); + const insensitive = paths.filter((path) => path.toLowerCase() === lower); + if (insensitive.length === 1) return insensitive[0]!; + + const suffix = paths.filter((path) => path.toLowerCase().endsWith(`/${lower}`)); + if (suffix.length === 1) return suffix[0]!; + if (suffix.length > 1) { + throw new DevmapError( + `"${input}" matches multiple files.`, + `Be more specific — options: ${suffix.slice(0, 8).join(", ")}` + ); + } + + const similar = paths + .filter((path) => path.toLowerCase().includes(lower)) + .slice(0, 5); + throw new DevmapError( + `"${input}" isn't a file in the DevMap snapshot.`, + similar.length > 0 + ? `Did you mean: ${similar.join(", ")}?` + : "Use a path relative to the project root, or try devmap_find to locate it." + ); +} + +export type ImpactResult = { + file: string; + purpose?: string; + scope: string; + features: string[]; + importance: number; + /** Files/areas likely affected when this file changes. */ + impacts: string[]; + /** Files that import this file directly. */ + dependents: string[]; + /** Local files this file imports. */ + imports: string[]; + truncated: boolean; +}; + +export function queryImpact(snapshot: ProjectMap, input: string, limit?: number): ImpactResult { + const file = resolveProjectFile(snapshot, input); + const cap = clampLimit(limit, 50); + const entry = snapshot.fileIndex[file]!; + const impact = snapshot.changeImpact[file] ?? { impacts: [], dependents: [] }; + const imports = snapshot.fileGraph[file] ?? []; + + return { + file, + ...(entry.purpose ? { purpose: entry.purpose } : {}), + scope: entry.scope, + features: entry.featureRefs, + importance: entry.importance, + impacts: impact.impacts.slice(0, cap), + dependents: impact.dependents.slice(0, cap), + imports: imports.slice(0, cap), + truncated: + impact.impacts.length > cap || impact.dependents.length > cap || imports.length > cap + }; +} + +// ── find ──────────────────────────────────────────────────────────────────── + +export type FindHit = + | { type: "feature"; name: string; purpose: string; entryPoint?: string; score: number } + | { type: "file"; path: string; purpose?: string; scope: string; score: number } + | { type: "route"; path: string; file: string; kind: string; methods?: string[]; score: number }; + +export type FindResult = { + query: string; + total: number; + truncated: boolean; + hits: FindHit[]; +}; + +function tokenize(value: string): string[] { + return value + .replace(/([a-z0-9])([A-Z])/g, "$1 $2") + .toLowerCase() + .split(/[^a-z0-9]+/) + .filter((token) => token.length > 0); +} + +function scoreText(haystack: string | undefined, tokens: string[], weight: number): number { + if (!haystack) return 0; + const lower = haystack.toLowerCase(); + let score = 0; + for (const token of tokens) { + if (lower.includes(token)) score += weight; + } + return score; +} + +export function queryFind(snapshot: ProjectMap, query: string, limit?: number): FindResult { + const tokens = tokenize(query); + if (tokens.length === 0) { + throw new DevmapError("The search query is empty.", "Pass one or more keywords, e.g. \"auth login\"."); + } + const cap = clampLimit(limit, 15); + const hits: FindHit[] = []; + + for (const feature of snapshot.features) { + const score = + scoreText(feature.name, tokens, 5) + + scoreText(feature.searchTerms.join(" "), tokens, 3) + + scoreText(feature.purpose, tokens, 1); + if (score > 0) { + hits.push({ + type: "feature", + name: feature.name, + purpose: feature.purpose, + ...(feature.entryPoint ? { entryPoint: feature.entryPoint } : {}), + score: score + 2 + }); + } + } + + for (const [path, entry] of Object.entries(snapshot.fileIndex)) { + const score = + scoreText(path, tokens, 3) + + scoreText(entry.exportedSymbols.join(" "), tokens, 3) + + scoreText(entry.topFunctions.map((fn) => fn.name).join(" "), tokens, 2) + + scoreText(entry.searchTerms.join(" "), tokens, 2) + + scoreText(entry.purpose, tokens, 1); + if (score > 0) { + hits.push({ + type: "file", + path, + ...(entry.purpose ? { purpose: entry.purpose } : {}), + scope: entry.scope, + // importance only breaks ties between otherwise equal matches + score: score + Math.min(entry.importance, 10) / 100 + }); + } + } + + for (const route of snapshot.routes) { + const score = scoreText(route.path, tokens, 4) + scoreText(route.file, tokens, 1); + if (score > 0) { + hits.push({ + type: "route", + path: route.path, + file: route.file, + kind: route.kind, + ...(route.methods ? { methods: route.methods } : {}), + score + }); + } + } + + hits.sort((a, b) => b.score - a.score); + return { + query, + total: hits.length, + truncated: hits.length > cap, + hits: hits.slice(0, cap) + }; +} diff --git a/packages/cli/src/mcp/server.ts b/packages/cli/src/mcp/server.ts new file mode 100644 index 0000000..f15a357 --- /dev/null +++ b/packages/cli/src/mcp/server.ts @@ -0,0 +1,321 @@ +import { readdir, readFile } from "node:fs/promises"; +import { join } from "node:path"; +import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { z } from "zod"; +import { runFlow } from "../commands/flow.js"; +import { runMap } from "../commands/map.js"; +import { DevmapError } from "../utils/errors.js"; +import { withJsonOutput } from "../utils/output.js"; +import { DEVMAP_VERSION } from "../utils/packageMetadata.js"; +import { + buildOverview, + queryFind, + queryImpact, + queryRoutes, + type SnapshotMeta +} from "./queries.js"; +import { createSnapshotProvider, type SnapshotProvider } from "./snapshotProvider.js"; + +export const MAX_TEXT_CHARS = 30_000; +const FEATURE_ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/; + +const SERVER_INSTRUCTIONS = [ + "DevMap gives you a precomputed map of this codebase (features, routes, flows, dependency graph).", + "Start with devmap_overview, then drill into one feature with devmap_map or devmap_flow instead of reading many files.", + "Call devmap_impact before changing a file, and devmap_find to locate code by keyword.", + "Every result includes snapshot.stale; when it is true the code changed after the last analysis, so treat results as approximate and ask the user to run `devmap analyze --fresh`." +].join(" "); + +export type DevmapServerOptions = { + projectRoot: string; + /** Inject a provider (tests); defaults to a cached provider for `projectRoot`. */ + provider?: SnapshotProvider; +}; + +export type DevmapServer = { + server: McpServer; + provider: SnapshotProvider; +}; + +export function truncateText(value: string, max = MAX_TEXT_CHARS): { text: string; truncated: boolean } { + if (value.length <= max) return { text: value, truncated: false }; + return { + text: `${value.slice(0, max)}\n\n… [truncated ${value.length - max} characters]`, + truncated: true + }; +} + +function snapshotMeta(generatedAt: string, stale: boolean): SnapshotMeta & { note?: string } { + return { + generatedAt, + stale, + ...(stale + ? { note: "The code changed after this snapshot was generated. Results may be outdated; run `devmap analyze --fresh`." } + : {}) + }; +} + +function ok(payload: unknown): CallToolResult { + return { content: [{ type: "text", text: JSON.stringify(payload) }] }; +} + +function fail(error: unknown): CallToolResult { + const payload = error instanceof DevmapError + ? { error: error.message, ...(error.hint ? { hint: error.hint } : {}) } + : { error: error instanceof Error ? error.message : String(error) }; + return { isError: true, content: [{ type: "text", text: JSON.stringify(payload) }] }; +} + +async function respond(action: () => Promise): Promise { + try { + return ok(await action()); + } catch (error) { + return fail(error); + } +} + +const readOnly = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }; + +export function createDevmapServer(options: DevmapServerOptions): DevmapServer { + const provider = options.provider ?? createSnapshotProvider(options.projectRoot); + const projectRoot = provider.projectRoot; + + const server = new McpServer( + { name: "devmap", version: DEVMAP_VERSION }, + { instructions: SERVER_INSTRUCTIONS } + ); + + // ── tools ──────────────────────────────────────────────────────────────── + + server.registerTool( + "devmap_overview", + { + title: "Project overview", + description: + "Entry point for understanding the project: framework, stats, entry points, most critical files, " + + "detected features, flows and route counts. Call this first.", + annotations: readOnly + }, + () => respond(async () => { + const { snapshot, stale } = await provider.get(); + return { + ...buildOverview(snapshot), + snapshot: snapshotMeta(snapshot.generatedAt, stale) + }; + }) + ); + + server.registerTool( + "devmap_map", + { + title: "Dependency map", + description: + "Dependency map for the whole project (omit target), one feature (feature name) or one file (project-relative path): " + + "what it imports, what imports it, and related files. Use it to find where to look before reading code.", + inputSchema: { + target: z.string().optional().describe("Feature name or project-relative file path. Omit for the project-wide map."), + depth: z.number().int().min(1).max(6).optional().describe("Traversal depth for file/feature maps."), + includeMermaid: z.boolean().optional().describe("Also return the Mermaid source (default false).") + }, + annotations: readOnly + }, + ({ target, depth, includeMermaid }) => respond(async () => { + const result = await withJsonOutput(() => runMap(target, { projectRoot, depth, write: false })); + const markdown = truncateText(result.markdown); + return { + mode: result.mode, + target: result.target, + markdown: markdown.text, + ...(includeMermaid ? { mermaid: truncateText(result.mermaid).text } : {}), + truncated: markdown.truncated, + snapshot: snapshotMeta(result.snapshot.generatedAt, result.snapshot.stale) + }; + }) + ); + + server.registerTool( + "devmap_flow", + { + title: "Trace a flow", + description: + "Trace how a feature or API request works end to end (ordered steps with file paths). " + + "Omit target to list the curated top flows. Static analysis only, no AI.", + inputSchema: { + target: z.string().optional().describe("Flow name (case-insensitive, partial match allowed)."), + all: z.boolean().optional().describe("Include lower-confidence features and non-API routes.") + }, + annotations: readOnly + }, + ({ target, all }) => respond(async () => { + const result = await withJsonOutput(() => runFlow( + target, + { projectRoot, all, write: false, narrate: false }, + {} + )); + let truncated = false; + const flows = result.flows.slice(0, MAX_FLOWS).map((flow) => { + const markdown = truncateText(flow.markdown, 8_000); + truncated ||= markdown.truncated; + return { name: flow.name, type: flow.type, purpose: flow.purpose, markdown: markdown.text }; + }); + return { + total: result.flows.length, + truncated: truncated || result.flows.length > MAX_FLOWS, + flows, + snapshot: snapshotMeta(result.snapshot.generatedAt, result.snapshot.stale) + }; + }) + ); + + server.registerTool( + "devmap_routes", + { + title: "List routes", + description: "List detected page and API routes with their handler file and HTTP methods. Filter by kind, path prefix or method.", + inputSchema: { + kind: z.enum(["page", "api", "all"]).optional().describe("Route kind (default all)."), + prefix: z.string().optional().describe("Only routes whose path starts with this, e.g. /api/users."), + method: z.string().optional().describe("HTTP method, e.g. POST."), + limit: z.number().int().min(1).max(100).optional().describe("Max routes to return (default 50).") + }, + annotations: readOnly + }, + (input) => respond(async () => { + const { snapshot, stale } = await provider.get(); + return { + ...queryRoutes(snapshot, input), + snapshot: snapshotMeta(snapshot.generatedAt, stale) + }; + }) + ); + + server.registerTool( + "devmap_impact", + { + title: "Change impact", + description: + "What is affected if a file changes: files that import it, areas likely impacted, features it belongs to, and what it imports. " + + "Call this before editing a shared file.", + inputSchema: { + file: z.string().describe("Project-relative file path (a unique path suffix also works)."), + limit: z.number().int().min(1).max(100).optional().describe("Max entries per list (default 50).") + }, + annotations: readOnly + }, + ({ file, limit }) => respond(async () => { + const { snapshot, stale } = await provider.get(); + return { + ...queryImpact(snapshot, file, limit), + snapshot: snapshotMeta(snapshot.generatedAt, stale) + }; + }) + ); + + server.registerTool( + "devmap_find", + { + title: "Find code", + description: + "Locate features, files and routes by keyword using DevMap's index (paths, exported symbols, function names, search terms, purposes). " + + "Faster and more precise than grepping the whole repo for a concept.", + inputSchema: { + query: z.string().describe("One or more keywords, e.g. \"auth login\" or \"InvoiceService\"."), + limit: z.number().int().min(1).max(100).optional().describe("Max hits to return (default 15).") + }, + annotations: readOnly + }, + ({ query, limit }) => respond(async () => { + const { snapshot, stale } = await provider.get(); + return { + ...queryFind(snapshot, query, limit), + snapshot: snapshotMeta(snapshot.generatedAt, stale) + }; + }) + ); + + // ── resources ──────────────────────────────────────────────────────────── + + server.registerResource( + "devmap-snapshot", + "devmap://snapshot", + { + title: "DevMap snapshot", + description: "Full analysis snapshot (.devmap/snapshot.json). Can be large; prefer the tools for targeted questions.", + mimeType: "application/json" + }, + async (uri) => { + const { snapshot } = await provider.get(); + return { contents: [{ uri: uri.href, mimeType: "application/json", text: JSON.stringify(snapshot) }] }; + } + ); + + server.registerResource( + "devmap-md", + "devmap://devmap-md", + { + title: "DEVMAP.md", + description: "The project's DEVMAP.md context file.", + mimeType: "text/markdown" + }, + async (uri) => { + try { + const text = await readFile(join(projectRoot, "DEVMAP.md"), "utf8"); + return { contents: [{ uri: uri.href, mimeType: "text/markdown", text }] }; + } catch { + throw new DevmapError("DEVMAP.md was not found in the project root.", "Run devmap init first."); + } + } + ); + + server.registerResource( + "devmap-feature", + new ResourceTemplate("devmap://feature/{id}", { + list: async () => { + const ids = await listFeatureIds(projectRoot); + return { + resources: ids.map((id) => ({ + uri: `devmap://feature/${id}`, + name: id, + mimeType: "application/json" + })) + }; + } + }), + { + title: "Feature map", + description: "Per-feature navigation data from .devmap/features/.json.", + mimeType: "application/json" + }, + async (uri, variables) => { + const raw = variables.id; + const id = Array.isArray(raw) ? raw[0] : raw; + if (typeof id !== "string" || !FEATURE_ID_PATTERN.test(id)) { + throw new DevmapError(`"${String(id)}" is not a valid feature id.`, "Feature ids are lowercase letters, digits and dashes."); + } + try { + const text = await readFile(join(projectRoot, ".devmap", "features", `${id}.json`), "utf8"); + return { contents: [{ uri: uri.href, mimeType: "application/json", text }] }; + } catch { + throw new DevmapError(`Feature "${id}" was not found.`, "Run devmap analyze, then list resources to see feature ids."); + } + } + ); + + return { server, provider }; +} + +const MAX_FLOWS = 20; + +async function listFeatureIds(projectRoot: string): Promise { + try { + const files = await readdir(join(projectRoot, ".devmap", "features")); + return files + .filter((file) => file.endsWith(".json")) + .map((file) => file.slice(0, -".json".length)) + .filter((id) => FEATURE_ID_PATTERN.test(id)) + .sort(); + } catch { + return []; + } +} diff --git a/packages/cli/src/mcp/snapshotProvider.ts b/packages/cli/src/mcp/snapshotProvider.ts new file mode 100644 index 0000000..812e1a4 --- /dev/null +++ b/packages/cli/src/mcp/snapshotProvider.ts @@ -0,0 +1,64 @@ +import { stat } from "node:fs/promises"; +import { resolve } from "node:path"; +import { + getSnapshotPath, + isSnapshotStale, + readSnapshotOrThrow, + type LoadedSnapshot +} from "../cache/snapshot.js"; + +const DEFAULT_STALE_TTL_MS = 10_000; + +export type SnapshotProvider = { + readonly projectRoot: string; + /** Read-only. Throws a DevmapError (with hint) when the snapshot is missing or unusable. */ + get(): Promise; +}; + +/** + * Serves the current snapshot to long-lived callers without re-reading and + * re-fingerprinting the whole project on every call: + * - the snapshot is re-read only when snapshot.json changes on disk + * - the staleness check (which scans project files) is memoized for a short TTL + */ +export function createSnapshotProvider( + root: string, + options: { staleTtlMs?: number; now?: () => number } = {} +): SnapshotProvider { + const projectRoot = resolve(root); + const ttl = options.staleTtlMs ?? DEFAULT_STALE_TTL_MS; + const now = options.now ?? Date.now; + + let cached: { mtimeMs: number; snapshot: LoadedSnapshot["snapshot"] } | undefined; + let staleCache: { value: boolean; at: number; mtimeMs: number } | undefined; + + return { + projectRoot, + async get(): Promise { + let mtimeMs: number | undefined; + try { + mtimeMs = (await stat(getSnapshotPath(projectRoot))).mtimeMs; + } catch { + mtimeMs = undefined; + } + + if (mtimeMs === undefined || !cached || cached.mtimeMs !== mtimeMs) { + // Throws DevmapError for missing/corrupt/unsupported snapshots. + const snapshot = await readSnapshotOrThrow(projectRoot); + cached = { mtimeMs: mtimeMs ?? 0, snapshot }; + staleCache = undefined; + } + + const currentMtime = cached.mtimeMs; + if (!staleCache || staleCache.mtimeMs !== currentMtime || now() - staleCache.at > ttl) { + staleCache = { + value: await isSnapshotStale(projectRoot, cached.snapshot), + at: now(), + mtimeMs: currentMtime + }; + } + + return { snapshot: cached.snapshot, stale: staleCache.value }; + } + }; +} diff --git a/packages/cli/test/mcp-server.test.ts b/packages/cli/test/mcp-server.test.ts new file mode 100644 index 0000000..58f4559 --- /dev/null +++ b/packages/cli/test/mcp-server.test.ts @@ -0,0 +1,386 @@ +import assert from "node:assert/strict"; +import { cp, mkdir, mkdtemp, rm, utimes, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import test from "node:test"; +import { fileURLToPath } from "node:url"; +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; +import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; +import { createProjectMap } from "../src/analyzers/pipeline/projectMap.js"; +import { writeAgentNavigationFiles } from "../src/cache/agentNavigation.js"; +import { saveSnapshot } from "../src/cache/snapshot.js"; +import { + createDevmapServer, + MAX_TEXT_CHARS, + truncateText +} from "../src/mcp/server.js"; +import { createSnapshotProvider } from "../src/mcp/snapshotProvider.js"; +import { + clampLimit, + queryFind, + queryImpact, + queryRoutes, + resolveProjectFile +} from "../src/mcp/queries.js"; +import { DevmapError } from "../src/utils/errors.js"; + +const testDirectory = dirname(fileURLToPath(import.meta.url)); +const packageRoot = resolve(testDirectory, ".."); +const nextFixture = join(testDirectory, "fixtures", "nextjs-project"); +const hubFixture = join(testDirectory, "fixtures", "hub-fanin"); + +/** + * A real project on disk: the fixture is copied to a temp root and analyzed there, + * so the snapshot fingerprint matches the files (stale === false until they change). + */ +async function projectFromFixture(fixtureRoot: string, withNavigation = false): Promise { + const projectRoot = await mkdtemp(join(tmpdir(), "devmap-mcp-test-")); + await cp(fixtureRoot, projectRoot, { recursive: true }); + const snapshot = await createProjectMap(projectRoot); + await saveSnapshot(projectRoot, snapshot); + if (withNavigation) { + await writeAgentNavigationFiles(projectRoot, snapshot); + } + return projectRoot; +} + +async function connect(projectRoot: string): Promise<{ client: Client; close: () => Promise }> { + const { server } = createDevmapServer({ projectRoot }); + const client = new Client({ name: "test-client", version: "0.0.0" }); + const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); + await Promise.all([server.connect(serverTransport), client.connect(clientTransport)]); + return { + client, + close: async () => { + await client.close(); + await server.close(); + } + }; +} + +type ToolResult = { isError?: boolean; content: Array<{ type: string; text: string }> }; + +async function callTool( + client: Client, + name: string, + args: Record = {} +): Promise<{ isError: boolean; data: any }> { + const result = (await client.callTool({ name, arguments: args })) as ToolResult; + return { + isError: result.isError === true, + data: JSON.parse(result.content[0]!.text) + }; +} + +test("server lists the read-only tools with read-only annotations", async () => { + const projectRoot = await projectFromFixture(nextFixture); + const { client, close } = await connect(projectRoot); + + try { + const { tools } = await client.listTools(); + const names = tools.map((tool) => tool.name).sort(); + assert.deepEqual(names, [ + "devmap_find", + "devmap_flow", + "devmap_impact", + "devmap_map", + "devmap_overview", + "devmap_routes" + ]); + for (const tool of tools) { + assert.equal(tool.annotations?.readOnlyHint, true, `${tool.name} must be read-only`); + assert.ok(tool.description && tool.description.length > 20, `${tool.name} needs a description`); + } + assert.match(client.getInstructions() ?? "", /devmap_overview/); + } finally { + await close(); + await rm(projectRoot, { recursive: true, force: true }); + } +}); + +test("devmap_overview returns project facts, features, and snapshot freshness", async () => { + const projectRoot = await projectFromFixture(nextFixture); + const { client, close } = await connect(projectRoot); + + try { + const { isError, data } = await callTool(client, "devmap_overview"); + assert.equal(isError, false); + assert.equal(data.project.framework, "nextjs"); + assert.ok(data.features.length > 0); + assert.ok(data.stats.totalFiles > 0); + assert.equal(typeof data.snapshot.generatedAt, "string"); + assert.equal(data.snapshot.stale, false); + assert.ok(Array.isArray(data.nextSteps)); + } finally { + await close(); + await rm(projectRoot, { recursive: true, force: true }); + } +}); + +test("devmap_map and devmap_flow are silent and never write .devmap artifacts", async () => { + const projectRoot = await projectFromFixture(nextFixture); + const { client, close } = await connect(projectRoot); + + try { + const project = await callTool(client, "devmap_map"); + assert.equal(project.isError, false); + assert.equal(project.data.mode, "project"); + assert.ok(project.data.markdown.length > 0); + assert.equal(project.data.mermaid, undefined); + + const withMermaid = await callTool(client, "devmap_map", { includeMermaid: true }); + assert.match(withMermaid.data.mermaid, /graph/); + + const flows = await callTool(client, "devmap_flow"); + assert.equal(flows.isError, false); + assert.ok(flows.data.flows.length > 0); + assert.ok(flows.data.flows.every((flow: { markdown: string }) => !/How it works/.test(flow.markdown))); + + const one = await callTool(client, "devmap_flow", { target: flows.data.flows[0].name }); + assert.equal(one.data.flows.length, 1); + + const { readdir } = await import("node:fs/promises"); + const entries = await readdir(join(projectRoot, ".devmap")); + assert.deepEqual(entries.sort(), ["snapshot.json"]); + } finally { + await close(); + await rm(projectRoot, { recursive: true, force: true }); + } +}); + +test("devmap_routes filters by kind, prefix and method, and reports truncation", async () => { + const projectRoot = await projectFromFixture(nextFixture); + const { client, close } = await connect(projectRoot); + + try { + const all = await callTool(client, "devmap_routes"); + assert.ok(all.data.total > 0); + + const api = await callTool(client, "devmap_routes", { kind: "api" }); + assert.ok(api.data.routes.every((route: { kind: string }) => route.kind === "api")); + + const none = await callTool(client, "devmap_routes", { prefix: "/definitely-not-a-route" }); + assert.equal(none.data.total, 0); + + const limited = await callTool(client, "devmap_routes", { limit: 1 }); + assert.equal(limited.data.returned, 1); + assert.equal(limited.data.truncated, limited.data.total > 1); + } finally { + await close(); + await rm(projectRoot, { recursive: true, force: true }); + } +}); + +test("devmap_impact reports dependents of a hub file and accepts path suffixes", async () => { + const projectRoot = await projectFromFixture(hubFixture); + const { client, close } = await connect(projectRoot); + + try { + const snapshot = await createProjectMap(hubFixture); + const [hub] = Object.entries(snapshot.changeImpact) + .sort((a, b) => b[1].dependents.length - a[1].dependents.length)[0]!; + + const exact = await callTool(client, "devmap_impact", { file: hub }); + assert.equal(exact.isError, false); + assert.equal(exact.data.file, hub); + assert.ok(exact.data.dependents.length > 0); + + const suffix = await callTool(client, "devmap_impact", { file: hub.split("/").slice(-1)[0] }); + // A bare basename either resolves uniquely or is reported as ambiguous with options. + if (!suffix.isError) { + assert.equal(suffix.data.file, hub); + } else { + assert.match(suffix.data.hint, /options/); + } + + const missing = await callTool(client, "devmap_impact", { file: "src/does-not-exist.ts" }); + assert.equal(missing.isError, true); + assert.match(missing.data.error, /isn't a file/); + } finally { + await close(); + await rm(projectRoot, { recursive: true, force: true }); + } +}); + +test("devmap_find ranks features, files and routes and rejects empty queries", async () => { + const projectRoot = await projectFromFixture(nextFixture); + const { client, close } = await connect(projectRoot); + + try { + const found = await callTool(client, "devmap_find", { query: "auth" }); + assert.equal(found.isError, false); + assert.ok(found.data.hits.length > 0); + const scores = found.data.hits.map((hit: { score: number }) => hit.score); + assert.deepEqual([...scores].sort((a, b) => b - a), scores); + + const empty = await callTool(client, "devmap_find", { query: " " }); + assert.equal(empty.isError, true); + assert.match(empty.data.hint, /keyword/); + } finally { + await close(); + await rm(projectRoot, { recursive: true, force: true }); + } +}); + +test("tools return a structured, actionable error when there is no snapshot", async () => { + const projectRoot = await mkdtemp(join(tmpdir(), "devmap-mcp-empty-")); + const { client, close } = await connect(projectRoot); + + try { + for (const name of ["devmap_overview", "devmap_map", "devmap_flow", "devmap_routes"]) { + const { isError, data } = await callTool(client, name); + assert.equal(isError, true, name); + assert.match(data.error, /No DevMap snapshot/); + assert.match(data.hint, /devmap analyze/); + } + } finally { + await close(); + await rm(projectRoot, { recursive: true, force: true }); + } +}); + +test("results flag a stale snapshot after the project changes", async () => { + const projectRoot = await mkdtemp(join(tmpdir(), "devmap-mcp-stale-")); + await mkdir(join(projectRoot, "src"), { recursive: true }); + await writeFile(join(projectRoot, "package.json"), JSON.stringify({ name: "stale-app", version: "1.0.0" })); + await writeFile(join(projectRoot, "src", "index.ts"), "export const a = 1;\n"); + await saveSnapshot(projectRoot, await createProjectMap(projectRoot)); + + const { server } = createDevmapServer({ + projectRoot, + provider: createSnapshotProvider(projectRoot, { staleTtlMs: 0 }) + }); + const client = new Client({ name: "test-client", version: "0.0.0" }); + const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); + await Promise.all([server.connect(serverTransport), client.connect(clientTransport)]); + + try { + const fresh = await callTool(client, "devmap_overview"); + assert.equal(fresh.data.snapshot.stale, false); + + await writeFile(join(projectRoot, "src", "extra.ts"), "export const b = 2;\n"); + + const stale = await callTool(client, "devmap_overview"); + assert.equal(stale.data.snapshot.stale, true); + assert.match(stale.data.snapshot.note, /devmap analyze --fresh/); + } finally { + await client.close(); + await server.close(); + await rm(projectRoot, { recursive: true, force: true }); + } +}); + +test("resources expose the snapshot, DEVMAP.md, and per-feature maps", async () => { + const projectRoot = await projectFromFixture(nextFixture, true); + await writeFile(join(projectRoot, "DEVMAP.md"), "# DEVMAP\nhello\n"); + const { client, close } = await connect(projectRoot); + + try { + const { resources } = await client.listResources(); + const uris = resources.map((resource) => resource.uri); + assert.ok(uris.includes("devmap://snapshot")); + assert.ok(uris.includes("devmap://devmap-md")); + const featureUris = uris.filter((uri) => uri.startsWith("devmap://feature/")); + assert.ok(featureUris.length > 0, "expected per-feature resources"); + + const snapshot = await client.readResource({ uri: "devmap://snapshot" }); + assert.equal(JSON.parse((snapshot.contents[0] as { text: string }).text).project.framework, "nextjs"); + + const doc = await client.readResource({ uri: "devmap://devmap-md" }); + assert.match((doc.contents[0] as { text: string }).text, /hello/); + + const feature = await client.readResource({ uri: featureUris[0]! }); + assert.ok(JSON.parse((feature.contents[0] as { text: string }).text)); + } finally { + await close(); + await rm(projectRoot, { recursive: true, force: true }); + } +}); + +test("feature resource rejects path traversal ids", async () => { + const projectRoot = await projectFromFixture(nextFixture, true); + const { client, close } = await connect(projectRoot); + + try { + await assert.rejects(() => client.readResource({ uri: "devmap://feature/..%2F..%2Fsnapshot" })); + await assert.rejects(() => client.readResource({ uri: "devmap://feature/nope" })); + } finally { + await close(); + await rm(projectRoot, { recursive: true, force: true }); + } +}); + +test("snapshot provider re-reads only when snapshot.json changes", async () => { + const projectRoot = await projectFromFixture(nextFixture); + const provider = createSnapshotProvider(projectRoot, { staleTtlMs: 60_000 }); + + try { + const first = await provider.get(); + const second = await provider.get(); + assert.equal(second.snapshot, first.snapshot, "unchanged file should reuse the parsed snapshot"); + + const fresh = await createProjectMap(nextFixture); + await saveSnapshot(projectRoot, { ...fresh, generatedAt: "2099-01-01T00:00:00.000Z" }); + const future = new Date(Date.now() + 5_000); + await utimes(join(projectRoot, ".devmap", "snapshot.json"), future, future); + + const third = await provider.get(); + assert.equal(third.snapshot.generatedAt, "2099-01-01T00:00:00.000Z"); + } finally { + await rm(projectRoot, { recursive: true, force: true }); + } +}); + +test("query helpers: limits, path resolution, and truncation", async () => { + assert.equal(clampLimit(undefined, 20), 20); + assert.equal(clampLimit(0), 1); + assert.equal(clampLimit(10_000), 100); + assert.equal(clampLimit(Number.NaN, 7), 7); + + const short = truncateText("abc"); + assert.deepEqual(short, { text: "abc", truncated: false }); + const long = truncateText("x".repeat(MAX_TEXT_CHARS + 50)); + assert.equal(long.truncated, true); + assert.match(long.text, /truncated 50 characters/); + + const snapshot = await createProjectMap(nextFixture); + assert.throws(() => resolveProjectFile(snapshot, ""), DevmapError); + assert.equal(queryRoutes(snapshot, { method: "ZZZ" }).total, 0); + assert.throws(() => queryImpact(snapshot, "nope/missing.ts"), DevmapError); + assert.ok(queryFind(snapshot, "route", 3).hits.length <= 3); +}); + +test("devmap mcp over stdio keeps stdout protocol-clean and answers real requests", async () => { + const projectRoot = await projectFromFixture(nextFixture); + const transport = new StdioClientTransport({ + command: process.execPath, + args: ["--import", "tsx", join(packageRoot, "src", "index.ts"), "mcp", "--root", projectRoot], + cwd: packageRoot, + stderr: "pipe" + }); + const client = new Client({ name: "stdio-test-client", version: "0.0.0" }); + + try { + // StdioClientTransport parses every stdout line as JSON-RPC; any stray log line + // (spinner, banner, console.log) surfaces as a protocol error and fails here. + const errors: unknown[] = []; + transport.onerror = (error) => errors.push(error); + + await client.connect(transport); + const { tools } = await client.listTools(); + assert.equal(tools.length, 6); + + const overview = await callTool(client, "devmap_overview"); + assert.equal(overview.isError, false); + assert.equal(overview.data.project.framework, "nextjs"); + + const flow = await callTool(client, "devmap_flow"); + assert.equal(flow.isError, false); + + assert.deepEqual(errors, []); + } finally { + await client.close(); + await rm(projectRoot, { recursive: true, force: true }); + } +}); diff --git a/packages/cli/test/package-e2e.mjs b/packages/cli/test/package-e2e.mjs index 9c73fa9..6ae7e5e 100644 --- a/packages/cli/test/package-e2e.mjs +++ b/packages/cli/test/package-e2e.mjs @@ -13,6 +13,8 @@ import { tmpdir } from "node:os"; import { dirname, join, resolve } from "node:path"; import { promisify } from "node:util"; import { fileURLToPath } from "node:url"; +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; const execute = promisify(execFile); const executeShell = promisify(exec); @@ -55,7 +57,7 @@ try { await verifyProject("express-project", "express"); await verifyProject("react-project", "react"); - console.log("Packed CLI E2E passed for Next.js, Express, and React fixtures."); + console.log("Packed CLI E2E passed for Next.js, Express, and React fixtures (CLI + MCP server)."); async function verifyProject(fixtureName, expectedFramework) { const projectRoot = join(temporaryRoot, fixtureName); @@ -142,6 +144,42 @@ try { "--json" ])); assert.equal(doctorJson.config, "missing"); + + await verifyMcpServer(projectRoot, expectedFramework); + } + + async function verifyMcpServer(projectRoot, expectedFramework) { + // The packed package must start as an MCP server, keep stdout protocol-clean + // (the SDK transport reports any non-JSON-RPC line through onerror) and answer tools. + const transport = new StdioClientTransport({ + command: npmExecutable, + args: ["exec", "--", "devmap", "mcp"], + cwd: projectRoot, + env: { + ...process.env, + HOME: isolatedHome, + USERPROFILE: isolatedHome, + NO_COLOR: "1" + }, + stderr: "pipe" + }); + const protocolErrors = []; + transport.onerror = (error) => protocolErrors.push(error); + const client = new Client({ name: "package-e2e", version: "0.0.0" }); + + try { + await client.connect(transport); + const { tools } = await client.listTools(); + assert.ok(tools.some((tool) => tool.name === "devmap_overview")); + + const overview = await client.callTool({ name: "devmap_overview", arguments: {} }); + assert.notEqual(overview.isError, true); + const payload = JSON.parse(overview.content[0].text); + assert.equal(payload.project.framework, expectedFramework); + assert.deepEqual(protocolErrors, []); + } finally { + await client.close(); + } } } finally { await rm(temporaryRoot, { recursive: true, force: true }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a91a894..ee3769e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: packages/cli: dependencies: + '@modelcontextprotocol/sdk': + specifier: ^1.32.2 + version: 1.32.2(zod@4.6.7) commander: specifier: ^15.0.0 version: 15.0.0 @@ -44,6 +47,9 @@ importers: ts-morph: specifier: ^28.0.0 version: 28.0.0 + zod: + specifier: ^4.6.7 + version: 4.6.7 devDependencies: '@types/node': specifier: ^22.20.5 @@ -449,6 +455,12 @@ packages: '@fontsource-variable/geist-mono@5.3.0': resolution: {integrity: sha512-vBbuwDEo9AkrqADMXOrlAR3DFcJi4/JxeuU43FoiQERnNwsfXNnvxvReZG02cQKmyk4DZkZdBZX3oTDvy2zBAw==} + '@hono/node-server@2.1.5': + resolution: {integrity: sha512-MgP/I06GdvsilDspMk/qmRzpk8LOUStP6jFAbSRi40W09DhoyUn+Mj7eyKICPqeYKybOjVFLVIAQzvu1bfIa7Q==} + engines: {node: '>=20'} + peerDependencies: + hono: ^4 || ^5.0.0-0 + '@img/colour@1.1.0': resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} engines: {node: '>=18'} @@ -614,6 +626,16 @@ packages: '@jridgewell/sourcemap-codec@1.6.0': resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + '@modelcontextprotocol/sdk@1.32.2': + resolution: {integrity: sha512-SWsVtH/0vULSQStoZ6xxH2K09Li8fXYTMC64ZeogCdpZ3XSi1eCPOByV1OpuJxuI3OcmHhosDWYyii07tpPsLw==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + '@napi-rs/wasm-runtime@1.2.4': resolution: {integrity: sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g==} engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} @@ -823,6 +845,10 @@ packages: '@vscode/l10n@0.0.18': resolution: {integrity: sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==} + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + ajv-draft-04@1.0.0: resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} peerDependencies: @@ -831,6 +857,14 @@ packages: ajv: optional: true + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + ajv-i18n@4.2.0: resolution: {integrity: sha512-v/ei2UkCEeuKNXh8RToiFsUclmU+G57LO1Oo22OagNMENIw+Yb8eMwvHu7Vn9fmkjJyv6XclhJ8TbuigSglPkg==} peerDependencies: @@ -880,6 +914,10 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} + body-parser@2.4.0: + resolution: {integrity: sha512-Ifk1KcLrxaGt1occQagQNFNNOqRt1LFuehEnHHzB8Z6cRgIxRU1TBaiIsUiCdA+gy5PL7Y1IX31DNKkRmxtXjQ==} + engines: {node: '>=18'} + boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -887,6 +925,18 @@ packages: resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==} engines: {node: 20 || >=22} + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -934,13 +984,41 @@ packages: resolution: {integrity: sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==} engines: {node: '>= 18'} + content-disposition@2.0.1: + resolution: {integrity: sha512-e+H0ZXHSWYrENhQzw1LPuP4oF5MzVKmDU6d3hxlvaPEYLLg62MxtQNPRx4SYSuYJSBUgnQIG4HIN2tEtNv7Dog==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + content-type@2.1.0: + resolution: {integrity: sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==} + engines: {node: '>=18'} + cookie-es@1.2.3: resolution: {integrity: sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==} + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + cookie@2.0.1: resolution: {integrity: sha512-yuToqVvRrj6pfDXREyQAAv8SkAEk/8GS3jQRTiUMm66TVtBYmqQeoEjL2Lmq8Rpo6271vH76InTChTitEAm65w==} engines: {node: '>=22'} + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + crossws@0.3.5: resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} @@ -963,9 +1041,22 @@ packages: resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + defu@6.1.7: resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -1005,19 +1096,42 @@ packages: resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==} engines: {node: '>=4'} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + emmet@2.4.11: resolution: {integrity: sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==} emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + encodeurl@2.0.1: + resolution: {integrity: sha512-Ev/hIyePLZtpL98x562lvPtenrUfGr0lfaf44HFZ+hDEwso7ccV9QAZrEVLg0hxNAzygaAFxy2dYaAKKM0G06A==} + engines: {node: '>= 0.8'} + entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + es-module-lexer@3.0.4: resolution: {integrity: sha512-BldNY5mwkVu+cQ5Ymn3e6hB9Sx8XHx4ZevWbiUyZbfKxJxYO27Hf0wfzkxIaD5CAJMaGl3YspKELj0ff/7nCMA==} + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + esbuild@0.28.2: resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} engines: {node: '>=18'} @@ -1027,9 +1141,34 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + eventemitter3@5.0.4: resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + eventsource-parser@3.1.1: + resolution: {integrity: sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + + express-rate-limit@8.7.1: + resolution: {integrity: sha512-2FXbqkygil3mZiXzEtM0IjE/UBmBNot0J1GEVrJT4JbrvwOSoEtqtO+wHbiMQELWe1eNvHoYRg2dkjOid+6JkA==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@5.3.0: + resolution: {integrity: sha512-qCqy1xSoaugxOD4IVHb1ZWdkvCF2W3aE2okKouWj8VwJ+ztskSiXoBnCSJqfKT+cVPJhuJqe9YLbY425+Zh00g==} + engines: {node: '>= 18'} + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -1057,6 +1196,10 @@ packages: picomatch: optional: true + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + flattie@1.1.1: resolution: {integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==} engines: {node: '>=8'} @@ -1071,11 +1214,22 @@ packages: resolution: {integrity: sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==} engines: {node: '>=20'} + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -1084,6 +1238,14 @@ packages: resolution: {integrity: sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==} engines: {node: '>=18'} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + get-tsconfig@5.0.0-beta.6: resolution: {integrity: sha512-X6fBC0pmImC70gvX2zm56go9hx0MyoGVdG0tUCkg/D+Xnh5TJsOZ7iDbOdI3PvmtrDxnu1YdDufpK2QJX1Meqw==} engines: {node: '>=20.20.0'} @@ -1091,15 +1253,31 @@ packages: github-slugger@2.0.0: resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + h3@1.15.11: resolution: {integrity: sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + hast-util-to-html@9.0.5: resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + hono@4.13.14: + resolution: {integrity: sha512-yhRLQ21U6qa9Hsu2N7Qcbh8tE+WKEcoErjIo56LeSGiGXGp9sA7UpE86Ui9uexb61KakiGwuHMn2QdV7w6i6kQ==} + engines: {node: '>=16.9.0'} + html-escaper@3.0.3: resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} @@ -1109,10 +1287,29 @@ packages: http-cache-semantics@4.3.0: resolution: {integrity: sha512-M5t5LlJpS1UHMjvwRQVdFHvPISGeLAxNcrWuJkeGh0KxsqCHZ1O3NXZU/8x7cD0BDcGW8kapxMKTvwlqrNkHkA==} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + iconv-lite@0.7.3: + resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} + engines: {node: '>=0.10.0'} + ignore@7.0.12: resolution: {integrity: sha512-/8UvqAPU9DGTI9k4mxtf49U37Isfwr8Uts96+SBHIkFxPJnHS0Ew4f00sM4Scd8V8EjM0jUNtVDdv1kPdt35lg==} engines: {node: '>= 4'} + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ip-address@10.7.5: + resolution: {integrity: sha512-t2v0rkILYv9WXmSL7TpAaU3HXgaFYOPUcH3QHgwQ++gpJuH3bqyMy4Fvt/DJCTKwzH3Cf+bAp5W/mFL39KPLzA==} + engines: {node: '>= 12'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + iron-webcrypto@1.2.1: resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} @@ -1125,13 +1322,25 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + jiti@1.21.7: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true + jose@6.2.13: + resolution: {integrity: sha512-7TIg10ZE1x82YediN+lcrPNvGMKge9yFHQD+Qho/b8y2adfugU3AsvvHfxV0fzyFllT6l9xCjf4RWY9g/Z9PJQ==} + json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + jsonc-parser@2.3.1: resolution: {integrity: sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==} @@ -1226,6 +1435,10 @@ packages: magicast@0.5.5: resolution: {integrity: sha512-UicdXN8zQ3JHlxVq+28afMXPr1z7WNY6+7EJnzTdQWkTAlMLF5fNCCKxJHBQwGaNGR11581EiQmQzx73+MvszA==} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + mdast-util-to-hast@13.2.1: resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} @@ -1235,6 +1448,14 @@ packages: mdn-data@2.27.1: resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + media-typer@1.1.1: + resolution: {integrity: sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + micromark-util-character@2.1.1: resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} @@ -1250,6 +1471,14 @@ packages: micromark-util-types@2.0.3: resolution: {integrity: sha512-oxB2Ik03hI0gv+VNn9tnh1t1YEe9MDPptViAEgfdf3YQHsn0pzGTgCdlSCJXcwhqm8phaEuM7zeEu3QQzVBrPg==} + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + minimatch@10.2.6: resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} engines: {node: 18 || 20 || >=22} @@ -1258,6 +1487,9 @@ packages: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} @@ -1266,6 +1498,10 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + negotiator@1.1.0: + resolution: {integrity: sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==} + engines: {node: '>=18'} + neotraverse@1.0.1: resolution: {integrity: sha512-WmmLty1YWwJl9yZi77v2dVIV6X2kuYV8YYBI/G3LWGKdGHmHUvL1z7FW0iDvEvGAwNEoc5x1tOOOyDnf5jJw/w==} engines: {node: '>= 10'} @@ -1286,6 +1522,14 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + obug@3.0.0: resolution: {integrity: sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==} engines: {node: '>=12.20.0'} @@ -1293,6 +1537,13 @@ packages: ofetch@1.5.1: resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} + on-finished@2.4.2: + resolution: {integrity: sha512-ATWUv387i+nLD49EiDoOft5scf32Vsdz3ktrQJG9bi7jRFmaXTssvyvj1EmeKVVgvesbqUtvQQvaS2TpTukAZQ==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + oniguruma-parser@0.12.2: resolution: {integrity: sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==} @@ -1314,9 +1565,20 @@ packages: package-manager-detector@1.9.0: resolution: {integrity: sha512-zAMfbta9nPA8AKkA4WdmNAT8R8s6Dk0/xwPsqNnRidMFcLo1YkLuPDTgs2f64XdzS8vbLNnoJ+uUIkgbz7oRHA==} + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + piccolore@0.1.3: resolution: {integrity: sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==} @@ -1331,6 +1593,10 @@ packages: resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} engines: {node: '>=12'} + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + postcss@8.5.30: resolution: {integrity: sha512-zkd+e9S1AQHhmaBKye9zlFHWjOwVgqXl1q3ECYy8561K7qxR2cREf/STLskulBFd78k6AOFeA+ff1TtEzk9bAA==} engines: {node: ^10 || ^12 || >=14} @@ -1351,9 +1617,25 @@ packages: property-information@7.2.0: resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} + proxy-addr@2.0.8: + resolution: {integrity: sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==} + engines: {node: '>= 0.10'} + + qs@6.17.1: + resolution: {integrity: sha512-jd3COkaKVgGUW+ZHyEHCG9bH7RoaaiWuMgzsIBjUMVjLAaWGWpoUAVwljVvBHxRu/2qEHnadLGZrkutYUBPQJA==} + engines: {node: '>=0.6'} + radix3@1.1.2: resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} + range-parser@1.3.0: + resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} @@ -1392,6 +1674,13 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + satteri@0.10.5: resolution: {integrity: sha512-Ao1LKpAEa9Wdg0otgbVKViZHEq9ebdXe4DMrp3s9vQAU0HNIuHnFEuMuOcm0ZIXyV0Yzxj91NvhLpvXZJO/5ZQ==} @@ -1404,6 +1693,17 @@ packages: engines: {node: '>=10'} hasBin: true + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + sharp@0.35.5: resolution: {integrity: sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==} engines: {node: '>=20.9.0'} @@ -1413,10 +1713,34 @@ packages: '@types/node': optional: true + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + shiki@4.5.0: resolution: {integrity: sha512-OcRW72ngQvc+MSVWBRIovC/BTW589iUE/Vxav7VeM4RcY9MUjqB+IR6ILG1k9nuW6LiG/nct9uma4/MBtnZbmQ==} engines: {node: '>=20'} + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -1431,6 +1755,10 @@ packages: space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + std-env@4.3.0: resolution: {integrity: sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==} @@ -1472,6 +1800,10 @@ packages: tinyps@0.5.0: resolution: {integrity: sha512-Llu7nEtVOGHh7jok32l6+udlu+4Np5vsYVB7zeTK614coSsro2qjwte7MFt/ouLM/h4kXqbGXktSMMvMsttSDw==} + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} @@ -1489,6 +1821,10 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} + typesafe-path@0.2.2: resolution: {integrity: sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==} @@ -1540,6 +1876,10 @@ packages: unist-util-visit@5.1.0: resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + unstorage@1.17.6: resolution: {integrity: sha512-gjKtcmn/KwZuJZO9o2sc0esAQnhmTw0LviDHwevTXbJMYs0dRTp8ZEEWXHU0vTpzbN3cA0yxlTeyXssMp3zDqw==} peerDependencies: @@ -1602,6 +1942,10 @@ packages: uploadthing: optional: true + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + verkit@0.5.0: resolution: {integrity: sha512-u4J1hr6Y+C/hit34GwL3eIcUf3wngT8TlYUjK7tk0jxydKLAq3sYvifjdTDA6U3KCKRcQCQDGf/srjxfZF3l7g==} engines: {node: '>=18.12.0'} @@ -1765,10 +2109,18 @@ packages: vscode-uri@3.2.0: resolution: {integrity: sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg==} + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + wrap-ansi@9.0.2: resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} engines: {node: '>=18'} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + xxhash-wasm@1.1.0: resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==} @@ -1802,6 +2154,11 @@ packages: resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} engines: {node: '>=12.20'} + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + peerDependencies: + zod: ^3.25.28 || ^4 + zod@4.6.7: resolution: {integrity: sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==} @@ -2134,6 +2491,10 @@ snapshots: '@fontsource-variable/geist-mono@5.3.0': {} + '@hono/node-server@2.1.5(hono@4.13.14)': + dependencies: + hono: 4.13.14 + '@img/colour@1.1.0': optional: true @@ -2243,6 +2604,28 @@ snapshots: '@jridgewell/sourcemap-codec@1.6.0': {} + '@modelcontextprotocol/sdk@1.32.2(zod@4.6.7)': + dependencies: + '@hono/node-server': 2.1.5(hono@4.13.14) + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.1.1 + express: 5.3.0 + express-rate-limit: 8.7.1(express@5.3.0) + hono: 4.13.14 + jose: 6.2.13 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 4.6.7 + zod-to-json-schema: 3.25.2(zod@4.6.7) + transitivePeerDependencies: + - supports-color + '@napi-rs/wasm-runtime@1.2.4(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': dependencies: '@emnapi/core': 1.11.1 @@ -2445,10 +2828,19 @@ snapshots: '@vscode/l10n@0.0.18': {} + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.1.0 + ajv-draft-04@1.0.0(ajv@8.20.0): optionalDependencies: ajv: 8.20.0 + ajv-formats@3.0.1(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + ajv-i18n@4.2.0(ajv@8.20.0): dependencies: ajv: 8.20.0 @@ -2570,12 +2962,38 @@ snapshots: balanced-match@4.0.4: {} + body-parser@2.4.0: + dependencies: + bytes: 3.1.2 + content-type: 2.1.0 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + on-finished: 2.4.2 + qs: 6.17.1 + raw-body: 3.0.2 + type-is: 2.1.0 + transitivePeerDependencies: + - supports-color + boolbase@1.0.0: {} brace-expansion@5.0.12: dependencies: balanced-match: 4.0.4 + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + ccount@2.0.1: {} character-entities-html4@2.1.0: {} @@ -2610,10 +3028,31 @@ snapshots: common-ancestor-path@2.0.0: {} + content-disposition@2.0.1: {} + + content-type@1.0.5: {} + + content-type@2.1.0: {} + cookie-es@1.2.3: {} + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + cookie@2.0.1: {} + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + crossws@0.3.5: dependencies: uncrypto: 0.1.3 @@ -2642,8 +3081,14 @@ snapshots: dependencies: css-tree: 2.2.1 + debug@4.4.3: + dependencies: + ms: 2.1.3 + defu@6.1.7: {} + depd@2.0.0: {} + dequal@2.0.3: {} destr@2.0.5: {} @@ -2678,6 +3123,14 @@ snapshots: dset@3.1.4: {} + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + emmet@2.4.11: dependencies: '@emmetio/abbreviation': 2.3.3 @@ -2685,10 +3138,20 @@ snapshots: emoji-regex@10.6.0: {} + encodeurl@2.0.1: {} + entities@4.5.0: {} + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + es-module-lexer@3.0.4: {} + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + esbuild@0.28.2: optionalDependencies: '@esbuild/aix-ppc64': 0.28.2 @@ -2720,8 +3183,59 @@ snapshots: escalade@3.2.0: {} + escape-html@1.0.3: {} + + etag@1.8.1: {} + eventemitter3@5.0.4: {} + eventsource-parser@3.1.1: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.1.1 + + express-rate-limit@8.7.1(express@5.3.0): + dependencies: + debug: 4.4.3 + express: 5.3.0 + ip-address: 10.7.5 + transitivePeerDependencies: + - supports-color + + express@5.3.0: + dependencies: + accepts: 2.0.0 + body-parser: 2.4.0 + content-disposition: 2.0.1 + content-type: 2.1.0 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.1 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.2 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.8 + qs: 6.17.1 + range-parser: 1.3.0 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.1.0 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + extend@3.0.2: {} fast-deep-equal@3.1.3: {} @@ -2742,6 +3256,17 @@ snapshots: optionalDependencies: picomatch: 4.0.7 + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.1 + escape-html: 1.0.3 + on-finished: 2.4.2 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + flattie@1.1.1: {} fnv1a-64@0.1.2: {} @@ -2754,19 +3279,45 @@ snapshots: dependencies: tiny-inflate: 1.0.3 + forwarded@0.2.0: {} + + fresh@2.0.0: {} + fsevents@2.3.3: optional: true + function-bind@1.1.2: {} + get-caller-file@2.0.5: {} get-east-asian-width@1.7.0: {} + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + get-tsconfig@5.0.0-beta.6: dependencies: resolve-pkg-maps: 1.0.0 github-slugger@2.0.0: {} + gopd@1.2.0: {} + h3@1.15.11: dependencies: cookie-es: 1.2.3 @@ -2779,6 +3330,12 @@ snapshots: ufo: 1.6.4 uncrypto: 0.1.3 + has-symbols@1.1.0: {} + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + hast-util-to-html@9.0.5: dependencies: '@types/hast': 3.0.5 @@ -2797,25 +3354,53 @@ snapshots: dependencies: '@types/hast': 3.0.5 + hono@4.13.14: {} + html-escaper@3.0.3: {} html-void-elements@3.0.0: {} http-cache-semantics@4.3.0: {} + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + iconv-lite@0.7.3: + dependencies: + safer-buffer: 2.1.2 + ignore@7.0.12: {} + inherits@2.0.4: {} + + ip-address@10.7.5: {} + + ipaddr.js@1.9.1: {} + iron-webcrypto@1.2.1: {} is-docker@4.0.0: {} is-plain-obj@4.1.0: {} + is-promise@4.0.0: {} + + isexe@2.0.0: {} + jiti@1.21.7: optional: true + jose@6.2.13: {} + json-schema-traverse@1.0.0: {} + json-schema-typed@8.0.2: {} + jsonc-parser@2.3.1: {} jsonc-parser@3.3.1: {} @@ -2883,6 +3468,8 @@ snapshots: '@babel/types': 7.29.8 source-map-js: 1.2.2 + math-intrinsics@1.1.0: {} + mdast-util-to-hast@13.2.1: dependencies: '@types/hast': 3.0.5 @@ -2899,6 +3486,10 @@ snapshots: mdn-data@2.27.1: {} + media-typer@1.1.1: {} + + merge-descriptors@2.0.0: {} + micromark-util-character@2.1.1: dependencies: micromark-util-symbol: 2.0.1 @@ -2916,16 +3507,28 @@ snapshots: micromark-util-types@2.0.3: {} + mime-db@1.54.0: {} + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + minimatch@10.2.6: dependencies: brace-expansion: 5.0.12 mrmime@2.0.1: {} + ms@2.1.3: {} + muggle-string@0.4.1: {} nanoid@3.3.20: {} + negotiator@1.1.0: + dependencies: + content-type: 2.1.0 + neotraverse@1.0.1: {} nlcst-to-string@4.0.0: @@ -2942,6 +3545,10 @@ snapshots: dependencies: boolbase: 1.0.0 + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + obug@3.0.0: {} ofetch@1.5.1: @@ -2950,6 +3557,14 @@ snapshots: node-fetch-native: 1.6.7 ufo: 1.6.4 + on-finished@2.4.2: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + oniguruma-parser@0.12.2: {} oniguruma-to-es@4.3.6: @@ -2971,8 +3586,14 @@ snapshots: package-manager-detector@1.9.0: {} + parseurl@1.3.3: {} + path-browserify@1.0.1: {} + path-key@3.1.1: {} + + path-to-regexp@8.4.2: {} + piccolore@0.1.3: {} picocolors@1.1.1: {} @@ -2981,6 +3602,8 @@ snapshots: picomatch@4.0.7: {} + pkce-challenge@5.0.1: {} + postcss@8.5.30: dependencies: nanoid: 3.3.20 @@ -2995,8 +3618,27 @@ snapshots: property-information@7.2.0: {} + proxy-addr@2.0.8: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + qs@6.17.1: + dependencies: + es-define-property: 1.0.1 + side-channel: 1.1.1 + radix3@1.1.2: {} + range-parser@1.3.0: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + unpipe: 1.0.0 + readdirp@4.1.2: {} readdirp@5.1.1: {} @@ -3046,6 +3688,18 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.2.14 '@rolldown/binding-win32-x64-msvc': 1.2.14 + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + + safer-buffer@2.1.2: {} + satteri@0.10.5: dependencies: '@types/estree-jsx': 1.0.5 @@ -3067,6 +3721,33 @@ snapshots: semver@7.8.5: {} + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.1 + 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.2 + range-parser: 1.3.0 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.1 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + sharp@0.35.5(@types/node@25.9.1): dependencies: '@img/colour': 1.1.0 @@ -3101,6 +3782,12 @@ snapshots: '@types/node': 25.9.1 optional: true + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + shiki@4.5.0: dependencies: '@shikijs/core': 4.5.0 @@ -3112,6 +3799,34 @@ snapshots: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.5 + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + sisteransi@1.0.5: {} smol-toml@1.9.1: {} @@ -3120,6 +3835,8 @@ snapshots: space-separated-tokens@2.0.2: {} + statuses@2.0.2: {} + std-env@4.3.0: {} string-width@7.2.0: @@ -3165,6 +3882,8 @@ snapshots: tinyps@0.5.0: {} + toidentifier@1.0.1: {} + trim-lines@3.0.1: {} trough@2.2.0: {} @@ -3182,6 +3901,12 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + type-is@2.1.0: + dependencies: + content-type: 2.1.0 + media-typer: 1.1.1 + mime-types: 3.0.2 + typesafe-path@0.2.2: {} typescript-auto-import-cache@0.3.6: @@ -3243,6 +3968,8 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 + unpipe@1.0.0: {} + unstorage@1.17.6: dependencies: anymatch: 3.1.3 @@ -3254,6 +3981,8 @@ snapshots: ofetch: 1.5.1 ufo: 1.6.4 + vary@1.1.2: {} + verkit@0.5.0: {} vfile-message@4.0.3: @@ -3391,12 +4120,18 @@ snapshots: vscode-uri@3.2.0: {} + which@2.0.2: + dependencies: + isexe: 2.0.0 + wrap-ansi@9.0.2: dependencies: ansi-styles: 6.2.3 string-width: 7.2.0 strip-ansi: 7.2.0 + wrappy@1.0.2: {} + xxhash-wasm@1.1.0: {} y18n@5.0.8: {} @@ -3433,6 +4168,10 @@ snapshots: yocto-queue@1.2.2: {} + zod-to-json-schema@3.25.2(zod@4.6.7): + dependencies: + zod: 4.6.7 + zod@4.6.7: {} zwitch@2.0.4: {}