From a594196658f0182a8b5eddb1e7f5618138079791 Mon Sep 17 00:00:00 2001 From: Segun Adebayo Date: Mon, 15 Sep 2025 16:56:06 +0100 Subject: [PATCH] chore: init --- .gitignore | 1 + packages/cli/__tests__/mcp.test.ts | 650 +++++++++++++++++++++ packages/cli/__tests__/samples/.gitkeep | 0 packages/cli/package.json | 8 +- packages/cli/src/cli-main.ts | 10 + packages/cli/src/mcp/index.ts | 50 ++ packages/cli/src/mcp/load-config.ts | 50 ++ packages/cli/src/mcp/server.ts | 330 +++++++++++ packages/cli/src/mcp/tools/animations.ts | 37 ++ packages/cli/src/mcp/tools/compositions.ts | 198 +++++++ packages/cli/src/mcp/tools/config.ts | 42 ++ packages/cli/src/mcp/tools/index.ts | 5 + packages/cli/src/mcp/tools/recipes.ts | 168 ++++++ packages/cli/src/mcp/tools/tokens.ts | 83 +++ packages/cli/src/types.ts | 5 + packages/cli/tsup.config.ts | 11 + pnpm-lock.yaml | 181 +++++- 17 files changed, 1817 insertions(+), 12 deletions(-) create mode 100644 packages/cli/__tests__/mcp.test.ts delete mode 100644 packages/cli/__tests__/samples/.gitkeep create mode 100644 packages/cli/src/mcp/index.ts create mode 100644 packages/cli/src/mcp/load-config.ts create mode 100644 packages/cli/src/mcp/server.ts create mode 100644 packages/cli/src/mcp/tools/animations.ts create mode 100644 packages/cli/src/mcp/tools/compositions.ts create mode 100644 packages/cli/src/mcp/tools/config.ts create mode 100644 packages/cli/src/mcp/tools/index.ts create mode 100644 packages/cli/src/mcp/tools/recipes.ts create mode 100644 packages/cli/src/mcp/tools/tokens.ts create mode 100644 packages/cli/tsup.config.ts diff --git a/.gitignore b/.gitignore index b91e0023e4..a4627d6db1 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ ts-import-map-outdir !packages/studio/styled-system packages/studio/src/lib/analysis.json +.cursor/mcp.json diff --git a/packages/cli/__tests__/mcp.test.ts b/packages/cli/__tests__/mcp.test.ts new file mode 100644 index 0000000000..5565209130 --- /dev/null +++ b/packages/cli/__tests__/mcp.test.ts @@ -0,0 +1,650 @@ +import { describe, expect, test, beforeAll, afterAll } from 'vitest' +import { spawn, ChildProcess } from 'node:child_process' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import fs from 'node:fs/promises' + +describe('MCP Server', () => { + const cwd = process.cwd() + const _dirname = path.dirname(fileURLToPath(import.meta.url)) + const cliPath = path.resolve(cwd, _dirname, '../dist/cli-default.js') + + const testsCwd = path.resolve(cwd, _dirname, './samples') + const configPath = path.resolve(testsCwd, 'panda.config.ts') + + // Helper function to create and configure MCP server process + const createMcpServer = (): ChildProcess => { + return spawn('node', [cliPath, 'mcp', '--config', configPath], { + stdio: 'pipe', + cwd: testsCwd, + }) + } + + // Helper function to capture output from process + const captureOutput = (child: ChildProcess): Promise<{ output: string; errorOutput: string }> => { + return new Promise((resolve) => { + let output = '' + let errorOutput = '' + + child.stdout?.on('data', (data) => { + output += data.toString() + }) + + child.stderr?.on('data', (data) => { + errorOutput += data.toString() + }) + + // Wait for output to accumulate + setTimeout(() => { + resolve({ output, errorOutput }) + }, 1000) + }) + } + + // Helper function to send JSON-RPC request and get response + const sendMcpRequest = async (child: ChildProcess, request: object, waitTime: number = 2000): Promise => { + let output = '' + child.stdout?.on('data', (data) => { + output += data.toString() + }) + + child.stdin?.write(JSON.stringify(request) + '\n') + + // Wait for response + await new Promise((resolve) => setTimeout(resolve, waitTime)) + + child.kill() + + // Parse the JSON response + const lines = output.split('\n').filter((line) => line.trim()) + const responseLine = lines.find((line) => line.includes('"result"')) + + if (responseLine) { + const response = JSON.parse(responseLine) + return JSON.parse(response.result.content[0].text) + } + + throw new Error('No valid response found') + } + + // Helper function to call a tool with arguments + const callMcpTool = async (toolName: string, args: object = {}, requestId: number = 1): Promise => { + const child = createMcpServer() + + const request = { + jsonrpc: '2.0', + id: requestId, + method: 'tools/call', + params: { + name: toolName, + arguments: args, + }, + } + + return sendMcpRequest(child, request) + } + + beforeAll(async () => { + // Create the `samples` folder and config + await fs.mkdir(testsCwd, { recursive: true }) + + // Create a minimal panda config for testing + const configContent = ` +import { defineConfig } from '@pandacss/dev' + +export default defineConfig({ + preflight: false, + include: ['./src/**/*.{js,jsx,ts,tsx}'], + exclude: [], + theme: { + extend: { + tokens: { + colors: { + red: { + 500: { value: '#ef4444' }, + 600: { value: '#dc2626' } + }, + blue: { + 500: { value: '#3b82f6' }, + 600: { value: '#2563eb' } + } + }, + spacing: { + sm: { value: '8px' }, + md: { value: '16px' } + } + }, + semanticTokens: { + colors: { + primary: { + value: { + base: '{colors.blue.500}', + _dark: '{colors.blue.400}' + } + }, + secondary: { + value: { + base: '{colors.red.500}', + _dark: '{colors.red.400}' + } + } + }, + spacing: { + container: { + value: { + base: '{spacing.md}', + lg: '{spacing.lg}' + } + } + } + }, + keyframes: { + fadeIn: { + '0%': { opacity: 0 }, + '100%': { opacity: 1 } + }, + slideUp: { + '0%': { transform: 'translateY(10px)' }, + '100%': { transform: 'translateY(0)' } + }, + spin: { + '0%': { transform: 'rotate(0deg)' }, + '100%': { transform: 'rotate(360deg)' } + } + } + }, + breakpoints: { + sm: '640px', + md: '768px', + lg: '1024px', + xl: '1280px' + }, + extend: { + animation: { + 'fade-in': 'fadeIn 0.5s ease-out', + 'slide-up': 'slideUp 0.3s ease-out', + 'spin-slow': 'spin 2s linear infinite' + } + } + }, + theme: { + recipes: { + button: { + className: 'button', + description: 'Button styles for the application', + base: { + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + }, + variants: { + size: { + sm: { padding: '8px 12px', fontSize: '14px' }, + md: { padding: '10px 16px', fontSize: '16px' }, + lg: { padding: '12px 20px', fontSize: '18px' } + }, + variant: { + solid: { bg: 'blue.500', color: 'white' }, + outline: { borderWidth: '1px', borderColor: 'blue.500', color: 'blue.500' }, + ghost: { color: 'blue.500' } + } + }, + defaultVariants: { + size: 'md', + variant: 'solid' + } + }, + card: { + className: 'card', + description: 'Card component styles', + base: { + borderRadius: '8px', + padding: '16px', + boxShadow: 'sm' + }, + variants: { + elevated: { + true: { boxShadow: 'lg' }, + false: { boxShadow: 'sm' } + } + } + }, + alert: { + className: 'alert', + description: 'Alert component with slots', + slots: ['root', 'icon', 'content'], + base: { + root: { display: 'flex', padding: '16px' }, + icon: { marginRight: '8px' }, + content: { flex: '1' } + }, + variants: { + status: { + success: { + root: { bg: 'green.100', color: 'green.800' }, + icon: { color: 'green.500' } + }, + error: { + root: { bg: 'red.100', color: 'red.800' }, + icon: { color: 'red.500' } + } + } + } + } + } + } +}) +` + await fs.writeFile(configPath, configContent) + }) + + afterAll(async () => { + try { + await fs.rm(testsCwd, { recursive: true, force: true }) + } catch { + // ignore cleanup errors + } + }) + + test('should start MCP server', async () => { + const child = createMcpServer() + const { output, errorOutput } = await captureOutput(child) + + const allOutput = output + errorOutput + expect(allOutput).toContain('Panda CSS MCP server started') + + child.kill() + }) + + test('should list tools via MCP protocol', async () => { + const child = createMcpServer() + + const request = { + jsonrpc: '2.0', + id: 1, + method: 'tools/list', + params: {}, + } + + let output = '' + child.stdout?.on('data', (data) => { + output += data.toString() + }) + + child.stdin?.write(JSON.stringify(request) + '\n') + + // Wait for response + await new Promise((resolve) => setTimeout(resolve, 2000)) + + child.kill() + + // Parse the JSON response + const lines = output.split('\n').filter((line) => line.trim()) + const responseLine = lines.find((line) => line.includes('"result"')) + + if (responseLine) { + const response = JSON.parse(responseLine) + expect(response.result).toBeDefined() + expect(response.result.tools).toBeInstanceOf(Array) + expect(response.result.tools.length).toBeGreaterThan(0) + + // Check for some expected tools + const toolNames = response.result.tools.map((tool: any) => tool.name) + expect(toolNames).toContain('list_tokens') + expect(toolNames).toContain('list_semantic_tokens') + expect(toolNames).toContain('list_breakpoints') + expect(toolNames).toContain('list_keyframes_n_animations') + } + }) + + test('should call list_tokens tool', async () => { + const result = await callMcpTool('list_tokens') + + expect(result).toBeInstanceOf(Array) + expect(result.length).toBeGreaterThan(0) + // Check for some common tokens that should be present + const hasColorTokens = result.some((token: string) => token.startsWith('colors.')) + const hasSpacingTokens = result.some((token: string) => token.startsWith('spacing.')) + expect(hasColorTokens).toBe(true) + expect(hasSpacingTokens || result.some((token: string) => token.startsWith('aspectRatios.'))).toBe(true) + }) + + test('should filter tokens by category', async () => { + const result = await callMcpTool('list_tokens', { category: 'colors' }) + + expect(result).toBeInstanceOf(Array) + expect(result.length).toBeGreaterThan(0) + // When category is specified, tokens should NOT have the category prefix + expect(result.every((token: string) => !token.startsWith('colors.'))).toBe(true) + // Should contain some color token names without prefix + expect(result.some((token: string) => token.includes('red') || token.includes('blue'))).toBe(true) + }) + + test('should call list_semantic_tokens tool', async () => { + const result = await callMcpTool('list_semantic_tokens') + + console.log('Semantic tokens found:', result.length) + expect(result).toBeInstanceOf(Array) + // The result might be empty if no semantic tokens are found in the default config + }) + + test('should filter semantic tokens by category', async () => { + const result = await callMcpTool('list_semantic_tokens', { category: 'colors' }) + + console.log('Semantic tokens in colors category:', result.length) + expect(result).toBeInstanceOf(Array) + // The result might be empty if no semantic tokens are found in the specified category + }) + + test('should call list_breakpoints tool', async () => { + const result = await callMcpTool('list_breakpoints') + expect(result).toMatchInlineSnapshot(` + [ + "base", + "sm", + "md", + "lg", + "xl", + "2xl", + ] + `) + }) + + test('should call list_keyframes_n_animations for all', async () => { + const result = await callMcpTool('list_keyframes_n_animations', { type: 'all' }) + + expect(result).toMatchInlineSnapshot(` + { + "animations": [ + "spin", + "ping", + "pulse", + "bounce", + ], + "keyframes": [ + "spin", + "ping", + "pulse", + "bounce", + ], + } + `) + }) + + test('should filter keyframes only', async () => { + const result = await callMcpTool('list_keyframes_n_animations', { type: 'keyframes' }) + + expect(result).toMatchInlineSnapshot(` + { + "animations": [], + "keyframes": [ + "spin", + "ping", + "pulse", + "bounce", + ], + } + `) + }) + + test('should filter animations only', async () => { + const result = await callMcpTool('list_keyframes_n_animations', { type: 'animations' }) + + expect(result).toMatchInlineSnapshot(` + { + "animations": [ + "spin", + "ping", + "pulse", + "bounce", + ], + "keyframes": [], + } + `) + }) + + test('should get full config', async () => { + const result = await callMcpTool('get_config') + + expect(result).toBeDefined() + expect(result.theme).toBeDefined() + expect(result.conditions).toBeDefined() + expect(result.theme.breakpoints).toMatchInlineSnapshot(` + { + "2xl": "1536px", + "lg": "1024px", + "md": "768px", + "sm": "640px", + "xl": "1280px", + } + `) + }) + + test('should get specific config section', async () => { + const result = await callMcpTool('get_config', { section: 'theme' }) + + expect(result.breakpoints).toMatchInlineSnapshot(` + { + "2xl": "1536px", + "lg": "1024px", + "md": "768px", + "sm": "640px", + "xl": "1280px", + } + `) + // The theme should have tokens and keyframes (after merging extend) + expect(result.tokens).toBeDefined() + expect(result.keyframes).toBeDefined() + }) + + test('should list all recipes', async () => { + const result = await callMcpTool('list_recipes') + + expect(result).toMatchInlineSnapshot(` + [ + "button", + "card", + "alert", + ] + `) + }) + + test('should get specific recipe details', async () => { + const result = await callMcpTool('get_recipe', { recipeName: 'button' }) + + expect(result).toMatchInlineSnapshot(` + { + "className": "button", + "defaultVariants": { + "size": "md", + "variant": "solid", + }, + "description": "Button styles for the application", + "name": "button", + "type": "recipe", + "usage": "import { button } from 'styled-system/recipes' + + function App() { + return ( +
+ + +
+ ) + }", + "variants": { + "size": [ + "sm", + "md", + "lg", + ], + "variant": [ + "solid", + "outline", + "ghost", + ], + }, + } + `) + }) + + test('should get slot recipe details', async () => { + const result = await callMcpTool('get_recipe', { recipeName: 'alert' }) + + expect(result.type).toBe('slotRecipe') + expect(result.slots).toEqual(['root', 'icon', 'content']) + expect(result.variants).toMatchInlineSnapshot(` + { + "status": [ + "success", + "error", + ], + } + `) + }) + + test('should return error for non-existent recipe', async () => { + const result = await callMcpTool('get_recipe', { recipeName: 'nonexistent' }) + + expect(result.error).toBeDefined() + expect(result.error).toContain('not found') + }) + + test('should list composition styles', async () => { + const result = await callMcpTool('list_composition_styles') + + expect(result).toMatchInlineSnapshot(` + [ + "2xl", + "3xl", + "4xl", + "5xl", + "6xl", + "7xl", + "8xl", + "9xl", + "lg", + "md", + "sm", + "xl", + "xs", + ] + `) + }) + + test('should get conditions config section', async () => { + const result = await callMcpTool('get_config', { section: 'conditions' }) + + expect(result).toMatchInlineSnapshot(` + { + "active": "&:is(:active, [data-active])", + "after": "&::after", + "atValue": "&[data-state=at-value]", + "autofill": "&:autofill", + "backdrop": "&::backdrop", + "before": "&::before", + "checked": "&:is(:checked, [data-checked], [aria-checked=true], [data-state="checked"])", + "closed": "&:is([closed], [data-closed], [data-state="closed"])", + "complete": "&[data-complete]", + "current": "&:is([aria-current=true], [data-current])", + "currentPage": "&[aria-current=page]", + "currentStep": "&[aria-current=step]", + "dark": ".dark &", + "default": "&:default", + "disabled": "&:is(:disabled, [disabled], [data-disabled], [aria-disabled=true])", + "dragging": "&[data-dragging]", + "empty": "&:is(:empty, [data-empty])", + "enabled": "&:enabled", + "even": "&:nth-child(even)", + "expanded": "&:is([aria-expanded=true], [data-expanded], [data-state="expanded"])", + "file": "&::file-selector-button", + "first": "&:first-child", + "firstLetter": "&::first-letter", + "firstLine": "&::first-line", + "firstOfType": "&:first-of-type", + "focus": "&:is(:focus, [data-focus])", + "focusVisible": "&:is(:focus-visible, [data-focus-visible])", + "focusWithin": "&:focus-within", + "fullscreen": "&:is(:fullscreen, [data-fullscreen])", + "grabbed": "&:is([aria-grabbed=true], [data-grabbed])", + "groupActive": ".group:is(:active, [data-active]) &", + "groupChecked": ".group:is(:checked, [data-checked], [aria-checked=true], [data-state="checked"]) &", + "groupDisabled": ".group:is(:disabled, [disabled], [data-disabled], [aria-disabled=true]) &", + "groupExpanded": ".group:is([aria-expanded=true], [data-expanded], [data-state="expanded"]) &", + "groupFocus": ".group:is(:focus, [data-focus]) &", + "groupFocusVisible": ".group:is(:focus-visible, [data-focus-visible]) &", + "groupFocusWithin": ".group:focus-within &", + "groupHover": ".group:is(:hover, [data-hover]) &", + "groupInvalid": ".group:is(:invalid, [data-invalid], [aria-invalid=true]) &", + "hidden": "&:is([hidden], [data-hidden])", + "highContrast": "@media (forced-colors: active)", + "highlighted": "&[data-highlighted]", + "horizontal": "&[data-orientation=horizontal]", + "hover": "&:is(:hover, [data-hover])", + "icon": "& :where(svg)", + "inRange": "&:is(:in-range, [data-in-range])", + "incomplete": "&[data-incomplete]", + "indeterminate": "&:is(:indeterminate, [data-indeterminate], [aria-checked=mixed], [data-state="indeterminate"])", + "invalid": "&:is(:invalid, [data-invalid], [aria-invalid=true])", + "invertedColors": "@media (inverted-colors: inverted)", + "landscape": "@media (orientation: landscape)", + "last": "&:last-child", + "lastOfType": "&:last-of-type", + "lessContrast": "@media (prefers-contrast: less)", + "light": ".light &", + "loading": "&:is([data-loading], [aria-busy=true])", + "ltr": ":where([dir=ltr], :dir(ltr)) &", + "marker": "&:is(::marker, ::-webkit-details-marker)", + "moreContrast": "@media (prefers-contrast: more)", + "motionReduce": "@media (prefers-reduced-motion: reduce)", + "motionSafe": "@media (prefers-reduced-motion: no-preference)", + "noscript": "@media (scripting: none)", + "now": "&[data-now]", + "odd": "&:nth-child(odd)", + "only": "&:only-child", + "onlyOfType": "&:only-of-type", + "open": "&:is([open], [data-open], [data-state="open"], :popover-open)", + "optional": "&:optional", + "osDark": "@media (prefers-color-scheme: dark)", + "osLight": "@media (prefers-color-scheme: light)", + "outOfRange": "&:is(:out-of-range, [data-outside-range])", + "overValue": "&[data-state=over-value]", + "peerActive": ".peer:is(:active, [data-active]) ~ &", + "peerChecked": ".peer:is(:checked, [data-checked], [aria-checked=true], [data-state="checked"]) ~ &", + "peerDisabled": ".peer:is(:disabled, [disabled], [data-disabled], [aria-disabled=true]) ~ &", + "peerExpanded": ".peer:is([aria-expanded=true], [data-expanded], [data-state="expanded"]) ~ &", + "peerFocus": ".peer:is(:focus, [data-focus]) ~ &", + "peerFocusVisible": ".peer:is(:focus-visible, [data-focus-visible]) ~ &", + "peerFocusWithin": ".peer:focus-within ~ &", + "peerHover": ".peer:is(:hover, [data-hover]) ~ &", + "peerInvalid": ".peer:is(:invalid, [data-invalid], [aria-invalid=true]) ~ &", + "peerPlaceholderShown": ".peer:placeholder-shown ~ &", + "placeholder": "&::placeholder, &[data-placeholder]", + "placeholderShown": "&:is(:placeholder-shown, [data-placeholder-shown])", + "portrait": "@media (orientation: portrait)", + "pressed": "&:is([aria-pressed=true], [data-pressed])", + "print": "@media print", + "rangeEnd": "&[data-range-end]", + "rangeStart": "&[data-range-start]", + "readOnly": "&:is(:read-only, [data-read-only], [aria-readonly=true])", + "readWrite": "&:read-write", + "required": "&:is(:required, [data-required], [aria-required=true])", + "rtl": ":where([dir=rtl], :dir(rtl)) &", + "scrollbar": "&::-webkit-scrollbar", + "scrollbarThumb": "&::-webkit-scrollbar-thumb", + "scrollbarTrack": "&::-webkit-scrollbar-track", + "selected": "&:is([aria-selected=true], [data-selected])", + "selection": "&::selection", + "starting": "@starting-style", + "target": "&:target", + "today": "&[data-today]", + "topmost": "&[data-topmost]", + "unavailable": "&[data-unavailable]", + "underValue": "&[data-state=under-value]", + "valid": "&:is(:valid, [data-valid])", + "vertical": "&[data-orientation=vertical]", + "visited": "&:visited", + } + `) + }) +}) diff --git a/packages/cli/__tests__/samples/.gitkeep b/packages/cli/__tests__/samples/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/cli/package.json b/packages/cli/package.json index f1ff304f4c..f8f3dd740e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -43,8 +43,8 @@ "access": "public" }, "scripts": { - "build": "tsup src --format=esm,cjs --dts --no-splitting --shims", - "build-fast": "tsup src --format=esm,cjs --no-dts --no-splitting --shims", + "build": "tsup --dts", + "build-fast": "tsup", "dev": "pnpm build-fast --watch src" }, "files": [ @@ -57,6 +57,7 @@ ], "dependencies": { "@clack/prompts": "0.9.1", + "@modelcontextprotocol/sdk": "^0.6.0", "@pandacss/config": "workspace:*", "@pandacss/logger": "workspace:*", "@pandacss/node": "workspace:*", @@ -65,7 +66,8 @@ "@pandacss/shared": "workspace:*", "@pandacss/token-dictionary": "workspace:*", "@pandacss/types": "workspace:*", - "cac": "6.7.14" + "cac": "6.7.14", + "zod": "^3.22.4" }, "devDependencies": { "@types/update-notifier": "6.0.8", diff --git a/packages/cli/src/cli-main.ts b/packages/cli/src/cli-main.ts index ee8ab1f25d..bfe699dc61 100644 --- a/packages/cli/src/cli-main.ts +++ b/packages/cli/src/cli-main.ts @@ -30,6 +30,7 @@ import type { EmitPackageCommandFlags, InitCommandFlags, MainCommandFlags, + McpCommandFlags, ShipCommandFlags, StudioCommandFlags, } from './types' @@ -604,6 +605,15 @@ export async function main() { logger.info('cli', `Emit package.json to ${pkgPath}`) }) + cli + .command('mcp', 'Start MCP server for AI assistants') + .option('-c, --config ', 'Path to panda config file') + .option('--cwd ', 'Current working directory', { default: cwd }) + .action(async (mcpFlags: McpCommandFlags) => { + const { startMcpServer } = await import('./mcp') + await startMcpServer(mcpFlags) + }) + cli.help() cli.version(version) diff --git a/packages/cli/src/mcp/index.ts b/packages/cli/src/mcp/index.ts new file mode 100644 index 0000000000..5978ef278f --- /dev/null +++ b/packages/cli/src/mcp/index.ts @@ -0,0 +1,50 @@ +import { logger } from '@pandacss/logger' +import { resolve } from 'path' +import { PandaMcpServer, type McpServerOptions } from './server' + +export interface StartMcpServerOptions { + cwd?: string + config?: string + silent?: boolean + logfile?: string +} + +export async function startMcpServer(options: StartMcpServerOptions = {}) { + const { cwd = process.cwd(), config: configPath, silent = false } = options + + if (silent) { + logger.level = 'silent' + } + + // Resolve paths + const resolvedCwd = resolve(cwd) + const resolvedConfigPath = configPath ? resolve(configPath) : undefined + + logger.info('mcp', 'Starting Panda CSS MCP server...') + + const serverOptions: McpServerOptions = { + cwd: resolvedCwd, + configPath: resolvedConfigPath, + } + + const server = new PandaMcpServer(serverOptions) + + // Always use stdio transport (MCP standard) + await server.start() + + // Handle process termination + process.on('SIGINT', async () => { + logger.info('mcp', 'Received SIGINT, shutting down MCP server...') + await server.stop() + process.exit(0) + }) + + process.on('SIGTERM', async () => { + logger.info('mcp', 'Received SIGTERM, shutting down MCP server...') + await server.stop() + process.exit(0) + }) +} + +// Export types and server class for external use +export { PandaMcpServer, type McpServerOptions } diff --git a/packages/cli/src/mcp/load-config.ts b/packages/cli/src/mcp/load-config.ts new file mode 100644 index 0000000000..b9e0c7f8f2 --- /dev/null +++ b/packages/cli/src/mcp/load-config.ts @@ -0,0 +1,50 @@ +import { loadConfigAndCreateContext } from '@pandacss/node' +import type { PandaContext } from '@pandacss/node' +import { resolve } from 'path' + +interface LoadConfigOptions { + cwd?: string + configPath?: string +} + +let cachedContext: PandaContext | null = null +let cacheKey: string | null = null +let globalOptions: LoadConfigOptions = {} + +export function setGlobalConfigOptions(options: LoadConfigOptions): void { + globalOptions = options +} + +export async function loadPandaContext(options: LoadConfigOptions = {}): Promise { + // Merge with global options + const finalOptions = { ...globalOptions, ...options } + const { cwd = process.cwd(), configPath } = finalOptions + + const currentCacheKey = `${cwd}:${configPath || 'default'}` + + // Return cached context if available and cache key matches + if (cachedContext && cacheKey === currentCacheKey) { + return cachedContext + } + + try { + const resolvedCwd = resolve(cwd) + const ctx = await loadConfigAndCreateContext({ + cwd: resolvedCwd, + configPath, + }) + + // Cache the context + cachedContext = ctx + cacheKey = currentCacheKey + + return ctx + } catch (error) { + throw new Error(`Failed to load Panda config: ${error instanceof Error ? error.message : 'Unknown error'}`) + } +} + +export function clearConfigCache(): void { + cachedContext = null + cacheKey = null +} diff --git a/packages/cli/src/mcp/server.ts b/packages/cli/src/mcp/server.ts new file mode 100644 index 0000000000..cf02f23c83 --- /dev/null +++ b/packages/cli/src/mcp/server.ts @@ -0,0 +1,330 @@ +import { Server } from '@modelcontextprotocol/sdk/server/index.js' +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' +import { CallToolRequestSchema, ListToolsRequestSchema, type CallToolRequest } from '@modelcontextprotocol/sdk/types.js' +import { logger } from '@pandacss/logger' +import { + getCompositions, + getRecipe, + getRecipeProps, + getRecipes, + getConfig, + listBreakpoints, + listKeyframesAndAnimations, + listLayerStyles, + listRecipes, + listSemanticTokens, + listCompositionStyles, + listTextStyles, + listTokens, +} from './tools' +import { clearConfigCache, setGlobalConfigOptions } from './load-config' + +export interface McpServerOptions { + cwd?: string + configPath?: string +} + +export class PandaMcpServer { + private server: Server + private options: McpServerOptions + + constructor(options: McpServerOptions = {}) { + this.server = new Server( + { + name: '@pandacss/dev', + version: '1.0.0', + }, + { + capabilities: { + tools: { + listChanged: true, + }, + }, + }, + ) + + this.options = options + this.setupToolHandlers() + } + + private setupToolHandlers() { + // List available tools + this.server.setRequestHandler(ListToolsRequestSchema, async () => { + const tools = [ + // Token tools + { + name: 'tokens', + description: 'List all design tokens with filtering options', + inputSchema: { + type: 'object', + properties: { + category: { type: 'string', description: 'Filter by token category' }, + search: { type: 'string', description: 'Search token names' }, + includeSemanticTokens: { type: 'boolean', description: 'Include semantic tokens' }, + }, + }, + }, + { + name: 'list_tokens', + description: + 'List regular design tokens (excludes semantic tokens) as simple strings, optionally filtered by category', + inputSchema: { + type: 'object', + properties: { + category: { + type: 'string', + description: + 'Filter by token category (colors, spacing, sizes, fonts, fontSizes, fontWeights, lineHeights, letterSpacings, shadows, radii, borders, durations, easings, animations, blurs, gradients, assets, borderWidths, aspectRatios, containerNames, zIndex, opacity, cursor)', + }, + }, + }, + }, + { + name: 'list_semantic_tokens', + description: 'List semantic tokens (tokens with conditions like responsive, color modes) as simple strings', + inputSchema: { + type: 'object', + properties: { + category: { + type: 'string', + description: + 'Filter by token category (colors, spacing, sizes, fonts, fontSizes, fontWeights, lineHeights, letterSpacings, shadows, radii, borders, durations, easings, animations, blurs, gradients, assets, borderWidths, aspectRatios, containerNames, zIndex, opacity, cursor)', + }, + }, + }, + }, + + // Style composition tools + { + name: 'list_composition_styles', + description: 'List reusable style compositions that help reduce CSS duplication', + inputSchema: { + type: 'object', + properties: { + type: { + type: 'string', + enum: ['all', 'textStyles', 'layerStyles', 'animationStyles'], + description: 'Filter by style composition type', + }, + }, + }, + }, + { + name: 'compositions', + description: 'Get all style compositions (textStyles and layerStyles)', + inputSchema: { + type: 'object', + properties: { + type: { type: 'string', enum: ['textStyles', 'layerStyles'], description: 'Type of composition to get' }, + }, + }, + }, + { + name: 'list_textStyle', + description: 'List text style compositions', + inputSchema: { + type: 'object', + properties: { + search: { type: 'string', description: 'Search text style names' }, + }, + }, + }, + { + name: 'list_layerStyle', + description: 'List layer style compositions', + inputSchema: { + type: 'object', + properties: { + search: { type: 'string', description: 'Search layer style names' }, + }, + }, + }, + + // Responsive and animation tools + { + name: 'list_breakpoints', + description: 'List responsive breakpoints', + inputSchema: { + type: 'object', + properties: { + format: { type: 'string', enum: ['raw', 'css'], description: 'Output format for breakpoints' }, + }, + }, + }, + { + name: 'list_keyframes_n_animations', + description: 'List keyframes and animations', + inputSchema: { + type: 'object', + properties: { + type: { + type: 'string', + enum: ['keyframes', 'animations', 'all'], + description: 'Filter by animation type', + }, + }, + }, + }, + + // Recipe tools + { + name: 'recipes', + description: 'Get all recipes with configuration', + inputSchema: { + type: 'object', + properties: { + format: { type: 'string', enum: ['summary', 'detailed'], description: 'Level of detail in output' }, + }, + }, + }, + { + name: 'list_recipes', + description: 'List all recipe names', + inputSchema: { + type: 'object', + properties: {}, + }, + }, + { + name: 'get_recipe', + description: 'Get detailed information about a specific recipe', + inputSchema: { + type: 'object', + properties: { + recipeName: { type: 'string', description: 'The recipe name to get details for' }, + }, + required: ['recipeName'], + }, + }, + { + name: 'get_recipe_props', + description: 'Get properties and variants for a specific recipe', + inputSchema: { + type: 'object', + properties: { + recipeName: { type: 'string', description: 'Name of the recipe to analyze' }, + }, + required: ['recipeName'], + }, + }, + + // Config tools + { + name: 'get_config', + description: 'Get resolved Panda configuration', + inputSchema: { + type: 'object', + properties: { + section: { + type: 'string', + enum: ['all', 'theme', 'conditions', 'utilities', 'patterns', 'recipes', 'globalCss', 'staticCss'], + description: 'Specific config section to return', + }, + }, + }, + }, + ] + + logger.info('mcp', `Registering ${tools.length} tools`) + return { tools } + }) + + // Handle tool calls + this.server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => { + try { + const { name, arguments: args = {} } = request.params + + // Set global config options for all tools to use + setGlobalConfigOptions({ + cwd: this.options.cwd, + configPath: this.options.configPath, + }) + + switch (name) { + // Token tools + case 'tokens': + case 'list_tokens': + return { content: [{ type: 'text', text: JSON.stringify(await listTokens(args), null, 2) }] } + case 'list_semantic_tokens': + return { content: [{ type: 'text', text: JSON.stringify(await listSemanticTokens(args), null, 2) }] } + + // Style composition tools + case 'list_composition_styles': + return { content: [{ type: 'text', text: JSON.stringify(await listCompositionStyles(args), null, 2) }] } + case 'compositions': + return { content: [{ type: 'text', text: JSON.stringify(await getCompositions(args), null, 2) }] } + case 'list_textStyle': + return { content: [{ type: 'text', text: JSON.stringify(await listTextStyles(args), null, 2) }] } + case 'list_layerStyle': + return { content: [{ type: 'text', text: JSON.stringify(await listLayerStyles(args), null, 2) }] } + + // Responsive and animation tools + case 'list_breakpoints': + return { content: [{ type: 'text', text: JSON.stringify(await listBreakpoints(), null, 2) }] } + case 'list_keyframes_n_animations': + return { + content: [{ type: 'text', text: JSON.stringify(await listKeyframesAndAnimations(args), null, 2) }], + } + + // Recipe tools + case 'recipes': + return { content: [{ type: 'text', text: JSON.stringify(await getRecipes(args), null, 2) }] } + case 'list_recipes': + return { content: [{ type: 'text', text: JSON.stringify(await listRecipes(args), null, 2) }] } + case 'get_recipe': + return { content: [{ type: 'text', text: JSON.stringify(await getRecipe(args as any), null, 2) }] } + case 'get_recipe_props': + return { content: [{ type: 'text', text: JSON.stringify(await getRecipeProps(args as any), null, 2) }] } + + // Config tools + case 'get_config': + return { content: [{ type: 'text', text: JSON.stringify(await getConfig(args), null, 2) }] } + + default: + throw new Error(`Unknown tool: ${name}`) + } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error' + logger.error('mcp:tool-call', `Tool ${request.params.name} failed: ${errorMessage}`) + + return { + content: [ + { + type: 'text', + text: JSON.stringify( + { + error: errorMessage, + tool: request.params.name, + timestamp: new Date().toISOString(), + }, + null, + 2, + ), + }, + ], + isError: true, + } + } + }) + } + + async start() { + // Clear any cached config when starting + clearConfigCache() + + const transport = new StdioServerTransport() + await this.server.connect(transport) + + logger.info('mcp', `Panda CSS MCP server started`) + logger.info('mcp', `Working directory: ${this.options.cwd || process.cwd()}`) + if (this.options.configPath) { + logger.info('mcp', `Config path: ${this.options.configPath}`) + } + } + + async stop() { + clearConfigCache() + // Note: MCP SDK doesn't have an explicit stop method + logger.info('mcp', 'Panda CSS MCP server stopped') + } +} diff --git a/packages/cli/src/mcp/tools/animations.ts b/packages/cli/src/mcp/tools/animations.ts new file mode 100644 index 0000000000..9dae6987da --- /dev/null +++ b/packages/cli/src/mcp/tools/animations.ts @@ -0,0 +1,37 @@ +import { z } from 'zod' +import { loadPandaContext } from '../load-config' + +// Schema definitions +export const listKeyframesAndAnimationsSchema = z.object({ + type: z.enum(['keyframes', 'animations', 'all']).optional().describe('Filter by animation type'), +}) + +// Tool implementations +export async function listBreakpoints(): Promise { + const ctx = await loadPandaContext() + return ctx.conditions.breakpoints.keys +} + +export async function listKeyframesAndAnimations( + args: z.infer, +): Promise<{ keyframes: string[]; animations: string[] }> { + const ctx = await loadPandaContext() + const { type = 'all' } = args + + const { config } = ctx.conf + + const keyframes = config.theme?.keyframes || {} + const animations = config?.theme?.tokens?.animations || {} + + const result: { keyframes: string[]; animations: string[] } = { keyframes: [], animations: [] } + + if (type === 'keyframes' || type === 'all') { + result.keyframes.push(...Object.keys(keyframes)) + } + + if (type === 'animations' || type === 'all') { + result.animations.push(...Object.keys(animations)) + } + + return result +} diff --git a/packages/cli/src/mcp/tools/compositions.ts b/packages/cli/src/mcp/tools/compositions.ts new file mode 100644 index 0000000000..cce970a5e5 --- /dev/null +++ b/packages/cli/src/mcp/tools/compositions.ts @@ -0,0 +1,198 @@ +import { z } from 'zod' +import { loadPandaContext } from '../load-config' +import { flatten } from '@pandacss/shared' + +// Schema definitions +export const listCompositionStylesSchema = z.object({ + type: z + .enum(['all', 'textStyles', 'layerStyles', 'animationStyles']) + .optional() + .describe('Filter by style composition type'), +}) + +export const listTextStyleSchema = z.object({ + search: z.string().optional().describe('Search text style names'), +}) + +export const listLayerStyleSchema = z.object({ + search: z.string().optional().describe('Search layer style names'), +}) + +export const compositionsSchema = z.object({ + type: z.enum(['textStyles', 'layerStyles', 'all']).optional().describe('Filter by composition type'), +}) + +// Tool implementations +export async function listCompositionStyles(args: z.infer): Promise { + const ctx = await loadPandaContext() + const { type = 'all' } = args + + const result: string[] = [] + + const textStyles = ctx.config.theme?.textStyles || {} + const layerStyles = ctx.config.theme?.layerStyles || {} + const animationStyles = ctx.config.theme?.animationStyles || {} + + if (type === 'textStyles' || type === 'all') { + // Use flatten like the core system does to get proper composition keys + const flatTextStyles = flatten(textStyles) + result.push(...Object.keys(flatTextStyles)) + } + + if (type === 'layerStyles' || type === 'all') { + // Use flatten like the core system does to get proper composition keys + const flatLayerStyles = flatten(layerStyles) + result.push(...Object.keys(flatLayerStyles)) + } + + if (type === 'animationStyles' || type === 'all') { + // Use flatten like the core system does to get proper composition keys + const flatAnimationStyles = flatten(animationStyles) + result.push(...Object.keys(flatAnimationStyles)) + } + + return result.sort() +} + +export async function listTextStyles(args: z.infer): Promise { + const ctx = await loadPandaContext() + const { search } = args + + const textStyles = ctx.config.theme?.textStyles || {} + const flatTextStyles = flatten(textStyles) + const styles = [] + + for (const [name, style] of Object.entries(flatTextStyles)) { + // Apply search filter + if (search && !name.toLowerCase().includes(search.toLowerCase())) continue + + styles.push({ + name, + style, + properties: typeof style === 'object' && style ? Object.keys(style) : [], + description: (style as any)?.description, + usage: `textStyle="${name}"`, + cssProperties: getCssProperties(style), + }) + } + + return { + textStyles: styles, + count: styles.length, + totalTextStyles: Object.keys(flatTextStyles).length, + description: 'Text styles are reusable typography compositions that help reduce CSS duplication', + } +} + +export async function listLayerStyles(args: z.infer): Promise { + const ctx = await loadPandaContext() + const { search } = args + + const layerStyles = ctx.config.theme?.layerStyles || {} + const flatLayerStyles = flatten(layerStyles) + const styles = [] + + for (const [name, style] of Object.entries(flatLayerStyles)) { + // Apply search filter + if (search && !name.toLowerCase().includes(search.toLowerCase())) continue + + styles.push({ + name, + style, + properties: typeof style === 'object' && style ? Object.keys(style) : [], + description: (style as any)?.description, + usage: `layerStyle="${name}"`, + cssProperties: getCssProperties(style), + }) + } + + return { + layerStyles: styles, + count: styles.length, + totalLayerStyles: Object.keys(flatLayerStyles).length, + description: 'Layer styles are reusable style compositions for common visual patterns', + } +} + +export async function getCompositions(args: z.infer): Promise { + const ctx = await loadPandaContext() + const { type = 'all' } = args + + const textStyles = ctx.config.theme?.textStyles || {} + const layerStyles = ctx.config.theme?.layerStyles || {} + const animationStyles = ctx.config.theme?.animationStyles || {} + + // Use flatten like the core system does + const flatTextStyles = flatten(textStyles) + const flatLayerStyles = flatten(layerStyles) + const flatAnimationStyles = flatten(animationStyles) + + const result: any = { + summary: { + textStyles: Object.keys(flatTextStyles).length, + layerStyles: Object.keys(flatLayerStyles).length, + animationStyles: Object.keys(flatAnimationStyles).length, + total: + Object.keys(flatTextStyles).length + + Object.keys(flatLayerStyles).length + + Object.keys(flatAnimationStyles).length, + }, + description: 'Style compositions are reusable style properties that help reduce the lines of CSS you write', + } + + if (type === 'textStyles' || type === 'all') { + result.textStyles = Object.entries(flatTextStyles).map(([name, style]) => ({ + name, + type: 'textStyle', + style, + properties: typeof style === 'object' && style ? Object.keys(style) : [], + cssProperties: getCssProperties(style), + usage: `textStyle="${name}"`, + description: 'Typography composition for consistent text styling', + })) + } + + if (type === 'layerStyles' || type === 'all') { + result.layerStyles = Object.entries(flatLayerStyles).map(([name, style]) => ({ + name, + type: 'layerStyle', + style, + properties: typeof style === 'object' && style ? Object.keys(style) : [], + cssProperties: getCssProperties(style), + usage: `layerStyle="${name}"`, + description: 'Visual composition for common layout patterns', + })) + } + + if (type === 'all') { + result.animationStyles = Object.entries(flatAnimationStyles).map(([name, style]) => ({ + name, + type: 'animationStyle', + style, + properties: typeof style === 'object' && style ? Object.keys(style) : [], + cssProperties: getCssProperties(style), + usage: `animationStyle="${name}"`, + description: 'Animation composition for consistent motion patterns', + })) + } + + return result +} + +// Helper function to extract CSS properties +function getCssProperties(style: any): string[] { + if (!style || typeof style !== 'object') return [] + + const cssProps = [] + + for (const [key, value] of Object.entries(style)) { + if (typeof value === 'string' || typeof value === 'number') { + cssProps.push(key) + } else if (typeof value === 'object' && value !== null) { + // Handle responsive or conditional values + cssProps.push(key) + } + } + + return cssProps +} diff --git a/packages/cli/src/mcp/tools/config.ts b/packages/cli/src/mcp/tools/config.ts new file mode 100644 index 0000000000..8475b9bb5c --- /dev/null +++ b/packages/cli/src/mcp/tools/config.ts @@ -0,0 +1,42 @@ +import { z } from 'zod' +import { loadPandaContext } from '../load-config' + +// Schema definitions +export const getConfigSchema = z.object({ + section: z + .enum(['all', 'theme', 'conditions', 'utilities', 'patterns', 'recipes', 'globalCss', 'staticCss']) + .optional() + .describe('Specific config property to return from the resolved panda.config file'), +}) + +// Tool implementation +export async function getConfig(args: z.infer): Promise { + const ctx = await loadPandaContext() + const { section = 'all' } = args + + const { config } = ctx.conf + + if (section === 'all') { + return config + } + + // Return specific section + switch (section) { + case 'theme': + return config.theme || {} + case 'conditions': + return config.conditions || {} + case 'utilities': + return config.utilities || {} + case 'patterns': + return config.patterns || {} + case 'recipes': + return ctx.recipes.config || {} + case 'globalCss': + return config.globalCss || {} + case 'staticCss': + return config.staticCss || {} + default: + return {} + } +} diff --git a/packages/cli/src/mcp/tools/index.ts b/packages/cli/src/mcp/tools/index.ts new file mode 100644 index 0000000000..40b53ff8af --- /dev/null +++ b/packages/cli/src/mcp/tools/index.ts @@ -0,0 +1,5 @@ +export * from './tokens' +export * from './compositions' +export * from './animations' +export * from './recipes' +export * from './config' diff --git a/packages/cli/src/mcp/tools/recipes.ts b/packages/cli/src/mcp/tools/recipes.ts new file mode 100644 index 0000000000..007b491f62 --- /dev/null +++ b/packages/cli/src/mcp/tools/recipes.ts @@ -0,0 +1,168 @@ +import { z } from 'zod' +import { loadPandaContext } from '../load-config' + +// Schema definitions +export const listRecipesSchema = z.object({}) + +export const getRecipeSchema = z.object({ + recipeName: z.string().describe('The recipe name to get details for'), +}) + +export const getRecipePropsSchema = z.object({ + recipeName: z.string().describe('The recipe name to get properties for'), +}) + +export const recipesSchema = z.object({ + includeConfig: z.boolean().optional().describe('Include recipe configuration details'), +}) + +// Tool implementations +export async function listRecipes(args: z.infer): Promise { + const ctx = await loadPandaContext() + const recipes = ctx.recipes as any + + try { + const recipeNames = recipes.getRecipeNames?.() || Object.keys(ctx.config.theme?.recipes || {}) + return recipeNames + } catch (error) { + return [] + } +} + +export async function getRecipe(args: z.infer): Promise { + const ctx = await loadPandaContext() + const { recipeName } = args + const recipes = ctx.recipes as any + + try { + const recipeConfig = recipes.getConfig?.(recipeName) || ctx.config.theme?.recipes?.[recipeName] + + if (!recipeConfig) { + return { + error: `Recipe "${recipeName}" not found`, + } + } + + // Determine if it's a slot recipe + const isSlotRecipe = + (recipeConfig as any).slots && + Array.isArray((recipeConfig as any).slots) && + (recipeConfig as any).slots.length > 0 + + // Extract variant keys + const variants: Record = {} + if ((recipeConfig as any).variants) { + for (const [variantName, variantValues] of Object.entries((recipeConfig as any).variants)) { + variants[variantName] = Object.keys(variantValues as any) + } + } + + // Generate usage example + const usage = `import { ${recipeName} } from 'styled-system/recipes' + +function App() { + return ( +
+ + +
+ ) +}` + + return { + type: isSlotRecipe ? 'slotRecipe' : 'recipe', + name: recipeName, + className: (recipeConfig as any)?.className || recipeName, + description: (recipeConfig as any).description || '', + variants, + defaultVariants: (recipeConfig as any).defaultVariants || {}, + usage, + ...(isSlotRecipe && { slots: (recipeConfig as any).slots }), + } + } catch (error) { + return { + error: error instanceof Error ? error.message : `Failed to get recipe "${recipeName}"`, + } + } +} + +export async function getRecipeProps(args: z.infer): Promise { + const ctx = await loadPandaContext() + const { recipeName } = args + + const recipes = ctx.recipes as any + + try { + const config = recipes.getConfig?.(recipeName) || ctx.config.theme?.recipes?.[recipeName] + + if (!config) { + return { + error: `Recipe "${recipeName}" not found`, + availableRecipes: recipes.getRecipeNames?.() || Object.keys(ctx.config.theme?.recipes || {}), + } + } + + const props = recipes.getRecipeProps?.(recipeName) || {} + + return { + recipe: recipeName, + props, + variants: (config as any)?.variants || {}, + defaultVariants: (config as any)?.defaultVariants || {}, + compoundVariants: (config as any)?.compoundVariants || [], + className: recipes.getClassName?.(recipeName, '', {}) || recipeName, + description: (config as any)?.description, + } + } catch (error) { + return { + error: `Could not access recipe information for "${recipeName}"`, + availableRecipes: [], + } + } +} + +export async function getRecipes(args: z.infer): Promise { + const ctx = await loadPandaContext() + const { includeConfig = false } = args + + const recipes = ctx.recipes as any + + try { + const recipeNames = recipes.getRecipeNames?.() || Object.keys(ctx.config.theme?.recipes || {}) + + const result = { + summary: { + total: recipeNames.length, + recipes: recipeNames, + }, + recipes: [] as any[], + } + + if (includeConfig) { + for (const recipeName of recipeNames) { + const config = recipes.getConfig?.(recipeName) || ctx.config.theme?.recipes?.[recipeName] + const props = recipes.getRecipeProps?.(recipeName) || {} + + result.recipes.push({ + name: recipeName, + className: recipes.getClassName?.(recipeName, '', {}) || recipeName, + config, + props, + variants: Object.keys((config as any)?.variants || {}), + variantCount: Object.keys((config as any)?.variants || {}).length, + }) + } + } + + return result + } catch (error) { + return { + summary: { + total: 0, + recipes: [], + }, + recipes: [], + error: 'Could not access recipe information', + } + } +} diff --git a/packages/cli/src/mcp/tools/tokens.ts b/packages/cli/src/mcp/tools/tokens.ts new file mode 100644 index 0000000000..fe78cb66e5 --- /dev/null +++ b/packages/cli/src/mcp/tools/tokens.ts @@ -0,0 +1,83 @@ +import { z } from 'zod' +import { loadPandaContext } from '../load-config' + +// Schema definitions + +export const listTokensSchema = z.object({ + category: z.string().optional().describe('Filter by token category (colors, spacing, sizes, fonts, etc.)'), +}) + +export const listSemanticTokensSchema = z.object({ + category: z.string().optional().describe('Filter by token category (colors, spacing, sizes, fonts, etc.)'), +}) + +// Tool implementations + +export async function listTokens(args: z.infer): Promise { + const ctx = await loadPandaContext() + const { category } = args + + // Get all semantic tokens efficiently using the view's conditionMap + const semanticTokens = new Set() + ctx.tokens.view.conditionMap.forEach((tokens, condition) => { + if (condition !== 'base') { + // Non-base conditions are semantic tokens + tokens.forEach((token) => semanticTokens.add(token.name)) + } + }) + + if (!category) { + // Return all regular token names (exclude semantic tokens) + return ctx.tokens.allTokens + .filter((token) => !semanticTokens.has(token.name) && token.extensions?.category !== 'semantic') + .map((token) => token.name) + } + + // Use the token dictionary's built-in category filtering + const categoryTokens = ctx.tokens.view.categoryMap.get(category as any) + if (!categoryTokens) { + return [] + } + + // Filter out semantic tokens and return names without category prefix + return Array.from(categoryTokens.values()) + .filter((token) => !semanticTokens.has(token.name) && token.extensions?.category !== 'semantic') + .map((token) => token.extensions.prop || token.name.substring(category.length + 1)) +} + +export async function listSemanticTokens(args: z.infer): Promise { + const ctx = await loadPandaContext() + const { category } = args + + // Get all semantic tokens efficiently using the view's conditionMap + const semanticTokens = new Set() + ctx.tokens.view.conditionMap.forEach((tokens, condition) => { + if (condition !== 'base') { + // Non-base conditions are semantic tokens + tokens.forEach((token) => semanticTokens.add(token.name)) + } + }) + + // Also include tokens with explicit semantic category + ctx.tokens.allTokens.forEach((token) => { + if (token.extensions?.category === 'semantic') { + semanticTokens.add(token.name) + } + }) + + if (!category) { + // Return all semantic token names + return Array.from(semanticTokens) + } + + // Use the token dictionary's built-in category filtering + const categoryTokens = ctx.tokens.view.categoryMap.get(category as any) + if (!categoryTokens) { + return [] + } + + // Filter for semantic tokens within the category and return without prefix + return Array.from(categoryTokens.values()) + .filter((token) => semanticTokens.has(token.name)) + .map((token) => token.extensions.prop || token.name.substring(category.length + 1)) +} diff --git a/packages/cli/src/types.ts b/packages/cli/src/types.ts index a26eb90dd4..395aa2d112 100644 --- a/packages/cli/src/types.ts +++ b/packages/cli/src/types.ts @@ -96,3 +96,8 @@ export interface EmitPackageCommandFlags { cwd: string base?: string } + +export interface McpCommandFlags { + cwd?: string + config?: string +} diff --git a/packages/cli/tsup.config.ts b/packages/cli/tsup.config.ts new file mode 100644 index 0000000000..06a6d102ca --- /dev/null +++ b/packages/cli/tsup.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'tsup' + +export default defineConfig({ + entry: ['src/cli-default.ts', 'src/index.ts', 'src/presets.ts'], + format: ['esm', 'cjs'], + splitting: false, + shims: true, + clean: true, + platform: 'node', + target: 'node18', +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f15f8fa1f5..84b6b0e446 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -112,6 +112,9 @@ importers: '@clack/prompts': specifier: 0.9.1 version: 0.9.1 + '@modelcontextprotocol/sdk': + specifier: ^0.6.0 + version: 0.6.1 '@pandacss/config': specifier: workspace:* version: link:../config @@ -139,6 +142,9 @@ importers: cac: specifier: 6.7.14 version: 6.7.14 + zod: + specifier: ^3.22.4 + version: 3.22.4 devDependencies: '@types/update-notifier': specifier: 6.0.8 @@ -903,7 +909,7 @@ importers: version: 1.15.0(vite@7.0.6(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0)) '@preact/preset-vite': specifier: 2.10.1 - version: 2.10.1(@babel/core@7.28.0)(preact@10.27.0)(vite@7.0.6(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0)) + version: 2.10.1(@babel/core@7.26.10)(preact@10.27.0)(vite@7.0.6(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0)) '@solidjs/testing-library': specifier: 0.8.10 version: 0.8.10(solid-js@1.9.7) @@ -992,7 +998,7 @@ importers: version: link:../css-lib nuxt: specifier: 3.16.0 - version: 3.16.0(@parcel/watcher@2.4.1)(@types/node@20.11.30)(db0@0.3.1)(eslint@8.56.0)(ioredis@5.6.0)(less@4.2.0)(lightningcss@1.25.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.36.0)(terser@5.44.0)(tsx@4.20.3)(typescript@5.8.3)(vite@7.0.6(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0))(yaml@2.7.0) + version: 3.16.0(@parcel/watcher@2.4.1)(@types/node@20.11.30)(db0@0.3.1)(eslint@8.56.0)(ioredis@5.6.0)(less@4.2.0)(lightningcss@1.25.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.36.0)(terser@5.44.0)(tsx@4.20.3)(typescript@5.8.3)(vite@6.3.5(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0))(yaml@2.7.0) postcss: specifier: 8.4.49 version: 8.4.49 @@ -4782,6 +4788,9 @@ packages: resolution: {integrity: sha512-iA7+tyVqfrATAIsIRWQG+a7ZLLD0VaOCKV2Wd/v4mqIU3J9c4jx9p7S0nw1XH3gJCKNBOOwACOPYYSUu9pgT+w==} engines: {node: '>=12.0.0'} + '@modelcontextprotocol/sdk@0.6.1': + resolution: {integrity: sha512-OkVXMix3EIbB5Z6yife2XTrSlOnVvCLR1Kg91I4pYFEsV9RbnoyQVScXCuVhGaZHOnTZgso8lMQN1Po2TadGKQ==} + '@monaco-editor/loader@1.4.0': resolution: {integrity: sha512-00ioBig0x642hytVspPl7DbQyaSWRaolYie/UFNjoTdvoKPzo6xrXLhTk9ixgIKcLH5b5vDOjVNiGyY+uDCUlg==} peerDependencies: @@ -11391,6 +11400,10 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} + iconv-lite@0.7.0: + resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} + engines: {node: '>=0.10.0'} + icss-utils@5.1.0: resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} engines: {node: ^10 || ^12 || >= 14} @@ -14912,6 +14925,10 @@ packages: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} + raw-body@3.0.1: + resolution: {integrity: sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==} + engines: {node: '>= 0.10'} + raw-loader@4.0.2: resolution: {integrity: sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==} engines: {node: '>= 10.13.0'} @@ -19354,6 +19371,13 @@ snapshots: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-transform-react-jsx-development@7.24.7(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.26.10) + transitivePeerDependencies: + - supports-color + '@babel/plugin-transform-react-jsx-development@7.24.7(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 @@ -22266,6 +22290,12 @@ snapshots: '@lezer/lr': 1.4.2 json5: 2.2.3 + '@modelcontextprotocol/sdk@0.6.1': + dependencies: + content-type: 1.0.5 + raw-body: 3.0.1 + zod: 3.24.2 + '@monaco-editor/loader@1.4.0(monaco-editor@0.46.0)': dependencies: monaco-editor: 0.46.0 @@ -22513,6 +22543,15 @@ snapshots: '@nuxt/devalue@2.0.2': {} + '@nuxt/devtools-kit@2.3.0(magicast@0.3.5)(vite@6.3.5(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0))': + dependencies: + '@nuxt/kit': 3.16.0(magicast@0.3.5) + '@nuxt/schema': 3.16.0 + execa: 9.5.2 + vite: 6.3.5(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0) + transitivePeerDependencies: + - magicast + '@nuxt/devtools-kit@2.3.0(magicast@0.3.5)(vite@7.0.6(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0))': dependencies: '@nuxt/kit': 3.16.0(magicast@0.3.5) @@ -22533,6 +22572,47 @@ snapshots: prompts: 2.4.2 semver: 7.7.2 + '@nuxt/devtools@2.3.0(vite@6.3.5(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0))(vue@3.5.18(typescript@5.8.3))': + dependencies: + '@nuxt/devtools-kit': 2.3.0(magicast@0.3.5)(vite@6.3.5(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0)) + '@nuxt/devtools-wizard': 2.3.0 + '@nuxt/kit': 3.16.0(magicast@0.3.5) + '@vue/devtools-core': 7.7.2(vite@6.3.5(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0))(vue@3.5.18(typescript@5.8.3)) + '@vue/devtools-kit': 7.7.2 + birpc: 2.2.0 + consola: 3.4.2 + destr: 2.0.3 + error-stack-parser-es: 1.0.5 + execa: 9.5.2 + fast-npm-meta: 0.3.1 + get-port-please: 3.1.2 + hookable: 5.5.3 + image-meta: 0.2.1 + is-installed-globally: 1.0.0 + launch-editor: 2.10.0 + local-pkg: 1.1.1 + magicast: 0.3.5 + nypm: 0.6.0 + ohash: 2.0.11 + pathe: 2.0.3 + perfect-debounce: 1.0.0 + pkg-types: 2.2.0 + semver: 7.7.2 + simple-git: 3.27.0 + sirv: 3.0.1 + structured-clone-es: 1.0.0 + tinyglobby: 0.2.14 + vite: 6.3.5(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0) + vite-plugin-inspect: 11.0.0(@nuxt/kit@3.16.0(magicast@0.3.5))(vite@6.3.5(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0)) + vite-plugin-vue-tracer: 0.1.1(vite@6.3.5(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0))(vue@3.5.18(typescript@5.8.3)) + which: 5.0.0 + ws: 8.18.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + - vue + '@nuxt/devtools@2.3.0(vite@7.0.6(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0))(vue@3.5.18(typescript@5.8.3))': dependencies: '@nuxt/devtools-kit': 2.3.0(magicast@0.3.5)(vite@7.0.6(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0)) @@ -23130,6 +23210,22 @@ snapshots: '@poppinss/exception@1.2.1': {} + '@preact/preset-vite@2.10.1(@babel/core@7.26.10)(preact@10.27.0)(vite@7.0.6(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0))': + dependencies: + '@babel/core': 7.26.10 + '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-react-jsx-development': 7.24.7(@babel/core@7.26.10) + '@prefresh/vite': 2.4.6(preact@10.27.0)(vite@7.0.6(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0)) + '@rollup/pluginutils': 4.2.1 + babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.26.10) + debug: 4.4.0(supports-color@9.4.0) + kolorist: 1.8.0 + vite: 7.0.6(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0) + vite-prerender-plugin: 0.5.5 + transitivePeerDependencies: + - preact + - supports-color + '@preact/preset-vite@2.10.1(@babel/core@7.28.0)(preact@10.27.0)(vite@7.0.6(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0))': dependencies: '@babel/core': 7.28.0 @@ -25972,6 +26068,18 @@ snapshots: '@vue/devtools-api@6.6.4': {} + '@vue/devtools-core@7.7.2(vite@6.3.5(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0))(vue@3.5.18(typescript@5.8.3))': + dependencies: + '@vue/devtools-kit': 7.7.2 + '@vue/devtools-shared': 7.7.2 + mitt: 3.0.1 + nanoid: 5.1.5 + pathe: 2.0.3 + vite-hot-client: 0.2.4(vite@6.3.5(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0)) + vue: 3.5.18(typescript@5.8.3) + transitivePeerDependencies: + - vite + '@vue/devtools-core@7.7.2(vite@7.0.6(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0))(vue@3.5.18(typescript@5.8.3))': dependencies: '@vue/devtools-kit': 7.7.2 @@ -27283,6 +27391,10 @@ snapshots: babel-plugin-syntax-trailing-function-commas@7.0.0-beta.0: {} + babel-plugin-transform-hook-names@1.0.2(@babel/core@7.26.10): + dependencies: + '@babel/core': 7.26.10 + babel-plugin-transform-hook-names@1.0.2(@babel/core@7.28.0): dependencies: '@babel/core': 7.28.0 @@ -29483,7 +29595,7 @@ snapshots: debug: 4.3.6 enhanced-resolve: 5.17.1 eslint: 8.56.0 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.56.0) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.56.0))(eslint@8.56.0) fast-glob: 3.3.3 get-tsconfig: 4.7.6 is-bun-module: 1.1.0 @@ -29502,7 +29614,7 @@ snapshots: debug: 4.3.6 enhanced-resolve: 5.17.1 eslint: 8.56.0 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.0.1(eslint@8.56.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.56.0) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.0.1(eslint@8.56.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.0.1(eslint@8.56.0)(typescript@5.8.3))(eslint-plugin-import@2.29.1)(eslint@8.56.0))(eslint@8.56.0) fast-glob: 3.3.3 get-tsconfig: 4.7.6 is-bun-module: 1.1.0 @@ -29525,7 +29637,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.56.0): + eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.56.0))(eslint@8.56.0): dependencies: debug: 3.2.7 optionalDependencies: @@ -29536,7 +29648,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@7.0.1(eslint@8.56.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.56.0): + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.0.1(eslint@8.56.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.0.1(eslint@8.56.0)(typescript@5.8.3))(eslint-plugin-import@2.29.1)(eslint@8.56.0))(eslint@8.56.0): dependencies: debug: 3.2.7 optionalDependencies: @@ -29590,7 +29702,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.56.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.0.1(eslint@8.56.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.56.0) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.0.1(eslint@8.56.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.0.1(eslint@8.56.0)(typescript@5.8.3))(eslint-plugin-import@2.29.1)(eslint@8.56.0))(eslint@8.56.0) hasown: 2.0.2 is-core-module: 2.15.0 is-glob: 4.0.3 @@ -31522,6 +31634,10 @@ snapshots: dependencies: safer-buffer: 2.1.2 + iconv-lite@0.7.0: + dependencies: + safer-buffer: 2.1.2 + icss-utils@5.1.0(postcss@8.4.49): dependencies: postcss: 8.4.49 @@ -33988,11 +34104,11 @@ snapshots: nullthrows@1.1.1: {} - nuxt@3.16.0(@parcel/watcher@2.4.1)(@types/node@20.11.30)(db0@0.3.1)(eslint@8.56.0)(ioredis@5.6.0)(less@4.2.0)(lightningcss@1.25.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.36.0)(terser@5.44.0)(tsx@4.20.3)(typescript@5.8.3)(vite@7.0.6(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0))(yaml@2.7.0): + nuxt@3.16.0(@parcel/watcher@2.4.1)(@types/node@20.11.30)(db0@0.3.1)(eslint@8.56.0)(ioredis@5.6.0)(less@4.2.0)(lightningcss@1.25.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.36.0)(terser@5.44.0)(tsx@4.20.3)(typescript@5.8.3)(vite@6.3.5(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0))(yaml@2.7.0): dependencies: '@nuxt/cli': 3.23.1(magicast@0.3.5) '@nuxt/devalue': 2.0.2 - '@nuxt/devtools': 2.3.0(vite@7.0.6(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0))(vue@3.5.18(typescript@5.8.3)) + '@nuxt/devtools': 2.3.0(vite@6.3.5(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0))(vue@3.5.18(typescript@5.8.3)) '@nuxt/kit': 3.16.0(magicast@0.3.5) '@nuxt/schema': 3.16.0 '@nuxt/telemetry': 2.6.6(magicast@0.3.5) @@ -35971,6 +36087,13 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 + raw-body@3.0.1: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.7.0 + unpipe: 1.0.0 + raw-loader@4.0.2(webpack@5.96.1(@swc/core@1.13.3)(esbuild@0.25.1)): dependencies: loader-utils: 2.0.4 @@ -38784,16 +38907,30 @@ snapshots: - rollup - supports-color + vite-dev-rpc@1.0.7(vite@6.3.5(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0)): + dependencies: + birpc: 2.2.0 + vite: 6.3.5(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0) + vite-hot-client: 2.0.4(vite@6.3.5(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0)) + vite-dev-rpc@1.0.7(vite@7.0.6(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0)): dependencies: birpc: 2.2.0 vite: 7.0.6(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0) vite-hot-client: 2.0.4(vite@7.0.6(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0)) + vite-hot-client@0.2.4(vite@6.3.5(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0)): + dependencies: + vite: 6.3.5(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0) + vite-hot-client@0.2.4(vite@7.0.6(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0)): dependencies: vite: 7.0.6(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0) + vite-hot-client@2.0.4(vite@6.3.5(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0)): + dependencies: + vite: 6.3.5(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0) + vite-hot-client@2.0.4(vite@7.0.6(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0)): dependencies: vite: 7.0.6(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0) @@ -38904,6 +39041,23 @@ snapshots: optionator: 0.9.4 typescript: 5.8.3 + vite-plugin-inspect@11.0.0(@nuxt/kit@3.16.0(magicast@0.3.5))(vite@6.3.5(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0)): + dependencies: + ansis: 3.17.0 + debug: 4.4.1(supports-color@9.4.0) + error-stack-parser-es: 1.0.5 + ohash: 2.0.11 + open: 10.1.0 + perfect-debounce: 1.0.0 + sirv: 3.0.1 + unplugin-utils: 0.2.4 + vite: 6.3.5(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0) + vite-dev-rpc: 1.0.7(vite@6.3.5(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0)) + optionalDependencies: + '@nuxt/kit': 3.16.0(magicast@0.3.5) + transitivePeerDependencies: + - supports-color + vite-plugin-inspect@11.0.0(@nuxt/kit@3.16.0(magicast@0.3.5))(vite@7.0.6(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0)): dependencies: ansis: 3.17.0 @@ -38970,6 +39124,15 @@ snapshots: dependencies: vite: 7.0.6(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0) + vite-plugin-vue-tracer@0.1.1(vite@6.3.5(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0))(vue@3.5.18(typescript@5.8.3)): + dependencies: + estree-walker: 3.0.3 + magic-string: 0.30.17 + pathe: 2.0.3 + source-map-js: 1.2.1 + vite: 6.3.5(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0) + vue: 3.5.18(typescript@5.8.3) + vite-plugin-vue-tracer@0.1.1(vite@7.0.6(@types/node@20.11.30)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.25.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.7.0))(vue@3.5.18(typescript@5.8.3)): dependencies: estree-walker: 3.0.3