@aprovan/patchwork-mcp 0.1.0-dev.5e426ca → 0.1.0-dev.78e095c
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/runtime/assets/index-v7RhD69T.js +1059 -0
- package/dist/runtime/index.html +1 -1
- package/dist/server.js +1 -0
- package/dist/server.js.map +1 -1
- package/package.json +4 -4
- package/dist/runtime/assets/index-Da_Aeq1x.js +0 -1060
package/dist/runtime/index.html
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
white-space: pre-wrap;
|
|
30
30
|
}
|
|
31
31
|
</style>
|
|
32
|
-
<script type="module" crossorigin src="./assets/index-
|
|
32
|
+
<script type="module" crossorigin src="./assets/index-v7RhD69T.js"></script>
|
|
33
33
|
</head>
|
|
34
34
|
<body>
|
|
35
35
|
<div id="pw-status">Loading runtime…</div>
|
package/dist/server.js
CHANGED
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts","../src/logger.ts","../src/live-update.ts","../src/registry-backend.ts","../src/tunnel.ts","../src/widget-store/store.ts","../src/widget-store/local-backend.ts","../src/index.ts","../src/services.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { createMcpExpressApp } from \"@modelcontextprotocol/sdk/server/express.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\nimport cors from \"cors\";\nimport express from \"express\";\nimport { registerSession, unregisterSession } from \"./live-update.js\";\nimport { log, error } from \"./logger.js\";\nimport { createRegistryBackend, type RegistryBackend } from \"./registry-backend.js\";\nimport { startTunnel, stopTunnel } from \"./tunnel.js\";\nimport { getWidgetStore } from \"./widget-store/index.js\";\nimport { createMcpAppServer, type McpAppServerOptions } from \"./index.js\";\nimport type { Request, Response } from \"express\";\n\nconst PORT = Number(process.env[\"PORT\"] ?? 3000);\nconst WIDGET_PORT = Number(process.env[\"WIDGET_PORT\"] ?? 3002);\nconst HOST = process.env[\"HOST\"] ?? \"0.0.0.0\";\nconst TRANSPORT = process.env[\"TRANSPORT\"] ?? (process.argv.includes(\"--stdio\") ? \"stdio\" : \"http\");\nconst WIDGET_TUNNEL = process.env[\"WIDGET_TUNNEL\"] === \"true\" || process.argv.includes(\"--tunnel\");\n\ntype SessionEntry = {\n server: ReturnType<typeof createMcpAppServer>;\n transport: StreamableHTTPServerTransport;\n};\n\n/**\n * Cross-process coordination for the shared widget host.\n *\n * Every stdio spawn of this server (Claude Desktop respawns it freely) would\n * otherwise start its own widget server and its own cloudflared quick tunnel.\n * Only one can bind {@link WIDGET_PORT}; the rest orphan extra tunnels and, worse,\n * each render returns a *different* hostname — and any instance whose ephemeral\n * tunnel has dropped serves a dead URL (Cloudflare 1033) into the widget HTML.\n *\n * We elect a single owner via the port bind: whoever binds the port establishes\n * (and verifies) the tunnel and publishes its base URL to a temp file keyed by\n * port; every other instance reuses that URL instead of starting a tunnel.\n */\ninterface WidgetHostState {\n baseUrl: string;\n pid: number;\n updatedAt: number;\n}\n\nconst WIDGET_STATE_FILE = join(tmpdir(), `patchwork-widget-${WIDGET_PORT}.json`);\n\nfunction readWidgetHostState(): WidgetHostState | null {\n try {\n return JSON.parse(readFileSync(WIDGET_STATE_FILE, \"utf8\")) as WidgetHostState;\n } catch {\n return null;\n }\n}\n\nfunction publishWidgetHostState(baseUrl: string): void {\n try {\n const state: WidgetHostState = { baseUrl, pid: process.pid, updatedAt: Date.now() };\n writeFileSync(WIDGET_STATE_FILE, JSON.stringify(state));\n } catch (err) {\n error(\"mcp-app-server\", \"Failed to publish widget host state:\", err);\n }\n}\n\nfunction isProcessAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n // ESRCH = no such process; EPERM = exists but not ours (still alive).\n return (err as NodeJS.ErrnoException).code === \"EPERM\";\n }\n}\n\n/**\n * Wait for the owning instance to publish its (verified) base URL. Only adopt\n * state whose owning pid is still alive, so a non-owner never reuses a dead\n * previous owner's (now-1033) hostname.\n */\nasync function awaitPublishedWidgetBaseUrl(timeoutMs = 35000): Promise<string | null> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n const state = readWidgetHostState();\n if (state?.baseUrl && isProcessAlive(state.pid)) return state.baseUrl;\n await new Promise((r) => setTimeout(r, 500));\n }\n return null;\n}\n\n/** Locate a built browser bundle dir (dist/<name>) from either dist or src. */\nfunction resolveDistDir(name: string): string {\n const candidates = [\n fileURLToPath(new URL(`./${name}`, import.meta.url)), // built: dist/<name>\n fileURLToPath(new URL(`../dist/${name}`, import.meta.url)), // dev via tsx: src → dist\n ];\n return candidates.find((dir) => existsSync(dir)) ?? candidates[0]!;\n}\n\n/** Locate the built runtime bundle (dist/runtime). */\nexport function resolveRuntimeDir(): string {\n return resolveDistDir(\"runtime\");\n}\n\n/** Locate the built MCP App shell bundle (dist/shell). */\nexport function resolveShellDir(): string {\n return resolveDistDir(\"shell\");\n}\n\nasync function setupRegistryBackend(): Promise<{\n registryBackend: RegistryBackend | null;\n serverOptions: McpAppServerOptions;\n}> {\n const toolboxUrl = process.env[\"TOOLBOX_MCP_URL\"];\n\n if (!toolboxUrl) {\n return { registryBackend: null, serverOptions: {} };\n }\n\n log(\"patchwork-mcp\", `Connecting to toolbox at ${toolboxUrl}`);\n\n try {\n const registryBackend = await createRegistryBackend({\n name: \"toolbox\",\n url: toolboxUrl,\n headers: process.env[\"APROVAN_WORKSPACE_ID\"]\n ? { \"X-Aprovan-Workspace\": process.env[\"APROVAN_WORKSPACE_ID\"] }\n : undefined,\n auth: process.env[\"TOOLBOX_TOKEN\"]\n ? { type: \"bearer\", token: process.env[\"TOOLBOX_TOKEN\"] }\n : { type: \"none\" },\n });\n\n const toolInfos = registryBackend.getToolInfos();\n const namespaces = [...new Set(toolInfos.map((t) => t.namespace))];\n log(\n \"patchwork-mcp\",\n `Registry ready: ${toolInfos.length} tools across namespaces: ${namespaces.join(\", \")}`\n );\n\n return {\n registryBackend,\n serverOptions: {\n services: {\n backend: registryBackend,\n tools: toolInfos,\n },\n },\n };\n } catch (err) {\n error(\"patchwork-mcp\", \"Failed to connect to toolbox:\", err);\n error(\"patchwork-mcp\", \"Starting without toolbox services.\");\n return { registryBackend: null, serverOptions: {} };\n }\n}\n\nfunction handleExistingSession(existing: SessionEntry, req: Request, res: Response): void {\n try {\n existing.transport.handleRequest(req, res, req.body);\n } catch (err) {\n error(\"mcp\", \"session request error\", err);\n if (!res.headersSent) {\n res.status(500).json({ error: \"Internal server error\" });\n }\n }\n}\n\nasync function createNewSession(\n serverOptions: McpAppServerOptions,\n sessionStore: Map<string, SessionEntry>,\n req: Request,\n res: Response\n): Promise<void> {\n const mcpServer = createMcpAppServer(serverOptions);\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: () => randomUUID(),\n onsessioninitialized: (id) => {\n sessionStore.set(id, { server: mcpServer, transport });\n registerSession(id, mcpServer);\n },\n onsessionclosed: (id) => {\n sessionStore.delete(id);\n unregisterSession(id);\n },\n });\n\n res.on(\"close\", () => {\n const id = transport.sessionId;\n if (id && !sessionStore.has(id)) {\n void mcpServer.close();\n }\n });\n\n try {\n await mcpServer.connect(transport);\n await transport.handleRequest(req, res, req.body);\n } catch (err) {\n error(\"mcp\", \"new session error\", err);\n if (!res.headersSent) {\n res.status(500).json({ error: \"Internal server error\" });\n }\n }\n}\n\nfunction registerMcpEndpoint(\n app: ReturnType<typeof createMcpExpressApp>,\n serverOptions: McpAppServerOptions\n): Map<string, SessionEntry> {\n const sessionStore = new Map<string, SessionEntry>();\n\n app.all(\"/mcp\", async (req, res) => {\n const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n\n if (sessionId) {\n const existing = sessionStore.get(sessionId);\n if (existing) {\n handleExistingSession(existing, req, res);\n return;\n }\n }\n\n await createNewSession(serverOptions, sessionStore, req, res);\n });\n\n return sessionStore;\n}\n\nfunction registerShutdownHandlers(registryBackend: RegistryBackend | null): void {\n const shutdown = () => {\n if (registryBackend) {\n registryBackend.close().catch(() => {\n /* ignore close errors on exit */\n });\n }\n process.exit(0);\n };\n process.on(\"SIGTERM\", shutdown);\n process.on(\"SIGINT\", shutdown);\n}\n\nasync function startServer(): Promise<void> {\n const { registryBackend, serverOptions } = await setupRegistryBackend();\n\n const app = createMcpExpressApp({ host: HOST });\n app.use(cors());\n\n registerMcpEndpoint(app, serverOptions);\n\n app.get(\"/health\", (_req, res) => {\n res.json({ status: \"ok\", service: \"patchwork-mcp-app-server\" });\n });\n\n app.listen(PORT, HOST, () => {\n log(\"mcp-app-server\", `MCP App Server listening on http://${HOST}:${PORT}`);\n log(\"mcp-app-server\", ` POST /mcp — MCP Streamable HTTP endpoint (stateful sessions)`);\n log(\"mcp-app-server\", ` GET /health — health check`);\n log(\"mcp-app-server\", \"\");\n log(\"mcp-app-server\", \"To expose locally via cloudflared:\");\n log(\"mcp-app-server\", ` cloudflared tunnel --url http://localhost:${PORT}`);\n });\n\n registerShutdownHandlers(registryBackend);\n}\n\nasync function startWidgetServer(): Promise<string> {\n const widgetApp = express();\n widgetApp.use(cors());\n\n const store = getWidgetStore();\n\n // Serve the MCP App shell (resource document's external script) and the shared\n // browser runtime bundle (compiles widgets in-browser). Both resolve to dist\n // whether running built (dist/server.js) or via tsx (src).\n widgetApp.use(\"/shell\", express.static(resolveShellDir()));\n widgetApp.use(\"/runtime\", express.static(resolveRuntimeDir()));\n\n // Raw widget source files — fetched by the runtime and compiled in-browser.\n widgetApp.get(\"/widget/:name/:hash/files\", async (req, res) => {\n const { name, hash } = req.params;\n try {\n const widget = await store.getWidget(name, hash);\n if (!widget) {\n res.status(404).json({ error: \"Widget not found\" });\n return;\n }\n res.json({ files: widget.files, entry: widget.entry, manifest: widget.manifest });\n } catch (err) {\n error(\"widget-server\", \"Error serving widget files:\", err);\n res.status(500).json({ error: \"Internal server error\" });\n }\n });\n\n // Convenience redirect: /widget/:name/:hash → runtime host with the widget preselected.\n widgetApp.get(\"/widget/:name/:hash\", (req, res) => {\n const { name, hash } = req.params;\n res.redirect(\n 302,\n `/runtime/?widget=${encodeURIComponent(name)}/${encodeURIComponent(hash)}`,\n );\n });\n\n widgetApp.get(\"/health\", (_req, res) => {\n res.json({ status: \"ok\", service: \"patchwork-widget-server\" });\n });\n\n // Elect the widget-host owner via the port bind. A failed bind (EADDRINUSE)\n // means another instance already owns the widget server + tunnel.\n const isOwner = await new Promise<boolean>((resolve) => {\n const httpServer = widgetApp.listen(WIDGET_PORT, HOST, () => {\n log(\"mcp-app-server\", `Widget server listening on http://${HOST}:${WIDGET_PORT}`);\n resolve(true);\n });\n httpServer.on(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\") {\n log(\n \"mcp-app-server\",\n `Widget port ${WIDGET_PORT} already owned by another instance; reusing its widget host.`,\n );\n } else {\n error(\"mcp-app-server\", \"Widget server failed to bind:\", err);\n }\n resolve(false);\n });\n });\n\n if (!isOwner) {\n // Reuse the owner's verified, published base URL so every instance hands out\n // the same live hostname instead of orphaning another (possibly dead) tunnel.\n const shared = await awaitPublishedWidgetBaseUrl();\n if (shared) {\n log(\"mcp-app-server\", `Reusing shared widget host: ${shared}`);\n return shared;\n }\n error(\n \"mcp-app-server\",\n \"Owner instance never published a base URL; falling back to localhost (widgets may not load in remote hosts).\",\n );\n return `http://localhost:${WIDGET_PORT}`;\n }\n\n // We own the widget server. Establish (and verify) the tunnel, then publish\n // the base URL for sibling instances.\n let widgetBaseUrl = `http://localhost:${WIDGET_PORT}`;\n\n if (WIDGET_TUNNEL) {\n try {\n widgetBaseUrl = await startTunnel(WIDGET_PORT);\n log(\"mcp-app-server\", `Widgets accessible at: ${widgetBaseUrl}`);\n } catch (err) {\n error(\"mcp-app-server\", \"Failed to start tunnel, using localhost:\", err);\n }\n }\n\n publishWidgetHostState(widgetBaseUrl);\n return widgetBaseUrl;\n}\n\nasync function startStdioServer(): Promise<void> {\n const { registryBackend, serverOptions } = await setupRegistryBackend();\n\n log(\"mcp-app-server\", \"Starting MCP App Server in stdio mode...\");\n\n // Start widget server and optionally tunnel\n const widgetBaseUrl = await startWidgetServer();\n\n // Create MCP server with widget base URL\n const mcpServer = createMcpAppServer({\n ...serverOptions,\n widgetBaseUrl,\n });\n const transport = new StdioServerTransport();\n\n await mcpServer.connect(transport);\n\n const shutdown = () => {\n stopTunnel();\n if (registryBackend) {\n registryBackend.close().catch(() => {});\n }\n process.exit(0);\n };\n process.on(\"SIGTERM\", shutdown);\n process.on(\"SIGINT\", shutdown);\n}\n\nasync function main(): Promise<void> {\n if (TRANSPORT === \"stdio\") {\n await startStdioServer();\n } else {\n await startServer();\n }\n}\n\nmain().catch((err: unknown) => {\n error(\"mcp-app-server\", \"Fatal startup error:\", err);\n process.exit(1);\n});\n","type LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\nconst CURRENT_LEVEL: LogLevel =\n (process.env['LOG_LEVEL'] as LogLevel | undefined) ?? 'info';\n\nconst LEVEL_RANK: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n};\n\nfunction shouldLog(level: LogLevel): boolean {\n return LEVEL_RANK[level] >= LEVEL_RANK[CURRENT_LEVEL];\n}\n\nfunction formatMessage(tag: string, ...args: unknown[]): unknown[] {\n return [`[${tag}]`, ...args];\n}\n\n// All logging goes to stderr to avoid interfering with MCP stdio protocol on stdout\nexport function debug(tag: string, ...args: unknown[]): void {\n if (shouldLog('debug')) {\n console.error(...formatMessage(tag, ...args));\n }\n}\n\nexport function log(tag: string, ...args: unknown[]): void {\n if (shouldLog('info')) {\n console.error(...formatMessage(tag, ...args));\n }\n}\n\nexport function warn(tag: string, ...args: unknown[]): void {\n if (shouldLog('warn')) {\n console.error(...formatMessage(tag, ...args));\n }\n}\n\nexport function error(tag: string, ...args: unknown[]): void {\n if (shouldLog('error')) {\n console.error(...formatMessage(tag, ...args));\n }\n}\n","import { type McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { warn } from \"./logger.js\";\n\n/**\n * A single buffered event for a data stream.\n */\nexport interface StreamEvent {\n seq: number;\n data: unknown;\n timestamp: number;\n}\n\nconst RING_BUFFER_MAX = 100;\n\n/**\n * Process-level ring buffer for each named stream.\n * Shared across all sessions so any request can read from it.\n */\nconst streamBuffers = new Map<string, StreamEvent[]>();\nlet globalSeq = 0;\n\nfunction getOrCreateBuffer(stream: string): StreamEvent[] {\n let buf = streamBuffers.get(stream);\n if (!buf) {\n buf = [];\n streamBuffers.set(stream, buf);\n }\n return buf;\n}\n\n/**\n * Retrieve buffered events for a stream with sequence numbers > afterSeq.\n */\nexport function getEvents(stream: string, afterSeq: number): StreamEvent[] {\n const buf = streamBuffers.get(stream);\n if (!buf) return [];\n return buf.filter((e) => e.seq > afterSeq);\n}\n\n/**\n * Write an event into the ring buffer for a stream.\n * Evicts the oldest entry when the buffer is full.\n */\nexport function appendEvent(stream: string, data: unknown): StreamEvent {\n const buf = getOrCreateBuffer(stream);\n const event: StreamEvent = {\n seq: ++globalSeq,\n data,\n timestamp: Date.now(),\n };\n buf.push(event);\n if (buf.length > RING_BUFFER_MAX) {\n buf.shift();\n }\n return event;\n}\n\n/**\n * Per-session subscription tracking.\n * A session is keyed by the MCP session ID; its McpServer is used to send\n * `notifications/tools/list_changed` as a push signal when stream events arrive.\n */\ninterface SessionEntry {\n server: McpServer;\n streams: Set<string>;\n}\n\nconst sessions = new Map<string, SessionEntry>();\n\n/**\n * Register a live MCP session so it can receive push notifications.\n */\nexport function registerSession(sessionId: string, server: McpServer): void {\n if (!sessions.has(sessionId)) {\n sessions.set(sessionId, { server, streams: new Set() });\n }\n}\n\n/**\n * Unregister a session (e.g. on transport close or DELETE).\n */\nexport function unregisterSession(sessionId: string): void {\n sessions.delete(sessionId);\n}\n\n/**\n * Subscribe a session to a named stream.\n */\nexport function subscribeSession(sessionId: string, stream: string): void {\n const entry = sessions.get(sessionId);\n if (entry) {\n entry.streams.add(stream);\n }\n}\n\n/**\n * Unsubscribe a session from a named stream.\n */\nexport function unsubscribeSession(sessionId: string, stream: string): void {\n const entry = sessions.get(sessionId);\n if (entry) {\n entry.streams.delete(stream);\n }\n}\n\n/**\n * Push a data update to all sessions that have subscribed to `stream`.\n *\n * The event is appended to the ring buffer. Each subscribed session's McpServer\n * then sends a `notifications/tools/list_changed` notification to the host.\n * The host forwards this to the widget, which uses it as a signal to call\n * `poll_updates` and retrieve the buffered data.\n *\n * @returns The new sequence number assigned to this event.\n */\nexport async function pushStreamUpdate(\n stream: string,\n data: unknown,\n): Promise<number> {\n const event = appendEvent(stream, data);\n\n const pushPromises: Promise<void>[] = [];\n for (const [, entry] of sessions) {\n if (entry.streams.has(stream)) {\n const notifyPromise = entry.server.server\n .notification({ method: \"notifications/tools/list_changed\" })\n .catch((err: unknown) => {\n warn(\"live-update\", \"Failed to notify session:\", err);\n });\n pushPromises.push(notifyPromise);\n }\n }\n\n await Promise.all(pushPromises);\n return event.seq;\n}\n\n/**\n * Return the current highest sequence number (useful for initial subscribe).\n */\nexport function currentSeq(): number {\n return globalSeq;\n}\n\n/** Clears all buffers and sessions — for testing only. */\nexport function _resetForTests(): void {\n streamBuffers.clear();\n sessions.clear();\n globalSeq = 0;\n}\n","import {\n PatchworkMcpClient,\n type McpServerConfig,\n type PatchworkTool,\n} from \"@aprovan/patchwork\";\nimport type { ServiceBackend, ServiceToolInfo } from \"./services.js\";\n\nexport type RegistryBackendOptions = Omit<McpServerConfig, \"mode\">;\n\nexport interface RegistryBackend extends ServiceBackend {\n getToolInfos(): ServiceToolInfo[];\n readArtifact(uri: string): Promise<unknown>;\n close(): Promise<void>;\n}\n\nfunction splitTool(tool: PatchworkTool): ServiceToolInfo {\n const separator = tool.name.indexOf(\".\");\n return {\n name: tool.name,\n namespace: separator < 0 ? tool.name : tool.name.slice(0, separator),\n procedure: separator < 0 ? tool.name : tool.name.slice(separator + 1),\n description: tool.description,\n parameters:\n tool.inputSchema && typeof tool.inputSchema === \"object\"\n ? (tool.inputSchema as Record<string, unknown>)\n : undefined,\n };\n}\n\nexport async function createRegistryBackend(\n options: RegistryBackendOptions,\n): Promise<RegistryBackend> {\n const client = new PatchworkMcpClient({ ...options, mode: \"toolbox\" });\n await client.connect();\n const tools = (await client.listTools()).map(splitTool);\n return {\n call(namespace, procedure, args) {\n return client.callTool(\n `${namespace}.${procedure}`,\n (args[0] as Record<string, unknown> | undefined) ?? {},\n );\n },\n getToolInfos: () => tools,\n readArtifact: (uri) => client.readResource(uri),\n close: () => client.close(),\n };\n}\n","import { spawn, type ChildProcess } from \"node:child_process\";\nimport { log, error } from \"./logger.js\";\n\nlet tunnelProcess: ChildProcess | null = null;\nlet tunnelUrl: string | null = null;\n\n/**\n * Poll the tunnel's public `/health` endpoint until it returns 200.\n *\n * cloudflared prints the `*.trycloudflare.com` URL as soon as the process\n * registers, but the edge route is frequently not live yet — requests in that\n * window return Cloudflare error 1033 (\"unable to resolve\"). Publishing the URL\n * before it's actually reachable bakes a dead host into rendered widgets, so we\n * verify reachability before adopting it.\n */\nasync function waitForTunnelLive(url: string, timeoutMs = 30000): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n try {\n const res = await fetch(`${url}/health`, { signal: AbortSignal.timeout(5000) });\n if (res.ok) return true;\n } catch {\n // not reachable yet — keep polling\n }\n await new Promise((r) => setTimeout(r, 1000));\n }\n return false;\n}\n\n/**\n * Start a cloudflare tunnel to expose a local port publicly.\n * Returns the public URL once the tunnel is established AND verified reachable.\n */\nexport async function startTunnel(port: number): Promise<string> {\n if (tunnelUrl) return tunnelUrl;\n\n const detectedUrl = await new Promise<string>((resolve, reject) => {\n const args = [\"tunnel\", \"--url\", `http://localhost:${port}`];\n\n log(\"tunnel\", `Starting cloudflared tunnel for port ${port}...`);\n\n tunnelProcess = spawn(\"cloudflared\", args, {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let resolved = false;\n const timeout = setTimeout(() => {\n if (!resolved) {\n resolved = true;\n reject(new Error(\"Tunnel startup timed out after 30s\"));\n }\n }, 30000);\n\n const handleOutput = (data: Buffer) => {\n const text = data.toString();\n\n // Look for the tunnel URL in the output\n // cloudflared outputs: \"https://xxx.trycloudflare.com\"\n const urlMatch = text.match(/https:\\/\\/[^\\s]+\\.trycloudflare\\.com\\/?/);\n if (urlMatch && !resolved) {\n resolved = true;\n clearTimeout(timeout);\n resolve(urlMatch[0].replace(/\\/$/, \"\"));\n }\n };\n\n tunnelProcess.stdout?.on(\"data\", handleOutput);\n tunnelProcess.stderr?.on(\"data\", handleOutput);\n\n tunnelProcess.on(\"error\", (err) => {\n if (!resolved) {\n resolved = true;\n clearTimeout(timeout);\n error(\"tunnel\", \"Failed to start cloudflared:\", err);\n reject(err);\n }\n });\n\n tunnelProcess.on(\"exit\", (code) => {\n if (!resolved) {\n resolved = true;\n clearTimeout(timeout);\n reject(new Error(`cloudflared exited with code ${code}`));\n }\n tunnelProcess = null;\n tunnelUrl = null;\n });\n });\n\n // Verify-then-publish: don't adopt the hostname until the edge route is live,\n // otherwise rendered widgets reference a host that returns Cloudflare 1033.\n log(\"tunnel\", `Tunnel hostname detected (${detectedUrl}); verifying reachability...`);\n const live = await waitForTunnelLive(detectedUrl);\n if (!live) {\n error(\n \"tunnel\",\n `Tunnel ${detectedUrl} did not become reachable within 30s — publishing anyway, but widgets may fail to load until the edge route propagates.`,\n );\n } else {\n log(\"tunnel\", `Tunnel verified reachable: ${detectedUrl}`);\n }\n\n tunnelUrl = detectedUrl;\n return tunnelUrl;\n}\n\n/**\n * Stop the cloudflare tunnel if running.\n */\nexport function stopTunnel(): void {\n if (tunnelProcess) {\n tunnelProcess.kill();\n tunnelProcess = null;\n tunnelUrl = null;\n }\n}\n\n/**\n * Get the current tunnel URL if available.\n */\nexport function getTunnelUrl(): string | null {\n return tunnelUrl;\n}\n","import { homedir } from \"node:os\";\nimport { join, resolve } from \"node:path\";\nimport { LocalFileBackend } from \"./local-backend.js\";\nimport type { FSProvider, StoredWidget, StoredWidgetInfo, WidgetStoreOptions } from \"./types.js\";\nimport type { Manifest, VirtualFile } from \"@aprovan/patchwork-compiler\";\n\nconst WIDGETS_PREFIX = \"widgets\";\nconst FILES_SUBDIR = \"files\";\nconst RESOURCE_URI_PREFIX = \"ui://widgets/\";\n\nfunction getDefaultStorageDir(): string {\n // Use WIDGET_STORE_PATH env var, or fall back to ~/.patchwork/widget-store\n return process.env[\"WIDGET_STORE_PATH\"] ?? join(homedir(), \".patchwork\", \"widget-store\");\n}\n\ninterface StoredManifest extends Manifest {\n entry: string;\n createdAt: number;\n}\n\n/**\n * Persistent store for **raw, uncompiled** widget source files.\n *\n * Widgets are saved as their original `.tsx`/`.ts` files plus a `manifest.json`;\n * compilation happens in the browser via the shared `@aprovan/patchwork-compiler`\n * runtime when the widget is rendered. Layout:\n *\n * ```\n * widgets/<name>/<hash>/\n * files/main.tsx\n * files/price-card.tsx\n * manifest.json { ...manifest, entry, createdAt }\n * ```\n */\nexport class WidgetStore {\n private provider: FSProvider;\n private storageDir: string;\n\n constructor(options: WidgetStoreOptions = {}) {\n this.storageDir = resolve(options.storageDir ?? getDefaultStorageDir());\n this.provider = options.backend ?? new LocalFileBackend(this.storageDir);\n }\n\n private fullPath(virtualPath: string): string {\n return join(WIDGETS_PREFIX, virtualPath);\n }\n\n private async readFilesRecursive(dir: string, base = \"\"): Promise<VirtualFile[]> {\n const files: VirtualFile[] = [];\n let entries;\n try {\n entries = await this.provider.readdir(dir);\n } catch {\n return files;\n }\n for (const entry of entries) {\n const childDir = join(dir, entry.name);\n const relPath = base ? `${base}/${entry.name}` : entry.name;\n if (entry.isDirectory()) {\n files.push(...(await this.readFilesRecursive(childDir, relPath)));\n } else {\n const content = await this.provider.readFile(childDir);\n files.push({ path: relPath, content });\n }\n }\n return files;\n }\n\n async saveWidget(\n hash: string,\n files: VirtualFile[],\n manifest: Manifest,\n entry: string,\n ): Promise<StoredWidget> {\n const widgetDir = `${manifest.name}/${hash}`;\n const createdAt = Date.now();\n\n await Promise.all(\n files.map((file) =>\n this.provider.writeFile(\n this.fullPath(`${widgetDir}/${FILES_SUBDIR}/${file.path}`),\n file.content,\n ),\n ),\n );\n\n const storedManifest: StoredManifest = { ...manifest, entry, createdAt };\n await this.provider.writeFile(\n this.fullPath(`${widgetDir}/manifest.json`),\n JSON.stringify(storedManifest),\n );\n\n return {\n path: this.fullPath(widgetDir),\n resourceUri: `${RESOURCE_URI_PREFIX}${widgetDir}/view.html`,\n files,\n entry,\n manifest,\n createdAt,\n };\n }\n\n async getWidget(name: string, hash: string): Promise<StoredWidget | null> {\n const widgetDir = `${name}/${hash}`;\n const manifestVirtualPath = this.fullPath(`${widgetDir}/manifest.json`);\n\n const exists = await this.provider.exists(manifestVirtualPath);\n if (!exists) return null;\n\n const files = await this.readFilesRecursive(this.fullPath(`${widgetDir}/${FILES_SUBDIR}`));\n\n let manifest: Manifest;\n let entry = files[0]?.path ?? \"main.tsx\";\n let createdAt = Date.now();\n try {\n const raw = await this.provider.readFile(manifestVirtualPath);\n const parsed = JSON.parse(raw) as Partial<StoredManifest>;\n manifest = {\n name: (parsed.name as string) ?? name,\n version: (parsed.version as string) ?? \"0.1.0\",\n platform: parsed.platform ?? \"browser\",\n image: (parsed.image as string) ?? \"@aprovan/patchwork-image-shadcn\",\n description: parsed.description,\n services: parsed.services,\n };\n if (parsed.entry) entry = parsed.entry;\n if (typeof parsed.createdAt === \"number\") createdAt = parsed.createdAt;\n } catch {\n manifest = {\n name,\n version: \"0.1.0\",\n platform: \"browser\",\n image: \"@aprovan/patchwork-image-shadcn\",\n };\n }\n\n return {\n path: this.fullPath(widgetDir),\n resourceUri: `${RESOURCE_URI_PREFIX}${widgetDir}/view.html`,\n files,\n entry,\n manifest,\n createdAt,\n };\n }\n\n async listWidgets(): Promise<StoredWidgetInfo[]> {\n const results: StoredWidgetInfo[] = [];\n const rootPath = this.fullPath(\"\");\n\n try {\n const names = await this.provider.readdir(rootPath);\n for (const nameEntry of names) {\n if (!nameEntry.isDirectory()) continue;\n const name = nameEntry.name;\n\n try {\n const hashes = await this.provider.readdir(join(rootPath, name));\n for (const hashEntry of hashes) {\n if (!hashEntry.isDirectory()) continue;\n const hash = hashEntry.name;\n const manifestVirtualPath = this.fullPath(`${name}/${hash}/manifest.json`);\n\n let manifest: Partial<StoredManifest> = {};\n try {\n const raw = await this.provider.readFile(manifestVirtualPath);\n manifest = JSON.parse(raw) as Partial<StoredManifest>;\n } catch {\n continue;\n }\n\n results.push({\n path: this.fullPath(`${name}/${hash}`),\n resourceUri: `${RESOURCE_URI_PREFIX}${name}/${hash}/view.html`,\n name: manifest.name ?? name,\n version: manifest.version ?? \"0.1.0\",\n description: manifest.description,\n services: manifest.services,\n entry: manifest.entry,\n createdAt: manifest.createdAt ?? 0,\n });\n }\n } catch {\n continue;\n }\n }\n } catch {\n return results;\n }\n\n return results.sort((a, b) => b.createdAt - a.createdAt);\n }\n\n async deleteWidget(name: string, hash: string): Promise<boolean> {\n const widgetDir = this.fullPath(`${name}/${hash}`);\n const exists = await this.provider.exists(widgetDir);\n if (!exists) return false;\n\n await this.provider.rmdir(widgetDir, { recursive: true });\n return true;\n }\n\n async hasWidget(name: string, hash: string): Promise<boolean> {\n return this.provider.exists(this.fullPath(`${name}/${hash}/manifest.json`));\n }\n\n resourceUriFor(name: string, hash: string): string {\n return `${RESOURCE_URI_PREFIX}${name}/${hash}/view.html`;\n }\n\n async loadAll(): Promise<StoredWidget[]> {\n const infos = await this.listWidgets();\n const widgets: StoredWidget[] = [];\n\n for (const info of infos) {\n const uriPath = info.resourceUri.replace(RESOURCE_URI_PREFIX, \"\").replace(/\\/view\\.html$/, \"\");\n const parts = uriPath.split(\"/\");\n const name = parts[0];\n const hash = parts[1];\n\n if (!name || !hash) continue;\n\n const widget = await this.getWidget(name, hash);\n if (widget) widgets.push(widget);\n }\n\n return widgets;\n }\n}\n\nlet _instance: WidgetStore | null = null;\n\nexport function getWidgetStore(options?: WidgetStoreOptions): WidgetStore {\n if (!_instance) {\n _instance = new WidgetStore(options);\n }\n return _instance;\n}\n\nexport function resetWidgetStore(): void {\n _instance = null;\n}\n","import { readFile, writeFile, unlink, stat, mkdir, readdir, rm, access } from \"node:fs/promises\";\nimport { join, dirname, normalize } from \"node:path\";\nimport type { DirEntry, FileStats, FSProvider } from \"./types.js\";\n\nfunction createDirEntry(name: string, isDir: boolean): DirEntry {\n return {\n name,\n isFile: () => !isDir,\n isDirectory: () => isDir,\n };\n}\n\nfunction createFileStats(size: number, mtime: Date, isDir: boolean): FileStats {\n return {\n size,\n mtime,\n isFile: () => !isDir,\n isDirectory: () => isDir,\n };\n}\n\nexport class LocalFileBackend implements FSProvider {\n constructor(private basePath: string) {}\n\n private resolve(path: string): string {\n return join(this.basePath, normalize(path));\n }\n\n async readFile(path: string): Promise<string> {\n return readFile(this.resolve(path), \"utf-8\");\n }\n\n async writeFile(path: string, content: string): Promise<void> {\n const fullPath = this.resolve(path);\n await mkdir(dirname(fullPath), { recursive: true });\n await writeFile(fullPath, content, \"utf-8\");\n }\n\n async unlink(path: string): Promise<void> {\n await unlink(this.resolve(path));\n }\n\n async stat(path: string): Promise<FileStats> {\n const fullPath = this.resolve(path);\n const s = await stat(fullPath);\n return createFileStats(s.size, s.mtime, s.isDirectory());\n }\n\n async mkdir(path: string, options?: { recursive?: boolean }): Promise<void> {\n await mkdir(this.resolve(path), options);\n }\n\n async readdir(path: string): Promise<DirEntry[]> {\n const fullPath = this.resolve(path);\n const entries = await readdir(fullPath, { withFileTypes: true });\n return entries.map((e) =>\n createDirEntry(e.name, e.isDirectory()),\n );\n }\n\n async rmdir(path: string, options?: { recursive?: boolean }): Promise<void> {\n if (options?.recursive) {\n await rm(this.resolve(path), { recursive: true, force: true });\n } else {\n await rm(this.resolve(path), { force: true });\n }\n }\n\n async exists(path: string): Promise<boolean> {\n try {\n await access(this.resolve(path));\n return true;\n } catch {\n return false;\n }\n }\n}\n","import {\n createProjectFromFiles,\n createSingleFileProject,\n type Manifest,\n type VirtualFile,\n type VirtualProject,\n} from \"@aprovan/patchwork-compiler\";\nimport {\n registerAppTool,\n RESOURCE_MIME_TYPE,\n} from \"@modelcontextprotocol/ext-apps/server\";\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport {\n subscribeSession,\n unsubscribeSession as _unsubscribeSession,\n getEvents,\n pushStreamUpdate,\n currentSeq,\n type StreamEvent,\n} from \"./live-update.js\";\nimport { warn } from \"./logger.js\";\nimport {\n ServiceBridge,\n type ServiceBackend,\n type ServiceToolInfo,\n type ServiceBridgeConfig,\n} from \"./services.js\";\nimport { getWidgetStore } from \"./widget-store/index.js\";\n\nexport type { ServiceBackend, ServiceToolInfo, ServiceBridgeConfig };\nexport type { StreamEvent };\nexport { pushStreamUpdate };\n\nconst DEFAULT_WIDGET_PORT = Number(process.env[\"WIDGET_PORT\"] ?? 3002);\nconst DEFAULT_WIDGET_HOST = process.env[\"WIDGET_HOST\"] ?? \"localhost\";\nconst DEFAULT_WIDGET_BASE_URL = `http://${DEFAULT_WIDGET_HOST}:${DEFAULT_WIDGET_PORT}`;\n\ninterface WidgetRef {\n name: string;\n hash: string;\n entry: string;\n}\n\n/**\n * Generate the MCP App resource document.\n *\n * Per the MCP Apps protocol the resource document itself must be the app that\n * connects to the host, and it runs under a strict CSP with no `unsafe-eval` —\n * so esbuild-wasm cannot run here. The resource therefore loads the bundled\n * ext-apps \"shell\" (served from the widget host, allow-listed via\n * `resourceDomains`) which connects to the host and embeds the CSP-free runtime\n * iframe that actually compiles the widget. The widget + inputs are passed to\n * the shell via a base64 `data-config` attribute (no inline script → CSP-safe).\n */\nfunction generateResourceHtml(\n shellUrl: string,\n runtimeUrl: string,\n widget: WidgetRef,\n inputs: Record<string, unknown>,\n): string {\n const config = JSON.stringify({\n runtime: runtimeUrl,\n widget: `${widget.name}/${widget.hash}`,\n inputs,\n });\n const configB64 = Buffer.from(config, \"utf-8\").toString(\"base64\");\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>${widget.name}</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html, body { width: 100%; }\n #pw-root { width: 100%; }\n </style>\n</head>\n<body>\n <div id=\"pw-root\"></div>\n <script src=\"${shellUrl}\" data-config=\"${configB64}\"></script>\n</body>\n</html>`;\n}\n\n/** Stable, non-cryptographic content hash used as the widget store key. */\nfunction hashFiles(files: VirtualFile[], manifest: Manifest): string {\n const input = JSON.stringify({\n name: manifest.name,\n image: manifest.image,\n files: files.map((f) => [f.path, f.content]),\n });\n let hash = 0;\n for (let i = 0; i < input.length; i++) {\n hash = ((hash << 5) - hash + input.charCodeAt(i)) | 0;\n }\n return Math.abs(hash).toString(16).padStart(8, \"0\");\n}\n\nconst MANIFEST_DEFAULTS: Manifest = {\n name: \"widget\",\n version: \"0.1.0\",\n platform: \"browser\",\n image: \"@aprovan/patchwork-image-shadcn\",\n};\n\n/**\n * Build the CSP the host applies to the resource document.\n *\n * - `resourceDomains` (→ `script-src`) must allow the external shell bundle.\n * - `frameDomains` (→ `frame-src`) must allow the nested runtime iframe.\n *\n * Both live on the widget host origin, so a single allow-listed origin covers\n * them. Entries are scheme-qualified per the CSP spec.\n */\nfunction buildCspConfig(\n widgetBaseUrl: string,\n): { frameDomains: string[]; resourceDomains: string[] } | undefined {\n try {\n const url = new URL(widgetBaseUrl);\n // Use the exact origin (scheme + host + port). The shell bundle and the\n // nested runtime iframe both live on this single origin, so one entry covers\n // script-src (resourceDomains) and frame-src (frameDomains). Hosts enforce\n // the resource CSP strictly and drop broad wildcard hosts like\n // `https://*.trycloudflare.com`, which would leave script-src without the\n // shell origin and block the bootstrap script — so never wildcard.\n const origin = url.port\n ? `${url.protocol}//${url.hostname}:${url.port}`\n : `${url.protocol}//${url.hostname}`;\n return { frameDomains: [origin], resourceDomains: [origin] };\n } catch {\n return undefined;\n }\n}\n\nfunction buildManifest(input?: Record<string, unknown>): Manifest {\n return {\n name: (input?.[\"name\"] as string) ?? MANIFEST_DEFAULTS.name,\n version: (input?.[\"version\"] as string) ?? MANIFEST_DEFAULTS.version,\n platform: \"browser\",\n image: (input?.[\"image\"] as string) ?? MANIFEST_DEFAULTS.image,\n services: input?.[\"services\"] as string[] | undefined,\n };\n}\n\nconst DEFAULT_WIDGET_SOURCE =\n \"export default function Widget() { return <div>Hello Patchwork</div>; }\";\n\nfunction buildProject(\n name: string,\n source?: string,\n files?: Array<{ path: string; content: string }>,\n entry?: string\n): VirtualProject {\n if (files && files.length > 0) {\n const virtualFiles: VirtualFile[] = files.map((f) => ({\n path: f.path,\n content: f.content,\n }));\n const project = createProjectFromFiles(virtualFiles, name);\n if (entry) project.entry = entry;\n return project;\n }\n return createSingleFileProject(source ?? DEFAULT_WIDGET_SOURCE, entry ?? \"main.tsx\", name);\n}\n\nexport interface McpAppServerOptions {\n services?: ServiceBridgeConfig;\n /** Base URL for serving widgets (e.g., tunnel URL). Defaults to localhost:3002. */\n widgetBaseUrl?: string;\n}\n\nexport function createMcpAppServer(options: McpAppServerOptions = {}): McpServer {\n const server = new McpServer({\n name: \"patchwork-mcp-app-server\",\n version: \"0.1.0\",\n });\n\n const serviceBridge = options.services ? new ServiceBridge(options.services) : null;\n const widgetBaseUrl = options.widgetBaseUrl ?? DEFAULT_WIDGET_BASE_URL;\n\n const runtimeUrl = `${widgetBaseUrl}/runtime/`;\n const shellUrl = `${widgetBaseUrl}/shell/shell.js`;\n const directUrl = (name: string, hash: string): string =>\n `${runtimeUrl}?widget=${encodeURIComponent(name)}/${encodeURIComponent(hash)}`;\n\n const store = getWidgetStore();\n\n /** Build the MCP App resource document that renders a stored widget. */\n const renderResource = (ref: WidgetRef, inputs: Record<string, unknown>) => {\n const csp = buildCspConfig(widgetBaseUrl);\n return {\n type: \"resource\" as const,\n resource: {\n uri: store.resourceUriFor(ref.name, ref.hash),\n mimeType: RESOURCE_MIME_TYPE,\n text: generateResourceHtml(shellUrl, runtimeUrl, ref, inputs),\n ...(csp ? { _meta: { ui: { csp } } } : {}),\n },\n };\n };\n\n registerAppTool(\n server,\n \"save_widget\",\n {\n description:\n \"Save a JSX/TSX widget's raw source files for reuse and render it as an MCP App resource. \" +\n \"Pass source code for a single-file widget, or a files array for a multi-file project. \" +\n \"The widget is stored uncompiled and compiled in the browser by the shared Patchwork \" +\n \"runtime when rendered — pass `inputs` to supply startup props to the widget.\",\n inputSchema: {\n source: z\n .string()\n .optional()\n .describe(\n \"JSX/TSX source code for a single-file widget. Must export a default React component.\"\n ),\n files: z\n .array(\n z.object({\n path: z.string().describe(\"File path relative to project root (e.g. 'main.tsx')\"),\n content: z.string().describe(\"File contents\"),\n })\n )\n .optional()\n .describe(\n \"Array of files for a multi-file widget project. At least one file should be the entry point (main.tsx or index.tsx).\"\n ),\n entry: z\n .string()\n .optional()\n .describe(\"Entry point file path. Defaults to auto-detection (main.tsx, index.tsx).\"),\n name: z.string().optional().describe(\"Widget name for the manifest. Defaults to 'widget'.\"),\n image: z\n .string()\n .optional()\n .describe(\n \"Patchwork image package to use. Defaults to '@aprovan/patchwork-image-shadcn'.\"\n ),\n services: z\n .array(z.string())\n .optional()\n .describe(\n \"Service namespaces the widget calls (e.g., ['weather', 'stripe']). \" +\n \"A proxy shim is injected so widget code can call namespace.procedure(args) directly.\"\n ),\n inputs: z\n .record(z.unknown())\n .optional()\n .describe(\"Startup props passed to the widget's default export when it is rendered.\"),\n },\n _meta: {\n ui: { resourceUri: \"ui://widgets/{name}/{hash}/view.html\" },\n },\n },\n async (args) => {\n const source = args?.[\"source\"] as string | undefined;\n const files = args?.[\"files\"] as Array<{ path: string; content: string }> | undefined;\n const entry = args?.[\"entry\"] as string | undefined;\n const requestedServices = args?.[\"services\"] as string[] | undefined;\n const inputs = (args?.[\"inputs\"] as Record<string, unknown> | undefined) ?? {};\n\n const manifestInput: Record<string, unknown> = {};\n if (args?.[\"name\"]) manifestInput[\"name\"] = args[\"name\"];\n if (args?.[\"image\"]) manifestInput[\"image\"] = args[\"image\"];\n\n // Validate requested services against the connected backend.\n let services = requestedServices ?? [];\n if (services.length > 0 && serviceBridge) {\n const availableNamespaces = serviceBridge.getNamespaces();\n const unavailable = services.filter((ns) => !availableNamespaces.includes(ns));\n if (unavailable.length > 0) {\n warn(\n \"mcp-app-server\",\n `Requested services not available: ${unavailable.join(\", \")}. Available: ${availableNamespaces.join(\", \")}`\n );\n }\n services = services.filter((ns) => availableNamespaces.includes(ns));\n }\n if (services.length > 0) manifestInput[\"services\"] = services;\n\n const manifest = buildManifest(manifestInput);\n const project = buildProject(manifest.name, source, files, entry);\n const projectFiles = Array.from(project.files.values());\n\n try {\n const hash = hashFiles(projectFiles, manifest);\n await store.saveWidget(hash, projectFiles, manifest, project.entry);\n\n const ref: WidgetRef = { name: manifest.name, hash, entry: project.entry };\n\n return {\n content: [\n renderResource(ref, inputs),\n {\n type: \"text\" as const,\n text:\n `Widget \"${manifest.name}\" saved. Hash: ${hash}\\n` +\n `Compiled in-browser at: ${directUrl(manifest.name, hash)}`,\n },\n ],\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Failed to save widget: ${message}`,\n },\n ],\n isError: true,\n };\n }\n }\n );\n\n if (serviceBridge) {\n // Register search_services and call_service tools only (not individual service tools)\n // This avoids exposing hundreds of tools with names that may exceed 64 chars\n serviceBridge.registerSearchServices(server);\n }\n\n registerAppTool(\n server,\n \"list_widgets\",\n {\n description:\n \"List all persisted widgets in the VFS widget store. \" +\n \"Returns each widget's name, version, description, path, and resource URI.\",\n _meta: { ui: { resourceUri: \"ui://widgets/list\" } },\n },\n async () => {\n const widgets = await store.listWidgets();\n\n if (widgets.length === 0) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: \"No widgets stored in the VFS widget store.\",\n },\n ],\n };\n }\n\n const lines = widgets.map((w) => {\n const parts = [`- **${w.name}** (v${w.version})`];\n if (w.description) parts.push(` ${w.description}`);\n parts.push(` Path: ${w.path}`);\n parts.push(` URI: ${w.resourceUri}`);\n if (w.entry) parts.push(` Entry: ${w.entry}`);\n if (w.services && w.services.length > 0) parts.push(` Services: ${w.services.join(\", \")}`);\n return parts.join(\"\\n\");\n });\n\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Stored widgets (${widgets.length}):\\n\\n${lines.join(\"\\n\\n\")}`,\n },\n ],\n };\n }\n );\n\n registerAppTool(\n server,\n \"render_widget\",\n {\n description:\n \"Render a persisted widget by its name and hash. \" +\n \"Serves the saved widget as an MCP App resource that compiles in the browser, \" +\n \"optionally supplying startup props via `inputs`.\",\n inputSchema: {\n name: z.string().describe(\"Widget name (as stored in the VFS widget store).\"),\n hash: z\n .string()\n .optional()\n .describe(\n \"Widget content hash. If omitted, renders the most recent version of the named widget.\"\n ),\n inputs: z\n .record(z.unknown())\n .optional()\n .describe(\"Startup props passed to the widget's default export when it is rendered.\"),\n },\n _meta: {\n ui: { resourceUri: \"ui://widgets/{name}/{hash}/view.html\" },\n },\n },\n async (args) => {\n const name = args?.[\"name\"] as string;\n const hashInput = args?.[\"hash\"] as string | undefined;\n const inputs = (args?.[\"inputs\"] as Record<string, unknown> | undefined) ?? {};\n\n if (!name) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: \"Widget name is required.\",\n },\n ],\n isError: true,\n };\n }\n\n let hash = hashInput;\n if (!hash) {\n const widgets = await store.listWidgets();\n const match = widgets.find((w) => w.name === name);\n if (!match) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `No stored widget found with name \"${name}\".`,\n },\n ],\n isError: true,\n };\n }\n const resourcePath = match.resourceUri\n .replace(\"ui://widgets/\", \"\")\n .replace(\"/view.html\", \"\");\n const parts = resourcePath.split(\"/\");\n hash = parts[1] ?? parts[0] ?? \"\";\n }\n\n if (!hash) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Could not determine hash for widget \"${name}\".`,\n },\n ],\n isError: true,\n };\n }\n\n const widget = await store.getWidget(name, hash);\n if (!widget) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Widget \"${name}\" with hash \"${hash}\" not found in the VFS store.`,\n },\n ],\n isError: true,\n };\n }\n\n const ref: WidgetRef = { name, hash, entry: widget.entry };\n\n return {\n content: [\n renderResource(ref, inputs),\n {\n type: \"text\" as const,\n text: `Rendered widget \"${name}\" (hash: ${hash}).\\nCompiled in-browser at: ${directUrl(name, hash)}`,\n },\n ],\n };\n }\n );\n\n registerLiveUpdateTools(server);\n\n return server;\n}\n\nexport { WidgetStore, getWidgetStore, resetWidgetStore } from \"./widget-store/index.js\";\nexport type { StoredWidget, StoredWidgetInfo, WidgetStoreOptions } from \"./widget-store/types.js\";\n\n// ---------------------------------------------------------------------------\n// Live-update tools\n// ---------------------------------------------------------------------------\n\n/**\n * Register the three tools that power the live-update channel:\n *\n * - `subscribe_stream` — widget declares interest in a named data stream.\n * - `poll_updates` — widget fetches buffered events since a given seq.\n * - `push_update` — backend/LLM pushes new data onto a stream.\n *\n * The session ID is extracted from the MCP request's `_meta` extra or from the\n * internal `RequestHandlerExtra`. Tools that need the session ID use the\n * `extra.meta?.sessionId` field that the SDK populates from the transport.\n */\nfunction registerLiveUpdateTools(server: McpServer): void {\n // subscribe_stream — widget registers interest in a named data stream.\n server.registerTool(\n \"subscribe_stream\",\n {\n description:\n \"Subscribe this widget session to a named data stream. \" +\n \"The server will send `notifications/tools/list_changed` whenever new \" +\n \"events arrive; the widget should then call `poll_updates` to fetch them. \" +\n \"Returns the current sequence number so the widget knows where to start polling.\",\n inputSchema: {\n stream: z.string().describe(\"Name of the data stream to subscribe to.\"),\n session_id: z\n .string()\n .optional()\n .describe(\n \"MCP session ID. Widgets should pass the value returned in the \" +\n \"Mcp-Session-Id response header during initialization.\"\n ),\n },\n },\n (args, extra) => {\n const stream = (args as Record<string, unknown>)[\"stream\"] as string;\n // Prefer an explicit session_id arg; fall back to the transport session\n const sessionId =\n ((args as Record<string, unknown>)[\"session_id\"] as string | undefined) ??\n ((extra as Record<string, unknown>)[\"sessionId\"] as string | undefined);\n\n if (sessionId) {\n subscribeSession(sessionId, stream);\n }\n\n const seq = currentSeq();\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({ success: true, stream, seq }),\n },\n ],\n };\n }\n );\n\n // poll_updates — returns buffered events for a stream since afterSeq.\n server.registerTool(\n \"poll_updates\",\n {\n description:\n \"Fetch buffered events for a data stream that arrived after the given \" +\n \"sequence number. Call this after receiving a `notifications/tools/list_changed` \" +\n \"notification (which the server sends when new data is available). \" +\n \"Pass the highest `seq` value from the last successful poll to avoid duplicates.\",\n inputSchema: {\n stream: z.string().describe(\"Name of the data stream to poll.\"),\n after_seq: z\n .number()\n .int()\n .default(0)\n .describe(\n \"Return only events with seq > after_seq. Pass 0 to retrieve all buffered events.\"\n ),\n },\n },\n (args) => {\n const stream = (args as Record<string, unknown>)[\"stream\"] as string;\n const afterSeq = ((args as Record<string, unknown>)[\"after_seq\"] as number) ?? 0;\n\n const events = getEvents(stream, afterSeq);\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({ success: true, stream, events }),\n },\n ],\n };\n }\n );\n\n // push_update — backend or LLM pushes new data onto a stream.\n server.registerTool(\n \"push_update\",\n {\n description:\n \"Push a data update onto a named stream, broadcasting it to all \" +\n \"subscribed widget sessions. Subscribing widgets will receive a \" +\n \"`notifications/tools/list_changed` signal and then call `poll_updates` \" +\n \"to retrieve the new data. Use this tool from server-side code or as an \" +\n \"LLM tool to drive real-time widget updates.\",\n inputSchema: {\n stream: z.string().describe(\"Name of the data stream to push to.\"),\n data: z\n .record(z.unknown())\n .describe(\"Arbitrary JSON-serialisable payload to push to subscribers.\"),\n },\n },\n async (args) => {\n const stream = (args as Record<string, unknown>)[\"stream\"] as string;\n const data = (args as Record<string, unknown>)[\"data\"];\n\n const seq = await pushStreamUpdate(stream, data);\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({ success: true, stream, seq }),\n },\n ],\n };\n }\n );\n}\n","import { type McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\n\nexport interface ServiceBackend {\n call(namespace: string, procedure: string, args: unknown[]): Promise<unknown>;\n}\n\nexport interface ServiceToolInfo {\n name: string;\n namespace: string;\n procedure: string;\n description?: string;\n parameters?: Record<string, unknown>;\n}\n\nexport interface ServiceBridgeConfig {\n backend: ServiceBackend;\n tools: ServiceToolInfo[];\n}\n\nconst TOOL_SEPARATOR = \"__\";\n\nfunction toMcpToolName(namespace: string, procedure: string): string {\n return `${namespace}${TOOL_SEPARATOR}${procedure}`;\n}\n\nfunction jsonSchemaToZodShape(schema?: Record<string, unknown>): Record<string, z.ZodTypeAny> {\n const properties = (schema?.[\"properties\"] ?? {}) as Record<\n string,\n { type?: string; description?: string }\n >;\n const required = new Set((schema?.[\"required\"] ?? []) as string[]);\n\n const shape: Record<string, z.ZodTypeAny> = {};\n for (const [key, prop] of Object.entries(properties)) {\n let field: z.ZodTypeAny;\n switch (prop.type) {\n case \"number\":\n case \"integer\":\n field = z.number();\n break;\n case \"boolean\":\n field = z.boolean();\n break;\n case \"array\":\n field = z.array(z.unknown());\n break;\n case \"object\":\n field = z.record(z.unknown());\n break;\n default:\n field = z.string();\n break;\n }\n if (prop.description) {\n field = field.describe(prop.description);\n }\n if (!required.has(key)) {\n field = field.optional();\n }\n shape[key] = field;\n }\n\n return shape;\n}\n\nexport class ServiceBridge {\n private backend: ServiceBackend;\n private tools: Map<string, ServiceToolInfo> = new Map();\n\n constructor(config: ServiceBridgeConfig) {\n this.backend = config.backend;\n for (const tool of config.tools) {\n this.tools.set(tool.name, tool);\n }\n }\n\n registerTools(server: McpServer): void {\n for (const [, info] of this.tools) {\n const mcpToolName = toMcpToolName(info.namespace, info.procedure);\n const inputShape = jsonSchemaToZodShape(info.parameters);\n\n server.registerTool(\n mcpToolName,\n {\n description: info.description ?? `Call ${info.namespace}.${info.procedure}`,\n inputSchema: inputShape,\n },\n async (args) => {\n try {\n const result = await this.backend.call(info.namespace, info.procedure, [args ?? {}]);\n return {\n content: [\n {\n type: \"text\" as const,\n text: typeof result === \"string\" ? result : JSON.stringify(result),\n },\n ],\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Service call failed: ${message}`,\n },\n ],\n isError: true,\n };\n }\n }\n );\n }\n }\n\n registerSearchServices(server: McpServer): void {\n server.registerTool(\n \"search_services\",\n {\n description:\n \"Search for available service tools that widgets can call. \" +\n \"Returns matching services with their namespaces, procedures, and parameter schemas.\",\n inputSchema: {\n query: z\n .string()\n .optional()\n .describe(\n 'Natural language description of what you want to do (e.g., \"get weather forecast\")'\n ),\n namespace: z\n .string()\n .optional()\n .describe('Filter results to a specific service namespace (e.g., \"weather\")'),\n tool_name: z\n .string()\n .optional()\n .describe(\"Get detailed info about a specific tool by name\"),\n limit: z.number().optional().describe(\"Maximum number of results to return\"),\n },\n },\n async (args) => {\n const query = args?.[\"query\"] as string | undefined;\n const namespace = args?.[\"namespace\"] as string | undefined;\n const toolName = args?.[\"tool_name\"] as string | undefined;\n const limit = (args?.[\"limit\"] as number) ?? 10;\n\n if (toolName) {\n const dotName = toolName.replace(/__/g, \".\");\n const info = this.tools.get(toolName) ?? this.tools.get(dotName);\n if (!info) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({\n success: false,\n error: `Tool '${toolName}' not found`,\n }),\n },\n ],\n };\n }\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({ success: true, tool: info }),\n },\n ],\n };\n }\n\n let results = Array.from(this.tools.values());\n\n if (namespace) {\n results = results.filter((info) => info.namespace === namespace);\n }\n\n if (query) {\n const queryLower = query.toLowerCase();\n const keywords = queryLower.split(/\\s+/).filter(Boolean);\n results = results\n .map((info) => {\n const searchText =\n `${info.name} ${info.namespace} ${info.procedure} ${info.description ?? \"\"}`.toLowerCase();\n const matchCount = keywords.filter((kw) => searchText.includes(kw)).length;\n return { info, score: matchCount / keywords.length };\n })\n .filter(({ score }) => score > 0)\n .sort((a, b) => b.score - a.score)\n .map(({ info }) => info);\n }\n\n results = results.slice(0, limit);\n\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({\n success: true,\n count: results.length,\n tools: results,\n namespaces: this.getNamespaces(),\n }),\n },\n ],\n };\n }\n );\n\n // call_service: Execute any service by namespace/procedure\n server.registerTool(\n \"call_service\",\n {\n description:\n \"Call a service tool by namespace and procedure. Use search_services to discover available tools first.\",\n inputSchema: {\n namespace: z.string().describe('Service namespace (e.g., \"github\", \"stripe\")'),\n procedure: z.string().describe('Procedure name (e.g., \"repos_list\", \"customers_create\")'),\n args: z.record(z.unknown()).optional().describe(\"Arguments to pass to the procedure\"),\n },\n },\n async (params) => {\n const namespace = params?.[\"namespace\"] as string;\n const procedure = params?.[\"procedure\"] as string;\n const args = (params?.[\"args\"] ?? {}) as Record<string, unknown>;\n\n if (!namespace || !procedure) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({\n success: false,\n error: \"namespace and procedure are required\",\n }),\n },\n ],\n isError: true,\n };\n }\n\n // Look up tool info for validation\n const toolKey = `${namespace}.${procedure}`;\n const info = this.tools.get(toolKey);\n if (!info) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({\n success: false,\n error: `Tool '${namespace}.${procedure}' not found. Use search_services to discover available tools.`,\n }),\n },\n ],\n isError: true,\n };\n }\n\n try {\n const result = await this.backend.call(namespace, procedure, [args]);\n return {\n content: [\n {\n type: \"text\" as const,\n text: typeof result === \"string\" ? result : JSON.stringify(result),\n },\n ],\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({\n success: false,\n error: `Service call failed: ${message}`,\n }),\n },\n ],\n isError: true,\n };\n }\n }\n );\n }\n\n getNamespaces(): string[] {\n const namespaces = new Set<string>();\n for (const info of this.tools.values()) {\n namespaces.add(info.namespace);\n }\n return Array.from(namespaces);\n }\n\n getToolInfos(): ServiceToolInfo[] {\n return Array.from(this.tools.values());\n }\n\n has(namespace: string, procedure: string): boolean {\n return this.tools.has(`${namespace}.${procedure}`);\n }\n}\n"],"mappings":";AAAA,SAAS,kBAAkB;AAC3B,SAAS,YAAY,cAAc,qBAAqB;AACxD,SAAS,cAAc;AACvB,SAAS,QAAAA,aAAY;AACrB,SAAS,qBAAqB;AAC9B,SAAS,2BAA2B;AACpC,SAAS,4BAA4B;AACrC,SAAS,qCAAqC;AAC9C,OAAO,UAAU;AACjB,OAAO,aAAa;;;ACPpB,IAAM,gBACH,QAAQ,IAAI,WAAW,KAA8B;AAExD,IAAM,aAAuC;AAAA,EAC3C,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AACT;AAEA,SAAS,UAAU,OAA0B;AAC3C,SAAO,WAAW,KAAK,KAAK,WAAW,aAAa;AACtD;AAEA,SAAS,cAAc,QAAgB,MAA4B;AACjE,SAAO,CAAC,IAAI,GAAG,KAAK,GAAG,IAAI;AAC7B;AASO,SAAS,IAAI,QAAgB,MAAuB;AACzD,MAAI,UAAU,MAAM,GAAG;AACrB,YAAQ,MAAM,GAAG,cAAc,KAAK,GAAG,IAAI,CAAC;AAAA,EAC9C;AACF;AAEO,SAAS,KAAK,QAAgB,MAAuB;AAC1D,MAAI,UAAU,MAAM,GAAG;AACrB,YAAQ,MAAM,GAAG,cAAc,KAAK,GAAG,IAAI,CAAC;AAAA,EAC9C;AACF;AAEO,SAAS,MAAM,QAAgB,MAAuB;AAC3D,MAAI,UAAU,OAAO,GAAG;AACtB,YAAQ,MAAM,GAAG,cAAc,KAAK,GAAG,IAAI,CAAC;AAAA,EAC9C;AACF;;;AC/BA,IAAM,kBAAkB;AAMxB,IAAM,gBAAgB,oBAAI,IAA2B;AACrD,IAAI,YAAY;AAEhB,SAAS,kBAAkB,QAA+B;AACxD,MAAI,MAAM,cAAc,IAAI,MAAM;AAClC,MAAI,CAAC,KAAK;AACR,UAAM,CAAC;AACP,kBAAc,IAAI,QAAQ,GAAG;AAAA,EAC/B;AACA,SAAO;AACT;AAKO,SAAS,UAAU,QAAgB,UAAiC;AACzE,QAAM,MAAM,cAAc,IAAI,MAAM;AACpC,MAAI,CAAC,IAAK,QAAO,CAAC;AAClB,SAAO,IAAI,OAAO,CAAC,MAAM,EAAE,MAAM,QAAQ;AAC3C;AAMO,SAAS,YAAY,QAAgB,MAA4B;AACtE,QAAM,MAAM,kBAAkB,MAAM;AACpC,QAAM,QAAqB;AAAA,IACzB,KAAK,EAAE;AAAA,IACP;AAAA,IACA,WAAW,KAAK,IAAI;AAAA,EACtB;AACA,MAAI,KAAK,KAAK;AACd,MAAI,IAAI,SAAS,iBAAiB;AAChC,QAAI,MAAM;AAAA,EACZ;AACA,SAAO;AACT;AAYA,IAAM,WAAW,oBAAI,IAA0B;AAKxC,SAAS,gBAAgB,WAAmB,QAAyB;AAC1E,MAAI,CAAC,SAAS,IAAI,SAAS,GAAG;AAC5B,aAAS,IAAI,WAAW,EAAE,QAAQ,SAAS,oBAAI,IAAI,EAAE,CAAC;AAAA,EACxD;AACF;AAKO,SAAS,kBAAkB,WAAyB;AACzD,WAAS,OAAO,SAAS;AAC3B;AAKO,SAAS,iBAAiB,WAAmB,QAAsB;AACxE,QAAM,QAAQ,SAAS,IAAI,SAAS;AACpC,MAAI,OAAO;AACT,UAAM,QAAQ,IAAI,MAAM;AAAA,EAC1B;AACF;AAsBA,eAAsB,iBACpB,QACA,MACiB;AACjB,QAAM,QAAQ,YAAY,QAAQ,IAAI;AAEtC,QAAM,eAAgC,CAAC;AACvC,aAAW,CAAC,EAAE,KAAK,KAAK,UAAU;AAChC,QAAI,MAAM,QAAQ,IAAI,MAAM,GAAG;AAC7B,YAAM,gBAAgB,MAAM,OAAO,OAChC,aAAa,EAAE,QAAQ,mCAAmC,CAAC,EAC3D,MAAM,CAAC,QAAiB;AACvB,aAAK,eAAe,6BAA6B,GAAG;AAAA,MACtD,CAAC;AACH,mBAAa,KAAK,aAAa;AAAA,IACjC;AAAA,EACF;AAEA,QAAM,QAAQ,IAAI,YAAY;AAC9B,SAAO,MAAM;AACf;AAKO,SAAS,aAAqB;AACnC,SAAO;AACT;;;AC9IA;AAAA,EACE;AAAA,OAGK;AAWP,SAAS,UAAU,MAAsC;AACvD,QAAM,YAAY,KAAK,KAAK,QAAQ,GAAG;AACvC,SAAO;AAAA,IACL,MAAM,KAAK;AAAA,IACX,WAAW,YAAY,IAAI,KAAK,OAAO,KAAK,KAAK,MAAM,GAAG,SAAS;AAAA,IACnE,WAAW,YAAY,IAAI,KAAK,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AAAA,IACpE,aAAa,KAAK;AAAA,IAClB,YACE,KAAK,eAAe,OAAO,KAAK,gBAAgB,WAC3C,KAAK,cACN;AAAA,EACR;AACF;AAEA,eAAsB,sBACpB,SAC0B;AAC1B,QAAM,SAAS,IAAI,mBAAmB,EAAE,GAAG,SAAS,MAAM,UAAU,CAAC;AACrE,QAAM,OAAO,QAAQ;AACrB,QAAM,SAAS,MAAM,OAAO,UAAU,GAAG,IAAI,SAAS;AACtD,SAAO;AAAA,IACL,KAAK,WAAW,WAAW,MAAM;AAC/B,aAAO,OAAO;AAAA,QACZ,GAAG,SAAS,IAAI,SAAS;AAAA,QACxB,KAAK,CAAC,KAA6C,CAAC;AAAA,MACvD;AAAA,IACF;AAAA,IACA,cAAc,MAAM;AAAA,IACpB,cAAc,CAAC,QAAQ,OAAO,aAAa,GAAG;AAAA,IAC9C,OAAO,MAAM,OAAO,MAAM;AAAA,EAC5B;AACF;;;AC9CA,SAAS,aAAgC;AAGzC,IAAI,gBAAqC;AACzC,IAAI,YAA2B;AAW/B,eAAe,kBAAkB,KAAa,YAAY,KAAyB;AACjF,QAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,SAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,GAAG,GAAG,WAAW,EAAE,QAAQ,YAAY,QAAQ,GAAI,EAAE,CAAC;AAC9E,UAAI,IAAI,GAAI,QAAO;AAAA,IACrB,QAAQ;AAAA,IAER;AACA,UAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,GAAI,CAAC;AAAA,EAC9C;AACA,SAAO;AACT;AAMA,eAAsB,YAAY,MAA+B;AAC/D,MAAI,UAAW,QAAO;AAEtB,QAAM,cAAc,MAAM,IAAI,QAAgB,CAACC,UAAS,WAAW;AACjE,UAAM,OAAO,CAAC,UAAU,SAAS,oBAAoB,IAAI,EAAE;AAE3D,QAAI,UAAU,wCAAwC,IAAI,KAAK;AAE/D,oBAAgB,MAAM,eAAe,MAAM;AAAA,MACzC,OAAO,CAAC,UAAU,QAAQ,MAAM;AAAA,IAClC,CAAC;AAED,QAAI,WAAW;AACf,UAAM,UAAU,WAAW,MAAM;AAC/B,UAAI,CAAC,UAAU;AACb,mBAAW;AACX,eAAO,IAAI,MAAM,oCAAoC,CAAC;AAAA,MACxD;AAAA,IACF,GAAG,GAAK;AAER,UAAM,eAAe,CAAC,SAAiB;AACrC,YAAM,OAAO,KAAK,SAAS;AAI3B,YAAM,WAAW,KAAK,MAAM,yCAAyC;AACrE,UAAI,YAAY,CAAC,UAAU;AACzB,mBAAW;AACX,qBAAa,OAAO;AACpB,QAAAA,SAAQ,SAAS,CAAC,EAAE,QAAQ,OAAO,EAAE,CAAC;AAAA,MACxC;AAAA,IACF;AAEA,kBAAc,QAAQ,GAAG,QAAQ,YAAY;AAC7C,kBAAc,QAAQ,GAAG,QAAQ,YAAY;AAE7C,kBAAc,GAAG,SAAS,CAAC,QAAQ;AACjC,UAAI,CAAC,UAAU;AACb,mBAAW;AACX,qBAAa,OAAO;AACpB,cAAM,UAAU,gCAAgC,GAAG;AACnD,eAAO,GAAG;AAAA,MACZ;AAAA,IACF,CAAC;AAED,kBAAc,GAAG,QAAQ,CAAC,SAAS;AACjC,UAAI,CAAC,UAAU;AACb,mBAAW;AACX,qBAAa,OAAO;AACpB,eAAO,IAAI,MAAM,gCAAgC,IAAI,EAAE,CAAC;AAAA,MAC1D;AACA,sBAAgB;AAChB,kBAAY;AAAA,IACd,CAAC;AAAA,EACH,CAAC;AAID,MAAI,UAAU,6BAA6B,WAAW,8BAA8B;AACpF,QAAM,OAAO,MAAM,kBAAkB,WAAW;AAChD,MAAI,CAAC,MAAM;AACT;AAAA,MACE;AAAA,MACA,UAAU,WAAW;AAAA,IACvB;AAAA,EACF,OAAO;AACL,QAAI,UAAU,8BAA8B,WAAW,EAAE;AAAA,EAC3D;AAEA,cAAY;AACZ,SAAO;AACT;AAKO,SAAS,aAAmB;AACjC,MAAI,eAAe;AACjB,kBAAc,KAAK;AACnB,oBAAgB;AAChB,gBAAY;AAAA,EACd;AACF;;;ACnHA,SAAS,eAAe;AACxB,SAAS,QAAAC,OAAM,eAAe;;;ACD9B,SAAS,UAAU,WAAW,QAAQ,MAAM,OAAO,SAAS,IAAI,cAAc;AAC9E,SAAS,MAAM,SAAS,iBAAiB;AAGzC,SAAS,eAAe,MAAc,OAA0B;AAC9D,SAAO;AAAA,IACL;AAAA,IACA,QAAQ,MAAM,CAAC;AAAA,IACf,aAAa,MAAM;AAAA,EACrB;AACF;AAEA,SAAS,gBAAgB,MAAc,OAAa,OAA2B;AAC7E,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,QAAQ,MAAM,CAAC;AAAA,IACf,aAAa,MAAM;AAAA,EACrB;AACF;AAEO,IAAM,mBAAN,MAA6C;AAAA,EAClD,YAAoB,UAAkB;AAAlB;AAAA,EAAmB;AAAA,EAE/B,QAAQ,MAAsB;AACpC,WAAO,KAAK,KAAK,UAAU,UAAU,IAAI,CAAC;AAAA,EAC5C;AAAA,EAEA,MAAM,SAAS,MAA+B;AAC5C,WAAO,SAAS,KAAK,QAAQ,IAAI,GAAG,OAAO;AAAA,EAC7C;AAAA,EAEA,MAAM,UAAU,MAAc,SAAgC;AAC5D,UAAM,WAAW,KAAK,QAAQ,IAAI;AAClC,UAAM,MAAM,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAClD,UAAM,UAAU,UAAU,SAAS,OAAO;AAAA,EAC5C;AAAA,EAEA,MAAM,OAAO,MAA6B;AACxC,UAAM,OAAO,KAAK,QAAQ,IAAI,CAAC;AAAA,EACjC;AAAA,EAEA,MAAM,KAAK,MAAkC;AAC3C,UAAM,WAAW,KAAK,QAAQ,IAAI;AAClC,UAAM,IAAI,MAAM,KAAK,QAAQ;AAC7B,WAAO,gBAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,CAAC;AAAA,EACzD;AAAA,EAEA,MAAM,MAAM,MAAc,SAAkD;AAC1E,UAAM,MAAM,KAAK,QAAQ,IAAI,GAAG,OAAO;AAAA,EACzC;AAAA,EAEA,MAAM,QAAQ,MAAmC;AAC/C,UAAM,WAAW,KAAK,QAAQ,IAAI;AAClC,UAAM,UAAU,MAAM,QAAQ,UAAU,EAAE,eAAe,KAAK,CAAC;AAC/D,WAAO,QAAQ;AAAA,MAAI,CAAC,MAClB,eAAe,EAAE,MAAM,EAAE,YAAY,CAAC;AAAA,IACxC;AAAA,EACF;AAAA,EAEA,MAAM,MAAM,MAAc,SAAkD;AAC1E,QAAI,SAAS,WAAW;AACtB,YAAM,GAAG,KAAK,QAAQ,IAAI,GAAG,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,IAC/D,OAAO;AACL,YAAM,GAAG,KAAK,QAAQ,IAAI,GAAG,EAAE,OAAO,KAAK,CAAC;AAAA,IAC9C;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,MAAgC;AAC3C,QAAI;AACF,YAAM,OAAO,KAAK,QAAQ,IAAI,CAAC;AAC/B,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;ADtEA,IAAM,iBAAiB;AACvB,IAAM,eAAe;AACrB,IAAM,sBAAsB;AAE5B,SAAS,uBAA+B;AAEtC,SAAO,QAAQ,IAAI,mBAAmB,KAAKC,MAAK,QAAQ,GAAG,cAAc,cAAc;AACzF;AAqBO,IAAM,cAAN,MAAkB;AAAA,EACf;AAAA,EACA;AAAA,EAER,YAAY,UAA8B,CAAC,GAAG;AAC5C,SAAK,aAAa,QAAQ,QAAQ,cAAc,qBAAqB,CAAC;AACtE,SAAK,WAAW,QAAQ,WAAW,IAAI,iBAAiB,KAAK,UAAU;AAAA,EACzE;AAAA,EAEQ,SAAS,aAA6B;AAC5C,WAAOA,MAAK,gBAAgB,WAAW;AAAA,EACzC;AAAA,EAEA,MAAc,mBAAmB,KAAa,OAAO,IAA4B;AAC/E,UAAM,QAAuB,CAAC;AAC9B,QAAI;AACJ,QAAI;AACF,gBAAU,MAAM,KAAK,SAAS,QAAQ,GAAG;AAAA,IAC3C,QAAQ;AACN,aAAO;AAAA,IACT;AACA,eAAW,SAAS,SAAS;AAC3B,YAAM,WAAWA,MAAK,KAAK,MAAM,IAAI;AACrC,YAAM,UAAU,OAAO,GAAG,IAAI,IAAI,MAAM,IAAI,KAAK,MAAM;AACvD,UAAI,MAAM,YAAY,GAAG;AACvB,cAAM,KAAK,GAAI,MAAM,KAAK,mBAAmB,UAAU,OAAO,CAAE;AAAA,MAClE,OAAO;AACL,cAAM,UAAU,MAAM,KAAK,SAAS,SAAS,QAAQ;AACrD,cAAM,KAAK,EAAE,MAAM,SAAS,QAAQ,CAAC;AAAA,MACvC;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,WACJ,MACA,OACA,UACA,OACuB;AACvB,UAAM,YAAY,GAAG,SAAS,IAAI,IAAI,IAAI;AAC1C,UAAM,YAAY,KAAK,IAAI;AAE3B,UAAM,QAAQ;AAAA,MACZ,MAAM;AAAA,QAAI,CAAC,SACT,KAAK,SAAS;AAAA,UACZ,KAAK,SAAS,GAAG,SAAS,IAAI,YAAY,IAAI,KAAK,IAAI,EAAE;AAAA,UACzD,KAAK;AAAA,QACP;AAAA,MACF;AAAA,IACF;AAEA,UAAM,iBAAiC,EAAE,GAAG,UAAU,OAAO,UAAU;AACvE,UAAM,KAAK,SAAS;AAAA,MAClB,KAAK,SAAS,GAAG,SAAS,gBAAgB;AAAA,MAC1C,KAAK,UAAU,cAAc;AAAA,IAC/B;AAEA,WAAO;AAAA,MACL,MAAM,KAAK,SAAS,SAAS;AAAA,MAC7B,aAAa,GAAG,mBAAmB,GAAG,SAAS;AAAA,MAC/C;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,UAAU,MAAc,MAA4C;AACxE,UAAM,YAAY,GAAG,IAAI,IAAI,IAAI;AACjC,UAAM,sBAAsB,KAAK,SAAS,GAAG,SAAS,gBAAgB;AAEtE,UAAM,SAAS,MAAM,KAAK,SAAS,OAAO,mBAAmB;AAC7D,QAAI,CAAC,OAAQ,QAAO;AAEpB,UAAM,QAAQ,MAAM,KAAK,mBAAmB,KAAK,SAAS,GAAG,SAAS,IAAI,YAAY,EAAE,CAAC;AAEzF,QAAI;AACJ,QAAI,QAAQ,MAAM,CAAC,GAAG,QAAQ;AAC9B,QAAI,YAAY,KAAK,IAAI;AACzB,QAAI;AACF,YAAM,MAAM,MAAM,KAAK,SAAS,SAAS,mBAAmB;AAC5D,YAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,iBAAW;AAAA,QACT,MAAO,OAAO,QAAmB;AAAA,QACjC,SAAU,OAAO,WAAsB;AAAA,QACvC,UAAU,OAAO,YAAY;AAAA,QAC7B,OAAQ,OAAO,SAAoB;AAAA,QACnC,aAAa,OAAO;AAAA,QACpB,UAAU,OAAO;AAAA,MACnB;AACA,UAAI,OAAO,MAAO,SAAQ,OAAO;AACjC,UAAI,OAAO,OAAO,cAAc,SAAU,aAAY,OAAO;AAAA,IAC/D,QAAQ;AACN,iBAAW;AAAA,QACT;AAAA,QACA,SAAS;AAAA,QACT,UAAU;AAAA,QACV,OAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO;AAAA,MACL,MAAM,KAAK,SAAS,SAAS;AAAA,MAC7B,aAAa,GAAG,mBAAmB,GAAG,SAAS;AAAA,MAC/C;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,cAA2C;AAC/C,UAAM,UAA8B,CAAC;AACrC,UAAM,WAAW,KAAK,SAAS,EAAE;AAEjC,QAAI;AACF,YAAM,QAAQ,MAAM,KAAK,SAAS,QAAQ,QAAQ;AAClD,iBAAW,aAAa,OAAO;AAC7B,YAAI,CAAC,UAAU,YAAY,EAAG;AAC9B,cAAM,OAAO,UAAU;AAEvB,YAAI;AACF,gBAAM,SAAS,MAAM,KAAK,SAAS,QAAQA,MAAK,UAAU,IAAI,CAAC;AAC/D,qBAAW,aAAa,QAAQ;AAC9B,gBAAI,CAAC,UAAU,YAAY,EAAG;AAC9B,kBAAM,OAAO,UAAU;AACvB,kBAAM,sBAAsB,KAAK,SAAS,GAAG,IAAI,IAAI,IAAI,gBAAgB;AAEzE,gBAAI,WAAoC,CAAC;AACzC,gBAAI;AACF,oBAAM,MAAM,MAAM,KAAK,SAAS,SAAS,mBAAmB;AAC5D,yBAAW,KAAK,MAAM,GAAG;AAAA,YAC3B,QAAQ;AACN;AAAA,YACF;AAEA,oBAAQ,KAAK;AAAA,cACX,MAAM,KAAK,SAAS,GAAG,IAAI,IAAI,IAAI,EAAE;AAAA,cACrC,aAAa,GAAG,mBAAmB,GAAG,IAAI,IAAI,IAAI;AAAA,cAClD,MAAM,SAAS,QAAQ;AAAA,cACvB,SAAS,SAAS,WAAW;AAAA,cAC7B,aAAa,SAAS;AAAA,cACtB,UAAU,SAAS;AAAA,cACnB,OAAO,SAAS;AAAA,cAChB,WAAW,SAAS,aAAa;AAAA,YACnC,CAAC;AAAA,UACH;AAAA,QACF,QAAQ;AACN;AAAA,QACF;AAAA,MACF;AAAA,IACF,QAAQ;AACN,aAAO;AAAA,IACT;AAEA,WAAO,QAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS;AAAA,EACzD;AAAA,EAEA,MAAM,aAAa,MAAc,MAAgC;AAC/D,UAAM,YAAY,KAAK,SAAS,GAAG,IAAI,IAAI,IAAI,EAAE;AACjD,UAAM,SAAS,MAAM,KAAK,SAAS,OAAO,SAAS;AACnD,QAAI,CAAC,OAAQ,QAAO;AAEpB,UAAM,KAAK,SAAS,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;AACxD,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,UAAU,MAAc,MAAgC;AAC5D,WAAO,KAAK,SAAS,OAAO,KAAK,SAAS,GAAG,IAAI,IAAI,IAAI,gBAAgB,CAAC;AAAA,EAC5E;AAAA,EAEA,eAAe,MAAc,MAAsB;AACjD,WAAO,GAAG,mBAAmB,GAAG,IAAI,IAAI,IAAI;AAAA,EAC9C;AAAA,EAEA,MAAM,UAAmC;AACvC,UAAM,QAAQ,MAAM,KAAK,YAAY;AACrC,UAAM,UAA0B,CAAC;AAEjC,eAAW,QAAQ,OAAO;AACxB,YAAM,UAAU,KAAK,YAAY,QAAQ,qBAAqB,EAAE,EAAE,QAAQ,iBAAiB,EAAE;AAC7F,YAAM,QAAQ,QAAQ,MAAM,GAAG;AAC/B,YAAM,OAAO,MAAM,CAAC;AACpB,YAAM,OAAO,MAAM,CAAC;AAEpB,UAAI,CAAC,QAAQ,CAAC,KAAM;AAEpB,YAAM,SAAS,MAAM,KAAK,UAAU,MAAM,IAAI;AAC9C,UAAI,OAAQ,SAAQ,KAAK,MAAM;AAAA,IACjC;AAEA,WAAO;AAAA,EACT;AACF;AAEA,IAAI,YAAgC;AAE7B,SAAS,eAAe,SAA2C;AACxE,MAAI,CAAC,WAAW;AACd,gBAAY,IAAI,YAAY,OAAO;AAAA,EACrC;AACA,SAAO;AACT;;;AE7OA;AAAA,EACE;AAAA,EACA;AAAA,OAIK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,iBAAiB;AAC1B,SAAS,KAAAC,UAAS;;;ACXlB,SAAS,SAAS;AAmBlB,IAAM,iBAAiB;AAEvB,SAAS,cAAc,WAAmB,WAA2B;AACnE,SAAO,GAAG,SAAS,GAAG,cAAc,GAAG,SAAS;AAClD;AAEA,SAAS,qBAAqB,QAAgE;AAC5F,QAAM,aAAc,SAAS,YAAY,KAAK,CAAC;AAI/C,QAAM,WAAW,IAAI,IAAK,SAAS,UAAU,KAAK,CAAC,CAAc;AAEjE,QAAM,QAAsC,CAAC;AAC7C,aAAW,CAAC,KAAK,IAAI,KAAK,OAAO,QAAQ,UAAU,GAAG;AACpD,QAAI;AACJ,YAAQ,KAAK,MAAM;AAAA,MACjB,KAAK;AAAA,MACL,KAAK;AACH,gBAAQ,EAAE,OAAO;AACjB;AAAA,MACF,KAAK;AACH,gBAAQ,EAAE,QAAQ;AAClB;AAAA,MACF,KAAK;AACH,gBAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;AAC3B;AAAA,MACF,KAAK;AACH,gBAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC;AAC5B;AAAA,MACF;AACE,gBAAQ,EAAE,OAAO;AACjB;AAAA,IACJ;AACA,QAAI,KAAK,aAAa;AACpB,cAAQ,MAAM,SAAS,KAAK,WAAW;AAAA,IACzC;AACA,QAAI,CAAC,SAAS,IAAI,GAAG,GAAG;AACtB,cAAQ,MAAM,SAAS;AAAA,IACzB;AACA,UAAM,GAAG,IAAI;AAAA,EACf;AAEA,SAAO;AACT;AAEO,IAAM,gBAAN,MAAoB;AAAA,EACjB;AAAA,EACA,QAAsC,oBAAI,IAAI;AAAA,EAEtD,YAAY,QAA6B;AACvC,SAAK,UAAU,OAAO;AACtB,eAAW,QAAQ,OAAO,OAAO;AAC/B,WAAK,MAAM,IAAI,KAAK,MAAM,IAAI;AAAA,IAChC;AAAA,EACF;AAAA,EAEA,cAAc,QAAyB;AACrC,eAAW,CAAC,EAAE,IAAI,KAAK,KAAK,OAAO;AACjC,YAAM,cAAc,cAAc,KAAK,WAAW,KAAK,SAAS;AAChE,YAAM,aAAa,qBAAqB,KAAK,UAAU;AAEvD,aAAO;AAAA,QACL;AAAA,QACA;AAAA,UACE,aAAa,KAAK,eAAe,QAAQ,KAAK,SAAS,IAAI,KAAK,SAAS;AAAA,UACzE,aAAa;AAAA,QACf;AAAA,QACA,OAAO,SAAS;AACd,cAAI;AACF,kBAAM,SAAS,MAAM,KAAK,QAAQ,KAAK,KAAK,WAAW,KAAK,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC;AACnF,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP;AAAA,kBACE,MAAM;AAAA,kBACN,MAAM,OAAO,WAAW,WAAW,SAAS,KAAK,UAAU,MAAM;AAAA,gBACnE;AAAA,cACF;AAAA,YACF;AAAA,UACF,SAASC,QAAO;AACd,kBAAM,UAAUA,kBAAiB,QAAQA,OAAM,UAAU,OAAOA,MAAK;AACrE,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP;AAAA,kBACE,MAAM;AAAA,kBACN,MAAM,wBAAwB,OAAO;AAAA,gBACvC;AAAA,cACF;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,uBAAuB,QAAyB;AAC9C,WAAO;AAAA,MACL;AAAA,MACA;AAAA,QACE,aACE;AAAA,QAEF,aAAa;AAAA,UACX,OAAO,EACJ,OAAO,EACP,SAAS,EACT;AAAA,YACC;AAAA,UACF;AAAA,UACF,WAAW,EACR,OAAO,EACP,SAAS,EACT,SAAS,kEAAkE;AAAA,UAC9E,WAAW,EACR,OAAO,EACP,SAAS,EACT,SAAS,iDAAiD;AAAA,UAC7D,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,qCAAqC;AAAA,QAC7E;AAAA,MACF;AAAA,MACA,OAAO,SAAS;AACd,cAAM,QAAQ,OAAO,OAAO;AAC5B,cAAM,YAAY,OAAO,WAAW;AACpC,cAAM,WAAW,OAAO,WAAW;AACnC,cAAM,QAAS,OAAO,OAAO,KAAgB;AAE7C,YAAI,UAAU;AACZ,gBAAM,UAAU,SAAS,QAAQ,OAAO,GAAG;AAC3C,gBAAM,OAAO,KAAK,MAAM,IAAI,QAAQ,KAAK,KAAK,MAAM,IAAI,OAAO;AAC/D,cAAI,CAAC,MAAM;AACT,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP;AAAA,kBACE,MAAM;AAAA,kBACN,MAAM,KAAK,UAAU;AAAA,oBACnB,SAAS;AAAA,oBACT,OAAO,SAAS,QAAQ;AAAA,kBAC1B,CAAC;AAAA,gBACH;AAAA,cACF;AAAA,YACF;AAAA,UACF;AACA,iBAAO;AAAA,YACL,SAAS;AAAA,cACP;AAAA,gBACE,MAAM;AAAA,gBACN,MAAM,KAAK,UAAU,EAAE,SAAS,MAAM,MAAM,KAAK,CAAC;AAAA,cACpD;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAEA,YAAI,UAAU,MAAM,KAAK,KAAK,MAAM,OAAO,CAAC;AAE5C,YAAI,WAAW;AACb,oBAAU,QAAQ,OAAO,CAAC,SAAS,KAAK,cAAc,SAAS;AAAA,QACjE;AAEA,YAAI,OAAO;AACT,gBAAM,aAAa,MAAM,YAAY;AACrC,gBAAM,WAAW,WAAW,MAAM,KAAK,EAAE,OAAO,OAAO;AACvD,oBAAU,QACP,IAAI,CAAC,SAAS;AACb,kBAAM,aACJ,GAAG,KAAK,IAAI,IAAI,KAAK,SAAS,IAAI,KAAK,SAAS,IAAI,KAAK,eAAe,EAAE,GAAG,YAAY;AAC3F,kBAAM,aAAa,SAAS,OAAO,CAAC,OAAO,WAAW,SAAS,EAAE,CAAC,EAAE;AACpE,mBAAO,EAAE,MAAM,OAAO,aAAa,SAAS,OAAO;AAAA,UACrD,CAAC,EACA,OAAO,CAAC,EAAE,MAAM,MAAM,QAAQ,CAAC,EAC/B,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,EAChC,IAAI,CAAC,EAAE,KAAK,MAAM,IAAI;AAAA,QAC3B;AAEA,kBAAU,QAAQ,MAAM,GAAG,KAAK;AAEhC,eAAO;AAAA,UACL,SAAS;AAAA,YACP;AAAA,cACE,MAAM;AAAA,cACN,MAAM,KAAK,UAAU;AAAA,gBACnB,SAAS;AAAA,gBACT,OAAO,QAAQ;AAAA,gBACf,OAAO;AAAA,gBACP,YAAY,KAAK,cAAc;AAAA,cACjC,CAAC;AAAA,YACH;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,QACE,aACE;AAAA,QACF,aAAa;AAAA,UACX,WAAW,EAAE,OAAO,EAAE,SAAS,8CAA8C;AAAA,UAC7E,WAAW,EAAE,OAAO,EAAE,SAAS,yDAAyD;AAAA,UACxF,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,EAAE,SAAS,oCAAoC;AAAA,QACtF;AAAA,MACF;AAAA,MACA,OAAO,WAAW;AAChB,cAAM,YAAY,SAAS,WAAW;AACtC,cAAM,YAAY,SAAS,WAAW;AACtC,cAAM,OAAQ,SAAS,MAAM,KAAK,CAAC;AAEnC,YAAI,CAAC,aAAa,CAAC,WAAW;AAC5B,iBAAO;AAAA,YACL,SAAS;AAAA,cACP;AAAA,gBACE,MAAM;AAAA,gBACN,MAAM,KAAK,UAAU;AAAA,kBACnB,SAAS;AAAA,kBACT,OAAO;AAAA,gBACT,CAAC;AAAA,cACH;AAAA,YACF;AAAA,YACA,SAAS;AAAA,UACX;AAAA,QACF;AAGA,cAAM,UAAU,GAAG,SAAS,IAAI,SAAS;AACzC,cAAM,OAAO,KAAK,MAAM,IAAI,OAAO;AACnC,YAAI,CAAC,MAAM;AACT,iBAAO;AAAA,YACL,SAAS;AAAA,cACP;AAAA,gBACE,MAAM;AAAA,gBACN,MAAM,KAAK,UAAU;AAAA,kBACnB,SAAS;AAAA,kBACT,OAAO,SAAS,SAAS,IAAI,SAAS;AAAA,gBACxC,CAAC;AAAA,cACH;AAAA,YACF;AAAA,YACA,SAAS;AAAA,UACX;AAAA,QACF;AAEA,YAAI;AACF,gBAAM,SAAS,MAAM,KAAK,QAAQ,KAAK,WAAW,WAAW,CAAC,IAAI,CAAC;AACnE,iBAAO;AAAA,YACL,SAAS;AAAA,cACP;AAAA,gBACE,MAAM;AAAA,gBACN,MAAM,OAAO,WAAW,WAAW,SAAS,KAAK,UAAU,MAAM;AAAA,cACnE;AAAA,YACF;AAAA,UACF;AAAA,QACF,SAASA,QAAO;AACd,gBAAM,UAAUA,kBAAiB,QAAQA,OAAM,UAAU,OAAOA,MAAK;AACrE,iBAAO;AAAA,YACL,SAAS;AAAA,cACP;AAAA,gBACE,MAAM;AAAA,gBACN,MAAM,KAAK,UAAU;AAAA,kBACnB,SAAS;AAAA,kBACT,OAAO,wBAAwB,OAAO;AAAA,gBACxC,CAAC;AAAA,cACH;AAAA,YACF;AAAA,YACA,SAAS;AAAA,UACX;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,gBAA0B;AACxB,UAAM,aAAa,oBAAI,IAAY;AACnC,eAAW,QAAQ,KAAK,MAAM,OAAO,GAAG;AACtC,iBAAW,IAAI,KAAK,SAAS;AAAA,IAC/B;AACA,WAAO,MAAM,KAAK,UAAU;AAAA,EAC9B;AAAA,EAEA,eAAkC;AAChC,WAAO,MAAM,KAAK,KAAK,MAAM,OAAO,CAAC;AAAA,EACvC;AAAA,EAEA,IAAI,WAAmB,WAA4B;AACjD,WAAO,KAAK,MAAM,IAAI,GAAG,SAAS,IAAI,SAAS,EAAE;AAAA,EACnD;AACF;;;ADhRA,IAAM,sBAAsB,OAAO,QAAQ,IAAI,aAAa,KAAK,IAAI;AACrE,IAAM,sBAAsB,QAAQ,IAAI,aAAa,KAAK;AAC1D,IAAM,0BAA0B,UAAU,mBAAmB,IAAI,mBAAmB;AAmBpF,SAAS,qBACP,UACA,YACA,QACA,QACQ;AACR,QAAM,SAAS,KAAK,UAAU;AAAA,IAC5B,SAAS;AAAA,IACT,QAAQ,GAAG,OAAO,IAAI,IAAI,OAAO,IAAI;AAAA,IACrC;AAAA,EACF,CAAC;AACD,QAAM,YAAY,OAAO,KAAK,QAAQ,OAAO,EAAE,SAAS,QAAQ;AAChE,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA,WAKE,OAAO,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBASL,QAAQ,kBAAkB,SAAS;AAAA;AAAA;AAGpD;AAGA,SAAS,UAAU,OAAsB,UAA4B;AACnE,QAAM,QAAQ,KAAK,UAAU;AAAA,IAC3B,MAAM,SAAS;AAAA,IACf,OAAO,SAAS;AAAA,IAChB,OAAO,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC;AAAA,EAC7C,CAAC;AACD,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAS,QAAQ,KAAK,OAAO,MAAM,WAAW,CAAC,IAAK;AAAA,EACtD;AACA,SAAO,KAAK,IAAI,IAAI,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AACpD;AAEA,IAAM,oBAA8B;AAAA,EAClC,MAAM;AAAA,EACN,SAAS;AAAA,EACT,UAAU;AAAA,EACV,OAAO;AACT;AAWA,SAAS,eACP,eACmE;AACnE,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,aAAa;AAOjC,UAAM,SAAS,IAAI,OACf,GAAG,IAAI,QAAQ,KAAK,IAAI,QAAQ,IAAI,IAAI,IAAI,KAC5C,GAAG,IAAI,QAAQ,KAAK,IAAI,QAAQ;AACpC,WAAO,EAAE,cAAc,CAAC,MAAM,GAAG,iBAAiB,CAAC,MAAM,EAAE;AAAA,EAC7D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,cAAc,OAA2C;AAChE,SAAO;AAAA,IACL,MAAO,QAAQ,MAAM,KAAgB,kBAAkB;AAAA,IACvD,SAAU,QAAQ,SAAS,KAAgB,kBAAkB;AAAA,IAC7D,UAAU;AAAA,IACV,OAAQ,QAAQ,OAAO,KAAgB,kBAAkB;AAAA,IACzD,UAAU,QAAQ,UAAU;AAAA,EAC9B;AACF;AAEA,IAAM,wBACJ;AAEF,SAAS,aACP,MACA,QACA,OACA,OACgB;AAChB,MAAI,SAAS,MAAM,SAAS,GAAG;AAC7B,UAAM,eAA8B,MAAM,IAAI,CAAC,OAAO;AAAA,MACpD,MAAM,EAAE;AAAA,MACR,SAAS,EAAE;AAAA,IACb,EAAE;AACF,UAAM,UAAU,uBAAuB,cAAc,IAAI;AACzD,QAAI,MAAO,SAAQ,QAAQ;AAC3B,WAAO;AAAA,EACT;AACA,SAAO,wBAAwB,UAAU,uBAAuB,SAAS,YAAY,IAAI;AAC3F;AAQO,SAAS,mBAAmB,UAA+B,CAAC,GAAc;AAC/E,QAAM,SAAS,IAAI,UAAU;AAAA,IAC3B,MAAM;AAAA,IACN,SAAS;AAAA,EACX,CAAC;AAED,QAAM,gBAAgB,QAAQ,WAAW,IAAI,cAAc,QAAQ,QAAQ,IAAI;AAC/E,QAAM,gBAAgB,QAAQ,iBAAiB;AAE/C,QAAM,aAAa,GAAG,aAAa;AACnC,QAAM,WAAW,GAAG,aAAa;AACjC,QAAM,YAAY,CAAC,MAAc,SAC/B,GAAG,UAAU,WAAW,mBAAmB,IAAI,CAAC,IAAI,mBAAmB,IAAI,CAAC;AAE9E,QAAM,QAAQ,eAAe;AAG7B,QAAM,iBAAiB,CAAC,KAAgB,WAAoC;AAC1E,UAAM,MAAM,eAAe,aAAa;AACxC,WAAO;AAAA,MACL,MAAM;AAAA,MACN,UAAU;AAAA,QACR,KAAK,MAAM,eAAe,IAAI,MAAM,IAAI,IAAI;AAAA,QAC5C,UAAU;AAAA,QACV,MAAM,qBAAqB,UAAU,YAAY,KAAK,MAAM;AAAA,QAC5D,GAAI,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,IAAI,CAAC;AAAA,MAC1C;AAAA,IACF;AAAA,EACF;AAEA;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,MACE,aACE;AAAA,MAIF,aAAa;AAAA,QACX,QAAQC,GACL,OAAO,EACP,SAAS,EACT;AAAA,UACC;AAAA,QACF;AAAA,QACF,OAAOA,GACJ;AAAA,UACCA,GAAE,OAAO;AAAA,YACP,MAAMA,GAAE,OAAO,EAAE,SAAS,sDAAsD;AAAA,YAChF,SAASA,GAAE,OAAO,EAAE,SAAS,eAAe;AAAA,UAC9C,CAAC;AAAA,QACH,EACC,SAAS,EACT;AAAA,UACC;AAAA,QACF;AAAA,QACF,OAAOA,GACJ,OAAO,EACP,SAAS,EACT,SAAS,0EAA0E;AAAA,QACtF,MAAMA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,qDAAqD;AAAA,QAC1F,OAAOA,GACJ,OAAO,EACP,SAAS,EACT;AAAA,UACC;AAAA,QACF;AAAA,QACF,UAAUA,GACP,MAAMA,GAAE,OAAO,CAAC,EAChB,SAAS,EACT;AAAA,UACC;AAAA,QAEF;AAAA,QACF,QAAQA,GACL,OAAOA,GAAE,QAAQ,CAAC,EAClB,SAAS,EACT,SAAS,0EAA0E;AAAA,MACxF;AAAA,MACA,OAAO;AAAA,QACL,IAAI,EAAE,aAAa,uCAAuC;AAAA,MAC5D;AAAA,IACF;AAAA,IACA,OAAO,SAAS;AACd,YAAM,SAAS,OAAO,QAAQ;AAC9B,YAAM,QAAQ,OAAO,OAAO;AAC5B,YAAM,QAAQ,OAAO,OAAO;AAC5B,YAAM,oBAAoB,OAAO,UAAU;AAC3C,YAAM,SAAU,OAAO,QAAQ,KAA6C,CAAC;AAE7E,YAAM,gBAAyC,CAAC;AAChD,UAAI,OAAO,MAAM,EAAG,eAAc,MAAM,IAAI,KAAK,MAAM;AACvD,UAAI,OAAO,OAAO,EAAG,eAAc,OAAO,IAAI,KAAK,OAAO;AAG1D,UAAI,WAAW,qBAAqB,CAAC;AACrC,UAAI,SAAS,SAAS,KAAK,eAAe;AACxC,cAAM,sBAAsB,cAAc,cAAc;AACxD,cAAM,cAAc,SAAS,OAAO,CAAC,OAAO,CAAC,oBAAoB,SAAS,EAAE,CAAC;AAC7E,YAAI,YAAY,SAAS,GAAG;AAC1B;AAAA,YACE;AAAA,YACA,qCAAqC,YAAY,KAAK,IAAI,CAAC,gBAAgB,oBAAoB,KAAK,IAAI,CAAC;AAAA,UAC3G;AAAA,QACF;AACA,mBAAW,SAAS,OAAO,CAAC,OAAO,oBAAoB,SAAS,EAAE,CAAC;AAAA,MACrE;AACA,UAAI,SAAS,SAAS,EAAG,eAAc,UAAU,IAAI;AAErD,YAAM,WAAW,cAAc,aAAa;AAC5C,YAAM,UAAU,aAAa,SAAS,MAAM,QAAQ,OAAO,KAAK;AAChE,YAAM,eAAe,MAAM,KAAK,QAAQ,MAAM,OAAO,CAAC;AAEtD,UAAI;AACF,cAAM,OAAO,UAAU,cAAc,QAAQ;AAC7C,cAAM,MAAM,WAAW,MAAM,cAAc,UAAU,QAAQ,KAAK;AAElE,cAAM,MAAiB,EAAE,MAAM,SAAS,MAAM,MAAM,OAAO,QAAQ,MAAM;AAEzE,eAAO;AAAA,UACL,SAAS;AAAA,YACP,eAAe,KAAK,MAAM;AAAA,YAC1B;AAAA,cACE,MAAM;AAAA,cACN,MACE,WAAW,SAAS,IAAI,kBAAkB,IAAI;AAAA,0BACnB,UAAU,SAAS,MAAM,IAAI,CAAC;AAAA,YAC7D;AAAA,UACF;AAAA,QACF;AAAA,MACF,SAASC,QAAO;AACd,cAAM,UAAUA,kBAAiB,QAAQA,OAAM,UAAU,OAAOA,MAAK;AACrE,eAAO;AAAA,UACL,SAAS;AAAA,YACP;AAAA,cACE,MAAM;AAAA,cACN,MAAM,0BAA0B,OAAO;AAAA,YACzC;AAAA,UACF;AAAA,UACA,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,eAAe;AAGjB,kBAAc,uBAAuB,MAAM;AAAA,EAC7C;AAEA;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,MACE,aACE;AAAA,MAEF,OAAO,EAAE,IAAI,EAAE,aAAa,oBAAoB,EAAE;AAAA,IACpD;AAAA,IACA,YAAY;AACV,YAAM,UAAU,MAAM,MAAM,YAAY;AAExC,UAAI,QAAQ,WAAW,GAAG;AACxB,eAAO;AAAA,UACL,SAAS;AAAA,YACP;AAAA,cACE,MAAM;AAAA,cACN,MAAM;AAAA,YACR;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAEA,YAAM,QAAQ,QAAQ,IAAI,CAAC,MAAM;AAC/B,cAAM,QAAQ,CAAC,OAAO,EAAE,IAAI,QAAQ,EAAE,OAAO,GAAG;AAChD,YAAI,EAAE,YAAa,OAAM,KAAK,KAAK,EAAE,WAAW,EAAE;AAClD,cAAM,KAAK,WAAW,EAAE,IAAI,EAAE;AAC9B,cAAM,KAAK,UAAU,EAAE,WAAW,EAAE;AACpC,YAAI,EAAE,MAAO,OAAM,KAAK,YAAY,EAAE,KAAK,EAAE;AAC7C,YAAI,EAAE,YAAY,EAAE,SAAS,SAAS,EAAG,OAAM,KAAK,eAAe,EAAE,SAAS,KAAK,IAAI,CAAC,EAAE;AAC1F,eAAO,MAAM,KAAK,IAAI;AAAA,MACxB,CAAC;AAED,aAAO;AAAA,QACL,SAAS;AAAA,UACP;AAAA,YACE,MAAM;AAAA,YACN,MAAM,mBAAmB,QAAQ,MAAM;AAAA;AAAA,EAAS,MAAM,KAAK,MAAM,CAAC;AAAA,UACpE;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,MACE,aACE;AAAA,MAGF,aAAa;AAAA,QACX,MAAMD,GAAE,OAAO,EAAE,SAAS,kDAAkD;AAAA,QAC5E,MAAMA,GACH,OAAO,EACP,SAAS,EACT;AAAA,UACC;AAAA,QACF;AAAA,QACF,QAAQA,GACL,OAAOA,GAAE,QAAQ,CAAC,EAClB,SAAS,EACT,SAAS,0EAA0E;AAAA,MACxF;AAAA,MACA,OAAO;AAAA,QACL,IAAI,EAAE,aAAa,uCAAuC;AAAA,MAC5D;AAAA,IACF;AAAA,IACA,OAAO,SAAS;AACd,YAAM,OAAO,OAAO,MAAM;AAC1B,YAAM,YAAY,OAAO,MAAM;AAC/B,YAAM,SAAU,OAAO,QAAQ,KAA6C,CAAC;AAE7E,UAAI,CAAC,MAAM;AACT,eAAO;AAAA,UACL,SAAS;AAAA,YACP;AAAA,cACE,MAAM;AAAA,cACN,MAAM;AAAA,YACR;AAAA,UACF;AAAA,UACA,SAAS;AAAA,QACX;AAAA,MACF;AAEA,UAAI,OAAO;AACX,UAAI,CAAC,MAAM;AACT,cAAM,UAAU,MAAM,MAAM,YAAY;AACxC,cAAM,QAAQ,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AACjD,YAAI,CAAC,OAAO;AACV,iBAAO;AAAA,YACL,SAAS;AAAA,cACP;AAAA,gBACE,MAAM;AAAA,gBACN,MAAM,qCAAqC,IAAI;AAAA,cACjD;AAAA,YACF;AAAA,YACA,SAAS;AAAA,UACX;AAAA,QACF;AACA,cAAM,eAAe,MAAM,YACxB,QAAQ,iBAAiB,EAAE,EAC3B,QAAQ,cAAc,EAAE;AAC3B,cAAM,QAAQ,aAAa,MAAM,GAAG;AACpC,eAAO,MAAM,CAAC,KAAK,MAAM,CAAC,KAAK;AAAA,MACjC;AAEA,UAAI,CAAC,MAAM;AACT,eAAO;AAAA,UACL,SAAS;AAAA,YACP;AAAA,cACE,MAAM;AAAA,cACN,MAAM,wCAAwC,IAAI;AAAA,YACpD;AAAA,UACF;AAAA,UACA,SAAS;AAAA,QACX;AAAA,MACF;AAEA,YAAM,SAAS,MAAM,MAAM,UAAU,MAAM,IAAI;AAC/C,UAAI,CAAC,QAAQ;AACX,eAAO;AAAA,UACL,SAAS;AAAA,YACP;AAAA,cACE,MAAM;AAAA,cACN,MAAM,WAAW,IAAI,gBAAgB,IAAI;AAAA,YAC3C;AAAA,UACF;AAAA,UACA,SAAS;AAAA,QACX;AAAA,MACF;AAEA,YAAM,MAAiB,EAAE,MAAM,MAAM,OAAO,OAAO,MAAM;AAEzD,aAAO;AAAA,QACL,SAAS;AAAA,UACP,eAAe,KAAK,MAAM;AAAA,UAC1B;AAAA,YACE,MAAM;AAAA,YACN,MAAM,oBAAoB,IAAI,YAAY,IAAI;AAAA,0BAA+B,UAAU,MAAM,IAAI,CAAC;AAAA,UACpG;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,0BAAwB,MAAM;AAE9B,SAAO;AACT;AAoBA,SAAS,wBAAwB,QAAyB;AAExD,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,aACE;AAAA,MAIF,aAAa;AAAA,QACX,QAAQA,GAAE,OAAO,EAAE,SAAS,0CAA0C;AAAA,QACtE,YAAYA,GACT,OAAO,EACP,SAAS,EACT;AAAA,UACC;AAAA,QAEF;AAAA,MACJ;AAAA,IACF;AAAA,IACA,CAAC,MAAM,UAAU;AACf,YAAM,SAAU,KAAiC,QAAQ;AAEzD,YAAM,YACF,KAAiC,YAAY,KAC7C,MAAkC,WAAW;AAEjD,UAAI,WAAW;AACb,yBAAiB,WAAW,MAAM;AAAA,MACpC;AAEA,YAAM,MAAM,WAAW;AACvB,aAAO;AAAA,QACL,SAAS;AAAA,UACP;AAAA,YACE,MAAM;AAAA,YACN,MAAM,KAAK,UAAU,EAAE,SAAS,MAAM,QAAQ,IAAI,CAAC;AAAA,UACrD;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,aACE;AAAA,MAIF,aAAa;AAAA,QACX,QAAQA,GAAE,OAAO,EAAE,SAAS,kCAAkC;AAAA,QAC9D,WAAWA,GACR,OAAO,EACP,IAAI,EACJ,QAAQ,CAAC,EACT;AAAA,UACC;AAAA,QACF;AAAA,MACJ;AAAA,IACF;AAAA,IACA,CAAC,SAAS;AACR,YAAM,SAAU,KAAiC,QAAQ;AACzD,YAAM,WAAa,KAAiC,WAAW,KAAgB;AAE/E,YAAM,SAAS,UAAU,QAAQ,QAAQ;AACzC,aAAO;AAAA,QACL,SAAS;AAAA,UACP;AAAA,YACE,MAAM;AAAA,YACN,MAAM,KAAK,UAAU,EAAE,SAAS,MAAM,QAAQ,OAAO,CAAC;AAAA,UACxD;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,aACE;AAAA,MAKF,aAAa;AAAA,QACX,QAAQA,GAAE,OAAO,EAAE,SAAS,qCAAqC;AAAA,QACjE,MAAMA,GACH,OAAOA,GAAE,QAAQ,CAAC,EAClB,SAAS,6DAA6D;AAAA,MAC3E;AAAA,IACF;AAAA,IACA,OAAO,SAAS;AACd,YAAM,SAAU,KAAiC,QAAQ;AACzD,YAAM,OAAQ,KAAiC,MAAM;AAErD,YAAM,MAAM,MAAM,iBAAiB,QAAQ,IAAI;AAC/C,aAAO;AAAA,QACL,SAAS;AAAA,UACP;AAAA,YACE,MAAM;AAAA,YACN,MAAM,KAAK,UAAU,EAAE,SAAS,MAAM,QAAQ,IAAI,CAAC;AAAA,UACrD;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;AP7kBA,IAAM,OAAO,OAAO,QAAQ,IAAI,MAAM,KAAK,GAAI;AAC/C,IAAM,cAAc,OAAO,QAAQ,IAAI,aAAa,KAAK,IAAI;AAC7D,IAAM,OAAO,QAAQ,IAAI,MAAM,KAAK;AACpC,IAAM,YAAY,QAAQ,IAAI,WAAW,MAAM,QAAQ,KAAK,SAAS,SAAS,IAAI,UAAU;AAC5F,IAAM,gBAAgB,QAAQ,IAAI,eAAe,MAAM,UAAU,QAAQ,KAAK,SAAS,UAAU;AA0BjG,IAAM,oBAAoBE,MAAK,OAAO,GAAG,oBAAoB,WAAW,OAAO;AAE/E,SAAS,sBAA8C;AACrD,MAAI;AACF,WAAO,KAAK,MAAM,aAAa,mBAAmB,MAAM,CAAC;AAAA,EAC3D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,uBAAuB,SAAuB;AACrD,MAAI;AACF,UAAM,QAAyB,EAAE,SAAS,KAAK,QAAQ,KAAK,WAAW,KAAK,IAAI,EAAE;AAClF,kBAAc,mBAAmB,KAAK,UAAU,KAAK,CAAC;AAAA,EACxD,SAAS,KAAK;AACZ,UAAM,kBAAkB,wCAAwC,GAAG;AAAA,EACrE;AACF;AAEA,SAAS,eAAe,KAAsB;AAC5C,MAAI;AACF,YAAQ,KAAK,KAAK,CAAC;AACnB,WAAO;AAAA,EACT,SAAS,KAAK;AAEZ,WAAQ,IAA8B,SAAS;AAAA,EACjD;AACF;AAOA,eAAe,4BAA4B,YAAY,MAA+B;AACpF,QAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,SAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,UAAM,QAAQ,oBAAoB;AAClC,QAAI,OAAO,WAAW,eAAe,MAAM,GAAG,EAAG,QAAO,MAAM;AAC9D,UAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;AAAA,EAC7C;AACA,SAAO;AACT;AAGA,SAAS,eAAe,MAAsB;AAC5C,QAAM,aAAa;AAAA,IACjB,cAAc,IAAI,IAAI,KAAK,IAAI,IAAI,YAAY,GAAG,CAAC;AAAA;AAAA,IACnD,cAAc,IAAI,IAAI,WAAW,IAAI,IAAI,YAAY,GAAG,CAAC;AAAA;AAAA,EAC3D;AACA,SAAO,WAAW,KAAK,CAAC,QAAQ,WAAW,GAAG,CAAC,KAAK,WAAW,CAAC;AAClE;AAGO,SAAS,oBAA4B;AAC1C,SAAO,eAAe,SAAS;AACjC;AAGO,SAAS,kBAA0B;AACxC,SAAO,eAAe,OAAO;AAC/B;AAEA,eAAe,uBAGZ;AACD,QAAM,aAAa,QAAQ,IAAI,iBAAiB;AAEhD,MAAI,CAAC,YAAY;AACf,WAAO,EAAE,iBAAiB,MAAM,eAAe,CAAC,EAAE;AAAA,EACpD;AAEA,MAAI,iBAAiB,4BAA4B,UAAU,EAAE;AAE7D,MAAI;AACF,UAAM,kBAAkB,MAAM,sBAAsB;AAAA,MAClD,MAAM;AAAA,MACN,KAAK;AAAA,MACL,SAAS,QAAQ,IAAI,sBAAsB,IACvC,EAAE,uBAAuB,QAAQ,IAAI,sBAAsB,EAAE,IAC7D;AAAA,MACJ,MAAM,QAAQ,IAAI,eAAe,IAC7B,EAAE,MAAM,UAAU,OAAO,QAAQ,IAAI,eAAe,EAAE,IACtD,EAAE,MAAM,OAAO;AAAA,IACrB,CAAC;AAED,UAAM,YAAY,gBAAgB,aAAa;AAC/C,UAAM,aAAa,CAAC,GAAG,IAAI,IAAI,UAAU,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AACjE;AAAA,MACE;AAAA,MACA,mBAAmB,UAAU,MAAM,6BAA6B,WAAW,KAAK,IAAI,CAAC;AAAA,IACvF;AAEA,WAAO;AAAA,MACL;AAAA,MACA,eAAe;AAAA,QACb,UAAU;AAAA,UACR,SAAS;AAAA,UACT,OAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF;AAAA,EACF,SAAS,KAAK;AACZ,UAAM,iBAAiB,iCAAiC,GAAG;AAC3D,UAAM,iBAAiB,oCAAoC;AAC3D,WAAO,EAAE,iBAAiB,MAAM,eAAe,CAAC,EAAE;AAAA,EACpD;AACF;AAEA,SAAS,sBAAsB,UAAwB,KAAc,KAAqB;AACxF,MAAI;AACF,aAAS,UAAU,cAAc,KAAK,KAAK,IAAI,IAAI;AAAA,EACrD,SAAS,KAAK;AACZ,UAAM,OAAO,yBAAyB,GAAG;AACzC,QAAI,CAAC,IAAI,aAAa;AACpB,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,wBAAwB,CAAC;AAAA,IACzD;AAAA,EACF;AACF;AAEA,eAAe,iBACb,eACA,cACA,KACA,KACe;AACf,QAAM,YAAY,mBAAmB,aAAa;AAClD,QAAM,YAAY,IAAI,8BAA8B;AAAA,IAClD,oBAAoB,MAAM,WAAW;AAAA,IACrC,sBAAsB,CAAC,OAAO;AAC5B,mBAAa,IAAI,IAAI,EAAE,QAAQ,WAAW,UAAU,CAAC;AACrD,sBAAgB,IAAI,SAAS;AAAA,IAC/B;AAAA,IACA,iBAAiB,CAAC,OAAO;AACvB,mBAAa,OAAO,EAAE;AACtB,wBAAkB,EAAE;AAAA,IACtB;AAAA,EACF,CAAC;AAED,MAAI,GAAG,SAAS,MAAM;AACpB,UAAM,KAAK,UAAU;AACrB,QAAI,MAAM,CAAC,aAAa,IAAI,EAAE,GAAG;AAC/B,WAAK,UAAU,MAAM;AAAA,IACvB;AAAA,EACF,CAAC;AAED,MAAI;AACF,UAAM,UAAU,QAAQ,SAAS;AACjC,UAAM,UAAU,cAAc,KAAK,KAAK,IAAI,IAAI;AAAA,EAClD,SAAS,KAAK;AACZ,UAAM,OAAO,qBAAqB,GAAG;AACrC,QAAI,CAAC,IAAI,aAAa;AACpB,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,wBAAwB,CAAC;AAAA,IACzD;AAAA,EACF;AACF;AAEA,SAAS,oBACP,KACA,eAC2B;AAC3B,QAAM,eAAe,oBAAI,IAA0B;AAEnD,MAAI,IAAI,QAAQ,OAAO,KAAK,QAAQ;AAClC,UAAM,YAAY,IAAI,QAAQ,gBAAgB;AAE9C,QAAI,WAAW;AACb,YAAM,WAAW,aAAa,IAAI,SAAS;AAC3C,UAAI,UAAU;AACZ,8BAAsB,UAAU,KAAK,GAAG;AACxC;AAAA,MACF;AAAA,IACF;AAEA,UAAM,iBAAiB,eAAe,cAAc,KAAK,GAAG;AAAA,EAC9D,CAAC;AAED,SAAO;AACT;AAEA,SAAS,yBAAyB,iBAA+C;AAC/E,QAAM,WAAW,MAAM;AACrB,QAAI,iBAAiB;AACnB,sBAAgB,MAAM,EAAE,MAAM,MAAM;AAAA,MAEpC,CAAC;AAAA,IACH;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,UAAQ,GAAG,WAAW,QAAQ;AAC9B,UAAQ,GAAG,UAAU,QAAQ;AAC/B;AAEA,eAAe,cAA6B;AAC1C,QAAM,EAAE,iBAAiB,cAAc,IAAI,MAAM,qBAAqB;AAEtE,QAAM,MAAM,oBAAoB,EAAE,MAAM,KAAK,CAAC;AAC9C,MAAI,IAAI,KAAK,CAAC;AAEd,sBAAoB,KAAK,aAAa;AAEtC,MAAI,IAAI,WAAW,CAAC,MAAM,QAAQ;AAChC,QAAI,KAAK,EAAE,QAAQ,MAAM,SAAS,2BAA2B,CAAC;AAAA,EAChE,CAAC;AAED,MAAI,OAAO,MAAM,MAAM,MAAM;AAC3B,QAAI,kBAAkB,sCAAsC,IAAI,IAAI,IAAI,EAAE;AAC1E,QAAI,kBAAkB,wEAAmE;AACzF,QAAI,kBAAkB,oCAA+B;AACrD,QAAI,kBAAkB,EAAE;AACxB,QAAI,kBAAkB,oCAAoC;AAC1D,QAAI,kBAAkB,+CAA+C,IAAI,EAAE;AAAA,EAC7E,CAAC;AAED,2BAAyB,eAAe;AAC1C;AAEA,eAAe,oBAAqC;AAClD,QAAM,YAAY,QAAQ;AAC1B,YAAU,IAAI,KAAK,CAAC;AAEpB,QAAM,QAAQ,eAAe;AAK7B,YAAU,IAAI,UAAU,QAAQ,OAAO,gBAAgB,CAAC,CAAC;AACzD,YAAU,IAAI,YAAY,QAAQ,OAAO,kBAAkB,CAAC,CAAC;AAG7D,YAAU,IAAI,6BAA6B,OAAO,KAAK,QAAQ;AAC7D,UAAM,EAAE,MAAM,KAAK,IAAI,IAAI;AAC3B,QAAI;AACF,YAAM,SAAS,MAAM,MAAM,UAAU,MAAM,IAAI;AAC/C,UAAI,CAAC,QAAQ;AACX,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,mBAAmB,CAAC;AAClD;AAAA,MACF;AACA,UAAI,KAAK,EAAE,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,UAAU,OAAO,SAAS,CAAC;AAAA,IAClF,SAAS,KAAK;AACZ,YAAM,iBAAiB,+BAA+B,GAAG;AACzD,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,wBAAwB,CAAC;AAAA,IACzD;AAAA,EACF,CAAC;AAGD,YAAU,IAAI,uBAAuB,CAAC,KAAK,QAAQ;AACjD,UAAM,EAAE,MAAM,KAAK,IAAI,IAAI;AAC3B,QAAI;AAAA,MACF;AAAA,MACA,oBAAoB,mBAAmB,IAAI,CAAC,IAAI,mBAAmB,IAAI,CAAC;AAAA,IAC1E;AAAA,EACF,CAAC;AAED,YAAU,IAAI,WAAW,CAAC,MAAM,QAAQ;AACtC,QAAI,KAAK,EAAE,QAAQ,MAAM,SAAS,0BAA0B,CAAC;AAAA,EAC/D,CAAC;AAID,QAAM,UAAU,MAAM,IAAI,QAAiB,CAACC,aAAY;AACtD,UAAM,aAAa,UAAU,OAAO,aAAa,MAAM,MAAM;AAC3D,UAAI,kBAAkB,qCAAqC,IAAI,IAAI,WAAW,EAAE;AAChF,MAAAA,SAAQ,IAAI;AAAA,IACd,CAAC;AACD,eAAW,GAAG,SAAS,CAAC,QAA+B;AACrD,UAAI,IAAI,SAAS,cAAc;AAC7B;AAAA,UACE;AAAA,UACA,eAAe,WAAW;AAAA,QAC5B;AAAA,MACF,OAAO;AACL,cAAM,kBAAkB,iCAAiC,GAAG;AAAA,MAC9D;AACA,MAAAA,SAAQ,KAAK;AAAA,IACf,CAAC;AAAA,EACH,CAAC;AAED,MAAI,CAAC,SAAS;AAGZ,UAAM,SAAS,MAAM,4BAA4B;AACjD,QAAI,QAAQ;AACV,UAAI,kBAAkB,+BAA+B,MAAM,EAAE;AAC7D,aAAO;AAAA,IACT;AACA;AAAA,MACE;AAAA,MACA;AAAA,IACF;AACA,WAAO,oBAAoB,WAAW;AAAA,EACxC;AAIA,MAAI,gBAAgB,oBAAoB,WAAW;AAEnD,MAAI,eAAe;AACjB,QAAI;AACF,sBAAgB,MAAM,YAAY,WAAW;AAC7C,UAAI,kBAAkB,0BAA0B,aAAa,EAAE;AAAA,IACjE,SAAS,KAAK;AACZ,YAAM,kBAAkB,4CAA4C,GAAG;AAAA,IACzE;AAAA,EACF;AAEA,yBAAuB,aAAa;AACpC,SAAO;AACT;AAEA,eAAe,mBAAkC;AAC/C,QAAM,EAAE,iBAAiB,cAAc,IAAI,MAAM,qBAAqB;AAEtE,MAAI,kBAAkB,0CAA0C;AAGhE,QAAM,gBAAgB,MAAM,kBAAkB;AAG9C,QAAM,YAAY,mBAAmB;AAAA,IACnC,GAAG;AAAA,IACH;AAAA,EACF,CAAC;AACD,QAAM,YAAY,IAAI,qBAAqB;AAE3C,QAAM,UAAU,QAAQ,SAAS;AAEjC,QAAM,WAAW,MAAM;AACrB,eAAW;AACX,QAAI,iBAAiB;AACnB,sBAAgB,MAAM,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACxC;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,UAAQ,GAAG,WAAW,QAAQ;AAC9B,UAAQ,GAAG,UAAU,QAAQ;AAC/B;AAEA,eAAe,OAAsB;AACnC,MAAI,cAAc,SAAS;AACzB,UAAM,iBAAiB;AAAA,EACzB,OAAO;AACL,UAAM,YAAY;AAAA,EACpB;AACF;AAEA,KAAK,EAAE,MAAM,CAAC,QAAiB;AAC7B,QAAM,kBAAkB,wBAAwB,GAAG;AACnD,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":["join","resolve","join","join","z","error","z","error","join","resolve"]}
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../src/logger.ts","../src/live-update.ts","../src/registry-backend.ts","../src/tunnel.ts","../src/widget-store/store.ts","../src/widget-store/local-backend.ts","../src/index.ts","../src/services.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { createMcpExpressApp } from \"@modelcontextprotocol/sdk/server/express.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\nimport cors from \"cors\";\nimport express from \"express\";\nimport { registerSession, unregisterSession } from \"./live-update.js\";\nimport { log, error } from \"./logger.js\";\nimport { createRegistryBackend, type RegistryBackend } from \"./registry-backend.js\";\nimport { startTunnel, stopTunnel } from \"./tunnel.js\";\nimport { getWidgetStore } from \"./widget-store/index.js\";\nimport { createMcpAppServer, type McpAppServerOptions } from \"./index.js\";\nimport type { Request, Response } from \"express\";\n\nconst PORT = Number(process.env[\"PORT\"] ?? 3000);\nconst WIDGET_PORT = Number(process.env[\"WIDGET_PORT\"] ?? 3002);\nconst HOST = process.env[\"HOST\"] ?? \"0.0.0.0\";\nconst TRANSPORT = process.env[\"TRANSPORT\"] ?? (process.argv.includes(\"--stdio\") ? \"stdio\" : \"http\");\nconst WIDGET_TUNNEL = process.env[\"WIDGET_TUNNEL\"] === \"true\" || process.argv.includes(\"--tunnel\");\n\ntype SessionEntry = {\n server: ReturnType<typeof createMcpAppServer>;\n transport: StreamableHTTPServerTransport;\n};\n\n/**\n * Cross-process coordination for the shared widget host.\n *\n * Every stdio spawn of this server (Claude Desktop respawns it freely) would\n * otherwise start its own widget server and its own cloudflared quick tunnel.\n * Only one can bind {@link WIDGET_PORT}; the rest orphan extra tunnels and, worse,\n * each render returns a *different* hostname — and any instance whose ephemeral\n * tunnel has dropped serves a dead URL (Cloudflare 1033) into the widget HTML.\n *\n * We elect a single owner via the port bind: whoever binds the port establishes\n * (and verifies) the tunnel and publishes its base URL to a temp file keyed by\n * port; every other instance reuses that URL instead of starting a tunnel.\n */\ninterface WidgetHostState {\n baseUrl: string;\n pid: number;\n updatedAt: number;\n}\n\nconst WIDGET_STATE_FILE = join(tmpdir(), `patchwork-widget-${WIDGET_PORT}.json`);\n\nfunction readWidgetHostState(): WidgetHostState | null {\n try {\n return JSON.parse(readFileSync(WIDGET_STATE_FILE, \"utf8\")) as WidgetHostState;\n } catch {\n return null;\n }\n}\n\nfunction publishWidgetHostState(baseUrl: string): void {\n try {\n const state: WidgetHostState = { baseUrl, pid: process.pid, updatedAt: Date.now() };\n writeFileSync(WIDGET_STATE_FILE, JSON.stringify(state));\n } catch (err) {\n error(\"mcp-app-server\", \"Failed to publish widget host state:\", err);\n }\n}\n\nfunction isProcessAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n // ESRCH = no such process; EPERM = exists but not ours (still alive).\n return (err as NodeJS.ErrnoException).code === \"EPERM\";\n }\n}\n\n/**\n * Wait for the owning instance to publish its (verified) base URL. Only adopt\n * state whose owning pid is still alive, so a non-owner never reuses a dead\n * previous owner's (now-1033) hostname.\n */\nasync function awaitPublishedWidgetBaseUrl(timeoutMs = 35000): Promise<string | null> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n const state = readWidgetHostState();\n if (state?.baseUrl && isProcessAlive(state.pid)) return state.baseUrl;\n await new Promise((r) => setTimeout(r, 500));\n }\n return null;\n}\n\n/** Locate a built browser bundle dir (dist/<name>) from either dist or src. */\nfunction resolveDistDir(name: string): string {\n const candidates = [\n fileURLToPath(new URL(`./${name}`, import.meta.url)), // built: dist/<name>\n fileURLToPath(new URL(`../dist/${name}`, import.meta.url)), // dev via tsx: src → dist\n ];\n return candidates.find((dir) => existsSync(dir)) ?? candidates[0]!;\n}\n\n/** Locate the built runtime bundle (dist/runtime). */\nexport function resolveRuntimeDir(): string {\n return resolveDistDir(\"runtime\");\n}\n\n/** Locate the built MCP App shell bundle (dist/shell). */\nexport function resolveShellDir(): string {\n return resolveDistDir(\"shell\");\n}\n\nasync function setupRegistryBackend(): Promise<{\n registryBackend: RegistryBackend | null;\n serverOptions: McpAppServerOptions;\n}> {\n const toolboxUrl = process.env[\"TOOLBOX_MCP_URL\"];\n\n if (!toolboxUrl) {\n return { registryBackend: null, serverOptions: {} };\n }\n\n log(\"patchwork-mcp\", `Connecting to toolbox at ${toolboxUrl}`);\n\n try {\n const registryBackend = await createRegistryBackend({\n name: \"toolbox\",\n url: toolboxUrl,\n headers: process.env[\"APROVAN_WORKSPACE_ID\"]\n ? { \"X-Aprovan-Workspace\": process.env[\"APROVAN_WORKSPACE_ID\"] }\n : undefined,\n auth: process.env[\"TOOLBOX_TOKEN\"]\n ? { type: \"bearer\", token: process.env[\"TOOLBOX_TOKEN\"] }\n : { type: \"none\" },\n });\n\n const toolInfos = registryBackend.getToolInfos();\n const namespaces = [...new Set(toolInfos.map((t) => t.namespace))];\n log(\n \"patchwork-mcp\",\n `Registry ready: ${toolInfos.length} tools across namespaces: ${namespaces.join(\", \")}`\n );\n\n return {\n registryBackend,\n serverOptions: {\n services: {\n backend: registryBackend,\n tools: toolInfos,\n },\n },\n };\n } catch (err) {\n error(\"patchwork-mcp\", \"Failed to connect to toolbox:\", err);\n error(\"patchwork-mcp\", \"Starting without toolbox services.\");\n return { registryBackend: null, serverOptions: {} };\n }\n}\n\nfunction handleExistingSession(existing: SessionEntry, req: Request, res: Response): void {\n try {\n existing.transport.handleRequest(req, res, req.body);\n } catch (err) {\n error(\"mcp\", \"session request error\", err);\n if (!res.headersSent) {\n res.status(500).json({ error: \"Internal server error\" });\n }\n }\n}\n\nasync function createNewSession(\n serverOptions: McpAppServerOptions,\n sessionStore: Map<string, SessionEntry>,\n req: Request,\n res: Response\n): Promise<void> {\n const mcpServer = createMcpAppServer(serverOptions);\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: () => randomUUID(),\n onsessioninitialized: (id) => {\n sessionStore.set(id, { server: mcpServer, transport });\n registerSession(id, mcpServer);\n },\n onsessionclosed: (id) => {\n sessionStore.delete(id);\n unregisterSession(id);\n },\n });\n\n res.on(\"close\", () => {\n const id = transport.sessionId;\n if (id && !sessionStore.has(id)) {\n void mcpServer.close();\n }\n });\n\n try {\n await mcpServer.connect(transport);\n await transport.handleRequest(req, res, req.body);\n } catch (err) {\n error(\"mcp\", \"new session error\", err);\n if (!res.headersSent) {\n res.status(500).json({ error: \"Internal server error\" });\n }\n }\n}\n\nfunction registerMcpEndpoint(\n app: ReturnType<typeof createMcpExpressApp>,\n serverOptions: McpAppServerOptions\n): Map<string, SessionEntry> {\n const sessionStore = new Map<string, SessionEntry>();\n\n app.all(\"/mcp\", async (req, res) => {\n const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n\n if (sessionId) {\n const existing = sessionStore.get(sessionId);\n if (existing) {\n handleExistingSession(existing, req, res);\n return;\n }\n }\n\n await createNewSession(serverOptions, sessionStore, req, res);\n });\n\n return sessionStore;\n}\n\nfunction registerShutdownHandlers(registryBackend: RegistryBackend | null): void {\n const shutdown = () => {\n if (registryBackend) {\n registryBackend.close().catch(() => {\n /* ignore close errors on exit */\n });\n }\n process.exit(0);\n };\n process.on(\"SIGTERM\", shutdown);\n process.on(\"SIGINT\", shutdown);\n}\n\nasync function startServer(): Promise<void> {\n const { registryBackend, serverOptions } = await setupRegistryBackend();\n\n const app = createMcpExpressApp({ host: HOST });\n app.use(cors());\n\n registerMcpEndpoint(app, serverOptions);\n\n app.get(\"/health\", (_req, res) => {\n res.json({ status: \"ok\", service: \"patchwork-mcp-app-server\" });\n });\n\n app.listen(PORT, HOST, () => {\n log(\"mcp-app-server\", `MCP App Server listening on http://${HOST}:${PORT}`);\n log(\"mcp-app-server\", ` POST /mcp — MCP Streamable HTTP endpoint (stateful sessions)`);\n log(\"mcp-app-server\", ` GET /health — health check`);\n log(\"mcp-app-server\", \"\");\n log(\"mcp-app-server\", \"To expose locally via cloudflared:\");\n log(\"mcp-app-server\", ` cloudflared tunnel --url http://localhost:${PORT}`);\n });\n\n registerShutdownHandlers(registryBackend);\n}\n\nasync function startWidgetServer(): Promise<string> {\n const widgetApp = express();\n widgetApp.use(cors());\n\n const store = getWidgetStore();\n\n // Serve the MCP App shell (resource document's external script) and the shared\n // browser runtime bundle (compiles widgets in-browser). Both resolve to dist\n // whether running built (dist/server.js) or via tsx (src).\n widgetApp.use(\"/shell\", express.static(resolveShellDir()));\n widgetApp.use(\"/runtime\", express.static(resolveRuntimeDir()));\n\n // Raw widget source files — fetched by the runtime and compiled in-browser.\n widgetApp.get(\"/widget/:name/:hash/files\", async (req, res) => {\n const { name, hash } = req.params;\n try {\n const widget = await store.getWidget(name, hash);\n if (!widget) {\n res.status(404).json({ error: \"Widget not found\" });\n return;\n }\n res.json({ files: widget.files, entry: widget.entry, manifest: widget.manifest });\n } catch (err) {\n error(\"widget-server\", \"Error serving widget files:\", err);\n res.status(500).json({ error: \"Internal server error\" });\n }\n });\n\n // Convenience redirect: /widget/:name/:hash → runtime host with the widget preselected.\n widgetApp.get(\"/widget/:name/:hash\", (req, res) => {\n const { name, hash } = req.params;\n res.redirect(\n 302,\n `/runtime/?widget=${encodeURIComponent(name)}/${encodeURIComponent(hash)}`,\n );\n });\n\n widgetApp.get(\"/health\", (_req, res) => {\n res.json({ status: \"ok\", service: \"patchwork-widget-server\" });\n });\n\n // Elect the widget-host owner via the port bind. A failed bind (EADDRINUSE)\n // means another instance already owns the widget server + tunnel.\n const isOwner = await new Promise<boolean>((resolve) => {\n const httpServer = widgetApp.listen(WIDGET_PORT, HOST, () => {\n log(\"mcp-app-server\", `Widget server listening on http://${HOST}:${WIDGET_PORT}`);\n resolve(true);\n });\n httpServer.on(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\") {\n log(\n \"mcp-app-server\",\n `Widget port ${WIDGET_PORT} already owned by another instance; reusing its widget host.`,\n );\n } else {\n error(\"mcp-app-server\", \"Widget server failed to bind:\", err);\n }\n resolve(false);\n });\n });\n\n if (!isOwner) {\n // Reuse the owner's verified, published base URL so every instance hands out\n // the same live hostname instead of orphaning another (possibly dead) tunnel.\n const shared = await awaitPublishedWidgetBaseUrl();\n if (shared) {\n log(\"mcp-app-server\", `Reusing shared widget host: ${shared}`);\n return shared;\n }\n error(\n \"mcp-app-server\",\n \"Owner instance never published a base URL; falling back to localhost (widgets may not load in remote hosts).\",\n );\n return `http://localhost:${WIDGET_PORT}`;\n }\n\n // We own the widget server. Establish (and verify) the tunnel, then publish\n // the base URL for sibling instances.\n let widgetBaseUrl = `http://localhost:${WIDGET_PORT}`;\n\n if (WIDGET_TUNNEL) {\n try {\n widgetBaseUrl = await startTunnel(WIDGET_PORT);\n log(\"mcp-app-server\", `Widgets accessible at: ${widgetBaseUrl}`);\n } catch (err) {\n error(\"mcp-app-server\", \"Failed to start tunnel, using localhost:\", err);\n }\n }\n\n publishWidgetHostState(widgetBaseUrl);\n return widgetBaseUrl;\n}\n\nasync function startStdioServer(): Promise<void> {\n const { registryBackend, serverOptions } = await setupRegistryBackend();\n\n log(\"mcp-app-server\", \"Starting MCP App Server in stdio mode...\");\n\n // Start widget server and optionally tunnel\n const widgetBaseUrl = await startWidgetServer();\n\n // Create MCP server with widget base URL\n const mcpServer = createMcpAppServer({\n ...serverOptions,\n widgetBaseUrl,\n });\n const transport = new StdioServerTransport();\n\n await mcpServer.connect(transport);\n\n const shutdown = () => {\n stopTunnel();\n if (registryBackend) {\n registryBackend.close().catch(() => {});\n }\n process.exit(0);\n };\n process.on(\"SIGTERM\", shutdown);\n process.on(\"SIGINT\", shutdown);\n}\n\nasync function main(): Promise<void> {\n if (TRANSPORT === \"stdio\") {\n await startStdioServer();\n } else {\n await startServer();\n }\n}\n\nmain().catch((err: unknown) => {\n error(\"mcp-app-server\", \"Fatal startup error:\", err);\n process.exit(1);\n});\n","type LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\nconst CURRENT_LEVEL: LogLevel =\n (process.env['LOG_LEVEL'] as LogLevel | undefined) ?? 'info';\n\nconst LEVEL_RANK: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n};\n\nfunction shouldLog(level: LogLevel): boolean {\n return LEVEL_RANK[level] >= LEVEL_RANK[CURRENT_LEVEL];\n}\n\nfunction formatMessage(tag: string, ...args: unknown[]): unknown[] {\n return [`[${tag}]`, ...args];\n}\n\n// All logging goes to stderr to avoid interfering with MCP stdio protocol on stdout\nexport function debug(tag: string, ...args: unknown[]): void {\n if (shouldLog('debug')) {\n console.error(...formatMessage(tag, ...args));\n }\n}\n\nexport function log(tag: string, ...args: unknown[]): void {\n if (shouldLog('info')) {\n console.error(...formatMessage(tag, ...args));\n }\n}\n\nexport function warn(tag: string, ...args: unknown[]): void {\n if (shouldLog('warn')) {\n console.error(...formatMessage(tag, ...args));\n }\n}\n\nexport function error(tag: string, ...args: unknown[]): void {\n if (shouldLog('error')) {\n console.error(...formatMessage(tag, ...args));\n }\n}\n","import { type McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { warn } from \"./logger.js\";\n\n/**\n * A single buffered event for a data stream.\n */\nexport interface StreamEvent {\n seq: number;\n data: unknown;\n timestamp: number;\n}\n\nconst RING_BUFFER_MAX = 100;\n\n/**\n * Process-level ring buffer for each named stream.\n * Shared across all sessions so any request can read from it.\n */\nconst streamBuffers = new Map<string, StreamEvent[]>();\nlet globalSeq = 0;\n\nfunction getOrCreateBuffer(stream: string): StreamEvent[] {\n let buf = streamBuffers.get(stream);\n if (!buf) {\n buf = [];\n streamBuffers.set(stream, buf);\n }\n return buf;\n}\n\n/**\n * Retrieve buffered events for a stream with sequence numbers > afterSeq.\n */\nexport function getEvents(stream: string, afterSeq: number): StreamEvent[] {\n const buf = streamBuffers.get(stream);\n if (!buf) return [];\n return buf.filter((e) => e.seq > afterSeq);\n}\n\n/**\n * Write an event into the ring buffer for a stream.\n * Evicts the oldest entry when the buffer is full.\n */\nexport function appendEvent(stream: string, data: unknown): StreamEvent {\n const buf = getOrCreateBuffer(stream);\n const event: StreamEvent = {\n seq: ++globalSeq,\n data,\n timestamp: Date.now(),\n };\n buf.push(event);\n if (buf.length > RING_BUFFER_MAX) {\n buf.shift();\n }\n return event;\n}\n\n/**\n * Per-session subscription tracking.\n * A session is keyed by the MCP session ID; its McpServer is used to send\n * `notifications/tools/list_changed` as a push signal when stream events arrive.\n */\ninterface SessionEntry {\n server: McpServer;\n streams: Set<string>;\n}\n\nconst sessions = new Map<string, SessionEntry>();\n\n/**\n * Register a live MCP session so it can receive push notifications.\n */\nexport function registerSession(sessionId: string, server: McpServer): void {\n if (!sessions.has(sessionId)) {\n sessions.set(sessionId, { server, streams: new Set() });\n }\n}\n\n/**\n * Unregister a session (e.g. on transport close or DELETE).\n */\nexport function unregisterSession(sessionId: string): void {\n sessions.delete(sessionId);\n}\n\n/**\n * Subscribe a session to a named stream.\n */\nexport function subscribeSession(sessionId: string, stream: string): void {\n const entry = sessions.get(sessionId);\n if (entry) {\n entry.streams.add(stream);\n }\n}\n\n/**\n * Unsubscribe a session from a named stream.\n */\nexport function unsubscribeSession(sessionId: string, stream: string): void {\n const entry = sessions.get(sessionId);\n if (entry) {\n entry.streams.delete(stream);\n }\n}\n\n/**\n * Push a data update to all sessions that have subscribed to `stream`.\n *\n * The event is appended to the ring buffer. Each subscribed session's McpServer\n * then sends a `notifications/tools/list_changed` notification to the host.\n * The host forwards this to the widget, which uses it as a signal to call\n * `poll_updates` and retrieve the buffered data.\n *\n * @returns The new sequence number assigned to this event.\n */\nexport async function pushStreamUpdate(\n stream: string,\n data: unknown,\n): Promise<number> {\n const event = appendEvent(stream, data);\n\n const pushPromises: Promise<void>[] = [];\n for (const [, entry] of sessions) {\n if (entry.streams.has(stream)) {\n const notifyPromise = entry.server.server\n .notification({ method: \"notifications/tools/list_changed\" })\n .catch((err: unknown) => {\n warn(\"live-update\", \"Failed to notify session:\", err);\n });\n pushPromises.push(notifyPromise);\n }\n }\n\n await Promise.all(pushPromises);\n return event.seq;\n}\n\n/**\n * Return the current highest sequence number (useful for initial subscribe).\n */\nexport function currentSeq(): number {\n return globalSeq;\n}\n\n/** Clears all buffers and sessions — for testing only. */\nexport function _resetForTests(): void {\n streamBuffers.clear();\n sessions.clear();\n globalSeq = 0;\n}\n","import {\n PatchworkMcpClient,\n type McpServerConfig,\n type PatchworkTool,\n} from \"@aprovan/patchwork\";\nimport type { ServiceBackend, ServiceToolInfo } from \"./services.js\";\n\nexport type RegistryBackendOptions = Omit<McpServerConfig, \"mode\">;\n\nexport interface RegistryBackend extends ServiceBackend {\n getToolInfos(): ServiceToolInfo[];\n readArtifact(uri: string): Promise<unknown>;\n close(): Promise<void>;\n}\n\nfunction splitTool(tool: PatchworkTool): ServiceToolInfo {\n const separator = tool.name.indexOf(\".\");\n return {\n name: tool.name,\n namespace: separator < 0 ? tool.name : tool.name.slice(0, separator),\n procedure: separator < 0 ? tool.name : tool.name.slice(separator + 1),\n description: tool.description,\n parameters:\n tool.inputSchema && typeof tool.inputSchema === \"object\"\n ? (tool.inputSchema as Record<string, unknown>)\n : undefined,\n };\n}\n\nexport async function createRegistryBackend(\n options: RegistryBackendOptions,\n): Promise<RegistryBackend> {\n const client = new PatchworkMcpClient({ ...options, mode: \"toolbox\" });\n await client.connect();\n const tools = (await client.listTools()).map(splitTool);\n return {\n call(namespace, procedure, args) {\n return client.callTool(\n `${namespace}.${procedure}`,\n (args[0] as Record<string, unknown> | undefined) ?? {},\n );\n },\n getToolInfos: () => tools,\n readArtifact: (uri) => client.readResource(uri),\n close: () => client.close(),\n };\n}\n","import { spawn, type ChildProcess } from \"node:child_process\";\nimport { log, error } from \"./logger.js\";\n\nlet tunnelProcess: ChildProcess | null = null;\nlet tunnelUrl: string | null = null;\n\n/**\n * Poll the tunnel's public `/health` endpoint until it returns 200.\n *\n * cloudflared prints the `*.trycloudflare.com` URL as soon as the process\n * registers, but the edge route is frequently not live yet — requests in that\n * window return Cloudflare error 1033 (\"unable to resolve\"). Publishing the URL\n * before it's actually reachable bakes a dead host into rendered widgets, so we\n * verify reachability before adopting it.\n */\nasync function waitForTunnelLive(url: string, timeoutMs = 30000): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n try {\n const res = await fetch(`${url}/health`, { signal: AbortSignal.timeout(5000) });\n if (res.ok) return true;\n } catch {\n // not reachable yet — keep polling\n }\n await new Promise((r) => setTimeout(r, 1000));\n }\n return false;\n}\n\n/**\n * Start a cloudflare tunnel to expose a local port publicly.\n * Returns the public URL once the tunnel is established AND verified reachable.\n */\nexport async function startTunnel(port: number): Promise<string> {\n if (tunnelUrl) return tunnelUrl;\n\n const detectedUrl = await new Promise<string>((resolve, reject) => {\n const args = [\"tunnel\", \"--url\", `http://localhost:${port}`];\n\n log(\"tunnel\", `Starting cloudflared tunnel for port ${port}...`);\n\n tunnelProcess = spawn(\"cloudflared\", args, {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let resolved = false;\n const timeout = setTimeout(() => {\n if (!resolved) {\n resolved = true;\n reject(new Error(\"Tunnel startup timed out after 30s\"));\n }\n }, 30000);\n\n const handleOutput = (data: Buffer) => {\n const text = data.toString();\n\n // Look for the tunnel URL in the output\n // cloudflared outputs: \"https://xxx.trycloudflare.com\"\n const urlMatch = text.match(/https:\\/\\/[^\\s]+\\.trycloudflare\\.com\\/?/);\n if (urlMatch && !resolved) {\n resolved = true;\n clearTimeout(timeout);\n resolve(urlMatch[0].replace(/\\/$/, \"\"));\n }\n };\n\n tunnelProcess.stdout?.on(\"data\", handleOutput);\n tunnelProcess.stderr?.on(\"data\", handleOutput);\n\n tunnelProcess.on(\"error\", (err) => {\n if (!resolved) {\n resolved = true;\n clearTimeout(timeout);\n error(\"tunnel\", \"Failed to start cloudflared:\", err);\n reject(err);\n }\n });\n\n tunnelProcess.on(\"exit\", (code) => {\n if (!resolved) {\n resolved = true;\n clearTimeout(timeout);\n reject(new Error(`cloudflared exited with code ${code}`));\n }\n tunnelProcess = null;\n tunnelUrl = null;\n });\n });\n\n // Verify-then-publish: don't adopt the hostname until the edge route is live,\n // otherwise rendered widgets reference a host that returns Cloudflare 1033.\n log(\"tunnel\", `Tunnel hostname detected (${detectedUrl}); verifying reachability...`);\n const live = await waitForTunnelLive(detectedUrl);\n if (!live) {\n error(\n \"tunnel\",\n `Tunnel ${detectedUrl} did not become reachable within 30s — publishing anyway, but widgets may fail to load until the edge route propagates.`,\n );\n } else {\n log(\"tunnel\", `Tunnel verified reachable: ${detectedUrl}`);\n }\n\n tunnelUrl = detectedUrl;\n return tunnelUrl;\n}\n\n/**\n * Stop the cloudflare tunnel if running.\n */\nexport function stopTunnel(): void {\n if (tunnelProcess) {\n tunnelProcess.kill();\n tunnelProcess = null;\n tunnelUrl = null;\n }\n}\n\n/**\n * Get the current tunnel URL if available.\n */\nexport function getTunnelUrl(): string | null {\n return tunnelUrl;\n}\n","import { homedir } from \"node:os\";\nimport { join, resolve } from \"node:path\";\nimport { LocalFileBackend } from \"./local-backend.js\";\nimport type { FSProvider, StoredWidget, StoredWidgetInfo, WidgetStoreOptions } from \"./types.js\";\nimport type { Manifest, VirtualFile } from \"@aprovan/patchwork-compiler\";\n\nconst WIDGETS_PREFIX = \"widgets\";\nconst FILES_SUBDIR = \"files\";\nconst RESOURCE_URI_PREFIX = \"ui://widgets/\";\n\nfunction getDefaultStorageDir(): string {\n // Use WIDGET_STORE_PATH env var, or fall back to ~/.patchwork/widget-store\n return process.env[\"WIDGET_STORE_PATH\"] ?? join(homedir(), \".patchwork\", \"widget-store\");\n}\n\ninterface StoredManifest extends Manifest {\n entry: string;\n createdAt: number;\n}\n\n/**\n * Persistent store for **raw, uncompiled** widget source files.\n *\n * Widgets are saved as their original `.tsx`/`.ts` files plus a `manifest.json`;\n * compilation happens in the browser via the shared `@aprovan/patchwork-compiler`\n * runtime when the widget is rendered. Layout:\n *\n * ```\n * widgets/<name>/<hash>/\n * files/main.tsx\n * files/price-card.tsx\n * manifest.json { ...manifest, entry, createdAt }\n * ```\n */\nexport class WidgetStore {\n private provider: FSProvider;\n private storageDir: string;\n\n constructor(options: WidgetStoreOptions = {}) {\n this.storageDir = resolve(options.storageDir ?? getDefaultStorageDir());\n this.provider = options.backend ?? new LocalFileBackend(this.storageDir);\n }\n\n private fullPath(virtualPath: string): string {\n return join(WIDGETS_PREFIX, virtualPath);\n }\n\n private async readFilesRecursive(dir: string, base = \"\"): Promise<VirtualFile[]> {\n const files: VirtualFile[] = [];\n let entries;\n try {\n entries = await this.provider.readdir(dir);\n } catch {\n return files;\n }\n for (const entry of entries) {\n const childDir = join(dir, entry.name);\n const relPath = base ? `${base}/${entry.name}` : entry.name;\n if (entry.isDirectory()) {\n files.push(...(await this.readFilesRecursive(childDir, relPath)));\n } else {\n const content = await this.provider.readFile(childDir);\n files.push({ path: relPath, content });\n }\n }\n return files;\n }\n\n async saveWidget(\n hash: string,\n files: VirtualFile[],\n manifest: Manifest,\n entry: string,\n ): Promise<StoredWidget> {\n const widgetDir = `${manifest.name}/${hash}`;\n const createdAt = Date.now();\n\n await Promise.all(\n files.map((file) =>\n this.provider.writeFile(\n this.fullPath(`${widgetDir}/${FILES_SUBDIR}/${file.path}`),\n file.content,\n ),\n ),\n );\n\n const storedManifest: StoredManifest = { ...manifest, entry, createdAt };\n await this.provider.writeFile(\n this.fullPath(`${widgetDir}/manifest.json`),\n JSON.stringify(storedManifest),\n );\n\n return {\n path: this.fullPath(widgetDir),\n resourceUri: `${RESOURCE_URI_PREFIX}${widgetDir}/view.html`,\n files,\n entry,\n manifest,\n createdAt,\n };\n }\n\n async getWidget(name: string, hash: string): Promise<StoredWidget | null> {\n const widgetDir = `${name}/${hash}`;\n const manifestVirtualPath = this.fullPath(`${widgetDir}/manifest.json`);\n\n const exists = await this.provider.exists(manifestVirtualPath);\n if (!exists) return null;\n\n const files = await this.readFilesRecursive(this.fullPath(`${widgetDir}/${FILES_SUBDIR}`));\n\n let manifest: Manifest;\n let entry = files[0]?.path ?? \"main.tsx\";\n let createdAt = Date.now();\n try {\n const raw = await this.provider.readFile(manifestVirtualPath);\n const parsed = JSON.parse(raw) as Partial<StoredManifest>;\n manifest = {\n name: (parsed.name as string) ?? name,\n version: (parsed.version as string) ?? \"0.1.0\",\n platform: parsed.platform ?? \"browser\",\n image: (parsed.image as string) ?? \"@aprovan/patchwork-image-shadcn\",\n description: parsed.description,\n services: parsed.services,\n };\n if (parsed.entry) entry = parsed.entry;\n if (typeof parsed.createdAt === \"number\") createdAt = parsed.createdAt;\n } catch {\n manifest = {\n name,\n version: \"0.1.0\",\n platform: \"browser\",\n image: \"@aprovan/patchwork-image-shadcn\",\n };\n }\n\n return {\n path: this.fullPath(widgetDir),\n resourceUri: `${RESOURCE_URI_PREFIX}${widgetDir}/view.html`,\n files,\n entry,\n manifest,\n createdAt,\n };\n }\n\n async listWidgets(): Promise<StoredWidgetInfo[]> {\n const results: StoredWidgetInfo[] = [];\n const rootPath = this.fullPath(\"\");\n\n try {\n const names = await this.provider.readdir(rootPath);\n for (const nameEntry of names) {\n if (!nameEntry.isDirectory()) continue;\n const name = nameEntry.name;\n\n try {\n const hashes = await this.provider.readdir(join(rootPath, name));\n for (const hashEntry of hashes) {\n if (!hashEntry.isDirectory()) continue;\n const hash = hashEntry.name;\n const manifestVirtualPath = this.fullPath(`${name}/${hash}/manifest.json`);\n\n let manifest: Partial<StoredManifest> = {};\n try {\n const raw = await this.provider.readFile(manifestVirtualPath);\n manifest = JSON.parse(raw) as Partial<StoredManifest>;\n } catch {\n continue;\n }\n\n results.push({\n path: this.fullPath(`${name}/${hash}`),\n resourceUri: `${RESOURCE_URI_PREFIX}${name}/${hash}/view.html`,\n name: manifest.name ?? name,\n version: manifest.version ?? \"0.1.0\",\n description: manifest.description,\n services: manifest.services,\n entry: manifest.entry,\n createdAt: manifest.createdAt ?? 0,\n });\n }\n } catch {\n continue;\n }\n }\n } catch {\n return results;\n }\n\n return results.sort((a, b) => b.createdAt - a.createdAt);\n }\n\n async deleteWidget(name: string, hash: string): Promise<boolean> {\n const widgetDir = this.fullPath(`${name}/${hash}`);\n const exists = await this.provider.exists(widgetDir);\n if (!exists) return false;\n\n await this.provider.rmdir(widgetDir, { recursive: true });\n return true;\n }\n\n async hasWidget(name: string, hash: string): Promise<boolean> {\n return this.provider.exists(this.fullPath(`${name}/${hash}/manifest.json`));\n }\n\n resourceUriFor(name: string, hash: string): string {\n return `${RESOURCE_URI_PREFIX}${name}/${hash}/view.html`;\n }\n\n async loadAll(): Promise<StoredWidget[]> {\n const infos = await this.listWidgets();\n const widgets: StoredWidget[] = [];\n\n for (const info of infos) {\n const uriPath = info.resourceUri.replace(RESOURCE_URI_PREFIX, \"\").replace(/\\/view\\.html$/, \"\");\n const parts = uriPath.split(\"/\");\n const name = parts[0];\n const hash = parts[1];\n\n if (!name || !hash) continue;\n\n const widget = await this.getWidget(name, hash);\n if (widget) widgets.push(widget);\n }\n\n return widgets;\n }\n}\n\nlet _instance: WidgetStore | null = null;\n\nexport function getWidgetStore(options?: WidgetStoreOptions): WidgetStore {\n if (!_instance) {\n _instance = new WidgetStore(options);\n }\n return _instance;\n}\n\nexport function resetWidgetStore(): void {\n _instance = null;\n}\n","import { readFile, writeFile, unlink, stat, mkdir, readdir, rm, access } from \"node:fs/promises\";\nimport { join, dirname, normalize } from \"node:path\";\nimport type { DirEntry, FileStats, FSProvider } from \"./types.js\";\n\nfunction createDirEntry(name: string, isDir: boolean): DirEntry {\n return {\n name,\n isFile: () => !isDir,\n isDirectory: () => isDir,\n };\n}\n\nfunction createFileStats(size: number, mtime: Date, isDir: boolean): FileStats {\n return {\n size,\n mtime,\n isFile: () => !isDir,\n isDirectory: () => isDir,\n };\n}\n\nexport class LocalFileBackend implements FSProvider {\n constructor(private basePath: string) {}\n\n private resolve(path: string): string {\n return join(this.basePath, normalize(path));\n }\n\n async readFile(path: string): Promise<string> {\n return readFile(this.resolve(path), \"utf-8\");\n }\n\n async writeFile(path: string, content: string): Promise<void> {\n const fullPath = this.resolve(path);\n await mkdir(dirname(fullPath), { recursive: true });\n await writeFile(fullPath, content, \"utf-8\");\n }\n\n async unlink(path: string): Promise<void> {\n await unlink(this.resolve(path));\n }\n\n async stat(path: string): Promise<FileStats> {\n const fullPath = this.resolve(path);\n const s = await stat(fullPath);\n return createFileStats(s.size, s.mtime, s.isDirectory());\n }\n\n async mkdir(path: string, options?: { recursive?: boolean }): Promise<void> {\n await mkdir(this.resolve(path), options);\n }\n\n async readdir(path: string): Promise<DirEntry[]> {\n const fullPath = this.resolve(path);\n const entries = await readdir(fullPath, { withFileTypes: true });\n return entries.map((e) =>\n createDirEntry(e.name, e.isDirectory()),\n );\n }\n\n async rmdir(path: string, options?: { recursive?: boolean }): Promise<void> {\n if (options?.recursive) {\n await rm(this.resolve(path), { recursive: true, force: true });\n } else {\n await rm(this.resolve(path), { force: true });\n }\n }\n\n async exists(path: string): Promise<boolean> {\n try {\n await access(this.resolve(path));\n return true;\n } catch {\n return false;\n }\n }\n}\n","import {\n createProjectFromFiles,\n createSingleFileProject,\n type Manifest,\n type VirtualFile,\n type VirtualProject,\n} from \"@aprovan/patchwork-compiler\";\nimport {\n registerAppTool,\n RESOURCE_MIME_TYPE,\n} from \"@modelcontextprotocol/ext-apps/server\";\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport {\n subscribeSession,\n unsubscribeSession as _unsubscribeSession,\n getEvents,\n pushStreamUpdate,\n currentSeq,\n type StreamEvent,\n} from \"./live-update.js\";\nimport { warn } from \"./logger.js\";\nimport {\n ServiceBridge,\n type ServiceBackend,\n type ServiceToolInfo,\n type ServiceBridgeConfig,\n} from \"./services.js\";\nimport { getWidgetStore } from \"./widget-store/index.js\";\n\nexport type { ServiceBackend, ServiceToolInfo, ServiceBridgeConfig };\nexport type { StreamEvent };\nexport { pushStreamUpdate };\n\nconst DEFAULT_WIDGET_PORT = Number(process.env[\"WIDGET_PORT\"] ?? 3002);\nconst DEFAULT_WIDGET_HOST = process.env[\"WIDGET_HOST\"] ?? \"localhost\";\nconst DEFAULT_WIDGET_BASE_URL = `http://${DEFAULT_WIDGET_HOST}:${DEFAULT_WIDGET_PORT}`;\n\ninterface WidgetRef {\n name: string;\n hash: string;\n entry: string;\n}\n\n/**\n * Generate the MCP App resource document.\n *\n * Per the MCP Apps protocol the resource document itself must be the app that\n * connects to the host, and it runs under a strict CSP with no `unsafe-eval` —\n * so esbuild-wasm cannot run here. The resource therefore loads the bundled\n * ext-apps \"shell\" (served from the widget host, allow-listed via\n * `resourceDomains`) which connects to the host and embeds the CSP-free runtime\n * iframe that actually compiles the widget. The widget + inputs are passed to\n * the shell via a base64 `data-config` attribute (no inline script → CSP-safe).\n */\nfunction generateResourceHtml(\n shellUrl: string,\n runtimeUrl: string,\n widget: WidgetRef,\n inputs: Record<string, unknown>,\n): string {\n const config = JSON.stringify({\n runtime: runtimeUrl,\n widget: `${widget.name}/${widget.hash}`,\n inputs,\n });\n const configB64 = Buffer.from(config, \"utf-8\").toString(\"base64\");\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>${widget.name}</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html, body { width: 100%; }\n #pw-root { width: 100%; }\n </style>\n</head>\n<body>\n <div id=\"pw-root\"></div>\n <script src=\"${shellUrl}\" data-config=\"${configB64}\"></script>\n</body>\n</html>`;\n}\n\n/** Stable, non-cryptographic content hash used as the widget store key. */\nfunction hashFiles(files: VirtualFile[], manifest: Manifest): string {\n const input = JSON.stringify({\n name: manifest.name,\n image: manifest.image,\n files: files.map((f) => [f.path, f.content]),\n });\n let hash = 0;\n for (let i = 0; i < input.length; i++) {\n hash = ((hash << 5) - hash + input.charCodeAt(i)) | 0;\n }\n return Math.abs(hash).toString(16).padStart(8, \"0\");\n}\n\nconst MANIFEST_DEFAULTS: Manifest = {\n name: \"widget\",\n version: \"0.1.0\",\n platform: \"browser\",\n image: \"@aprovan/patchwork-image-shadcn\",\n};\n\n/**\n * Build the CSP the host applies to the resource document.\n *\n * - `resourceDomains` (→ `script-src`) must allow the external shell bundle.\n * - `frameDomains` (→ `frame-src`) must allow the nested runtime iframe.\n *\n * Both live on the widget host origin, so a single allow-listed origin covers\n * them. Entries are scheme-qualified per the CSP spec.\n */\nfunction buildCspConfig(\n widgetBaseUrl: string,\n): { frameDomains: string[]; resourceDomains: string[] } | undefined {\n try {\n const url = new URL(widgetBaseUrl);\n // Use the exact origin (scheme + host + port). The shell bundle and the\n // nested runtime iframe both live on this single origin, so one entry covers\n // script-src (resourceDomains) and frame-src (frameDomains). Hosts enforce\n // the resource CSP strictly and drop broad wildcard hosts like\n // `https://*.trycloudflare.com`, which would leave script-src without the\n // shell origin and block the bootstrap script — so never wildcard.\n const origin = url.port\n ? `${url.protocol}//${url.hostname}:${url.port}`\n : `${url.protocol}//${url.hostname}`;\n return { frameDomains: [origin], resourceDomains: [origin] };\n } catch {\n return undefined;\n }\n}\n\nfunction buildManifest(input?: Record<string, unknown>): Manifest {\n return {\n name: (input?.[\"name\"] as string) ?? MANIFEST_DEFAULTS.name,\n version: (input?.[\"version\"] as string) ?? MANIFEST_DEFAULTS.version,\n platform: \"browser\",\n image: (input?.[\"image\"] as string) ?? MANIFEST_DEFAULTS.image,\n services: input?.[\"services\"] as string[] | undefined,\n };\n}\n\nconst DEFAULT_WIDGET_SOURCE =\n \"export default function Widget() { return <div>Hello Patchwork</div>; }\";\n\nfunction buildProject(\n name: string,\n source?: string,\n files?: Array<{ path: string; content: string }>,\n entry?: string\n): VirtualProject {\n if (files && files.length > 0) {\n const virtualFiles: VirtualFile[] = files.map((f) => ({\n path: f.path,\n content: f.content,\n }));\n const project = createProjectFromFiles(virtualFiles, name);\n if (entry) project.entry = entry;\n return project;\n }\n return createSingleFileProject(source ?? DEFAULT_WIDGET_SOURCE, entry ?? \"main.tsx\", name);\n}\n\nexport interface McpAppServerOptions {\n services?: ServiceBridgeConfig;\n /** Base URL for serving widgets (e.g., tunnel URL). Defaults to localhost:3002. */\n widgetBaseUrl?: string;\n}\n\nexport function createMcpAppServer(options: McpAppServerOptions = {}): McpServer {\n const server = new McpServer({\n name: \"patchwork-mcp-app-server\",\n version: \"0.1.0\",\n });\n\n const serviceBridge = options.services ? new ServiceBridge(options.services) : null;\n const widgetBaseUrl = options.widgetBaseUrl ?? DEFAULT_WIDGET_BASE_URL;\n\n const runtimeUrl = `${widgetBaseUrl}/runtime/`;\n const shellUrl = `${widgetBaseUrl}/shell/shell.js`;\n const directUrl = (name: string, hash: string): string =>\n `${runtimeUrl}?widget=${encodeURIComponent(name)}/${encodeURIComponent(hash)}`;\n\n const store = getWidgetStore();\n\n /** Build the MCP App resource document that renders a stored widget. */\n const renderResource = (ref: WidgetRef, inputs: Record<string, unknown>) => {\n const csp = buildCspConfig(widgetBaseUrl);\n return {\n type: \"resource\" as const,\n resource: {\n uri: store.resourceUriFor(ref.name, ref.hash),\n mimeType: RESOURCE_MIME_TYPE,\n text: generateResourceHtml(shellUrl, runtimeUrl, ref, inputs),\n ...(csp ? { _meta: { ui: { csp } } } : {}),\n },\n };\n };\n\n registerAppTool(\n server,\n \"save_widget\",\n {\n description:\n \"Save a JSX/TSX widget's raw source files for reuse and render it as an MCP App resource. \" +\n \"Pass source code for a single-file widget, or a files array for a multi-file project. \" +\n \"The widget is stored uncompiled and compiled in the browser by the shared Patchwork \" +\n \"runtime when rendered — pass `inputs` to supply startup props to the widget.\",\n inputSchema: {\n source: z\n .string()\n .optional()\n .describe(\n \"JSX/TSX source code for a single-file widget. Must export a default React component.\"\n ),\n files: z\n .array(\n z.object({\n path: z.string().describe(\"File path relative to project root (e.g. 'main.tsx')\"),\n content: z.string().describe(\"File contents\"),\n })\n )\n .optional()\n .describe(\n \"Array of files for a multi-file widget project. At least one file should be the entry point (main.tsx or index.tsx).\"\n ),\n entry: z\n .string()\n .optional()\n .describe(\"Entry point file path. Defaults to auto-detection (main.tsx, index.tsx).\"),\n name: z.string().optional().describe(\"Widget name for the manifest. Defaults to 'widget'.\"),\n image: z\n .string()\n .optional()\n .describe(\n \"Patchwork image package to use. Defaults to '@aprovan/patchwork-image-shadcn'.\"\n ),\n services: z\n .array(z.string())\n .optional()\n .describe(\n \"Service namespaces the widget calls (e.g., ['weather', 'stripe']). \" +\n \"A proxy shim is injected so widget code can call namespace.procedure(args) directly.\"\n ),\n inputs: z\n .record(z.unknown())\n .optional()\n .describe(\"Startup props passed to the widget's default export when it is rendered.\"),\n },\n _meta: {\n ui: { resourceUri: \"ui://widgets/{name}/{hash}/view.html\" },\n },\n },\n async (args) => {\n const source = args?.[\"source\"] as string | undefined;\n const files = args?.[\"files\"] as Array<{ path: string; content: string }> | undefined;\n const entry = args?.[\"entry\"] as string | undefined;\n const requestedServices = args?.[\"services\"] as string[] | undefined;\n const inputs = (args?.[\"inputs\"] as Record<string, unknown> | undefined) ?? {};\n\n const manifestInput: Record<string, unknown> = {};\n if (args?.[\"name\"]) manifestInput[\"name\"] = args[\"name\"];\n if (args?.[\"image\"]) manifestInput[\"image\"] = args[\"image\"];\n\n // Validate requested services against the connected backend.\n let services = requestedServices ?? [];\n if (services.length > 0 && serviceBridge) {\n const availableNamespaces = serviceBridge.getNamespaces();\n const unavailable = services.filter((ns) => !availableNamespaces.includes(ns));\n if (unavailable.length > 0) {\n warn(\n \"mcp-app-server\",\n `Requested services not available: ${unavailable.join(\", \")}. Available: ${availableNamespaces.join(\", \")}`\n );\n }\n services = services.filter((ns) => availableNamespaces.includes(ns));\n }\n if (services.length > 0) manifestInput[\"services\"] = services;\n\n const manifest = buildManifest(manifestInput);\n const project = buildProject(manifest.name, source, files, entry);\n const projectFiles = Array.from(project.files.values());\n\n try {\n const hash = hashFiles(projectFiles, manifest);\n await store.saveWidget(hash, projectFiles, manifest, project.entry);\n\n const ref: WidgetRef = { name: manifest.name, hash, entry: project.entry };\n\n return {\n content: [\n renderResource(ref, inputs),\n {\n type: \"text\" as const,\n text:\n `Widget \"${manifest.name}\" saved. Hash: ${hash}\\n` +\n `Compiled in-browser at: ${directUrl(manifest.name, hash)}`,\n },\n ],\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Failed to save widget: ${message}`,\n },\n ],\n isError: true,\n };\n }\n }\n );\n\n if (serviceBridge) {\n // Register search_services and call_service tools only (not individual service tools)\n // This avoids exposing hundreds of tools with names that may exceed 64 chars\n serviceBridge.registerSearchServices(server);\n }\n\n registerAppTool(\n server,\n \"list_widgets\",\n {\n description:\n \"List all persisted widgets in the VFS widget store. \" +\n \"Returns each widget's name, version, description, path, and resource URI.\",\n _meta: { ui: { resourceUri: \"ui://widgets/list\" } },\n },\n async () => {\n const widgets = await store.listWidgets();\n\n if (widgets.length === 0) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: \"No widgets stored in the VFS widget store.\",\n },\n ],\n };\n }\n\n const lines = widgets.map((w) => {\n const parts = [`- **${w.name}** (v${w.version})`];\n if (w.description) parts.push(` ${w.description}`);\n parts.push(` Path: ${w.path}`);\n parts.push(` URI: ${w.resourceUri}`);\n if (w.entry) parts.push(` Entry: ${w.entry}`);\n if (w.services && w.services.length > 0) parts.push(` Services: ${w.services.join(\", \")}`);\n return parts.join(\"\\n\");\n });\n\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Stored widgets (${widgets.length}):\\n\\n${lines.join(\"\\n\\n\")}`,\n },\n ],\n };\n }\n );\n\n registerAppTool(\n server,\n \"render_widget\",\n {\n description:\n \"Render a persisted widget by its name and hash. \" +\n \"Serves the saved widget as an MCP App resource that compiles in the browser, \" +\n \"optionally supplying startup props via `inputs`.\",\n inputSchema: {\n name: z.string().describe(\"Widget name (as stored in the VFS widget store).\"),\n hash: z\n .string()\n .optional()\n .describe(\n \"Widget content hash. If omitted, renders the most recent version of the named widget.\"\n ),\n inputs: z\n .record(z.unknown())\n .optional()\n .describe(\"Startup props passed to the widget's default export when it is rendered.\"),\n },\n _meta: {\n ui: { resourceUri: \"ui://widgets/{name}/{hash}/view.html\" },\n },\n },\n async (args) => {\n const name = args?.[\"name\"] as string;\n const hashInput = args?.[\"hash\"] as string | undefined;\n const inputs = (args?.[\"inputs\"] as Record<string, unknown> | undefined) ?? {};\n\n if (!name) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: \"Widget name is required.\",\n },\n ],\n isError: true,\n };\n }\n\n let hash = hashInput;\n if (!hash) {\n const widgets = await store.listWidgets();\n const match = widgets.find((w) => w.name === name);\n if (!match) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `No stored widget found with name \"${name}\".`,\n },\n ],\n isError: true,\n };\n }\n const resourcePath = match.resourceUri\n .replace(\"ui://widgets/\", \"\")\n .replace(\"/view.html\", \"\");\n const parts = resourcePath.split(\"/\");\n hash = parts[1] ?? parts[0] ?? \"\";\n }\n\n if (!hash) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Could not determine hash for widget \"${name}\".`,\n },\n ],\n isError: true,\n };\n }\n\n const widget = await store.getWidget(name, hash);\n if (!widget) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Widget \"${name}\" with hash \"${hash}\" not found in the VFS store.`,\n },\n ],\n isError: true,\n };\n }\n\n const ref: WidgetRef = { name, hash, entry: widget.entry };\n\n return {\n content: [\n renderResource(ref, inputs),\n {\n type: \"text\" as const,\n text: `Rendered widget \"${name}\" (hash: ${hash}).\\nCompiled in-browser at: ${directUrl(name, hash)}`,\n },\n ],\n };\n }\n );\n\n registerLiveUpdateTools(server);\n\n return server;\n}\n\nexport { WidgetStore, getWidgetStore, resetWidgetStore } from \"./widget-store/index.js\";\nexport type { StoredWidget, StoredWidgetInfo, WidgetStoreOptions } from \"./widget-store/types.js\";\n\n// ---------------------------------------------------------------------------\n// Live-update tools\n// ---------------------------------------------------------------------------\n\n/**\n * Register the three tools that power the live-update channel:\n *\n * - `subscribe_stream` — widget declares interest in a named data stream.\n * - `poll_updates` — widget fetches buffered events since a given seq.\n * - `push_update` — backend/LLM pushes new data onto a stream.\n *\n * The session ID is extracted from the MCP request's `_meta` extra or from the\n * internal `RequestHandlerExtra`. Tools that need the session ID use the\n * `extra.meta?.sessionId` field that the SDK populates from the transport.\n */\nfunction registerLiveUpdateTools(server: McpServer): void {\n // subscribe_stream — widget registers interest in a named data stream.\n server.registerTool(\n \"subscribe_stream\",\n {\n description:\n \"Subscribe this widget session to a named data stream. \" +\n \"The server will send `notifications/tools/list_changed` whenever new \" +\n \"events arrive; the widget should then call `poll_updates` to fetch them. \" +\n \"Returns the current sequence number so the widget knows where to start polling.\",\n inputSchema: {\n stream: z.string().describe(\"Name of the data stream to subscribe to.\"),\n session_id: z\n .string()\n .optional()\n .describe(\n \"MCP session ID. Widgets should pass the value returned in the \" +\n \"Mcp-Session-Id response header during initialization.\"\n ),\n },\n },\n (args, extra) => {\n const stream = (args as Record<string, unknown>)[\"stream\"] as string;\n // Prefer an explicit session_id arg; fall back to the transport session\n const sessionId =\n ((args as Record<string, unknown>)[\"session_id\"] as string | undefined) ??\n ((extra as Record<string, unknown>)[\"sessionId\"] as string | undefined);\n\n if (sessionId) {\n subscribeSession(sessionId, stream);\n }\n\n const seq = currentSeq();\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({ success: true, stream, seq }),\n },\n ],\n };\n }\n );\n\n // poll_updates — returns buffered events for a stream since afterSeq.\n server.registerTool(\n \"poll_updates\",\n {\n description:\n \"Fetch buffered events for a data stream that arrived after the given \" +\n \"sequence number. Call this after receiving a `notifications/tools/list_changed` \" +\n \"notification (which the server sends when new data is available). \" +\n \"Pass the highest `seq` value from the last successful poll to avoid duplicates.\",\n inputSchema: {\n stream: z.string().describe(\"Name of the data stream to poll.\"),\n after_seq: z\n .number()\n .int()\n .default(0)\n .describe(\n \"Return only events with seq > after_seq. Pass 0 to retrieve all buffered events.\"\n ),\n },\n },\n (args) => {\n const stream = (args as Record<string, unknown>)[\"stream\"] as string;\n const afterSeq = ((args as Record<string, unknown>)[\"after_seq\"] as number) ?? 0;\n\n const events = getEvents(stream, afterSeq);\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({ success: true, stream, events }),\n },\n ],\n };\n }\n );\n\n // push_update — backend or LLM pushes new data onto a stream.\n server.registerTool(\n \"push_update\",\n {\n description:\n \"Push a data update onto a named stream, broadcasting it to all \" +\n \"subscribed widget sessions. Subscribing widgets will receive a \" +\n \"`notifications/tools/list_changed` signal and then call `poll_updates` \" +\n \"to retrieve the new data. Use this tool from server-side code or as an \" +\n \"LLM tool to drive real-time widget updates.\",\n inputSchema: {\n stream: z.string().describe(\"Name of the data stream to push to.\"),\n data: z\n .record(z.unknown())\n .describe(\"Arbitrary JSON-serialisable payload to push to subscribers.\"),\n },\n },\n async (args) => {\n const stream = (args as Record<string, unknown>)[\"stream\"] as string;\n const data = (args as Record<string, unknown>)[\"data\"];\n\n const seq = await pushStreamUpdate(stream, data);\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({ success: true, stream, seq }),\n },\n ],\n };\n }\n );\n}\n","import { type McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\n\nexport interface ServiceBackend {\n call(namespace: string, procedure: string, args: unknown[]): Promise<unknown>;\n}\n\nexport interface ServiceToolInfo {\n name: string;\n namespace: string;\n procedure: string;\n description?: string;\n parameters?: Record<string, unknown>;\n}\n\nexport interface ServiceBridgeConfig {\n backend: ServiceBackend;\n tools: ServiceToolInfo[];\n}\n\nconst TOOL_SEPARATOR = \"__\";\n\nfunction toMcpToolName(namespace: string, procedure: string): string {\n return `${namespace}${TOOL_SEPARATOR}${procedure}`;\n}\n\nfunction jsonSchemaToZodShape(schema?: Record<string, unknown>): Record<string, z.ZodTypeAny> {\n const properties = (schema?.[\"properties\"] ?? {}) as Record<\n string,\n { type?: string; description?: string }\n >;\n const required = new Set((schema?.[\"required\"] ?? []) as string[]);\n\n const shape: Record<string, z.ZodTypeAny> = {};\n for (const [key, prop] of Object.entries(properties)) {\n let field: z.ZodTypeAny;\n switch (prop.type) {\n case \"number\":\n case \"integer\":\n field = z.number();\n break;\n case \"boolean\":\n field = z.boolean();\n break;\n case \"array\":\n field = z.array(z.unknown());\n break;\n case \"object\":\n field = z.record(z.unknown());\n break;\n default:\n field = z.string();\n break;\n }\n if (prop.description) {\n field = field.describe(prop.description);\n }\n if (!required.has(key)) {\n field = field.optional();\n }\n shape[key] = field;\n }\n\n return shape;\n}\n\nexport class ServiceBridge {\n private backend: ServiceBackend;\n private tools: Map<string, ServiceToolInfo> = new Map();\n\n constructor(config: ServiceBridgeConfig) {\n this.backend = config.backend;\n for (const tool of config.tools) {\n this.tools.set(tool.name, tool);\n }\n }\n\n registerTools(server: McpServer): void {\n for (const [, info] of this.tools) {\n const mcpToolName = toMcpToolName(info.namespace, info.procedure);\n const inputShape = jsonSchemaToZodShape(info.parameters);\n\n server.registerTool(\n mcpToolName,\n {\n description: info.description ?? `Call ${info.namespace}.${info.procedure}`,\n inputSchema: inputShape,\n },\n async (args) => {\n try {\n const result = await this.backend.call(info.namespace, info.procedure, [args ?? {}]);\n return {\n content: [\n {\n type: \"text\" as const,\n text: typeof result === \"string\" ? result : JSON.stringify(result),\n },\n ],\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Service call failed: ${message}`,\n },\n ],\n isError: true,\n };\n }\n }\n );\n }\n }\n\n registerSearchServices(server: McpServer): void {\n server.registerTool(\n \"search_services\",\n {\n description:\n \"Search for available service tools that widgets can call. \" +\n \"Returns matching services with their namespaces, procedures, and parameter schemas.\",\n inputSchema: {\n query: z\n .string()\n .optional()\n .describe(\n 'Natural language description of what you want to do (e.g., \"get weather forecast\")'\n ),\n namespace: z\n .string()\n .optional()\n .describe('Filter results to a specific service namespace (e.g., \"weather\")'),\n tool_name: z\n .string()\n .optional()\n .describe(\"Get detailed info about a specific tool by name\"),\n limit: z.number().optional().describe(\"Maximum number of results to return\"),\n },\n },\n async (args) => {\n const query = args?.[\"query\"] as string | undefined;\n const namespace = args?.[\"namespace\"] as string | undefined;\n const toolName = args?.[\"tool_name\"] as string | undefined;\n const limit = (args?.[\"limit\"] as number) ?? 10;\n\n if (toolName) {\n const dotName = toolName.replace(/__/g, \".\");\n const info = this.tools.get(toolName) ?? this.tools.get(dotName);\n if (!info) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({\n success: false,\n error: `Tool '${toolName}' not found`,\n }),\n },\n ],\n };\n }\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({ success: true, tool: info }),\n },\n ],\n };\n }\n\n let results = Array.from(this.tools.values());\n\n if (namespace) {\n results = results.filter((info) => info.namespace === namespace);\n }\n\n if (query) {\n const queryLower = query.toLowerCase();\n const keywords = queryLower.split(/\\s+/).filter(Boolean);\n results = results\n .map((info) => {\n const searchText =\n `${info.name} ${info.namespace} ${info.procedure} ${info.description ?? \"\"}`.toLowerCase();\n const matchCount = keywords.filter((kw) => searchText.includes(kw)).length;\n return { info, score: matchCount / keywords.length };\n })\n .filter(({ score }) => score > 0)\n .sort((a, b) => b.score - a.score)\n .map(({ info }) => info);\n }\n\n results = results.slice(0, limit);\n\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({\n success: true,\n count: results.length,\n tools: results,\n namespaces: this.getNamespaces(),\n }),\n },\n ],\n };\n }\n );\n\n // call_service: Execute any service by namespace/procedure\n server.registerTool(\n \"call_service\",\n {\n description:\n \"Call a service tool by namespace and procedure. Use search_services to discover available tools first.\",\n inputSchema: {\n namespace: z.string().describe('Service namespace (e.g., \"github\", \"stripe\")'),\n procedure: z.string().describe('Procedure name (e.g., \"repos_list\", \"customers_create\")'),\n args: z.record(z.unknown()).optional().describe(\"Arguments to pass to the procedure\"),\n },\n },\n async (params) => {\n const namespace = params?.[\"namespace\"] as string;\n const procedure = params?.[\"procedure\"] as string;\n const args = (params?.[\"args\"] ?? {}) as Record<string, unknown>;\n\n if (!namespace || !procedure) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({\n success: false,\n error: \"namespace and procedure are required\",\n }),\n },\n ],\n isError: true,\n };\n }\n\n // Look up tool info for validation\n const toolKey = `${namespace}.${procedure}`;\n const info = this.tools.get(toolKey);\n if (!info) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({\n success: false,\n error: `Tool '${namespace}.${procedure}' not found. Use search_services to discover available tools.`,\n }),\n },\n ],\n isError: true,\n };\n }\n\n try {\n const result = await this.backend.call(namespace, procedure, [args]);\n return {\n content: [\n {\n type: \"text\" as const,\n text: typeof result === \"string\" ? result : JSON.stringify(result),\n },\n ],\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({\n success: false,\n error: `Service call failed: ${message}`,\n }),\n },\n ],\n isError: true,\n };\n }\n }\n );\n }\n\n getNamespaces(): string[] {\n const namespaces = new Set<string>();\n for (const info of this.tools.values()) {\n namespaces.add(info.namespace);\n }\n return Array.from(namespaces);\n }\n\n getToolInfos(): ServiceToolInfo[] {\n return Array.from(this.tools.values());\n }\n\n has(namespace: string, procedure: string): boolean {\n return this.tools.has(`${namespace}.${procedure}`);\n }\n}\n"],"mappings":";AAAA,SAAS,kBAAkB;AAC3B,SAAS,YAAY,cAAc,qBAAqB;AACxD,SAAS,cAAc;AACvB,SAAS,QAAAA,aAAY;AACrB,SAAS,qBAAqB;AAC9B,SAAS,2BAA2B;AACpC,SAAS,4BAA4B;AACrC,SAAS,qCAAqC;AAC9C,OAAO,UAAU;AACjB,OAAO,aAAa;;;ACPpB,IAAM,gBACH,QAAQ,IAAI,WAAW,KAA8B;AAExD,IAAM,aAAuC;AAAA,EAC3C,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AACT;AAEA,SAAS,UAAU,OAA0B;AAC3C,SAAO,WAAW,KAAK,KAAK,WAAW,aAAa;AACtD;AAEA,SAAS,cAAc,QAAgB,MAA4B;AACjE,SAAO,CAAC,IAAI,GAAG,KAAK,GAAG,IAAI;AAC7B;AASO,SAAS,IAAI,QAAgB,MAAuB;AACzD,MAAI,UAAU,MAAM,GAAG;AACrB,YAAQ,MAAM,GAAG,cAAc,KAAK,GAAG,IAAI,CAAC;AAAA,EAC9C;AACF;AAEO,SAAS,KAAK,QAAgB,MAAuB;AAC1D,MAAI,UAAU,MAAM,GAAG;AACrB,YAAQ,MAAM,GAAG,cAAc,KAAK,GAAG,IAAI,CAAC;AAAA,EAC9C;AACF;AAEO,SAAS,MAAM,QAAgB,MAAuB;AAC3D,MAAI,UAAU,OAAO,GAAG;AACtB,YAAQ,MAAM,GAAG,cAAc,KAAK,GAAG,IAAI,CAAC;AAAA,EAC9C;AACF;;;AC/BA,IAAM,kBAAkB;AAMxB,IAAM,gBAAgB,oBAAI,IAA2B;AACrD,IAAI,YAAY;AAEhB,SAAS,kBAAkB,QAA+B;AACxD,MAAI,MAAM,cAAc,IAAI,MAAM;AAClC,MAAI,CAAC,KAAK;AACR,UAAM,CAAC;AACP,kBAAc,IAAI,QAAQ,GAAG;AAAA,EAC/B;AACA,SAAO;AACT;AAKO,SAAS,UAAU,QAAgB,UAAiC;AACzE,QAAM,MAAM,cAAc,IAAI,MAAM;AACpC,MAAI,CAAC,IAAK,QAAO,CAAC;AAClB,SAAO,IAAI,OAAO,CAAC,MAAM,EAAE,MAAM,QAAQ;AAC3C;AAMO,SAAS,YAAY,QAAgB,MAA4B;AACtE,QAAM,MAAM,kBAAkB,MAAM;AACpC,QAAM,QAAqB;AAAA,IACzB,KAAK,EAAE;AAAA,IACP;AAAA,IACA,WAAW,KAAK,IAAI;AAAA,EACtB;AACA,MAAI,KAAK,KAAK;AACd,MAAI,IAAI,SAAS,iBAAiB;AAChC,QAAI,MAAM;AAAA,EACZ;AACA,SAAO;AACT;AAYA,IAAM,WAAW,oBAAI,IAA0B;AAKxC,SAAS,gBAAgB,WAAmB,QAAyB;AAC1E,MAAI,CAAC,SAAS,IAAI,SAAS,GAAG;AAC5B,aAAS,IAAI,WAAW,EAAE,QAAQ,SAAS,oBAAI,IAAI,EAAE,CAAC;AAAA,EACxD;AACF;AAKO,SAAS,kBAAkB,WAAyB;AACzD,WAAS,OAAO,SAAS;AAC3B;AAKO,SAAS,iBAAiB,WAAmB,QAAsB;AACxE,QAAM,QAAQ,SAAS,IAAI,SAAS;AACpC,MAAI,OAAO;AACT,UAAM,QAAQ,IAAI,MAAM;AAAA,EAC1B;AACF;AAsBA,eAAsB,iBACpB,QACA,MACiB;AACjB,QAAM,QAAQ,YAAY,QAAQ,IAAI;AAEtC,QAAM,eAAgC,CAAC;AACvC,aAAW,CAAC,EAAE,KAAK,KAAK,UAAU;AAChC,QAAI,MAAM,QAAQ,IAAI,MAAM,GAAG;AAC7B,YAAM,gBAAgB,MAAM,OAAO,OAChC,aAAa,EAAE,QAAQ,mCAAmC,CAAC,EAC3D,MAAM,CAAC,QAAiB;AACvB,aAAK,eAAe,6BAA6B,GAAG;AAAA,MACtD,CAAC;AACH,mBAAa,KAAK,aAAa;AAAA,IACjC;AAAA,EACF;AAEA,QAAM,QAAQ,IAAI,YAAY;AAC9B,SAAO,MAAM;AACf;AAKO,SAAS,aAAqB;AACnC,SAAO;AACT;;;AC9IA;AAAA,EACE;AAAA,OAGK;AAWP,SAAS,UAAU,MAAsC;AACvD,QAAM,YAAY,KAAK,KAAK,QAAQ,GAAG;AACvC,SAAO;AAAA,IACL,MAAM,KAAK;AAAA,IACX,WAAW,YAAY,IAAI,KAAK,OAAO,KAAK,KAAK,MAAM,GAAG,SAAS;AAAA,IACnE,WAAW,YAAY,IAAI,KAAK,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AAAA,IACpE,aAAa,KAAK;AAAA,IAClB,YACE,KAAK,eAAe,OAAO,KAAK,gBAAgB,WAC3C,KAAK,cACN;AAAA,EACR;AACF;AAEA,eAAsB,sBACpB,SAC0B;AAC1B,QAAM,SAAS,IAAI,mBAAmB,EAAE,GAAG,SAAS,MAAM,UAAU,CAAC;AACrE,QAAM,OAAO,QAAQ;AACrB,QAAM,SAAS,MAAM,OAAO,UAAU,GAAG,IAAI,SAAS;AACtD,SAAO;AAAA,IACL,KAAK,WAAW,WAAW,MAAM;AAC/B,aAAO,OAAO;AAAA,QACZ,GAAG,SAAS,IAAI,SAAS;AAAA,QACxB,KAAK,CAAC,KAA6C,CAAC;AAAA,MACvD;AAAA,IACF;AAAA,IACA,cAAc,MAAM;AAAA,IACpB,cAAc,CAAC,QAAQ,OAAO,aAAa,GAAG;AAAA,IAC9C,OAAO,MAAM,OAAO,MAAM;AAAA,EAC5B;AACF;;;AC9CA,SAAS,aAAgC;AAGzC,IAAI,gBAAqC;AACzC,IAAI,YAA2B;AAW/B,eAAe,kBAAkB,KAAa,YAAY,KAAyB;AACjF,QAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,SAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,GAAG,GAAG,WAAW,EAAE,QAAQ,YAAY,QAAQ,GAAI,EAAE,CAAC;AAC9E,UAAI,IAAI,GAAI,QAAO;AAAA,IACrB,QAAQ;AAAA,IAER;AACA,UAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,GAAI,CAAC;AAAA,EAC9C;AACA,SAAO;AACT;AAMA,eAAsB,YAAY,MAA+B;AAC/D,MAAI,UAAW,QAAO;AAEtB,QAAM,cAAc,MAAM,IAAI,QAAgB,CAACC,UAAS,WAAW;AACjE,UAAM,OAAO,CAAC,UAAU,SAAS,oBAAoB,IAAI,EAAE;AAE3D,QAAI,UAAU,wCAAwC,IAAI,KAAK;AAE/D,oBAAgB,MAAM,eAAe,MAAM;AAAA,MACzC,OAAO,CAAC,UAAU,QAAQ,MAAM;AAAA,IAClC,CAAC;AAED,QAAI,WAAW;AACf,UAAM,UAAU,WAAW,MAAM;AAC/B,UAAI,CAAC,UAAU;AACb,mBAAW;AACX,eAAO,IAAI,MAAM,oCAAoC,CAAC;AAAA,MACxD;AAAA,IACF,GAAG,GAAK;AAER,UAAM,eAAe,CAAC,SAAiB;AACrC,YAAM,OAAO,KAAK,SAAS;AAI3B,YAAM,WAAW,KAAK,MAAM,yCAAyC;AACrE,UAAI,YAAY,CAAC,UAAU;AACzB,mBAAW;AACX,qBAAa,OAAO;AACpB,QAAAA,SAAQ,SAAS,CAAC,EAAE,QAAQ,OAAO,EAAE,CAAC;AAAA,MACxC;AAAA,IACF;AAEA,kBAAc,QAAQ,GAAG,QAAQ,YAAY;AAC7C,kBAAc,QAAQ,GAAG,QAAQ,YAAY;AAE7C,kBAAc,GAAG,SAAS,CAAC,QAAQ;AACjC,UAAI,CAAC,UAAU;AACb,mBAAW;AACX,qBAAa,OAAO;AACpB,cAAM,UAAU,gCAAgC,GAAG;AACnD,eAAO,GAAG;AAAA,MACZ;AAAA,IACF,CAAC;AAED,kBAAc,GAAG,QAAQ,CAAC,SAAS;AACjC,UAAI,CAAC,UAAU;AACb,mBAAW;AACX,qBAAa,OAAO;AACpB,eAAO,IAAI,MAAM,gCAAgC,IAAI,EAAE,CAAC;AAAA,MAC1D;AACA,sBAAgB;AAChB,kBAAY;AAAA,IACd,CAAC;AAAA,EACH,CAAC;AAID,MAAI,UAAU,6BAA6B,WAAW,8BAA8B;AACpF,QAAM,OAAO,MAAM,kBAAkB,WAAW;AAChD,MAAI,CAAC,MAAM;AACT;AAAA,MACE;AAAA,MACA,UAAU,WAAW;AAAA,IACvB;AAAA,EACF,OAAO;AACL,QAAI,UAAU,8BAA8B,WAAW,EAAE;AAAA,EAC3D;AAEA,cAAY;AACZ,SAAO;AACT;AAKO,SAAS,aAAmB;AACjC,MAAI,eAAe;AACjB,kBAAc,KAAK;AACnB,oBAAgB;AAChB,gBAAY;AAAA,EACd;AACF;;;ACnHA,SAAS,eAAe;AACxB,SAAS,QAAAC,OAAM,eAAe;;;ACD9B,SAAS,UAAU,WAAW,QAAQ,MAAM,OAAO,SAAS,IAAI,cAAc;AAC9E,SAAS,MAAM,SAAS,iBAAiB;AAGzC,SAAS,eAAe,MAAc,OAA0B;AAC9D,SAAO;AAAA,IACL;AAAA,IACA,QAAQ,MAAM,CAAC;AAAA,IACf,aAAa,MAAM;AAAA,EACrB;AACF;AAEA,SAAS,gBAAgB,MAAc,OAAa,OAA2B;AAC7E,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,QAAQ,MAAM,CAAC;AAAA,IACf,aAAa,MAAM;AAAA,EACrB;AACF;AAEO,IAAM,mBAAN,MAA6C;AAAA,EAClD,YAAoB,UAAkB;AAAlB;AAAA,EAAmB;AAAA,EAAnB;AAAA,EAEZ,QAAQ,MAAsB;AACpC,WAAO,KAAK,KAAK,UAAU,UAAU,IAAI,CAAC;AAAA,EAC5C;AAAA,EAEA,MAAM,SAAS,MAA+B;AAC5C,WAAO,SAAS,KAAK,QAAQ,IAAI,GAAG,OAAO;AAAA,EAC7C;AAAA,EAEA,MAAM,UAAU,MAAc,SAAgC;AAC5D,UAAM,WAAW,KAAK,QAAQ,IAAI;AAClC,UAAM,MAAM,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAClD,UAAM,UAAU,UAAU,SAAS,OAAO;AAAA,EAC5C;AAAA,EAEA,MAAM,OAAO,MAA6B;AACxC,UAAM,OAAO,KAAK,QAAQ,IAAI,CAAC;AAAA,EACjC;AAAA,EAEA,MAAM,KAAK,MAAkC;AAC3C,UAAM,WAAW,KAAK,QAAQ,IAAI;AAClC,UAAM,IAAI,MAAM,KAAK,QAAQ;AAC7B,WAAO,gBAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,CAAC;AAAA,EACzD;AAAA,EAEA,MAAM,MAAM,MAAc,SAAkD;AAC1E,UAAM,MAAM,KAAK,QAAQ,IAAI,GAAG,OAAO;AAAA,EACzC;AAAA,EAEA,MAAM,QAAQ,MAAmC;AAC/C,UAAM,WAAW,KAAK,QAAQ,IAAI;AAClC,UAAM,UAAU,MAAM,QAAQ,UAAU,EAAE,eAAe,KAAK,CAAC;AAC/D,WAAO,QAAQ;AAAA,MAAI,CAAC,MAClB,eAAe,EAAE,MAAM,EAAE,YAAY,CAAC;AAAA,IACxC;AAAA,EACF;AAAA,EAEA,MAAM,MAAM,MAAc,SAAkD;AAC1E,QAAI,SAAS,WAAW;AACtB,YAAM,GAAG,KAAK,QAAQ,IAAI,GAAG,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,IAC/D,OAAO;AACL,YAAM,GAAG,KAAK,QAAQ,IAAI,GAAG,EAAE,OAAO,KAAK,CAAC;AAAA,IAC9C;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,MAAgC;AAC3C,QAAI;AACF,YAAM,OAAO,KAAK,QAAQ,IAAI,CAAC;AAC/B,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;ADtEA,IAAM,iBAAiB;AACvB,IAAM,eAAe;AACrB,IAAM,sBAAsB;AAE5B,SAAS,uBAA+B;AAEtC,SAAO,QAAQ,IAAI,mBAAmB,KAAKC,MAAK,QAAQ,GAAG,cAAc,cAAc;AACzF;AAqBO,IAAM,cAAN,MAAkB;AAAA,EACf;AAAA,EACA;AAAA,EAER,YAAY,UAA8B,CAAC,GAAG;AAC5C,SAAK,aAAa,QAAQ,QAAQ,cAAc,qBAAqB,CAAC;AACtE,SAAK,WAAW,QAAQ,WAAW,IAAI,iBAAiB,KAAK,UAAU;AAAA,EACzE;AAAA,EAEQ,SAAS,aAA6B;AAC5C,WAAOA,MAAK,gBAAgB,WAAW;AAAA,EACzC;AAAA,EAEA,MAAc,mBAAmB,KAAa,OAAO,IAA4B;AAC/E,UAAM,QAAuB,CAAC;AAC9B,QAAI;AACJ,QAAI;AACF,gBAAU,MAAM,KAAK,SAAS,QAAQ,GAAG;AAAA,IAC3C,QAAQ;AACN,aAAO;AAAA,IACT;AACA,eAAW,SAAS,SAAS;AAC3B,YAAM,WAAWA,MAAK,KAAK,MAAM,IAAI;AACrC,YAAM,UAAU,OAAO,GAAG,IAAI,IAAI,MAAM,IAAI,KAAK,MAAM;AACvD,UAAI,MAAM,YAAY,GAAG;AACvB,cAAM,KAAK,GAAI,MAAM,KAAK,mBAAmB,UAAU,OAAO,CAAE;AAAA,MAClE,OAAO;AACL,cAAM,UAAU,MAAM,KAAK,SAAS,SAAS,QAAQ;AACrD,cAAM,KAAK,EAAE,MAAM,SAAS,QAAQ,CAAC;AAAA,MACvC;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,WACJ,MACA,OACA,UACA,OACuB;AACvB,UAAM,YAAY,GAAG,SAAS,IAAI,IAAI,IAAI;AAC1C,UAAM,YAAY,KAAK,IAAI;AAE3B,UAAM,QAAQ;AAAA,MACZ,MAAM;AAAA,QAAI,CAAC,SACT,KAAK,SAAS;AAAA,UACZ,KAAK,SAAS,GAAG,SAAS,IAAI,YAAY,IAAI,KAAK,IAAI,EAAE;AAAA,UACzD,KAAK;AAAA,QACP;AAAA,MACF;AAAA,IACF;AAEA,UAAM,iBAAiC,EAAE,GAAG,UAAU,OAAO,UAAU;AACvE,UAAM,KAAK,SAAS;AAAA,MAClB,KAAK,SAAS,GAAG,SAAS,gBAAgB;AAAA,MAC1C,KAAK,UAAU,cAAc;AAAA,IAC/B;AAEA,WAAO;AAAA,MACL,MAAM,KAAK,SAAS,SAAS;AAAA,MAC7B,aAAa,GAAG,mBAAmB,GAAG,SAAS;AAAA,MAC/C;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,UAAU,MAAc,MAA4C;AACxE,UAAM,YAAY,GAAG,IAAI,IAAI,IAAI;AACjC,UAAM,sBAAsB,KAAK,SAAS,GAAG,SAAS,gBAAgB;AAEtE,UAAM,SAAS,MAAM,KAAK,SAAS,OAAO,mBAAmB;AAC7D,QAAI,CAAC,OAAQ,QAAO;AAEpB,UAAM,QAAQ,MAAM,KAAK,mBAAmB,KAAK,SAAS,GAAG,SAAS,IAAI,YAAY,EAAE,CAAC;AAEzF,QAAI;AACJ,QAAI,QAAQ,MAAM,CAAC,GAAG,QAAQ;AAC9B,QAAI,YAAY,KAAK,IAAI;AACzB,QAAI;AACF,YAAM,MAAM,MAAM,KAAK,SAAS,SAAS,mBAAmB;AAC5D,YAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,iBAAW;AAAA,QACT,MAAO,OAAO,QAAmB;AAAA,QACjC,SAAU,OAAO,WAAsB;AAAA,QACvC,UAAU,OAAO,YAAY;AAAA,QAC7B,OAAQ,OAAO,SAAoB;AAAA,QACnC,aAAa,OAAO;AAAA,QACpB,UAAU,OAAO;AAAA,MACnB;AACA,UAAI,OAAO,MAAO,SAAQ,OAAO;AACjC,UAAI,OAAO,OAAO,cAAc,SAAU,aAAY,OAAO;AAAA,IAC/D,QAAQ;AACN,iBAAW;AAAA,QACT;AAAA,QACA,SAAS;AAAA,QACT,UAAU;AAAA,QACV,OAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO;AAAA,MACL,MAAM,KAAK,SAAS,SAAS;AAAA,MAC7B,aAAa,GAAG,mBAAmB,GAAG,SAAS;AAAA,MAC/C;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,cAA2C;AAC/C,UAAM,UAA8B,CAAC;AACrC,UAAM,WAAW,KAAK,SAAS,EAAE;AAEjC,QAAI;AACF,YAAM,QAAQ,MAAM,KAAK,SAAS,QAAQ,QAAQ;AAClD,iBAAW,aAAa,OAAO;AAC7B,YAAI,CAAC,UAAU,YAAY,EAAG;AAC9B,cAAM,OAAO,UAAU;AAEvB,YAAI;AACF,gBAAM,SAAS,MAAM,KAAK,SAAS,QAAQA,MAAK,UAAU,IAAI,CAAC;AAC/D,qBAAW,aAAa,QAAQ;AAC9B,gBAAI,CAAC,UAAU,YAAY,EAAG;AAC9B,kBAAM,OAAO,UAAU;AACvB,kBAAM,sBAAsB,KAAK,SAAS,GAAG,IAAI,IAAI,IAAI,gBAAgB;AAEzE,gBAAI,WAAoC,CAAC;AACzC,gBAAI;AACF,oBAAM,MAAM,MAAM,KAAK,SAAS,SAAS,mBAAmB;AAC5D,yBAAW,KAAK,MAAM,GAAG;AAAA,YAC3B,QAAQ;AACN;AAAA,YACF;AAEA,oBAAQ,KAAK;AAAA,cACX,MAAM,KAAK,SAAS,GAAG,IAAI,IAAI,IAAI,EAAE;AAAA,cACrC,aAAa,GAAG,mBAAmB,GAAG,IAAI,IAAI,IAAI;AAAA,cAClD,MAAM,SAAS,QAAQ;AAAA,cACvB,SAAS,SAAS,WAAW;AAAA,cAC7B,aAAa,SAAS;AAAA,cACtB,UAAU,SAAS;AAAA,cACnB,OAAO,SAAS;AAAA,cAChB,WAAW,SAAS,aAAa;AAAA,YACnC,CAAC;AAAA,UACH;AAAA,QACF,QAAQ;AACN;AAAA,QACF;AAAA,MACF;AAAA,IACF,QAAQ;AACN,aAAO;AAAA,IACT;AAEA,WAAO,QAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS;AAAA,EACzD;AAAA,EAEA,MAAM,aAAa,MAAc,MAAgC;AAC/D,UAAM,YAAY,KAAK,SAAS,GAAG,IAAI,IAAI,IAAI,EAAE;AACjD,UAAM,SAAS,MAAM,KAAK,SAAS,OAAO,SAAS;AACnD,QAAI,CAAC,OAAQ,QAAO;AAEpB,UAAM,KAAK,SAAS,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;AACxD,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,UAAU,MAAc,MAAgC;AAC5D,WAAO,KAAK,SAAS,OAAO,KAAK,SAAS,GAAG,IAAI,IAAI,IAAI,gBAAgB,CAAC;AAAA,EAC5E;AAAA,EAEA,eAAe,MAAc,MAAsB;AACjD,WAAO,GAAG,mBAAmB,GAAG,IAAI,IAAI,IAAI;AAAA,EAC9C;AAAA,EAEA,MAAM,UAAmC;AACvC,UAAM,QAAQ,MAAM,KAAK,YAAY;AACrC,UAAM,UAA0B,CAAC;AAEjC,eAAW,QAAQ,OAAO;AACxB,YAAM,UAAU,KAAK,YAAY,QAAQ,qBAAqB,EAAE,EAAE,QAAQ,iBAAiB,EAAE;AAC7F,YAAM,QAAQ,QAAQ,MAAM,GAAG;AAC/B,YAAM,OAAO,MAAM,CAAC;AACpB,YAAM,OAAO,MAAM,CAAC;AAEpB,UAAI,CAAC,QAAQ,CAAC,KAAM;AAEpB,YAAM,SAAS,MAAM,KAAK,UAAU,MAAM,IAAI;AAC9C,UAAI,OAAQ,SAAQ,KAAK,MAAM;AAAA,IACjC;AAEA,WAAO;AAAA,EACT;AACF;AAEA,IAAI,YAAgC;AAE7B,SAAS,eAAe,SAA2C;AACxE,MAAI,CAAC,WAAW;AACd,gBAAY,IAAI,YAAY,OAAO;AAAA,EACrC;AACA,SAAO;AACT;;;AE7OA;AAAA,EACE;AAAA,EACA;AAAA,OAIK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,iBAAiB;AAC1B,SAAS,KAAAC,UAAS;;;ACXlB,SAAS,SAAS;AAmBlB,IAAM,iBAAiB;AAEvB,SAAS,cAAc,WAAmB,WAA2B;AACnE,SAAO,GAAG,SAAS,GAAG,cAAc,GAAG,SAAS;AAClD;AAEA,SAAS,qBAAqB,QAAgE;AAC5F,QAAM,aAAc,SAAS,YAAY,KAAK,CAAC;AAI/C,QAAM,WAAW,IAAI,IAAK,SAAS,UAAU,KAAK,CAAC,CAAc;AAEjE,QAAM,QAAsC,CAAC;AAC7C,aAAW,CAAC,KAAK,IAAI,KAAK,OAAO,QAAQ,UAAU,GAAG;AACpD,QAAI;AACJ,YAAQ,KAAK,MAAM;AAAA,MACjB,KAAK;AAAA,MACL,KAAK;AACH,gBAAQ,EAAE,OAAO;AACjB;AAAA,MACF,KAAK;AACH,gBAAQ,EAAE,QAAQ;AAClB;AAAA,MACF,KAAK;AACH,gBAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;AAC3B;AAAA,MACF,KAAK;AACH,gBAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC;AAC5B;AAAA,MACF;AACE,gBAAQ,EAAE,OAAO;AACjB;AAAA,IACJ;AACA,QAAI,KAAK,aAAa;AACpB,cAAQ,MAAM,SAAS,KAAK,WAAW;AAAA,IACzC;AACA,QAAI,CAAC,SAAS,IAAI,GAAG,GAAG;AACtB,cAAQ,MAAM,SAAS;AAAA,IACzB;AACA,UAAM,GAAG,IAAI;AAAA,EACf;AAEA,SAAO;AACT;AAEO,IAAM,gBAAN,MAAoB;AAAA,EACjB;AAAA,EACA,QAAsC,oBAAI,IAAI;AAAA,EAEtD,YAAY,QAA6B;AACvC,SAAK,UAAU,OAAO;AACtB,eAAW,QAAQ,OAAO,OAAO;AAC/B,WAAK,MAAM,IAAI,KAAK,MAAM,IAAI;AAAA,IAChC;AAAA,EACF;AAAA,EAEA,cAAc,QAAyB;AACrC,eAAW,CAAC,EAAE,IAAI,KAAK,KAAK,OAAO;AACjC,YAAM,cAAc,cAAc,KAAK,WAAW,KAAK,SAAS;AAChE,YAAM,aAAa,qBAAqB,KAAK,UAAU;AAEvD,aAAO;AAAA,QACL;AAAA,QACA;AAAA,UACE,aAAa,KAAK,eAAe,QAAQ,KAAK,SAAS,IAAI,KAAK,SAAS;AAAA,UACzE,aAAa;AAAA,QACf;AAAA,QACA,OAAO,SAAS;AACd,cAAI;AACF,kBAAM,SAAS,MAAM,KAAK,QAAQ,KAAK,KAAK,WAAW,KAAK,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC;AACnF,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP;AAAA,kBACE,MAAM;AAAA,kBACN,MAAM,OAAO,WAAW,WAAW,SAAS,KAAK,UAAU,MAAM;AAAA,gBACnE;AAAA,cACF;AAAA,YACF;AAAA,UACF,SAASC,QAAO;AACd,kBAAM,UAAUA,kBAAiB,QAAQA,OAAM,UAAU,OAAOA,MAAK;AACrE,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP;AAAA,kBACE,MAAM;AAAA,kBACN,MAAM,wBAAwB,OAAO;AAAA,gBACvC;AAAA,cACF;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,uBAAuB,QAAyB;AAC9C,WAAO;AAAA,MACL;AAAA,MACA;AAAA,QACE,aACE;AAAA,QAEF,aAAa;AAAA,UACX,OAAO,EACJ,OAAO,EACP,SAAS,EACT;AAAA,YACC;AAAA,UACF;AAAA,UACF,WAAW,EACR,OAAO,EACP,SAAS,EACT,SAAS,kEAAkE;AAAA,UAC9E,WAAW,EACR,OAAO,EACP,SAAS,EACT,SAAS,iDAAiD;AAAA,UAC7D,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,qCAAqC;AAAA,QAC7E;AAAA,MACF;AAAA,MACA,OAAO,SAAS;AACd,cAAM,QAAQ,OAAO,OAAO;AAC5B,cAAM,YAAY,OAAO,WAAW;AACpC,cAAM,WAAW,OAAO,WAAW;AACnC,cAAM,QAAS,OAAO,OAAO,KAAgB;AAE7C,YAAI,UAAU;AACZ,gBAAM,UAAU,SAAS,QAAQ,OAAO,GAAG;AAC3C,gBAAM,OAAO,KAAK,MAAM,IAAI,QAAQ,KAAK,KAAK,MAAM,IAAI,OAAO;AAC/D,cAAI,CAAC,MAAM;AACT,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP;AAAA,kBACE,MAAM;AAAA,kBACN,MAAM,KAAK,UAAU;AAAA,oBACnB,SAAS;AAAA,oBACT,OAAO,SAAS,QAAQ;AAAA,kBAC1B,CAAC;AAAA,gBACH;AAAA,cACF;AAAA,YACF;AAAA,UACF;AACA,iBAAO;AAAA,YACL,SAAS;AAAA,cACP;AAAA,gBACE,MAAM;AAAA,gBACN,MAAM,KAAK,UAAU,EAAE,SAAS,MAAM,MAAM,KAAK,CAAC;AAAA,cACpD;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAEA,YAAI,UAAU,MAAM,KAAK,KAAK,MAAM,OAAO,CAAC;AAE5C,YAAI,WAAW;AACb,oBAAU,QAAQ,OAAO,CAAC,SAAS,KAAK,cAAc,SAAS;AAAA,QACjE;AAEA,YAAI,OAAO;AACT,gBAAM,aAAa,MAAM,YAAY;AACrC,gBAAM,WAAW,WAAW,MAAM,KAAK,EAAE,OAAO,OAAO;AACvD,oBAAU,QACP,IAAI,CAAC,SAAS;AACb,kBAAM,aACJ,GAAG,KAAK,IAAI,IAAI,KAAK,SAAS,IAAI,KAAK,SAAS,IAAI,KAAK,eAAe,EAAE,GAAG,YAAY;AAC3F,kBAAM,aAAa,SAAS,OAAO,CAAC,OAAO,WAAW,SAAS,EAAE,CAAC,EAAE;AACpE,mBAAO,EAAE,MAAM,OAAO,aAAa,SAAS,OAAO;AAAA,UACrD,CAAC,EACA,OAAO,CAAC,EAAE,MAAM,MAAM,QAAQ,CAAC,EAC/B,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,EAChC,IAAI,CAAC,EAAE,KAAK,MAAM,IAAI;AAAA,QAC3B;AAEA,kBAAU,QAAQ,MAAM,GAAG,KAAK;AAEhC,eAAO;AAAA,UACL,SAAS;AAAA,YACP;AAAA,cACE,MAAM;AAAA,cACN,MAAM,KAAK,UAAU;AAAA,gBACnB,SAAS;AAAA,gBACT,OAAO,QAAQ;AAAA,gBACf,OAAO;AAAA,gBACP,YAAY,KAAK,cAAc;AAAA,cACjC,CAAC;AAAA,YACH;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,QACE,aACE;AAAA,QACF,aAAa;AAAA,UACX,WAAW,EAAE,OAAO,EAAE,SAAS,8CAA8C;AAAA,UAC7E,WAAW,EAAE,OAAO,EAAE,SAAS,yDAAyD;AAAA,UACxF,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,EAAE,SAAS,oCAAoC;AAAA,QACtF;AAAA,MACF;AAAA,MACA,OAAO,WAAW;AAChB,cAAM,YAAY,SAAS,WAAW;AACtC,cAAM,YAAY,SAAS,WAAW;AACtC,cAAM,OAAQ,SAAS,MAAM,KAAK,CAAC;AAEnC,YAAI,CAAC,aAAa,CAAC,WAAW;AAC5B,iBAAO;AAAA,YACL,SAAS;AAAA,cACP;AAAA,gBACE,MAAM;AAAA,gBACN,MAAM,KAAK,UAAU;AAAA,kBACnB,SAAS;AAAA,kBACT,OAAO;AAAA,gBACT,CAAC;AAAA,cACH;AAAA,YACF;AAAA,YACA,SAAS;AAAA,UACX;AAAA,QACF;AAGA,cAAM,UAAU,GAAG,SAAS,IAAI,SAAS;AACzC,cAAM,OAAO,KAAK,MAAM,IAAI,OAAO;AACnC,YAAI,CAAC,MAAM;AACT,iBAAO;AAAA,YACL,SAAS;AAAA,cACP;AAAA,gBACE,MAAM;AAAA,gBACN,MAAM,KAAK,UAAU;AAAA,kBACnB,SAAS;AAAA,kBACT,OAAO,SAAS,SAAS,IAAI,SAAS;AAAA,gBACxC,CAAC;AAAA,cACH;AAAA,YACF;AAAA,YACA,SAAS;AAAA,UACX;AAAA,QACF;AAEA,YAAI;AACF,gBAAM,SAAS,MAAM,KAAK,QAAQ,KAAK,WAAW,WAAW,CAAC,IAAI,CAAC;AACnE,iBAAO;AAAA,YACL,SAAS;AAAA,cACP;AAAA,gBACE,MAAM;AAAA,gBACN,MAAM,OAAO,WAAW,WAAW,SAAS,KAAK,UAAU,MAAM;AAAA,cACnE;AAAA,YACF;AAAA,UACF;AAAA,QACF,SAASA,QAAO;AACd,gBAAM,UAAUA,kBAAiB,QAAQA,OAAM,UAAU,OAAOA,MAAK;AACrE,iBAAO;AAAA,YACL,SAAS;AAAA,cACP;AAAA,gBACE,MAAM;AAAA,gBACN,MAAM,KAAK,UAAU;AAAA,kBACnB,SAAS;AAAA,kBACT,OAAO,wBAAwB,OAAO;AAAA,gBACxC,CAAC;AAAA,cACH;AAAA,YACF;AAAA,YACA,SAAS;AAAA,UACX;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,gBAA0B;AACxB,UAAM,aAAa,oBAAI,IAAY;AACnC,eAAW,QAAQ,KAAK,MAAM,OAAO,GAAG;AACtC,iBAAW,IAAI,KAAK,SAAS;AAAA,IAC/B;AACA,WAAO,MAAM,KAAK,UAAU;AAAA,EAC9B;AAAA,EAEA,eAAkC;AAChC,WAAO,MAAM,KAAK,KAAK,MAAM,OAAO,CAAC;AAAA,EACvC;AAAA,EAEA,IAAI,WAAmB,WAA4B;AACjD,WAAO,KAAK,MAAM,IAAI,GAAG,SAAS,IAAI,SAAS,EAAE;AAAA,EACnD;AACF;;;ADhRA,IAAM,sBAAsB,OAAO,QAAQ,IAAI,aAAa,KAAK,IAAI;AACrE,IAAM,sBAAsB,QAAQ,IAAI,aAAa,KAAK;AAC1D,IAAM,0BAA0B,UAAU,mBAAmB,IAAI,mBAAmB;AAmBpF,SAAS,qBACP,UACA,YACA,QACA,QACQ;AACR,QAAM,SAAS,KAAK,UAAU;AAAA,IAC5B,SAAS;AAAA,IACT,QAAQ,GAAG,OAAO,IAAI,IAAI,OAAO,IAAI;AAAA,IACrC;AAAA,EACF,CAAC;AACD,QAAM,YAAY,OAAO,KAAK,QAAQ,OAAO,EAAE,SAAS,QAAQ;AAChE,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA,WAKE,OAAO,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBASL,QAAQ,kBAAkB,SAAS;AAAA;AAAA;AAGpD;AAGA,SAAS,UAAU,OAAsB,UAA4B;AACnE,QAAM,QAAQ,KAAK,UAAU;AAAA,IAC3B,MAAM,SAAS;AAAA,IACf,OAAO,SAAS;AAAA,IAChB,OAAO,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC;AAAA,EAC7C,CAAC;AACD,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAS,QAAQ,KAAK,OAAO,MAAM,WAAW,CAAC,IAAK;AAAA,EACtD;AACA,SAAO,KAAK,IAAI,IAAI,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AACpD;AAEA,IAAM,oBAA8B;AAAA,EAClC,MAAM;AAAA,EACN,SAAS;AAAA,EACT,UAAU;AAAA,EACV,OAAO;AACT;AAWA,SAAS,eACP,eACmE;AACnE,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,aAAa;AAOjC,UAAM,SAAS,IAAI,OACf,GAAG,IAAI,QAAQ,KAAK,IAAI,QAAQ,IAAI,IAAI,IAAI,KAC5C,GAAG,IAAI,QAAQ,KAAK,IAAI,QAAQ;AACpC,WAAO,EAAE,cAAc,CAAC,MAAM,GAAG,iBAAiB,CAAC,MAAM,EAAE;AAAA,EAC7D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,cAAc,OAA2C;AAChE,SAAO;AAAA,IACL,MAAO,QAAQ,MAAM,KAAgB,kBAAkB;AAAA,IACvD,SAAU,QAAQ,SAAS,KAAgB,kBAAkB;AAAA,IAC7D,UAAU;AAAA,IACV,OAAQ,QAAQ,OAAO,KAAgB,kBAAkB;AAAA,IACzD,UAAU,QAAQ,UAAU;AAAA,EAC9B;AACF;AAEA,IAAM,wBACJ;AAEF,SAAS,aACP,MACA,QACA,OACA,OACgB;AAChB,MAAI,SAAS,MAAM,SAAS,GAAG;AAC7B,UAAM,eAA8B,MAAM,IAAI,CAAC,OAAO;AAAA,MACpD,MAAM,EAAE;AAAA,MACR,SAAS,EAAE;AAAA,IACb,EAAE;AACF,UAAM,UAAU,uBAAuB,cAAc,IAAI;AACzD,QAAI,MAAO,SAAQ,QAAQ;AAC3B,WAAO;AAAA,EACT;AACA,SAAO,wBAAwB,UAAU,uBAAuB,SAAS,YAAY,IAAI;AAC3F;AAQO,SAAS,mBAAmB,UAA+B,CAAC,GAAc;AAC/E,QAAM,SAAS,IAAI,UAAU;AAAA,IAC3B,MAAM;AAAA,IACN,SAAS;AAAA,EACX,CAAC;AAED,QAAM,gBAAgB,QAAQ,WAAW,IAAI,cAAc,QAAQ,QAAQ,IAAI;AAC/E,QAAM,gBAAgB,QAAQ,iBAAiB;AAE/C,QAAM,aAAa,GAAG,aAAa;AACnC,QAAM,WAAW,GAAG,aAAa;AACjC,QAAM,YAAY,CAAC,MAAc,SAC/B,GAAG,UAAU,WAAW,mBAAmB,IAAI,CAAC,IAAI,mBAAmB,IAAI,CAAC;AAE9E,QAAM,QAAQ,eAAe;AAG7B,QAAM,iBAAiB,CAAC,KAAgB,WAAoC;AAC1E,UAAM,MAAM,eAAe,aAAa;AACxC,WAAO;AAAA,MACL,MAAM;AAAA,MACN,UAAU;AAAA,QACR,KAAK,MAAM,eAAe,IAAI,MAAM,IAAI,IAAI;AAAA,QAC5C,UAAU;AAAA,QACV,MAAM,qBAAqB,UAAU,YAAY,KAAK,MAAM;AAAA,QAC5D,GAAI,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,IAAI,CAAC;AAAA,MAC1C;AAAA,IACF;AAAA,EACF;AAEA;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,MACE,aACE;AAAA,MAIF,aAAa;AAAA,QACX,QAAQC,GACL,OAAO,EACP,SAAS,EACT;AAAA,UACC;AAAA,QACF;AAAA,QACF,OAAOA,GACJ;AAAA,UACCA,GAAE,OAAO;AAAA,YACP,MAAMA,GAAE,OAAO,EAAE,SAAS,sDAAsD;AAAA,YAChF,SAASA,GAAE,OAAO,EAAE,SAAS,eAAe;AAAA,UAC9C,CAAC;AAAA,QACH,EACC,SAAS,EACT;AAAA,UACC;AAAA,QACF;AAAA,QACF,OAAOA,GACJ,OAAO,EACP,SAAS,EACT,SAAS,0EAA0E;AAAA,QACtF,MAAMA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,qDAAqD;AAAA,QAC1F,OAAOA,GACJ,OAAO,EACP,SAAS,EACT;AAAA,UACC;AAAA,QACF;AAAA,QACF,UAAUA,GACP,MAAMA,GAAE,OAAO,CAAC,EAChB,SAAS,EACT;AAAA,UACC;AAAA,QAEF;AAAA,QACF,QAAQA,GACL,OAAOA,GAAE,QAAQ,CAAC,EAClB,SAAS,EACT,SAAS,0EAA0E;AAAA,MACxF;AAAA,MACA,OAAO;AAAA,QACL,IAAI,EAAE,aAAa,uCAAuC;AAAA,MAC5D;AAAA,IACF;AAAA,IACA,OAAO,SAAS;AACd,YAAM,SAAS,OAAO,QAAQ;AAC9B,YAAM,QAAQ,OAAO,OAAO;AAC5B,YAAM,QAAQ,OAAO,OAAO;AAC5B,YAAM,oBAAoB,OAAO,UAAU;AAC3C,YAAM,SAAU,OAAO,QAAQ,KAA6C,CAAC;AAE7E,YAAM,gBAAyC,CAAC;AAChD,UAAI,OAAO,MAAM,EAAG,eAAc,MAAM,IAAI,KAAK,MAAM;AACvD,UAAI,OAAO,OAAO,EAAG,eAAc,OAAO,IAAI,KAAK,OAAO;AAG1D,UAAI,WAAW,qBAAqB,CAAC;AACrC,UAAI,SAAS,SAAS,KAAK,eAAe;AACxC,cAAM,sBAAsB,cAAc,cAAc;AACxD,cAAM,cAAc,SAAS,OAAO,CAAC,OAAO,CAAC,oBAAoB,SAAS,EAAE,CAAC;AAC7E,YAAI,YAAY,SAAS,GAAG;AAC1B;AAAA,YACE;AAAA,YACA,qCAAqC,YAAY,KAAK,IAAI,CAAC,gBAAgB,oBAAoB,KAAK,IAAI,CAAC;AAAA,UAC3G;AAAA,QACF;AACA,mBAAW,SAAS,OAAO,CAAC,OAAO,oBAAoB,SAAS,EAAE,CAAC;AAAA,MACrE;AACA,UAAI,SAAS,SAAS,EAAG,eAAc,UAAU,IAAI;AAErD,YAAM,WAAW,cAAc,aAAa;AAC5C,YAAM,UAAU,aAAa,SAAS,MAAM,QAAQ,OAAO,KAAK;AAChE,YAAM,eAAe,MAAM,KAAK,QAAQ,MAAM,OAAO,CAAC;AAEtD,UAAI;AACF,cAAM,OAAO,UAAU,cAAc,QAAQ;AAC7C,cAAM,MAAM,WAAW,MAAM,cAAc,UAAU,QAAQ,KAAK;AAElE,cAAM,MAAiB,EAAE,MAAM,SAAS,MAAM,MAAM,OAAO,QAAQ,MAAM;AAEzE,eAAO;AAAA,UACL,SAAS;AAAA,YACP,eAAe,KAAK,MAAM;AAAA,YAC1B;AAAA,cACE,MAAM;AAAA,cACN,MACE,WAAW,SAAS,IAAI,kBAAkB,IAAI;AAAA,0BACnB,UAAU,SAAS,MAAM,IAAI,CAAC;AAAA,YAC7D;AAAA,UACF;AAAA,QACF;AAAA,MACF,SAASC,QAAO;AACd,cAAM,UAAUA,kBAAiB,QAAQA,OAAM,UAAU,OAAOA,MAAK;AACrE,eAAO;AAAA,UACL,SAAS;AAAA,YACP;AAAA,cACE,MAAM;AAAA,cACN,MAAM,0BAA0B,OAAO;AAAA,YACzC;AAAA,UACF;AAAA,UACA,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,eAAe;AAGjB,kBAAc,uBAAuB,MAAM;AAAA,EAC7C;AAEA;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,MACE,aACE;AAAA,MAEF,OAAO,EAAE,IAAI,EAAE,aAAa,oBAAoB,EAAE;AAAA,IACpD;AAAA,IACA,YAAY;AACV,YAAM,UAAU,MAAM,MAAM,YAAY;AAExC,UAAI,QAAQ,WAAW,GAAG;AACxB,eAAO;AAAA,UACL,SAAS;AAAA,YACP;AAAA,cACE,MAAM;AAAA,cACN,MAAM;AAAA,YACR;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAEA,YAAM,QAAQ,QAAQ,IAAI,CAAC,MAAM;AAC/B,cAAM,QAAQ,CAAC,OAAO,EAAE,IAAI,QAAQ,EAAE,OAAO,GAAG;AAChD,YAAI,EAAE,YAAa,OAAM,KAAK,KAAK,EAAE,WAAW,EAAE;AAClD,cAAM,KAAK,WAAW,EAAE,IAAI,EAAE;AAC9B,cAAM,KAAK,UAAU,EAAE,WAAW,EAAE;AACpC,YAAI,EAAE,MAAO,OAAM,KAAK,YAAY,EAAE,KAAK,EAAE;AAC7C,YAAI,EAAE,YAAY,EAAE,SAAS,SAAS,EAAG,OAAM,KAAK,eAAe,EAAE,SAAS,KAAK,IAAI,CAAC,EAAE;AAC1F,eAAO,MAAM,KAAK,IAAI;AAAA,MACxB,CAAC;AAED,aAAO;AAAA,QACL,SAAS;AAAA,UACP;AAAA,YACE,MAAM;AAAA,YACN,MAAM,mBAAmB,QAAQ,MAAM;AAAA;AAAA,EAAS,MAAM,KAAK,MAAM,CAAC;AAAA,UACpE;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,MACE,aACE;AAAA,MAGF,aAAa;AAAA,QACX,MAAMD,GAAE,OAAO,EAAE,SAAS,kDAAkD;AAAA,QAC5E,MAAMA,GACH,OAAO,EACP,SAAS,EACT;AAAA,UACC;AAAA,QACF;AAAA,QACF,QAAQA,GACL,OAAOA,GAAE,QAAQ,CAAC,EAClB,SAAS,EACT,SAAS,0EAA0E;AAAA,MACxF;AAAA,MACA,OAAO;AAAA,QACL,IAAI,EAAE,aAAa,uCAAuC;AAAA,MAC5D;AAAA,IACF;AAAA,IACA,OAAO,SAAS;AACd,YAAM,OAAO,OAAO,MAAM;AAC1B,YAAM,YAAY,OAAO,MAAM;AAC/B,YAAM,SAAU,OAAO,QAAQ,KAA6C,CAAC;AAE7E,UAAI,CAAC,MAAM;AACT,eAAO;AAAA,UACL,SAAS;AAAA,YACP;AAAA,cACE,MAAM;AAAA,cACN,MAAM;AAAA,YACR;AAAA,UACF;AAAA,UACA,SAAS;AAAA,QACX;AAAA,MACF;AAEA,UAAI,OAAO;AACX,UAAI,CAAC,MAAM;AACT,cAAM,UAAU,MAAM,MAAM,YAAY;AACxC,cAAM,QAAQ,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AACjD,YAAI,CAAC,OAAO;AACV,iBAAO;AAAA,YACL,SAAS;AAAA,cACP;AAAA,gBACE,MAAM;AAAA,gBACN,MAAM,qCAAqC,IAAI;AAAA,cACjD;AAAA,YACF;AAAA,YACA,SAAS;AAAA,UACX;AAAA,QACF;AACA,cAAM,eAAe,MAAM,YACxB,QAAQ,iBAAiB,EAAE,EAC3B,QAAQ,cAAc,EAAE;AAC3B,cAAM,QAAQ,aAAa,MAAM,GAAG;AACpC,eAAO,MAAM,CAAC,KAAK,MAAM,CAAC,KAAK;AAAA,MACjC;AAEA,UAAI,CAAC,MAAM;AACT,eAAO;AAAA,UACL,SAAS;AAAA,YACP;AAAA,cACE,MAAM;AAAA,cACN,MAAM,wCAAwC,IAAI;AAAA,YACpD;AAAA,UACF;AAAA,UACA,SAAS;AAAA,QACX;AAAA,MACF;AAEA,YAAM,SAAS,MAAM,MAAM,UAAU,MAAM,IAAI;AAC/C,UAAI,CAAC,QAAQ;AACX,eAAO;AAAA,UACL,SAAS;AAAA,YACP;AAAA,cACE,MAAM;AAAA,cACN,MAAM,WAAW,IAAI,gBAAgB,IAAI;AAAA,YAC3C;AAAA,UACF;AAAA,UACA,SAAS;AAAA,QACX;AAAA,MACF;AAEA,YAAM,MAAiB,EAAE,MAAM,MAAM,OAAO,OAAO,MAAM;AAEzD,aAAO;AAAA,QACL,SAAS;AAAA,UACP,eAAe,KAAK,MAAM;AAAA,UAC1B;AAAA,YACE,MAAM;AAAA,YACN,MAAM,oBAAoB,IAAI,YAAY,IAAI;AAAA,0BAA+B,UAAU,MAAM,IAAI,CAAC;AAAA,UACpG;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,0BAAwB,MAAM;AAE9B,SAAO;AACT;AAoBA,SAAS,wBAAwB,QAAyB;AAExD,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,aACE;AAAA,MAIF,aAAa;AAAA,QACX,QAAQA,GAAE,OAAO,EAAE,SAAS,0CAA0C;AAAA,QACtE,YAAYA,GACT,OAAO,EACP,SAAS,EACT;AAAA,UACC;AAAA,QAEF;AAAA,MACJ;AAAA,IACF;AAAA,IACA,CAAC,MAAM,UAAU;AACf,YAAM,SAAU,KAAiC,QAAQ;AAEzD,YAAM,YACF,KAAiC,YAAY,KAC7C,MAAkC,WAAW;AAEjD,UAAI,WAAW;AACb,yBAAiB,WAAW,MAAM;AAAA,MACpC;AAEA,YAAM,MAAM,WAAW;AACvB,aAAO;AAAA,QACL,SAAS;AAAA,UACP;AAAA,YACE,MAAM;AAAA,YACN,MAAM,KAAK,UAAU,EAAE,SAAS,MAAM,QAAQ,IAAI,CAAC;AAAA,UACrD;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,aACE;AAAA,MAIF,aAAa;AAAA,QACX,QAAQA,GAAE,OAAO,EAAE,SAAS,kCAAkC;AAAA,QAC9D,WAAWA,GACR,OAAO,EACP,IAAI,EACJ,QAAQ,CAAC,EACT;AAAA,UACC;AAAA,QACF;AAAA,MACJ;AAAA,IACF;AAAA,IACA,CAAC,SAAS;AACR,YAAM,SAAU,KAAiC,QAAQ;AACzD,YAAM,WAAa,KAAiC,WAAW,KAAgB;AAE/E,YAAM,SAAS,UAAU,QAAQ,QAAQ;AACzC,aAAO;AAAA,QACL,SAAS;AAAA,UACP;AAAA,YACE,MAAM;AAAA,YACN,MAAM,KAAK,UAAU,EAAE,SAAS,MAAM,QAAQ,OAAO,CAAC;AAAA,UACxD;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,aACE;AAAA,MAKF,aAAa;AAAA,QACX,QAAQA,GAAE,OAAO,EAAE,SAAS,qCAAqC;AAAA,QACjE,MAAMA,GACH,OAAOA,GAAE,QAAQ,CAAC,EAClB,SAAS,6DAA6D;AAAA,MAC3E;AAAA,IACF;AAAA,IACA,OAAO,SAAS;AACd,YAAM,SAAU,KAAiC,QAAQ;AACzD,YAAM,OAAQ,KAAiC,MAAM;AAErD,YAAM,MAAM,MAAM,iBAAiB,QAAQ,IAAI;AAC/C,aAAO;AAAA,QACL,SAAS;AAAA,UACP;AAAA,YACE,MAAM;AAAA,YACN,MAAM,KAAK,UAAU,EAAE,SAAS,MAAM,QAAQ,IAAI,CAAC;AAAA,UACrD;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;AP7kBA,IAAM,OAAO,OAAO,QAAQ,IAAI,MAAM,KAAK,GAAI;AAC/C,IAAM,cAAc,OAAO,QAAQ,IAAI,aAAa,KAAK,IAAI;AAC7D,IAAM,OAAO,QAAQ,IAAI,MAAM,KAAK;AACpC,IAAM,YAAY,QAAQ,IAAI,WAAW,MAAM,QAAQ,KAAK,SAAS,SAAS,IAAI,UAAU;AAC5F,IAAM,gBAAgB,QAAQ,IAAI,eAAe,MAAM,UAAU,QAAQ,KAAK,SAAS,UAAU;AA0BjG,IAAM,oBAAoBE,MAAK,OAAO,GAAG,oBAAoB,WAAW,OAAO;AAE/E,SAAS,sBAA8C;AACrD,MAAI;AACF,WAAO,KAAK,MAAM,aAAa,mBAAmB,MAAM,CAAC;AAAA,EAC3D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,uBAAuB,SAAuB;AACrD,MAAI;AACF,UAAM,QAAyB,EAAE,SAAS,KAAK,QAAQ,KAAK,WAAW,KAAK,IAAI,EAAE;AAClF,kBAAc,mBAAmB,KAAK,UAAU,KAAK,CAAC;AAAA,EACxD,SAAS,KAAK;AACZ,UAAM,kBAAkB,wCAAwC,GAAG;AAAA,EACrE;AACF;AAEA,SAAS,eAAe,KAAsB;AAC5C,MAAI;AACF,YAAQ,KAAK,KAAK,CAAC;AACnB,WAAO;AAAA,EACT,SAAS,KAAK;AAEZ,WAAQ,IAA8B,SAAS;AAAA,EACjD;AACF;AAOA,eAAe,4BAA4B,YAAY,MAA+B;AACpF,QAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,SAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,UAAM,QAAQ,oBAAoB;AAClC,QAAI,OAAO,WAAW,eAAe,MAAM,GAAG,EAAG,QAAO,MAAM;AAC9D,UAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;AAAA,EAC7C;AACA,SAAO;AACT;AAGA,SAAS,eAAe,MAAsB;AAC5C,QAAM,aAAa;AAAA,IACjB,cAAc,IAAI,IAAI,KAAK,IAAI,IAAI,YAAY,GAAG,CAAC;AAAA;AAAA,IACnD,cAAc,IAAI,IAAI,WAAW,IAAI,IAAI,YAAY,GAAG,CAAC;AAAA;AAAA,EAC3D;AACA,SAAO,WAAW,KAAK,CAAC,QAAQ,WAAW,GAAG,CAAC,KAAK,WAAW,CAAC;AAClE;AAGO,SAAS,oBAA4B;AAC1C,SAAO,eAAe,SAAS;AACjC;AAGO,SAAS,kBAA0B;AACxC,SAAO,eAAe,OAAO;AAC/B;AAEA,eAAe,uBAGZ;AACD,QAAM,aAAa,QAAQ,IAAI,iBAAiB;AAEhD,MAAI,CAAC,YAAY;AACf,WAAO,EAAE,iBAAiB,MAAM,eAAe,CAAC,EAAE;AAAA,EACpD;AAEA,MAAI,iBAAiB,4BAA4B,UAAU,EAAE;AAE7D,MAAI;AACF,UAAM,kBAAkB,MAAM,sBAAsB;AAAA,MAClD,MAAM;AAAA,MACN,KAAK;AAAA,MACL,SAAS,QAAQ,IAAI,sBAAsB,IACvC,EAAE,uBAAuB,QAAQ,IAAI,sBAAsB,EAAE,IAC7D;AAAA,MACJ,MAAM,QAAQ,IAAI,eAAe,IAC7B,EAAE,MAAM,UAAU,OAAO,QAAQ,IAAI,eAAe,EAAE,IACtD,EAAE,MAAM,OAAO;AAAA,IACrB,CAAC;AAED,UAAM,YAAY,gBAAgB,aAAa;AAC/C,UAAM,aAAa,CAAC,GAAG,IAAI,IAAI,UAAU,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AACjE;AAAA,MACE;AAAA,MACA,mBAAmB,UAAU,MAAM,6BAA6B,WAAW,KAAK,IAAI,CAAC;AAAA,IACvF;AAEA,WAAO;AAAA,MACL;AAAA,MACA,eAAe;AAAA,QACb,UAAU;AAAA,UACR,SAAS;AAAA,UACT,OAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF;AAAA,EACF,SAAS,KAAK;AACZ,UAAM,iBAAiB,iCAAiC,GAAG;AAC3D,UAAM,iBAAiB,oCAAoC;AAC3D,WAAO,EAAE,iBAAiB,MAAM,eAAe,CAAC,EAAE;AAAA,EACpD;AACF;AAEA,SAAS,sBAAsB,UAAwB,KAAc,KAAqB;AACxF,MAAI;AACF,aAAS,UAAU,cAAc,KAAK,KAAK,IAAI,IAAI;AAAA,EACrD,SAAS,KAAK;AACZ,UAAM,OAAO,yBAAyB,GAAG;AACzC,QAAI,CAAC,IAAI,aAAa;AACpB,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,wBAAwB,CAAC;AAAA,IACzD;AAAA,EACF;AACF;AAEA,eAAe,iBACb,eACA,cACA,KACA,KACe;AACf,QAAM,YAAY,mBAAmB,aAAa;AAClD,QAAM,YAAY,IAAI,8BAA8B;AAAA,IAClD,oBAAoB,MAAM,WAAW;AAAA,IACrC,sBAAsB,CAAC,OAAO;AAC5B,mBAAa,IAAI,IAAI,EAAE,QAAQ,WAAW,UAAU,CAAC;AACrD,sBAAgB,IAAI,SAAS;AAAA,IAC/B;AAAA,IACA,iBAAiB,CAAC,OAAO;AACvB,mBAAa,OAAO,EAAE;AACtB,wBAAkB,EAAE;AAAA,IACtB;AAAA,EACF,CAAC;AAED,MAAI,GAAG,SAAS,MAAM;AACpB,UAAM,KAAK,UAAU;AACrB,QAAI,MAAM,CAAC,aAAa,IAAI,EAAE,GAAG;AAC/B,WAAK,UAAU,MAAM;AAAA,IACvB;AAAA,EACF,CAAC;AAED,MAAI;AACF,UAAM,UAAU,QAAQ,SAAS;AACjC,UAAM,UAAU,cAAc,KAAK,KAAK,IAAI,IAAI;AAAA,EAClD,SAAS,KAAK;AACZ,UAAM,OAAO,qBAAqB,GAAG;AACrC,QAAI,CAAC,IAAI,aAAa;AACpB,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,wBAAwB,CAAC;AAAA,IACzD;AAAA,EACF;AACF;AAEA,SAAS,oBACP,KACA,eAC2B;AAC3B,QAAM,eAAe,oBAAI,IAA0B;AAEnD,MAAI,IAAI,QAAQ,OAAO,KAAK,QAAQ;AAClC,UAAM,YAAY,IAAI,QAAQ,gBAAgB;AAE9C,QAAI,WAAW;AACb,YAAM,WAAW,aAAa,IAAI,SAAS;AAC3C,UAAI,UAAU;AACZ,8BAAsB,UAAU,KAAK,GAAG;AACxC;AAAA,MACF;AAAA,IACF;AAEA,UAAM,iBAAiB,eAAe,cAAc,KAAK,GAAG;AAAA,EAC9D,CAAC;AAED,SAAO;AACT;AAEA,SAAS,yBAAyB,iBAA+C;AAC/E,QAAM,WAAW,MAAM;AACrB,QAAI,iBAAiB;AACnB,sBAAgB,MAAM,EAAE,MAAM,MAAM;AAAA,MAEpC,CAAC;AAAA,IACH;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,UAAQ,GAAG,WAAW,QAAQ;AAC9B,UAAQ,GAAG,UAAU,QAAQ;AAC/B;AAEA,eAAe,cAA6B;AAC1C,QAAM,EAAE,iBAAiB,cAAc,IAAI,MAAM,qBAAqB;AAEtE,QAAM,MAAM,oBAAoB,EAAE,MAAM,KAAK,CAAC;AAC9C,MAAI,IAAI,KAAK,CAAC;AAEd,sBAAoB,KAAK,aAAa;AAEtC,MAAI,IAAI,WAAW,CAAC,MAAM,QAAQ;AAChC,QAAI,KAAK,EAAE,QAAQ,MAAM,SAAS,2BAA2B,CAAC;AAAA,EAChE,CAAC;AAED,MAAI,OAAO,MAAM,MAAM,MAAM;AAC3B,QAAI,kBAAkB,sCAAsC,IAAI,IAAI,IAAI,EAAE;AAC1E,QAAI,kBAAkB,wEAAmE;AACzF,QAAI,kBAAkB,oCAA+B;AACrD,QAAI,kBAAkB,EAAE;AACxB,QAAI,kBAAkB,oCAAoC;AAC1D,QAAI,kBAAkB,+CAA+C,IAAI,EAAE;AAAA,EAC7E,CAAC;AAED,2BAAyB,eAAe;AAC1C;AAEA,eAAe,oBAAqC;AAClD,QAAM,YAAY,QAAQ;AAC1B,YAAU,IAAI,KAAK,CAAC;AAEpB,QAAM,QAAQ,eAAe;AAK7B,YAAU,IAAI,UAAU,QAAQ,OAAO,gBAAgB,CAAC,CAAC;AACzD,YAAU,IAAI,YAAY,QAAQ,OAAO,kBAAkB,CAAC,CAAC;AAG7D,YAAU,IAAI,6BAA6B,OAAO,KAAK,QAAQ;AAC7D,UAAM,EAAE,MAAM,KAAK,IAAI,IAAI;AAC3B,QAAI;AACF,YAAM,SAAS,MAAM,MAAM,UAAU,MAAM,IAAI;AAC/C,UAAI,CAAC,QAAQ;AACX,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,mBAAmB,CAAC;AAClD;AAAA,MACF;AACA,UAAI,KAAK,EAAE,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,UAAU,OAAO,SAAS,CAAC;AAAA,IAClF,SAAS,KAAK;AACZ,YAAM,iBAAiB,+BAA+B,GAAG;AACzD,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,wBAAwB,CAAC;AAAA,IACzD;AAAA,EACF,CAAC;AAGD,YAAU,IAAI,uBAAuB,CAAC,KAAK,QAAQ;AACjD,UAAM,EAAE,MAAM,KAAK,IAAI,IAAI;AAC3B,QAAI;AAAA,MACF;AAAA,MACA,oBAAoB,mBAAmB,IAAI,CAAC,IAAI,mBAAmB,IAAI,CAAC;AAAA,IAC1E;AAAA,EACF,CAAC;AAED,YAAU,IAAI,WAAW,CAAC,MAAM,QAAQ;AACtC,QAAI,KAAK,EAAE,QAAQ,MAAM,SAAS,0BAA0B,CAAC;AAAA,EAC/D,CAAC;AAID,QAAM,UAAU,MAAM,IAAI,QAAiB,CAACC,aAAY;AACtD,UAAM,aAAa,UAAU,OAAO,aAAa,MAAM,MAAM;AAC3D,UAAI,kBAAkB,qCAAqC,IAAI,IAAI,WAAW,EAAE;AAChF,MAAAA,SAAQ,IAAI;AAAA,IACd,CAAC;AACD,eAAW,GAAG,SAAS,CAAC,QAA+B;AACrD,UAAI,IAAI,SAAS,cAAc;AAC7B;AAAA,UACE;AAAA,UACA,eAAe,WAAW;AAAA,QAC5B;AAAA,MACF,OAAO;AACL,cAAM,kBAAkB,iCAAiC,GAAG;AAAA,MAC9D;AACA,MAAAA,SAAQ,KAAK;AAAA,IACf,CAAC;AAAA,EACH,CAAC;AAED,MAAI,CAAC,SAAS;AAGZ,UAAM,SAAS,MAAM,4BAA4B;AACjD,QAAI,QAAQ;AACV,UAAI,kBAAkB,+BAA+B,MAAM,EAAE;AAC7D,aAAO;AAAA,IACT;AACA;AAAA,MACE;AAAA,MACA;AAAA,IACF;AACA,WAAO,oBAAoB,WAAW;AAAA,EACxC;AAIA,MAAI,gBAAgB,oBAAoB,WAAW;AAEnD,MAAI,eAAe;AACjB,QAAI;AACF,sBAAgB,MAAM,YAAY,WAAW;AAC7C,UAAI,kBAAkB,0BAA0B,aAAa,EAAE;AAAA,IACjE,SAAS,KAAK;AACZ,YAAM,kBAAkB,4CAA4C,GAAG;AAAA,IACzE;AAAA,EACF;AAEA,yBAAuB,aAAa;AACpC,SAAO;AACT;AAEA,eAAe,mBAAkC;AAC/C,QAAM,EAAE,iBAAiB,cAAc,IAAI,MAAM,qBAAqB;AAEtE,MAAI,kBAAkB,0CAA0C;AAGhE,QAAM,gBAAgB,MAAM,kBAAkB;AAG9C,QAAM,YAAY,mBAAmB;AAAA,IACnC,GAAG;AAAA,IACH;AAAA,EACF,CAAC;AACD,QAAM,YAAY,IAAI,qBAAqB;AAE3C,QAAM,UAAU,QAAQ,SAAS;AAEjC,QAAM,WAAW,MAAM;AACrB,eAAW;AACX,QAAI,iBAAiB;AACnB,sBAAgB,MAAM,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACxC;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,UAAQ,GAAG,WAAW,QAAQ;AAC9B,UAAQ,GAAG,UAAU,QAAQ;AAC/B;AAEA,eAAe,OAAsB;AACnC,MAAI,cAAc,SAAS;AACzB,UAAM,iBAAiB;AAAA,EACzB,OAAO;AACL,UAAM,YAAY;AAAA,EACpB;AACF;AAEA,KAAK,EAAE,MAAM,CAAC,QAAiB;AAC7B,QAAM,kBAAkB,wBAAwB,GAAG;AACnD,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":["join","resolve","join","join","z","error","z","error","join","resolve"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aprovan/patchwork-mcp",
|
|
3
|
-
"version": "0.1.0-dev.
|
|
3
|
+
"version": "0.1.0-dev.78e095c",
|
|
4
4
|
"description": "MCP App Server — hosts MCP tools that render interactive UIs in Claude Desktop and Claude web.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"cors": "^2.8.5",
|
|
21
21
|
"express": "^5.1.0",
|
|
22
22
|
"zod": "^3.23.0",
|
|
23
|
-
"@aprovan/patchwork": "0.1.
|
|
24
|
-
"@aprovan/patchwork
|
|
25
|
-
"@aprovan/patchwork-
|
|
23
|
+
"@aprovan/patchwork-compiler": "0.1.2",
|
|
24
|
+
"@aprovan/patchwork": "0.1.0-dev.78e095c",
|
|
25
|
+
"@aprovan/patchwork-image-shadcn": "0.1.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@playwright/test": "^1.60.0",
|