open-mcp-app 0.1.2 → 0.1.4

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.
@@ -1,18 +1,5 @@
1
1
  import { Plugin } from 'vite';
2
2
 
3
- /**
4
- * HMR Client Script
5
- *
6
- * This script is injected into MCP App HTML during development mode.
7
- * It connects to Vite's HMR WebSocket and notifies the parent frame
8
- * when a full reload is needed.
9
- *
10
- * The parent frame (Creature host) will then:
11
- * 1. Save current widget state
12
- * 2. Re-fetch fresh HTML from the MCP server
13
- * 3. Reload the iframe with new content
14
- * 4. Restore widget state
15
- */
16
3
  /**
17
4
  * Generate the HMR client script as a string.
18
5
  * The port is injected at generation time.
@@ -7,6 +7,7 @@ import { createHash } from "crypto";
7
7
  import { spawnSync } from "child_process";
8
8
 
9
9
  // src/vite/hmr-client.ts
10
+ var HMR_RELOAD_NOTIFICATION = "ui/notifications/hmr-reload";
10
11
  function generateHmrClientScript(port) {
11
12
  return `
12
13
  (function() {
@@ -71,11 +72,12 @@ function generateHmrClientScript(port) {
71
72
  };
72
73
  }
73
74
 
75
+ // Note: Method name must match HMR_RELOAD_NOTIFICATION constant
74
76
  function notifyParent() {
75
77
  console.log('[Creature HMR] Sending hmr-reload to parent frame');
76
78
  window.parent.postMessage({
77
79
  jsonrpc: '2.0',
78
- method: 'ui/notifications/hmr-reload',
80
+ method: '${HMR_RELOAD_NOTIFICATION}',
79
81
  params: {}
80
82
  }, '*');
81
83
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/vite/index.ts","../../src/vite/hmr-client.ts"],"sourcesContent":["import { resolve, join, relative } from \"node:path\";\nimport { readdirSync, statSync, existsSync, writeFileSync, mkdirSync, rmSync, readFileSync } from \"node:fs\";\nimport { createServer as createNetServer } from \"node:net\";\nimport { createServer as createHttpServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { createHash } from \"node:crypto\";\nimport { spawnSync } from \"node:child_process\";\nimport type { Duplex } from \"node:stream\";\nimport type { Plugin, UserConfig } from \"vite\";\n\nexport interface CreaturePluginOptions {\n uiDir?: string;\n outDir?: string;\n hmrPort?: number;\n /**\n * Generate a JS module exporting bundled HTML for serverless deployments.\n * When enabled, creates `dist/ui/bundle.js` with named exports for each page.\n * \n * @example\n * // In server code:\n * import { main } from \"./dist/ui/bundle.js\";\n * app.resource({ html: main });\n * \n * @default false\n */\n generateBundle?: boolean;\n}\n\nexport interface HmrConfig {\n port: number;\n}\n\n/**\n * Offset added to MCP_PORT to derive the HMR port.\n * When MCP_PORT is set (by Creature), both Vite and the SDK server\n * can independently calculate the same HMR port without coordination.\n */\nexport const HMR_PORT_OFFSET = 1000;\n\nfunction findAvailablePort(startPort: number): Promise<number> {\n return new Promise((resolve) => {\n const server = createNetServer();\n server.listen(startPort, () => {\n const port = (server.address() as { port: number }).port;\n server.close(() => resolve(port));\n });\n server.on(\"error\", () => {\n resolve(findAvailablePort(startPort + 1));\n });\n });\n}\n\ninterface EntryPoint {\n name: string;\n pagePath: string;\n}\n\nfunction findPages(dir: string, baseDir: string): EntryPoint[] {\n const entries: EntryPoint[] = [];\n if (!existsSync(dir)) return entries;\n\n const items = readdirSync(dir);\n\n if (items.includes(\"page.tsx\")) {\n const relativePath = dir.slice(baseDir.length + 1);\n entries.push({\n name: relativePath || \"main\",\n pagePath: join(dir, \"page.tsx\"),\n });\n }\n\n for (const item of items) {\n const fullPath = join(dir, item);\n if (statSync(fullPath).isDirectory() && !item.startsWith(\"_\") && item !== \"node_modules\") {\n entries.push(...findPages(fullPath, baseDir));\n }\n }\n\n return entries;\n}\n\nlet hmrServer: ReturnType<typeof createHttpServer> | null = null;\nlet hmrClients: Set<Duplex> = new Set();\n\nfunction sendWebSocketFrame(socket: Duplex, data: string): void {\n const payload = Buffer.from(data);\n const length = payload.length;\n \n let frame: Buffer;\n if (length < 126) {\n frame = Buffer.alloc(2 + length);\n frame[0] = 0x81;\n frame[1] = length;\n payload.copy(frame, 2);\n } else if (length < 65536) {\n frame = Buffer.alloc(4 + length);\n frame[0] = 0x81;\n frame[1] = 126;\n frame.writeUInt16BE(length, 2);\n payload.copy(frame, 4);\n } else {\n frame = Buffer.alloc(10 + length);\n frame[0] = 0x81;\n frame[1] = 127;\n frame.writeBigUInt64BE(BigInt(length), 2);\n payload.copy(frame, 10);\n }\n \n socket.write(frame);\n}\n\nfunction startHmrServer(port: number): void {\n if (hmrServer) return;\n \n hmrServer = createHttpServer((_req: IncomingMessage, res: ServerResponse) => {\n res.writeHead(200);\n res.end(\"Creature HMR Server\");\n });\n \n hmrServer.on(\"upgrade\", (req: IncomingMessage, socket: Duplex, head: Buffer) => {\n const key = req.headers[\"sec-websocket-key\"];\n if (!key) {\n socket.destroy();\n return;\n }\n \n const acceptKey = createHash(\"sha1\")\n .update(key + \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\")\n .digest(\"base64\");\n \n socket.write(\n \"HTTP/1.1 101 Switching Protocols\\r\\n\" +\n \"Upgrade: websocket\\r\\n\" +\n \"Connection: Upgrade\\r\\n\" +\n `Sec-WebSocket-Accept: ${acceptKey}\\r\\n` +\n \"\\r\\n\"\n );\n \n hmrClients.add(socket);\n sendWebSocketFrame(socket, JSON.stringify({ type: \"connected\" }));\n \n socket.on(\"close\", () => {\n hmrClients.delete(socket);\n });\n \n socket.on(\"error\", () => {\n hmrClients.delete(socket);\n });\n });\n \n hmrServer.listen(port);\n}\n\nfunction notifyHmrClients(): void {\n const message = JSON.stringify({ type: \"full-reload\" });\n const toRemove: Duplex[] = [];\n \n for (const client of hmrClients) {\n try {\n if (!client.destroyed) {\n sendWebSocketFrame(client, message);\n } else {\n toRemove.push(client);\n }\n } catch {\n toRemove.push(client);\n }\n }\n \n for (const client of toRemove) {\n hmrClients.delete(client);\n }\n \n if (hmrClients.size > 0) {\n console.log(\"App UI reloaded\");\n }\n}\n\n/**\n * Vite plugin for Creature MCP Apps.\n * \n * Just write page.tsx files - no HTML or entry files needed.\n * \n * ```\n * src/ui/\n * ├── page.tsx → dist/ui/main.html\n * ├── inline/page.tsx → dist/ui/inline.html\n * └── _components/ → ignored\n * ```\n * \n * When using vite-plugin-singlefile, multiple pages are built automatically\n * via sequential builds (singlefile requires single entry per build).\n */\nexport function creature(options: CreaturePluginOptions = {}): Plugin {\n const uiDir = options.uiDir || \"src/ui\";\n const outDir = options.outDir || \"dist/ui\";\n const preferredHmrPort = options.hmrPort || 5899;\n const generateBundle = options.generateBundle || false;\n \n let root: string;\n let tempDir: string;\n let entries: EntryPoint[] = [];\n let hasSingleFilePlugin = false;\n let hmrPort: number | null = null;\n let isWatchMode = false;\n let remainingPages: string[] = [];\n\n return {\n name: \"creature\",\n \n async config(config) {\n root = config.root || process.cwd();\n const uiPath = resolve(root, uiDir);\n entries = findPages(uiPath, uiPath);\n\n if (entries.length === 0) {\n return;\n }\n\n const plugins = config.plugins?.flat() || [];\n hasSingleFilePlugin = plugins.some(p => p && typeof p === 'object' && 'name' in p && p.name === 'vite:singlefile');\n\n tempDir = resolve(root, \"node_modules/.creature\");\n \n const selectedPage = process.env.CREATURE_PAGE;\n \n if (!selectedPage) {\n rmSync(tempDir, { recursive: true, force: true });\n }\n mkdirSync(tempDir, { recursive: true });\n\n for (const entry of entries) {\n const relativePagePath = relative(tempDir, entry.pagePath).replace(/\\\\/g, \"/\");\n writeFileSync(join(tempDir, `${entry.name}.entry.tsx`), \n`import { createElement } from \"react\";\nimport { createRoot } from \"react-dom/client\";\nimport Page from \"${relativePagePath.replace(/\\.tsx$/, \"\")}\";\ncreateRoot(document.getElementById(\"root\")!).render(createElement(Page));\n`);\n\n writeFileSync(join(tempDir, `${entry.name}.html`),\n`<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>${entry.name}</title>\n</head>\n<body>\n <div id=\"root\"></div>\n <script type=\"module\" src=\"./${entry.name}.entry.tsx\"></script>\n</body>\n</html>`);\n }\n \n let inputs: Record<string, string>;\n \n if (hasSingleFilePlugin) {\n const targetEntry = selectedPage \n ? entries.find(e => e.name === selectedPage)\n : entries[0];\n \n if (!targetEntry) {\n console.error(`Page \"${selectedPage}\" not found`);\n return;\n }\n \n inputs = { [targetEntry.name]: join(tempDir, `${targetEntry.name}.html`) };\n \n if (!selectedPage && entries.length > 1) {\n remainingPages = entries.slice(1).map(e => e.name);\n }\n } else {\n inputs = Object.fromEntries(\n entries.map(e => [e.name, join(tempDir, `${e.name}.html`)])\n );\n }\n\n return {\n root: tempDir,\n publicDir: false,\n logLevel: \"silent\" as const,\n build: {\n outDir: resolve(root, outDir),\n emptyOutDir: !selectedPage,\n rollupOptions: { input: inputs },\n reportCompressedSize: false,\n },\n } satisfies UserConfig;\n },\n\n async buildStart() {\n if (!tempDir) return;\n \n isWatchMode = this.meta.watchMode === true;\n \n if (isWatchMode && !hmrServer) {\n // Derive HMR port from MCP_PORT when available (set by Creature).\n // This allows the SDK server to know the HMR port immediately without\n // waiting for hmr.json to be written, eliminating the race condition.\n const mcpPort = process.env.MCP_PORT ? parseInt(process.env.MCP_PORT, 10) : null;\n hmrPort = mcpPort ? mcpPort + HMR_PORT_OFFSET : await findAvailablePort(preferredHmrPort);\n startHmrServer(hmrPort);\n \n // Still write hmr.json for non-Creature environments (manual npm run dev)\n mkdirSync(tempDir, { recursive: true });\n const hmrConfig: HmrConfig = { port: hmrPort };\n writeFileSync(\n join(tempDir, \"hmr.json\"),\n JSON.stringify(hmrConfig, null, 2)\n );\n }\n },\n\n writeBundle() {\n if (!hasSingleFilePlugin || remainingPages.length === 0) {\n if (isWatchMode) notifyHmrClients();\n return;\n }\n \n for (const pageName of remainingPages) {\n spawnSync(\"npx\", [\"vite\", \"build\"], {\n cwd: root,\n env: { ...process.env, CREATURE_PAGE: pageName },\n stdio: \"inherit\",\n });\n }\n \n if (isWatchMode) {\n notifyHmrClients();\n } else {\n remainingPages = [];\n }\n },\n\n closeBundle() {\n if (isWatchMode) return;\n \n // Generate bundle.js for serverless deployments\n if (generateBundle && entries.length > 0 && !process.env.CREATURE_PAGE) {\n const bundleOutputDir = resolve(root, outDir);\n const exports: string[] = [];\n \n for (const entry of entries) {\n const htmlPath = join(bundleOutputDir, `${entry.name}.html`);\n if (existsSync(htmlPath)) {\n const html = readFileSync(htmlPath, \"utf-8\");\n // Escape backticks and ${} for template literal\n const escaped = html.replace(/\\\\/g, \"\\\\\\\\\").replace(/`/g, \"\\\\`\").replace(/\\$\\{/g, \"\\\\${\");\n exports.push(`export const ${entry.name.replace(/-/g, \"_\")} = \\`${escaped}\\`;`);\n }\n }\n \n if (exports.length > 0) {\n const bundleContent = `/**\n * Auto-generated UI bundle for serverless deployments.\n * Import these exports in your MCP server for Vercel/Lambda.\n * \n * @example\n * import { main } from \"./dist/ui/bundle.js\";\n * app.resource({ html: main });\n */\n${exports.join(\"\\n\\n\")}\n`;\n writeFileSync(join(bundleOutputDir, \"bundle.js\"), bundleContent);\n console.log(`Generated ${outDir}/bundle.js for serverless`);\n }\n }\n \n if (tempDir && existsSync(tempDir) && !process.env.CREATURE_PAGE) {\n rmSync(tempDir, { recursive: true, force: true });\n }\n },\n };\n}\n\nexport { generateHmrClientScript, generateHmrClientScriptTag } from \"./hmr-client.js\";\n\nexport default creature;\n","/**\n * HMR Client Script\n * \n * This script is injected into MCP App HTML during development mode.\n * It connects to Vite's HMR WebSocket and notifies the parent frame\n * when a full reload is needed.\n * \n * The parent frame (Creature host) will then:\n * 1. Save current widget state\n * 2. Re-fetch fresh HTML from the MCP server\n * 3. Reload the iframe with new content\n * 4. Restore widget state\n */\n\n/**\n * Generate the HMR client script as a string.\n * The port is injected at generation time.\n */\nexport function generateHmrClientScript(port: number): string {\n return `\n(function() {\n if (window.parent === window) {\n console.log('[Creature HMR] Not in iframe, skipping HMR client');\n return;\n }\n if (window.__CREATURE_HMR_CONNECTED__) {\n console.log('[Creature HMR] Already connected, skipping');\n return;\n }\n window.__CREATURE_HMR_CONNECTED__ = true;\n\n var HMR_PORT = ${port};\n var reconnectAttempts = 0;\n var maxReconnectAttempts = 10;\n var reconnectDelay = 1000;\n\n console.log('[Creature HMR] Initializing HMR client, will connect to port ' + HMR_PORT);\n\n function connect() {\n if (reconnectAttempts >= maxReconnectAttempts) {\n console.log('[Creature HMR] Max reconnection attempts reached, giving up');\n return;\n }\n\n console.log('[Creature HMR] Attempting to connect to ws://localhost:' + HMR_PORT + ' (attempt ' + (reconnectAttempts + 1) + ')');\n var ws = new WebSocket('ws://localhost:' + HMR_PORT);\n\n ws.onopen = function() {\n console.log('[Creature HMR] Connected to HMR server on port ' + HMR_PORT);\n reconnectAttempts = 0;\n };\n\n ws.onmessage = function(event) {\n console.log('[Creature HMR] Received message:', event.data);\n try {\n var data = JSON.parse(event.data);\n \n if (data.type === 'full-reload') {\n console.log('[Creature HMR] Full reload triggered, notifying parent');\n notifyParent();\n } else if (data.type === 'update') {\n console.log('[Creature HMR] Update detected, notifying parent');\n notifyParent();\n } else if (data.type === 'connected') {\n console.log('[Creature HMR] Server acknowledged connection');\n }\n } catch (e) {\n console.log('[Creature HMR] Failed to parse message:', e);\n }\n };\n\n ws.onclose = function() {\n console.log('[Creature HMR] Disconnected, reconnecting in ' + reconnectDelay + 'ms...');\n reconnectAttempts++;\n setTimeout(connect, reconnectDelay);\n };\n\n ws.onerror = function(err) {\n console.log('[Creature HMR] WebSocket error:', err);\n };\n }\n\n function notifyParent() {\n console.log('[Creature HMR] Sending hmr-reload to parent frame');\n window.parent.postMessage({\n jsonrpc: '2.0',\n method: 'ui/notifications/hmr-reload',\n params: {}\n }, '*');\n }\n\n // Start connection\n connect();\n})();\n`.trim();\n}\n\n/**\n * Generate a script tag with the HMR client code.\n */\nexport function generateHmrClientScriptTag(port: number): string {\n return `<script>${generateHmrClientScript(port)}</script>`;\n}\n"],"mappings":"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","names":["resolve"]}
1
+ {"version":3,"sources":["../../src/vite/index.ts","../../src/vite/hmr-client.ts"],"sourcesContent":["import { resolve, join, relative } from \"node:path\";\nimport { readdirSync, statSync, existsSync, writeFileSync, mkdirSync, rmSync, readFileSync } from \"node:fs\";\nimport { createServer as createNetServer } from \"node:net\";\nimport { createServer as createHttpServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { createHash } from \"node:crypto\";\nimport { spawnSync } from \"node:child_process\";\nimport type { Duplex } from \"node:stream\";\nimport type { Plugin, UserConfig } from \"vite\";\n\nexport interface CreaturePluginOptions {\n uiDir?: string;\n outDir?: string;\n hmrPort?: number;\n /**\n * Generate a JS module exporting bundled HTML for serverless deployments.\n * When enabled, creates `dist/ui/bundle.js` with named exports for each page.\n * \n * @example\n * // In server code:\n * import { main } from \"./dist/ui/bundle.js\";\n * app.resource({ html: main });\n * \n * @default false\n */\n generateBundle?: boolean;\n}\n\nexport interface HmrConfig {\n port: number;\n}\n\n/**\n * Offset added to MCP_PORT to derive the HMR port.\n * When MCP_PORT is set (by Creature), both Vite and the SDK server\n * can independently calculate the same HMR port without coordination.\n */\nexport const HMR_PORT_OFFSET = 1000;\n\nfunction findAvailablePort(startPort: number): Promise<number> {\n return new Promise((resolve) => {\n const server = createNetServer();\n server.listen(startPort, () => {\n const port = (server.address() as { port: number }).port;\n server.close(() => resolve(port));\n });\n server.on(\"error\", () => {\n resolve(findAvailablePort(startPort + 1));\n });\n });\n}\n\ninterface EntryPoint {\n name: string;\n pagePath: string;\n}\n\nfunction findPages(dir: string, baseDir: string): EntryPoint[] {\n const entries: EntryPoint[] = [];\n if (!existsSync(dir)) return entries;\n\n const items = readdirSync(dir);\n\n if (items.includes(\"page.tsx\")) {\n const relativePath = dir.slice(baseDir.length + 1);\n entries.push({\n name: relativePath || \"main\",\n pagePath: join(dir, \"page.tsx\"),\n });\n }\n\n for (const item of items) {\n const fullPath = join(dir, item);\n if (statSync(fullPath).isDirectory() && !item.startsWith(\"_\") && item !== \"node_modules\") {\n entries.push(...findPages(fullPath, baseDir));\n }\n }\n\n return entries;\n}\n\nlet hmrServer: ReturnType<typeof createHttpServer> | null = null;\nlet hmrClients: Set<Duplex> = new Set();\n\nfunction sendWebSocketFrame(socket: Duplex, data: string): void {\n const payload = Buffer.from(data);\n const length = payload.length;\n \n let frame: Buffer;\n if (length < 126) {\n frame = Buffer.alloc(2 + length);\n frame[0] = 0x81;\n frame[1] = length;\n payload.copy(frame, 2);\n } else if (length < 65536) {\n frame = Buffer.alloc(4 + length);\n frame[0] = 0x81;\n frame[1] = 126;\n frame.writeUInt16BE(length, 2);\n payload.copy(frame, 4);\n } else {\n frame = Buffer.alloc(10 + length);\n frame[0] = 0x81;\n frame[1] = 127;\n frame.writeBigUInt64BE(BigInt(length), 2);\n payload.copy(frame, 10);\n }\n \n socket.write(frame);\n}\n\nfunction startHmrServer(port: number): void {\n if (hmrServer) return;\n \n hmrServer = createHttpServer((_req: IncomingMessage, res: ServerResponse) => {\n res.writeHead(200);\n res.end(\"Creature HMR Server\");\n });\n \n hmrServer.on(\"upgrade\", (req: IncomingMessage, socket: Duplex, head: Buffer) => {\n const key = req.headers[\"sec-websocket-key\"];\n if (!key) {\n socket.destroy();\n return;\n }\n \n const acceptKey = createHash(\"sha1\")\n .update(key + \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\")\n .digest(\"base64\");\n \n socket.write(\n \"HTTP/1.1 101 Switching Protocols\\r\\n\" +\n \"Upgrade: websocket\\r\\n\" +\n \"Connection: Upgrade\\r\\n\" +\n `Sec-WebSocket-Accept: ${acceptKey}\\r\\n` +\n \"\\r\\n\"\n );\n \n hmrClients.add(socket);\n sendWebSocketFrame(socket, JSON.stringify({ type: \"connected\" }));\n \n socket.on(\"close\", () => {\n hmrClients.delete(socket);\n });\n \n socket.on(\"error\", () => {\n hmrClients.delete(socket);\n });\n });\n \n hmrServer.listen(port);\n}\n\nfunction notifyHmrClients(): void {\n const message = JSON.stringify({ type: \"full-reload\" });\n const toRemove: Duplex[] = [];\n \n for (const client of hmrClients) {\n try {\n if (!client.destroyed) {\n sendWebSocketFrame(client, message);\n } else {\n toRemove.push(client);\n }\n } catch {\n toRemove.push(client);\n }\n }\n \n for (const client of toRemove) {\n hmrClients.delete(client);\n }\n \n if (hmrClients.size > 0) {\n console.log(\"App UI reloaded\");\n }\n}\n\n/**\n * Vite plugin for Creature MCP Apps.\n * \n * Just write page.tsx files - no HTML or entry files needed.\n * \n * ```\n * src/ui/\n * ├── page.tsx → dist/ui/main.html\n * ├── inline/page.tsx → dist/ui/inline.html\n * └── _components/ → ignored\n * ```\n * \n * When using vite-plugin-singlefile, multiple pages are built automatically\n * via sequential builds (singlefile requires single entry per build).\n */\nexport function creature(options: CreaturePluginOptions = {}): Plugin {\n const uiDir = options.uiDir || \"src/ui\";\n const outDir = options.outDir || \"dist/ui\";\n const preferredHmrPort = options.hmrPort || 5899;\n const generateBundle = options.generateBundle || false;\n \n let root: string;\n let tempDir: string;\n let entries: EntryPoint[] = [];\n let hasSingleFilePlugin = false;\n let hmrPort: number | null = null;\n let isWatchMode = false;\n let remainingPages: string[] = [];\n\n return {\n name: \"creature\",\n \n async config(config) {\n root = config.root || process.cwd();\n const uiPath = resolve(root, uiDir);\n entries = findPages(uiPath, uiPath);\n\n if (entries.length === 0) {\n return;\n }\n\n const plugins = config.plugins?.flat() || [];\n hasSingleFilePlugin = plugins.some(p => p && typeof p === 'object' && 'name' in p && p.name === 'vite:singlefile');\n\n tempDir = resolve(root, \"node_modules/.creature\");\n \n const selectedPage = process.env.CREATURE_PAGE;\n \n if (!selectedPage) {\n rmSync(tempDir, { recursive: true, force: true });\n }\n mkdirSync(tempDir, { recursive: true });\n\n for (const entry of entries) {\n const relativePagePath = relative(tempDir, entry.pagePath).replace(/\\\\/g, \"/\");\n writeFileSync(join(tempDir, `${entry.name}.entry.tsx`), \n`import { createElement } from \"react\";\nimport { createRoot } from \"react-dom/client\";\nimport Page from \"${relativePagePath.replace(/\\.tsx$/, \"\")}\";\ncreateRoot(document.getElementById(\"root\")!).render(createElement(Page));\n`);\n\n writeFileSync(join(tempDir, `${entry.name}.html`),\n`<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>${entry.name}</title>\n</head>\n<body>\n <div id=\"root\"></div>\n <script type=\"module\" src=\"./${entry.name}.entry.tsx\"></script>\n</body>\n</html>`);\n }\n \n let inputs: Record<string, string>;\n \n if (hasSingleFilePlugin) {\n const targetEntry = selectedPage \n ? entries.find(e => e.name === selectedPage)\n : entries[0];\n \n if (!targetEntry) {\n console.error(`Page \"${selectedPage}\" not found`);\n return;\n }\n \n inputs = { [targetEntry.name]: join(tempDir, `${targetEntry.name}.html`) };\n \n if (!selectedPage && entries.length > 1) {\n remainingPages = entries.slice(1).map(e => e.name);\n }\n } else {\n inputs = Object.fromEntries(\n entries.map(e => [e.name, join(tempDir, `${e.name}.html`)])\n );\n }\n\n return {\n root: tempDir,\n publicDir: false,\n logLevel: \"silent\" as const,\n build: {\n outDir: resolve(root, outDir),\n emptyOutDir: !selectedPage,\n rollupOptions: { input: inputs },\n reportCompressedSize: false,\n },\n } satisfies UserConfig;\n },\n\n async buildStart() {\n if (!tempDir) return;\n \n isWatchMode = this.meta.watchMode === true;\n \n if (isWatchMode && !hmrServer) {\n // Derive HMR port from MCP_PORT when available (set by Creature).\n // This allows the SDK server to know the HMR port immediately without\n // waiting for hmr.json to be written, eliminating the race condition.\n const mcpPort = process.env.MCP_PORT ? parseInt(process.env.MCP_PORT, 10) : null;\n hmrPort = mcpPort ? mcpPort + HMR_PORT_OFFSET : await findAvailablePort(preferredHmrPort);\n startHmrServer(hmrPort);\n \n // Still write hmr.json for non-Creature environments (manual npm run dev)\n mkdirSync(tempDir, { recursive: true });\n const hmrConfig: HmrConfig = { port: hmrPort };\n writeFileSync(\n join(tempDir, \"hmr.json\"),\n JSON.stringify(hmrConfig, null, 2)\n );\n }\n },\n\n writeBundle() {\n if (!hasSingleFilePlugin || remainingPages.length === 0) {\n if (isWatchMode) notifyHmrClients();\n return;\n }\n \n for (const pageName of remainingPages) {\n spawnSync(\"npx\", [\"vite\", \"build\"], {\n cwd: root,\n env: { ...process.env, CREATURE_PAGE: pageName },\n stdio: \"inherit\",\n });\n }\n \n if (isWatchMode) {\n notifyHmrClients();\n } else {\n remainingPages = [];\n }\n },\n\n closeBundle() {\n if (isWatchMode) return;\n \n // Generate bundle.js for serverless deployments\n if (generateBundle && entries.length > 0 && !process.env.CREATURE_PAGE) {\n const bundleOutputDir = resolve(root, outDir);\n const exports: string[] = [];\n \n for (const entry of entries) {\n const htmlPath = join(bundleOutputDir, `${entry.name}.html`);\n if (existsSync(htmlPath)) {\n const html = readFileSync(htmlPath, \"utf-8\");\n // Escape backticks and ${} for template literal\n const escaped = html.replace(/\\\\/g, \"\\\\\\\\\").replace(/`/g, \"\\\\`\").replace(/\\$\\{/g, \"\\\\${\");\n exports.push(`export const ${entry.name.replace(/-/g, \"_\")} = \\`${escaped}\\`;`);\n }\n }\n \n if (exports.length > 0) {\n const bundleContent = `/**\n * Auto-generated UI bundle for serverless deployments.\n * Import these exports in your MCP server for Vercel/Lambda.\n * \n * @example\n * import { main } from \"./dist/ui/bundle.js\";\n * app.resource({ html: main });\n */\n${exports.join(\"\\n\\n\")}\n`;\n writeFileSync(join(bundleOutputDir, \"bundle.js\"), bundleContent);\n console.log(`Generated ${outDir}/bundle.js for serverless`);\n }\n }\n \n if (tempDir && existsSync(tempDir) && !process.env.CREATURE_PAGE) {\n rmSync(tempDir, { recursive: true, force: true });\n }\n },\n };\n}\n\nexport { generateHmrClientScript, generateHmrClientScriptTag } from \"./hmr-client.js\";\n\nexport default creature;\n","/**\n * HMR Client Script\n *\n * This script is injected into MCP App HTML during development mode.\n * It connects to Vite's HMR WebSocket and notifies the parent frame\n * when a full reload is needed.\n *\n * The parent frame (Creature host) will then:\n * 1. Save current widget state\n * 2. Re-fetch fresh HTML from the MCP server\n * 3. Reload the iframe with new content\n * 4. Restore widget state\n *\n * ## Internal Protocol Extension\n *\n * This module uses the `ui/notifications/hmr-reload` notification method,\n * which is an **internal, dev-only Creature extension** not part of the\n * MCP Apps specification. It is NOT exposed via the public SDK client API.\n *\n * The host (Creature desktop) listens for this notification and triggers\n * a graceful iframe reload while preserving widget state.\n */\n\n/**\n * Internal notification method for HMR reload.\n *\n * This is a Creature-specific, dev-only extension NOT part of the MCP Apps spec.\n * It is used internally by the Vite HMR integration and should not be used directly.\n *\n * @internal\n */\nexport const HMR_RELOAD_NOTIFICATION = \"ui/notifications/hmr-reload\" as const;\n\n/**\n * Generate the HMR client script as a string.\n * The port is injected at generation time.\n */\nexport function generateHmrClientScript(port: number): string {\n return `\n(function() {\n if (window.parent === window) {\n console.log('[Creature HMR] Not in iframe, skipping HMR client');\n return;\n }\n if (window.__CREATURE_HMR_CONNECTED__) {\n console.log('[Creature HMR] Already connected, skipping');\n return;\n }\n window.__CREATURE_HMR_CONNECTED__ = true;\n\n var HMR_PORT = ${port};\n var reconnectAttempts = 0;\n var maxReconnectAttempts = 10;\n var reconnectDelay = 1000;\n\n console.log('[Creature HMR] Initializing HMR client, will connect to port ' + HMR_PORT);\n\n function connect() {\n if (reconnectAttempts >= maxReconnectAttempts) {\n console.log('[Creature HMR] Max reconnection attempts reached, giving up');\n return;\n }\n\n console.log('[Creature HMR] Attempting to connect to ws://localhost:' + HMR_PORT + ' (attempt ' + (reconnectAttempts + 1) + ')');\n var ws = new WebSocket('ws://localhost:' + HMR_PORT);\n\n ws.onopen = function() {\n console.log('[Creature HMR] Connected to HMR server on port ' + HMR_PORT);\n reconnectAttempts = 0;\n };\n\n ws.onmessage = function(event) {\n console.log('[Creature HMR] Received message:', event.data);\n try {\n var data = JSON.parse(event.data);\n \n if (data.type === 'full-reload') {\n console.log('[Creature HMR] Full reload triggered, notifying parent');\n notifyParent();\n } else if (data.type === 'update') {\n console.log('[Creature HMR] Update detected, notifying parent');\n notifyParent();\n } else if (data.type === 'connected') {\n console.log('[Creature HMR] Server acknowledged connection');\n }\n } catch (e) {\n console.log('[Creature HMR] Failed to parse message:', e);\n }\n };\n\n ws.onclose = function() {\n console.log('[Creature HMR] Disconnected, reconnecting in ' + reconnectDelay + 'ms...');\n reconnectAttempts++;\n setTimeout(connect, reconnectDelay);\n };\n\n ws.onerror = function(err) {\n console.log('[Creature HMR] WebSocket error:', err);\n };\n }\n\n // Note: Method name must match HMR_RELOAD_NOTIFICATION constant\n function notifyParent() {\n console.log('[Creature HMR] Sending hmr-reload to parent frame');\n window.parent.postMessage({\n jsonrpc: '2.0',\n method: '${HMR_RELOAD_NOTIFICATION}',\n params: {}\n }, '*');\n }\n\n // Start connection\n connect();\n})();\n`.trim();\n}\n\n/**\n * Generate a script tag with the HMR client code.\n */\nexport function generateHmrClientScriptTag(port: number): string {\n return `<script>${generateHmrClientScript(port)}</script>`;\n}\n"],"mappings":";AAAA,SAAS,SAAS,MAAM,gBAAgB;AACxC,SAAS,aAAa,UAAU,YAAY,eAAe,WAAW,QAAQ,oBAAoB;AAClG,SAAS,gBAAgB,uBAAuB;AAChD,SAAS,gBAAgB,wBAAmE;AAC5F,SAAS,kBAAkB;AAC3B,SAAS,iBAAiB;;;AC0BnB,IAAM,0BAA0B;AAMhC,SAAS,wBAAwB,MAAsB;AAC5D,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAYU,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAwDN,uBAAuB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQtC,KAAK;AACP;AAKO,SAAS,2BAA2B,MAAsB;AAC/D,SAAO,WAAW,wBAAwB,IAAI,CAAC;AACjD;;;ADtFO,IAAM,kBAAkB;AAE/B,SAAS,kBAAkB,WAAoC;AAC7D,SAAO,IAAI,QAAQ,CAACA,aAAY;AAC9B,UAAM,SAAS,gBAAgB;AAC/B,WAAO,OAAO,WAAW,MAAM;AAC7B,YAAM,OAAQ,OAAO,QAAQ,EAAuB;AACpD,aAAO,MAAM,MAAMA,SAAQ,IAAI,CAAC;AAAA,IAClC,CAAC;AACD,WAAO,GAAG,SAAS,MAAM;AACvB,MAAAA,SAAQ,kBAAkB,YAAY,CAAC,CAAC;AAAA,IAC1C,CAAC;AAAA,EACH,CAAC;AACH;AAOA,SAAS,UAAU,KAAa,SAA+B;AAC7D,QAAM,UAAwB,CAAC;AAC/B,MAAI,CAAC,WAAW,GAAG,EAAG,QAAO;AAE7B,QAAM,QAAQ,YAAY,GAAG;AAE7B,MAAI,MAAM,SAAS,UAAU,GAAG;AAC9B,UAAM,eAAe,IAAI,MAAM,QAAQ,SAAS,CAAC;AACjD,YAAQ,KAAK;AAAA,MACX,MAAM,gBAAgB;AAAA,MACtB,UAAU,KAAK,KAAK,UAAU;AAAA,IAChC,CAAC;AAAA,EACH;AAEA,aAAW,QAAQ,OAAO;AACxB,UAAM,WAAW,KAAK,KAAK,IAAI;AAC/B,QAAI,SAAS,QAAQ,EAAE,YAAY,KAAK,CAAC,KAAK,WAAW,GAAG,KAAK,SAAS,gBAAgB;AACxF,cAAQ,KAAK,GAAG,UAAU,UAAU,OAAO,CAAC;AAAA,IAC9C;AAAA,EACF;AAEA,SAAO;AACT;AAEA,IAAI,YAAwD;AAC5D,IAAI,aAA0B,oBAAI,IAAI;AAEtC,SAAS,mBAAmB,QAAgB,MAAoB;AAC9D,QAAM,UAAU,OAAO,KAAK,IAAI;AAChC,QAAM,SAAS,QAAQ;AAEvB,MAAI;AACJ,MAAI,SAAS,KAAK;AAChB,YAAQ,OAAO,MAAM,IAAI,MAAM;AAC/B,UAAM,CAAC,IAAI;AACX,UAAM,CAAC,IAAI;AACX,YAAQ,KAAK,OAAO,CAAC;AAAA,EACvB,WAAW,SAAS,OAAO;AACzB,YAAQ,OAAO,MAAM,IAAI,MAAM;AAC/B,UAAM,CAAC,IAAI;AACX,UAAM,CAAC,IAAI;AACX,UAAM,cAAc,QAAQ,CAAC;AAC7B,YAAQ,KAAK,OAAO,CAAC;AAAA,EACvB,OAAO;AACL,YAAQ,OAAO,MAAM,KAAK,MAAM;AAChC,UAAM,CAAC,IAAI;AACX,UAAM,CAAC,IAAI;AACX,UAAM,iBAAiB,OAAO,MAAM,GAAG,CAAC;AACxC,YAAQ,KAAK,OAAO,EAAE;AAAA,EACxB;AAEA,SAAO,MAAM,KAAK;AACpB;AAEA,SAAS,eAAe,MAAoB;AAC1C,MAAI,UAAW;AAEf,cAAY,iBAAiB,CAAC,MAAuB,QAAwB;AAC3E,QAAI,UAAU,GAAG;AACjB,QAAI,IAAI,qBAAqB;AAAA,EAC/B,CAAC;AAED,YAAU,GAAG,WAAW,CAAC,KAAsB,QAAgB,SAAiB;AAC9E,UAAM,MAAM,IAAI,QAAQ,mBAAmB;AAC3C,QAAI,CAAC,KAAK;AACR,aAAO,QAAQ;AACf;AAAA,IACF;AAEA,UAAM,YAAY,WAAW,MAAM,EAChC,OAAO,MAAM,sCAAsC,EACnD,OAAO,QAAQ;AAElB,WAAO;AAAA,MACL;AAAA;AAAA;AAAA,wBAGyB,SAAS;AAAA;AAAA;AAAA,IAEpC;AAEA,eAAW,IAAI,MAAM;AACrB,uBAAmB,QAAQ,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC,CAAC;AAEhE,WAAO,GAAG,SAAS,MAAM;AACvB,iBAAW,OAAO,MAAM;AAAA,IAC1B,CAAC;AAED,WAAO,GAAG,SAAS,MAAM;AACvB,iBAAW,OAAO,MAAM;AAAA,IAC1B,CAAC;AAAA,EACH,CAAC;AAED,YAAU,OAAO,IAAI;AACvB;AAEA,SAAS,mBAAyB;AAChC,QAAM,UAAU,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AACtD,QAAM,WAAqB,CAAC;AAE5B,aAAW,UAAU,YAAY;AAC/B,QAAI;AACF,UAAI,CAAC,OAAO,WAAW;AACrB,2BAAmB,QAAQ,OAAO;AAAA,MACpC,OAAO;AACL,iBAAS,KAAK,MAAM;AAAA,MACtB;AAAA,IACF,QAAQ;AACN,eAAS,KAAK,MAAM;AAAA,IACtB;AAAA,EACF;AAEA,aAAW,UAAU,UAAU;AAC7B,eAAW,OAAO,MAAM;AAAA,EAC1B;AAEA,MAAI,WAAW,OAAO,GAAG;AACvB,YAAQ,IAAI,iBAAiB;AAAA,EAC/B;AACF;AAiBO,SAAS,SAAS,UAAiC,CAAC,GAAW;AACpE,QAAM,QAAQ,QAAQ,SAAS;AAC/B,QAAM,SAAS,QAAQ,UAAU;AACjC,QAAM,mBAAmB,QAAQ,WAAW;AAC5C,QAAM,iBAAiB,QAAQ,kBAAkB;AAEjD,MAAI;AACJ,MAAI;AACJ,MAAI,UAAwB,CAAC;AAC7B,MAAI,sBAAsB;AAC1B,MAAI,UAAyB;AAC7B,MAAI,cAAc;AAClB,MAAI,iBAA2B,CAAC;AAEhC,SAAO;AAAA,IACL,MAAM;AAAA,IAEN,MAAM,OAAO,QAAQ;AACnB,aAAO,OAAO,QAAQ,QAAQ,IAAI;AAClC,YAAM,SAAS,QAAQ,MAAM,KAAK;AAClC,gBAAU,UAAU,QAAQ,MAAM;AAElC,UAAI,QAAQ,WAAW,GAAG;AACxB;AAAA,MACF;AAEA,YAAM,UAAU,OAAO,SAAS,KAAK,KAAK,CAAC;AAC3C,4BAAsB,QAAQ,KAAK,OAAK,KAAK,OAAO,MAAM,YAAY,UAAU,KAAK,EAAE,SAAS,iBAAiB;AAEjH,gBAAU,QAAQ,MAAM,wBAAwB;AAEhD,YAAM,eAAe,QAAQ,IAAI;AAEjC,UAAI,CAAC,cAAc;AACjB,eAAO,SAAS,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,MAClD;AACA,gBAAU,SAAS,EAAE,WAAW,KAAK,CAAC;AAEtC,iBAAW,SAAS,SAAS;AAC3B,cAAM,mBAAmB,SAAS,SAAS,MAAM,QAAQ,EAAE,QAAQ,OAAO,GAAG;AAC7E;AAAA,UAAc,KAAK,SAAS,GAAG,MAAM,IAAI,YAAY;AAAA,UAC7D;AAAA;AAAA,oBAEoB,iBAAiB,QAAQ,UAAU,EAAE,CAAC;AAAA;AAAA;AAAA,QAEzD;AAEO;AAAA,UAAc,KAAK,SAAS,GAAG,MAAM,IAAI,OAAO;AAAA,UACxD;AAAA;AAAA;AAAA;AAAA;AAAA,WAKW,MAAM,IAAI;AAAA;AAAA;AAAA;AAAA,iCAIY,MAAM,IAAI;AAAA;AAAA;AAAA,QAEnC;AAAA,MACF;AAEA,UAAI;AAEJ,UAAI,qBAAqB;AACvB,cAAM,cAAc,eAChB,QAAQ,KAAK,OAAK,EAAE,SAAS,YAAY,IACzC,QAAQ,CAAC;AAEb,YAAI,CAAC,aAAa;AAChB,kBAAQ,MAAM,SAAS,YAAY,aAAa;AAChD;AAAA,QACF;AAEA,iBAAS,EAAE,CAAC,YAAY,IAAI,GAAG,KAAK,SAAS,GAAG,YAAY,IAAI,OAAO,EAAE;AAEzE,YAAI,CAAC,gBAAgB,QAAQ,SAAS,GAAG;AACvC,2BAAiB,QAAQ,MAAM,CAAC,EAAE,IAAI,OAAK,EAAE,IAAI;AAAA,QACnD;AAAA,MACF,OAAO;AACL,iBAAS,OAAO;AAAA,UACd,QAAQ,IAAI,OAAK,CAAC,EAAE,MAAM,KAAK,SAAS,GAAG,EAAE,IAAI,OAAO,CAAC,CAAC;AAAA,QAC5D;AAAA,MACF;AAEA,aAAO;AAAA,QACL,MAAM;AAAA,QACN,WAAW;AAAA,QACX,UAAU;AAAA,QACV,OAAO;AAAA,UACL,QAAQ,QAAQ,MAAM,MAAM;AAAA,UAC5B,aAAa,CAAC;AAAA,UACd,eAAe,EAAE,OAAO,OAAO;AAAA,UAC/B,sBAAsB;AAAA,QACxB;AAAA,MACF;AAAA,IACF;AAAA,IAEA,MAAM,aAAa;AACjB,UAAI,CAAC,QAAS;AAEd,oBAAc,KAAK,KAAK,cAAc;AAEtC,UAAI,eAAe,CAAC,WAAW;AAI7B,cAAM,UAAU,QAAQ,IAAI,WAAW,SAAS,QAAQ,IAAI,UAAU,EAAE,IAAI;AAC5E,kBAAU,UAAU,UAAU,kBAAkB,MAAM,kBAAkB,gBAAgB;AACxF,uBAAe,OAAO;AAGtB,kBAAU,SAAS,EAAE,WAAW,KAAK,CAAC;AACtC,cAAM,YAAuB,EAAE,MAAM,QAAQ;AAC7C;AAAA,UACE,KAAK,SAAS,UAAU;AAAA,UACxB,KAAK,UAAU,WAAW,MAAM,CAAC;AAAA,QACnC;AAAA,MACF;AAAA,IACF;AAAA,IAEA,cAAc;AACZ,UAAI,CAAC,uBAAuB,eAAe,WAAW,GAAG;AACvD,YAAI,YAAa,kBAAiB;AAClC;AAAA,MACF;AAEA,iBAAW,YAAY,gBAAgB;AACrC,kBAAU,OAAO,CAAC,QAAQ,OAAO,GAAG;AAAA,UAClC,KAAK;AAAA,UACL,KAAK,EAAE,GAAG,QAAQ,KAAK,eAAe,SAAS;AAAA,UAC/C,OAAO;AAAA,QACT,CAAC;AAAA,MACH;AAEA,UAAI,aAAa;AACf,yBAAiB;AAAA,MACnB,OAAO;AACL,yBAAiB,CAAC;AAAA,MACpB;AAAA,IACF;AAAA,IAEA,cAAc;AACZ,UAAI,YAAa;AAGjB,UAAI,kBAAkB,QAAQ,SAAS,KAAK,CAAC,QAAQ,IAAI,eAAe;AACtE,cAAM,kBAAkB,QAAQ,MAAM,MAAM;AAC5C,cAAM,UAAoB,CAAC;AAE3B,mBAAW,SAAS,SAAS;AAC3B,gBAAM,WAAW,KAAK,iBAAiB,GAAG,MAAM,IAAI,OAAO;AAC3D,cAAI,WAAW,QAAQ,GAAG;AACxB,kBAAM,OAAO,aAAa,UAAU,OAAO;AAE3C,kBAAM,UAAU,KAAK,QAAQ,OAAO,MAAM,EAAE,QAAQ,MAAM,KAAK,EAAE,QAAQ,SAAS,MAAM;AACxF,oBAAQ,KAAK,gBAAgB,MAAM,KAAK,QAAQ,MAAM,GAAG,CAAC,QAAQ,OAAO,KAAK;AAAA,UAChF;AAAA,QACF;AAEA,YAAI,QAAQ,SAAS,GAAG;AACtB,gBAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ9B,QAAQ,KAAK,MAAM,CAAC;AAAA;AAEZ,wBAAc,KAAK,iBAAiB,WAAW,GAAG,aAAa;AAC/D,kBAAQ,IAAI,aAAa,MAAM,2BAA2B;AAAA,QAC5D;AAAA,MACF;AAEA,UAAI,WAAW,WAAW,OAAO,KAAK,CAAC,QAAQ,IAAI,eAAe;AAChE,eAAO,SAAS,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,MAClD;AAAA,IACF;AAAA,EACF;AACF;AAIA,IAAO,eAAQ;","names":["resolve"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-mcp-app",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "SDK for building MCP Apps that work on both Creature and ChatGPT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -37,7 +37,9 @@
37
37
  "build": "tsup",
38
38
  "dev": "tsup --watch",
39
39
  "typecheck": "tsc --noEmit",
40
- "clean": "rm -rf dist"
40
+ "clean": "rm -rf dist",
41
+ "test": "vitest run",
42
+ "test:watch": "vitest"
41
43
  },
42
44
  "dependencies": {
43
45
  "@modelcontextprotocol/ext-apps": "^0.2.2",
@@ -55,7 +57,8 @@
55
57
  "react": "^19.1.0",
56
58
  "tsup": "^8.5.1",
57
59
  "typescript": "^5.8.3",
58
- "vite": "^6.3.5"
60
+ "vite": "^6.3.5",
61
+ "vitest": "^3.1.0"
59
62
  },
60
63
  "peerDependencies": {
61
64
  "react": ">=18.0.0"