@genart-dev/plugin-symbols 0.1.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/symbol-layer.ts","../src/symbol-tools.ts"],"sourcesContent":["import type { DesignPlugin, PluginContext } from \"@genart-dev/core\";\nimport { symbolLayerType } from \"./symbol-layer.js\";\nimport { symbolMcpTools } from \"./symbol-tools.js\";\n\nconst symbolsPlugin: DesignPlugin = {\n id: \"symbols\",\n name: \"Symbols & Icons\",\n version: \"0.1.0\",\n tier: \"free\",\n description:\n \"Vector symbol library with Iconify integration. Search, fetch, and place symbols on the design canvas or embed in algorithms.\",\n layerTypes: [symbolLayerType],\n tools: [],\n exportHandlers: [],\n mcpTools: symbolMcpTools,\n async initialize(_context: PluginContext): Promise<void> {},\n dispose(): void {},\n};\n\nexport default symbolsPlugin;\nexport { symbolsPlugin };\nexport { symbolLayerType };\nexport { symbolMcpTools };\n","import type {\n LayerTypeDefinition,\n LayerPropertySchema,\n LayerProperties,\n LayerBounds,\n RenderResources,\n} from \"@genart-dev/core\";\ninterface SymbolPath {\n readonly d: string;\n readonly fill?: string;\n readonly stroke?: string;\n readonly strokeWidth?: number;\n readonly role?: string;\n}\n\nconst SYMBOL_PROPERTIES: LayerPropertySchema[] = [\n { key: \"symbolId\", label: \"Symbol ID\", type: \"string\", default: \"\", group: \"symbol\" },\n { key: \"iconifyId\", label: \"Iconify ID\", type: \"string\", default: \"\", group: \"symbol\" },\n { key: \"viewBox\", label: \"ViewBox\", type: \"string\", default: \"0 0 24 24\", group: \"symbol\" },\n { key: \"paths\", label: \"Paths (JSON)\", type: \"string\", default: \"[]\", group: \"data\" },\n { key: \"fillOverride\", label: \"Fill Override\", type: \"string\", default: \"\", group: \"appearance\" },\n { key: \"strokeOverride\", label: \"Stroke Override\", type: \"string\", default: \"\", group: \"appearance\" },\n { key: \"preserveAspect\", label: \"Preserve Aspect\", type: \"boolean\", default: true, group: \"layout\" },\n];\n\nexport const symbolLayerType: LayerTypeDefinition = {\n typeId: \"symbols:symbol\",\n displayName: \"Symbol\",\n icon: \"symbol\",\n category: \"image\",\n properties: SYMBOL_PROPERTIES,\n propertyEditorId: \"symbols:symbol-editor\",\n\n createDefault(): LayerProperties {\n return {\n symbolId: \"\",\n iconifyId: \"\",\n viewBox: \"0 0 24 24\",\n paths: \"[]\",\n fillOverride: \"\",\n strokeOverride: \"\",\n preserveAspect: true,\n };\n },\n\n render(\n properties: LayerProperties,\n ctx: CanvasRenderingContext2D,\n bounds: LayerBounds,\n _resources: RenderResources,\n ): void {\n const pathsJson = String(properties.paths ?? \"[]\");\n let paths: SymbolPath[];\n try {\n paths = JSON.parse(pathsJson) as SymbolPath[];\n } catch {\n return;\n }\n if (paths.length === 0) return;\n\n const viewBoxStr = String(properties.viewBox ?? \"0 0 24 24\");\n const vb = viewBoxStr.split(/\\s+/).map(Number);\n const vbX = vb[0] ?? 0;\n const vbY = vb[1] ?? 0;\n const vbW = vb[2] ?? 24;\n const vbH = vb[3] ?? 24;\n\n const preserveAspect = Boolean(properties.preserveAspect ?? true);\n const fillOverride = String(properties.fillOverride ?? \"\") || undefined;\n const strokeOverride = String(properties.strokeOverride ?? \"\") || undefined;\n\n let scaleX = bounds.width / vbW;\n let scaleY = bounds.height / vbH;\n\n let offsetX = 0;\n let offsetY = 0;\n\n if (preserveAspect) {\n const uniformScale = Math.min(scaleX, scaleY);\n offsetX = (bounds.width - vbW * uniformScale) / 2;\n offsetY = (bounds.height - vbH * uniformScale) / 2;\n scaleX = uniformScale;\n scaleY = uniformScale;\n }\n\n ctx.save();\n ctx.translate(bounds.x + offsetX - vbX * scaleX, bounds.y + offsetY - vbY * scaleY);\n ctx.scale(scaleX, scaleY);\n\n for (const p of paths) {\n const path = new Path2D(p.d);\n if (p.fill !== \"none\") {\n ctx.fillStyle = fillOverride ?? p.fill ?? \"#000000\";\n ctx.fill(path);\n }\n if (p.stroke || strokeOverride) {\n ctx.strokeStyle = strokeOverride ?? p.stroke ?? \"#000000\";\n ctx.lineWidth = (p.strokeWidth ?? 1) / Math.max(scaleX, scaleY);\n ctx.stroke(path);\n }\n }\n\n ctx.restore();\n },\n\n validate(): null { return null; },\n};\n","import type {\n McpToolDefinition,\n McpToolContext,\n McpToolResult,\n JsonSchema,\n} from \"@genart-dev/core\";\nimport { resolveComponents } from \"@genart-dev/core\";\nimport type {\n SymbolCategory,\n SymbolStyle,\n SketchSymbolDef,\n} from \"@genart-dev/symbols\";\n\ninterface SymbolPath {\n readonly d: string;\n readonly fill?: string;\n readonly stroke?: string;\n readonly strokeWidth?: number;\n readonly role?: string;\n}\nimport {\n SYMBOL_REGISTRY,\n listCategories,\n searchSymbols,\n resolveSymbol,\n validateSymbol,\n searchIconify,\n fetchAndParseIcon,\n SAFE_PREFIXES,\n} from \"@genart-dev/symbols\";\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\ninterface ThirdPartyNotice {\n readonly name: string;\n readonly license: string;\n readonly copyright: string;\n readonly url: string;\n}\n\nfunction textResult(text: string): McpToolResult {\n return { content: [{ type: \"text\", text }] };\n}\n\nfunction errorResult(text: string): McpToolResult {\n return { content: [{ type: \"text\", text }], isError: true };\n}\n\nconst VALID_STYLES: readonly SymbolStyle[] = [\"geometric\", \"organic\", \"silhouette\", \"sketch\"];\nconst JS_RENDERERS = new Set([\"p5\", \"three\", \"canvas2d\", \"svg\"]);\n\nconst ICONIFY_NOTICES: Readonly<Record<string, ThirdPartyNotice>> = {\n ph: {\n name: \"Phosphor Icons\",\n license: \"MIT\",\n copyright: \"Copyright (c) 2023 Phosphor Icons\",\n url: \"https://github.com/phosphor-icons/core\",\n },\n lucide: {\n name: \"Lucide\",\n license: \"ISC\",\n copyright: \"Copyright (c) 2022 Lucide Contributors\",\n url: \"https://github.com/lucide-icons/lucide\",\n },\n tabler: {\n name: \"Tabler Icons\",\n license: \"MIT\",\n copyright: \"Copyright (c) 2020-2024 Paweł Kuna\",\n url: \"https://github.com/tabler/tabler-icons\",\n },\n heroicons: {\n name: \"Heroicons\",\n license: \"MIT\",\n copyright: \"Copyright (c) 2020 Tailwind Labs, Inc.\",\n url: \"https://github.com/tailwindlabs/heroicons\",\n },\n bi: {\n name: \"Bootstrap Icons\",\n license: \"MIT\",\n copyright: \"Copyright (c) 2019-2024 The Bootstrap Authors\",\n url: \"https://github.com/twbs/icons\",\n },\n mdi: {\n name: \"Material Design Icons\",\n license: \"Apache-2.0\",\n copyright: \"Copyright (c) Google LLC\",\n url: \"https://github.com/google/material-design-icons\",\n },\n ri: {\n name: \"Remix Icon\",\n license: \"Remix Icon License v1.0\",\n copyright: \"Copyright (c) 2017-2024 Remix Design\",\n url: \"https://github.com/Remix-Design/RemixIcon\",\n },\n carbon: {\n name: \"Carbon Icons\",\n license: \"Apache-2.0\",\n copyright: \"Copyright (c) 2015 IBM Corp.\",\n url: \"https://github.com/carbon-design-system/carbon\",\n },\n fluent: {\n name: \"Fluent UI System Icons\",\n license: \"MIT\",\n copyright: \"Copyright (c) 2020 Microsoft Corporation\",\n url: \"https://github.com/microsoft/fluentui-system-icons\",\n },\n};\n\n// ---------------------------------------------------------------------------\n// search_symbols\n// ---------------------------------------------------------------------------\n\nexport const searchSymbolsTool: McpToolDefinition = {\n name: \"search_symbols\",\n description: \"Search the symbol registry by keyword, category, and/or style. Returns symbols with their IDs, tags, and available styles.\",\n inputSchema: {\n type: \"object\",\n properties: {\n query: { type: \"string\", description: \"Keyword to match against symbol name, tags, and description\" },\n category: { type: \"string\", enum: [\"nature\", \"architecture\", \"people\", \"vehicles\", \"objects\", \"animals\", \"abstract\", \"celestial\", \"flora\", \"weather\"], description: \"Filter by category\" },\n style: { type: \"string\", enum: [\"geometric\", \"organic\", \"silhouette\", \"sketch\"], description: \"Filter by available style\" },\n limit: { type: \"number\", description: \"Maximum results to return (default: 20)\" },\n } as Record<string, JsonSchema>,\n },\n async handler(input: Record<string, unknown>, _context: McpToolContext): Promise<McpToolResult> {\n const results = searchSymbols({\n query: input[\"query\"] as string | undefined,\n category: input[\"category\"] as SymbolCategory | undefined,\n style: input[\"style\"] as SymbolStyle | undefined,\n limit: (input[\"limit\"] as number | undefined) ?? 20,\n });\n return textResult(JSON.stringify({ count: results.length, symbols: results }));\n },\n};\n\n// ---------------------------------------------------------------------------\n// list_symbol_categories\n// ---------------------------------------------------------------------------\n\nexport const listSymbolCategoriesTool: McpToolDefinition = {\n name: \"list_symbol_categories\",\n description: \"List all symbol categories with symbol counts. Use this to browse the symbol library before searching.\",\n inputSchema: {\n type: \"object\",\n properties: {} as Record<string, JsonSchema>,\n },\n async handler(_input: Record<string, unknown>, _context: McpToolContext): Promise<McpToolResult> {\n const categories = listCategories();\n const total = Object.keys(SYMBOL_REGISTRY).length;\n const breakdown = categories.map((cat) => ({\n category: cat,\n count: Object.values(SYMBOL_REGISTRY).filter((s) => s.category === cat).length,\n }));\n return textResult(JSON.stringify({ categories: breakdown, total }));\n },\n};\n\n// ---------------------------------------------------------------------------\n// add_symbol\n// ---------------------------------------------------------------------------\n\nexport const addSymbolTool: McpToolDefinition = {\n name: \"add_symbol\",\n description: \"Add a symbol from the registry to a sketch. Resolves the symbol's SVG path data and caches it in the sketch file. Automatically adds the symbol-draw component if not already present.\",\n inputSchema: {\n type: \"object\",\n required: [\"symbol\"],\n properties: {\n symbol: { type: \"string\", description: \"Symbol ID (e.g. 'pine-tree', 'sailboat', 'mountain')\" },\n style: { type: \"string\", enum: [\"geometric\", \"organic\", \"silhouette\", \"sketch\"], description: \"Style variant to use (default: 'geometric')\" },\n } as Record<string, JsonSchema>,\n },\n async handler(input: Record<string, unknown>, context: McpToolContext): Promise<McpToolResult> {\n const symbolName = input[\"symbol\"] as string;\n const renderer = context.sketch.getRenderer();\n\n if (!JS_RENDERERS.has(renderer)) {\n return errorResult(\n `Symbols require a JS-based renderer (p5, canvas2d, svg, three). Renderer \"${renderer}\" does not support symbols.`,\n );\n }\n\n const style = ((input[\"style\"] as string | undefined) ?? \"geometric\") as SymbolStyle;\n if (!VALID_STYLES.includes(style)) {\n return errorResult(`Unknown style \"${style}\". Valid styles: ${VALID_STYLES.join(\", \")}`);\n }\n\n const resolved = resolveSymbol(symbolName, style);\n\n const existing = context.sketch.getSymbols();\n if (existing[symbolName]) {\n return errorResult(`Symbol \"${symbolName}\" is already present in the sketch`);\n }\n\n const newSymbols: Record<string, unknown> = { ...existing, [symbolName]: resolved };\n\n // Ensure symbol-draw component is present\n const existingComponents = context.sketch.getComponents();\n if (!existingComponents[\"symbol-draw\"]) {\n const compMap: Record<string, string> = {};\n for (const [name, value] of Object.entries(existingComponents)) {\n if (typeof value === \"string\") {\n compMap[name] = value;\n } else if (value && typeof value === \"object\" && \"version\" in value) {\n compMap[name] = (value as { version: string }).version;\n }\n }\n compMap[\"symbol-draw\"] = \"^1.0.0\";\n const resolved2 = resolveComponents(compMap, renderer as Parameters<typeof resolveComponents>[1]);\n const resolvedRecord: Record<string, unknown> = {};\n for (const rc of resolved2) {\n resolvedRecord[rc.name] = { version: rc.version, code: rc.code, exports: [...rc.exports] };\n }\n context.sketch.setComponents(resolvedRecord);\n }\n\n context.sketch.setSymbols(newSymbols);\n context.sketch.setGenartVersion(\"1.3\");\n context.emitChange(\"sketch-updated\");\n\n const added = Object.keys(newSymbols).filter((k) => !existing[k]);\n\n return textResult(JSON.stringify({\n success: true,\n added,\n symbolCount: Object.keys(newSymbols).length,\n }));\n },\n};\n\n// ---------------------------------------------------------------------------\n// remove_symbol\n// ---------------------------------------------------------------------------\n\nexport const removeSymbolTool: McpToolDefinition = {\n name: \"remove_symbol\",\n description: \"Remove a symbol from a sketch. Warns if the algorithm references the symbol ID.\",\n inputSchema: {\n type: \"object\",\n required: [\"symbol\"],\n properties: {\n symbol: { type: \"string\", description: \"Symbol ID to remove\" },\n } as Record<string, JsonSchema>,\n },\n async handler(input: Record<string, unknown>, context: McpToolContext): Promise<McpToolResult> {\n const symbolName = input[\"symbol\"] as string;\n\n const existing = context.sketch.getSymbols();\n if (!existing[symbolName]) {\n return errorResult(`Symbol \"${symbolName}\" is not present in the sketch`);\n }\n\n const newSymbols = { ...existing };\n delete newSymbols[symbolName];\n\n context.sketch.setSymbols(\n Object.keys(newSymbols).length > 0 ? newSymbols : undefined,\n );\n context.emitChange(\"sketch-updated\");\n\n return textResult(JSON.stringify({\n success: true,\n removed: symbolName,\n symbolCount: Object.keys(newSymbols).length,\n }));\n },\n};\n\n// ---------------------------------------------------------------------------\n// create_symbol\n// ---------------------------------------------------------------------------\n\nexport const createSymbolTool: McpToolDefinition = {\n name: \"create_symbol\",\n description: \"Create a custom AI-generated symbol with SVG path data. Validates path syntax and enforces a 10KB size limit. Caches it in the sketch file for use with drawSymbol().\",\n inputSchema: {\n type: \"object\",\n required: [\"name\", \"category\", \"tags\", \"description\", \"paths\", \"viewBox\", \"style\"],\n properties: {\n name: { type: \"string\", description: \"Human-readable symbol name (e.g. 'Weeping Willow')\" },\n id: { type: \"string\", description: \"Symbol ID (kebab-case, auto-generated from name if omitted)\" },\n category: { type: \"string\", enum: [\"nature\", \"architecture\", \"people\", \"vehicles\", \"objects\", \"animals\", \"abstract\", \"celestial\", \"flora\", \"weather\"], description: \"Symbol category\" },\n tags: { type: \"array\", items: { type: \"string\" }, description: \"Search tags (e.g. ['tree', 'weeping', 'willow', 'nature'])\" },\n description: { type: \"string\", description: \"Short description of the symbol\" },\n paths: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n d: { type: \"string\", description: \"SVG path d attribute\" },\n fill: { type: \"string\" },\n stroke: { type: \"string\" },\n strokeWidth: { type: \"number\" },\n role: { type: \"string\", description: \"Semantic role (e.g. 'trunk', 'canopy')\" },\n },\n required: [\"d\"],\n },\n description: \"Array of SVG path objects\",\n },\n viewBox: { type: \"string\", description: \"SVG viewBox (e.g. '0 0 100 100')\" },\n style: { type: \"string\", enum: [\"geometric\", \"organic\", \"silhouette\", \"sketch\"], description: \"Visual style of these paths\" },\n } as Record<string, JsonSchema>,\n },\n async handler(input: Record<string, unknown>, context: McpToolContext): Promise<McpToolResult> {\n const paths = input[\"paths\"] as SymbolPath[];\n const viewBox = input[\"viewBox\"] as string;\n\n const errors = validateSymbol(paths, viewBox);\n if (errors.length > 0) {\n return errorResult(`Symbol validation failed:\\n${errors.join(\"\\n\")}`);\n }\n\n const style = ((input[\"style\"] as string | undefined) ?? \"geometric\") as SymbolStyle;\n if (!VALID_STYLES.includes(style)) {\n return errorResult(`Unknown style \"${style}\". Valid styles: ${VALID_STYLES.join(\", \")}`);\n }\n\n const name = input[\"name\"] as string;\n const id = (input[\"id\"] as string | undefined) ?? name.toLowerCase().replace(/\\s+/g, \"-\").replace(/[^a-z0-9-]/g, \"\");\n\n const symbolDef: SketchSymbolDef = {\n id,\n name,\n style,\n paths,\n viewBox,\n custom: true,\n };\n\n const renderer = context.sketch.getRenderer();\n if (!JS_RENDERERS.has(renderer)) {\n return errorResult(\n `Symbols require a JS-based renderer. Renderer \"${renderer}\" does not support symbols.`,\n );\n }\n\n const existing = context.sketch.getSymbols();\n const newSymbols: Record<string, unknown> = { ...existing, [id]: symbolDef };\n context.sketch.setSymbols(newSymbols);\n context.sketch.setGenartVersion(\"1.3\");\n context.emitChange(\"sketch-updated\");\n\n return textResult(JSON.stringify({\n success: true,\n symbol: symbolDef,\n tip: `Use drawSymbol(ctx, \"${id}\", x, y, width, height) to render this symbol in your algorithm.`,\n }));\n },\n};\n\n// ---------------------------------------------------------------------------\n// fetch_symbol\n// ---------------------------------------------------------------------------\n\nexport const fetchSymbolTool: McpToolDefinition = {\n name: \"fetch_symbol\",\n description: \"Search Iconify for professional icons (275k+ from Phosphor, Lucide, Tabler, MDI, etc.) or embed one into a sketch. Two modes: (1) Search — provide query to get a list of iconifyIds; (2) Embed — provide iconifyId (e.g. 'ph:cat') to fetch SVG, parse paths, and embed as a SketchSymbolDef. Approved prefixes: ph, lucide, tabler, heroicons, bi, mdi, ri, carbon, fluent.\",\n inputSchema: {\n type: \"object\",\n properties: {\n query: { type: \"string\", description: \"Keyword to search Iconify (e.g. 'cat', 'arrow left', 'sun'). Returns a list of iconifyIds — no SVG fetched yet.\" },\n iconifyId: { type: \"string\", description: \"Iconify icon ID to embed (e.g. 'ph:cat', 'lucide:arrow-left'). Fetches SVG, parses paths, embeds in sketch.\" },\n prefix: { type: \"string\", description: \"Limit search to a specific icon set (e.g. 'ph', 'lucide', 'tabler'). Ignored when using iconifyId.\" },\n limit: { type: \"number\", description: \"Maximum search results to return (default: 10)\" },\n } as Record<string, JsonSchema>,\n },\n async handler(input: Record<string, unknown>, context: McpToolContext): Promise<McpToolResult> {\n const query = input[\"query\"] as string | undefined;\n const iconifyId = input[\"iconifyId\"] as string | undefined;\n\n // ---- Search mode ----\n if (query && !iconifyId) {\n const prefixes = input[\"prefix\"] ? [input[\"prefix\"] as string] : undefined;\n const results = await searchIconify(query, (input[\"limit\"] as number | undefined) ?? 10, prefixes);\n return textResult(JSON.stringify({\n mode: \"search\",\n results,\n tip: \"Call fetch_symbol with iconifyId to embed one of these icons into a sketch\",\n }));\n }\n\n // ---- Embed mode ----\n if (!iconifyId) {\n return errorResult(\n \"Provide either query (to search) or iconifyId (to embed). Approved prefixes: \" +\n Object.keys(SAFE_PREFIXES).join(\", \"),\n );\n }\n\n let iconData: Awaited<ReturnType<typeof fetchAndParseIcon>>;\n try {\n iconData = await fetchAndParseIcon(iconifyId);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (msg.includes(\"not in the approved list\")) {\n return textResult(JSON.stringify({ warning: msg }));\n }\n return errorResult(msg);\n }\n\n const symbolId = iconData.iconifyId.replace(\":\", \"-\");\n\n const symbolDef: SketchSymbolDef = {\n id: symbolId,\n name: iconData.name,\n paths: iconData.paths,\n viewBox: iconData.viewBox,\n iconifyId: iconData.iconifyId,\n license: iconData.license,\n };\n\n // Embed into sketch\n const renderer = context.sketch.getRenderer();\n if (!JS_RENDERERS.has(renderer)) {\n return errorResult(\n `Symbols require a JS-based renderer. Renderer \"${renderer}\" does not support symbols.`,\n );\n }\n\n const existing = context.sketch.getSymbols();\n const newSymbols: Record<string, unknown> = { ...existing, [symbolId]: symbolDef };\n context.sketch.setSymbols(newSymbols);\n\n // Merge third-party notice\n const notice = ICONIFY_NOTICES[iconData.prefix];\n const existingNotices = context.sketch.getThirdParty();\n const alreadyPresent = existingNotices.some(\n (n: Record<string, unknown>) => (n as { name?: string }).name === notice?.name,\n );\n if (notice && !alreadyPresent) {\n context.sketch.setThirdParty([\n ...existingNotices as Record<string, unknown>[],\n notice as unknown as Record<string, unknown>,\n ]);\n }\n\n context.sketch.setGenartVersion(\"1.3\");\n context.emitChange(\"sketch-updated\");\n\n return textResult(JSON.stringify({\n mode: \"embedded\",\n symbolId,\n iconifyId: iconData.iconifyId,\n license: iconData.license,\n viewBox: iconData.viewBox,\n pathCount: iconData.paths.length,\n tip: `drawSymbol(ctx, \"${symbolId}\", x, y, width, height)`,\n }));\n },\n};\n\n// ---------------------------------------------------------------------------\n// place_symbol\n// ---------------------------------------------------------------------------\n\nexport const placeSymbolTool: McpToolDefinition = {\n name: \"place_symbol\",\n description: \"Place a symbol as a design layer on the canvas. Creates a symbols:symbol layer with embedded SVG path data for visual composition.\",\n inputSchema: {\n type: \"object\",\n required: [\"symbolId\", \"viewBox\", \"paths\"],\n properties: {\n symbolId: { type: \"string\", description: \"Symbol ID (e.g. 'ph-cat')\" },\n iconifyId: { type: \"string\", description: \"Original Iconify ID (e.g. 'ph:cat')\" },\n viewBox: { type: \"string\", description: \"SVG viewBox (e.g. '0 0 256 256')\" },\n paths: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n d: { type: \"string\", description: \"SVG path d attribute\" },\n fill: { type: \"string\" },\n stroke: { type: \"string\" },\n strokeWidth: { type: \"number\" },\n role: { type: \"string\" },\n },\n required: [\"d\"],\n },\n description: \"Array of SVG path objects\",\n },\n x: { type: \"number\", description: \"X position (default: 0)\" },\n y: { type: \"number\", description: \"Y position (default: 0)\" },\n width: { type: \"number\", description: \"Layer width (default: 100)\" },\n height: { type: \"number\", description: \"Layer height (default: 100)\" },\n } as Record<string, JsonSchema>,\n },\n async handler(input: Record<string, unknown>, context: McpToolContext): Promise<McpToolResult> {\n const symbolId = input[\"symbolId\"] as string;\n const iconifyId = (input[\"iconifyId\"] as string | undefined) ?? \"\";\n const viewBox = input[\"viewBox\"] as string;\n const paths = input[\"paths\"] as SymbolPath[];\n const x = (input[\"x\"] as number | undefined) ?? 0;\n const y = (input[\"y\"] as number | undefined) ?? 0;\n const width = (input[\"width\"] as number | undefined) ?? 100;\n const height = (input[\"height\"] as number | undefined) ?? 100;\n\n const id = `symbol-${symbolId}-${Date.now()}`;\n context.layers.add({\n id,\n type: \"symbols:symbol\",\n name: symbolId,\n visible: true,\n locked: false,\n opacity: 1,\n blendMode: \"normal\",\n transform: {\n x, y, width, height,\n rotation: 0, scaleX: 1, scaleY: 1, anchorX: 0.5, anchorY: 0.5,\n },\n properties: {\n symbolId,\n iconifyId,\n viewBox,\n paths: JSON.stringify(paths),\n fillOverride: \"\",\n strokeOverride: \"\",\n preserveAspect: true,\n },\n } as Parameters<typeof context.layers.add>[0]);\n\n context.emitChange(\"layer-added\");\n\n return textResult(JSON.stringify({\n layerId: id,\n symbolId,\n position: { x, y },\n size: { width, height },\n }));\n },\n};\n\n// ---------------------------------------------------------------------------\n// Export all tools\n// ---------------------------------------------------------------------------\n\nexport const symbolMcpTools: McpToolDefinition[] = [\n searchSymbolsTool,\n listSymbolCategoriesTool,\n addSymbolTool,\n removeSymbolTool,\n createSymbolTool,\n fetchSymbolTool,\n placeSymbolTool,\n];\n"],"mappings":"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","names":[]}
@@ -0,0 +1,9 @@
1
+ import { LayerTypeDefinition, McpToolDefinition, DesignPlugin } from '@genart-dev/core';
2
+
3
+ declare const symbolLayerType: LayerTypeDefinition;
4
+
5
+ declare const symbolMcpTools: McpToolDefinition[];
6
+
7
+ declare const symbolsPlugin: DesignPlugin;
8
+
9
+ export { symbolsPlugin as default, symbolLayerType, symbolMcpTools, symbolsPlugin };
@@ -0,0 +1,9 @@
1
+ import { LayerTypeDefinition, McpToolDefinition, DesignPlugin } from '@genart-dev/core';
2
+
3
+ declare const symbolLayerType: LayerTypeDefinition;
4
+
5
+ declare const symbolMcpTools: McpToolDefinition[];
6
+
7
+ declare const symbolsPlugin: DesignPlugin;
8
+
9
+ export { symbolsPlugin as default, symbolLayerType, symbolMcpTools, symbolsPlugin };
package/dist/index.js ADDED
@@ -0,0 +1,546 @@
1
+ // src/symbol-layer.ts
2
+ var SYMBOL_PROPERTIES = [
3
+ { key: "symbolId", label: "Symbol ID", type: "string", default: "", group: "symbol" },
4
+ { key: "iconifyId", label: "Iconify ID", type: "string", default: "", group: "symbol" },
5
+ { key: "viewBox", label: "ViewBox", type: "string", default: "0 0 24 24", group: "symbol" },
6
+ { key: "paths", label: "Paths (JSON)", type: "string", default: "[]", group: "data" },
7
+ { key: "fillOverride", label: "Fill Override", type: "string", default: "", group: "appearance" },
8
+ { key: "strokeOverride", label: "Stroke Override", type: "string", default: "", group: "appearance" },
9
+ { key: "preserveAspect", label: "Preserve Aspect", type: "boolean", default: true, group: "layout" }
10
+ ];
11
+ var symbolLayerType = {
12
+ typeId: "symbols:symbol",
13
+ displayName: "Symbol",
14
+ icon: "symbol",
15
+ category: "image",
16
+ properties: SYMBOL_PROPERTIES,
17
+ propertyEditorId: "symbols:symbol-editor",
18
+ createDefault() {
19
+ return {
20
+ symbolId: "",
21
+ iconifyId: "",
22
+ viewBox: "0 0 24 24",
23
+ paths: "[]",
24
+ fillOverride: "",
25
+ strokeOverride: "",
26
+ preserveAspect: true
27
+ };
28
+ },
29
+ render(properties, ctx, bounds, _resources) {
30
+ const pathsJson = String(properties.paths ?? "[]");
31
+ let paths;
32
+ try {
33
+ paths = JSON.parse(pathsJson);
34
+ } catch {
35
+ return;
36
+ }
37
+ if (paths.length === 0) return;
38
+ const viewBoxStr = String(properties.viewBox ?? "0 0 24 24");
39
+ const vb = viewBoxStr.split(/\s+/).map(Number);
40
+ const vbX = vb[0] ?? 0;
41
+ const vbY = vb[1] ?? 0;
42
+ const vbW = vb[2] ?? 24;
43
+ const vbH = vb[3] ?? 24;
44
+ const preserveAspect = Boolean(properties.preserveAspect ?? true);
45
+ const fillOverride = String(properties.fillOverride ?? "") || void 0;
46
+ const strokeOverride = String(properties.strokeOverride ?? "") || void 0;
47
+ let scaleX = bounds.width / vbW;
48
+ let scaleY = bounds.height / vbH;
49
+ let offsetX = 0;
50
+ let offsetY = 0;
51
+ if (preserveAspect) {
52
+ const uniformScale = Math.min(scaleX, scaleY);
53
+ offsetX = (bounds.width - vbW * uniformScale) / 2;
54
+ offsetY = (bounds.height - vbH * uniformScale) / 2;
55
+ scaleX = uniformScale;
56
+ scaleY = uniformScale;
57
+ }
58
+ ctx.save();
59
+ ctx.translate(bounds.x + offsetX - vbX * scaleX, bounds.y + offsetY - vbY * scaleY);
60
+ ctx.scale(scaleX, scaleY);
61
+ for (const p of paths) {
62
+ const path = new Path2D(p.d);
63
+ if (p.fill !== "none") {
64
+ ctx.fillStyle = fillOverride ?? p.fill ?? "#000000";
65
+ ctx.fill(path);
66
+ }
67
+ if (p.stroke || strokeOverride) {
68
+ ctx.strokeStyle = strokeOverride ?? p.stroke ?? "#000000";
69
+ ctx.lineWidth = (p.strokeWidth ?? 1) / Math.max(scaleX, scaleY);
70
+ ctx.stroke(path);
71
+ }
72
+ }
73
+ ctx.restore();
74
+ },
75
+ validate() {
76
+ return null;
77
+ }
78
+ };
79
+
80
+ // src/symbol-tools.ts
81
+ import { resolveComponents } from "@genart-dev/core";
82
+ import {
83
+ SYMBOL_REGISTRY,
84
+ listCategories,
85
+ searchSymbols,
86
+ resolveSymbol,
87
+ validateSymbol,
88
+ searchIconify,
89
+ fetchAndParseIcon,
90
+ SAFE_PREFIXES
91
+ } from "@genart-dev/symbols";
92
+ function textResult(text) {
93
+ return { content: [{ type: "text", text }] };
94
+ }
95
+ function errorResult(text) {
96
+ return { content: [{ type: "text", text }], isError: true };
97
+ }
98
+ var VALID_STYLES = ["geometric", "organic", "silhouette", "sketch"];
99
+ var JS_RENDERERS = /* @__PURE__ */ new Set(["p5", "three", "canvas2d", "svg"]);
100
+ var ICONIFY_NOTICES = {
101
+ ph: {
102
+ name: "Phosphor Icons",
103
+ license: "MIT",
104
+ copyright: "Copyright (c) 2023 Phosphor Icons",
105
+ url: "https://github.com/phosphor-icons/core"
106
+ },
107
+ lucide: {
108
+ name: "Lucide",
109
+ license: "ISC",
110
+ copyright: "Copyright (c) 2022 Lucide Contributors",
111
+ url: "https://github.com/lucide-icons/lucide"
112
+ },
113
+ tabler: {
114
+ name: "Tabler Icons",
115
+ license: "MIT",
116
+ copyright: "Copyright (c) 2020-2024 Pawe\u0142 Kuna",
117
+ url: "https://github.com/tabler/tabler-icons"
118
+ },
119
+ heroicons: {
120
+ name: "Heroicons",
121
+ license: "MIT",
122
+ copyright: "Copyright (c) 2020 Tailwind Labs, Inc.",
123
+ url: "https://github.com/tailwindlabs/heroicons"
124
+ },
125
+ bi: {
126
+ name: "Bootstrap Icons",
127
+ license: "MIT",
128
+ copyright: "Copyright (c) 2019-2024 The Bootstrap Authors",
129
+ url: "https://github.com/twbs/icons"
130
+ },
131
+ mdi: {
132
+ name: "Material Design Icons",
133
+ license: "Apache-2.0",
134
+ copyright: "Copyright (c) Google LLC",
135
+ url: "https://github.com/google/material-design-icons"
136
+ },
137
+ ri: {
138
+ name: "Remix Icon",
139
+ license: "Remix Icon License v1.0",
140
+ copyright: "Copyright (c) 2017-2024 Remix Design",
141
+ url: "https://github.com/Remix-Design/RemixIcon"
142
+ },
143
+ carbon: {
144
+ name: "Carbon Icons",
145
+ license: "Apache-2.0",
146
+ copyright: "Copyright (c) 2015 IBM Corp.",
147
+ url: "https://github.com/carbon-design-system/carbon"
148
+ },
149
+ fluent: {
150
+ name: "Fluent UI System Icons",
151
+ license: "MIT",
152
+ copyright: "Copyright (c) 2020 Microsoft Corporation",
153
+ url: "https://github.com/microsoft/fluentui-system-icons"
154
+ }
155
+ };
156
+ var searchSymbolsTool = {
157
+ name: "search_symbols",
158
+ description: "Search the symbol registry by keyword, category, and/or style. Returns symbols with their IDs, tags, and available styles.",
159
+ inputSchema: {
160
+ type: "object",
161
+ properties: {
162
+ query: { type: "string", description: "Keyword to match against symbol name, tags, and description" },
163
+ category: { type: "string", enum: ["nature", "architecture", "people", "vehicles", "objects", "animals", "abstract", "celestial", "flora", "weather"], description: "Filter by category" },
164
+ style: { type: "string", enum: ["geometric", "organic", "silhouette", "sketch"], description: "Filter by available style" },
165
+ limit: { type: "number", description: "Maximum results to return (default: 20)" }
166
+ }
167
+ },
168
+ async handler(input, _context) {
169
+ const results = searchSymbols({
170
+ query: input["query"],
171
+ category: input["category"],
172
+ style: input["style"],
173
+ limit: input["limit"] ?? 20
174
+ });
175
+ return textResult(JSON.stringify({ count: results.length, symbols: results }));
176
+ }
177
+ };
178
+ var listSymbolCategoriesTool = {
179
+ name: "list_symbol_categories",
180
+ description: "List all symbol categories with symbol counts. Use this to browse the symbol library before searching.",
181
+ inputSchema: {
182
+ type: "object",
183
+ properties: {}
184
+ },
185
+ async handler(_input, _context) {
186
+ const categories = listCategories();
187
+ const total = Object.keys(SYMBOL_REGISTRY).length;
188
+ const breakdown = categories.map((cat) => ({
189
+ category: cat,
190
+ count: Object.values(SYMBOL_REGISTRY).filter((s) => s.category === cat).length
191
+ }));
192
+ return textResult(JSON.stringify({ categories: breakdown, total }));
193
+ }
194
+ };
195
+ var addSymbolTool = {
196
+ name: "add_symbol",
197
+ description: "Add a symbol from the registry to a sketch. Resolves the symbol's SVG path data and caches it in the sketch file. Automatically adds the symbol-draw component if not already present.",
198
+ inputSchema: {
199
+ type: "object",
200
+ required: ["symbol"],
201
+ properties: {
202
+ symbol: { type: "string", description: "Symbol ID (e.g. 'pine-tree', 'sailboat', 'mountain')" },
203
+ style: { type: "string", enum: ["geometric", "organic", "silhouette", "sketch"], description: "Style variant to use (default: 'geometric')" }
204
+ }
205
+ },
206
+ async handler(input, context) {
207
+ const symbolName = input["symbol"];
208
+ const renderer = context.sketch.getRenderer();
209
+ if (!JS_RENDERERS.has(renderer)) {
210
+ return errorResult(
211
+ `Symbols require a JS-based renderer (p5, canvas2d, svg, three). Renderer "${renderer}" does not support symbols.`
212
+ );
213
+ }
214
+ const style = input["style"] ?? "geometric";
215
+ if (!VALID_STYLES.includes(style)) {
216
+ return errorResult(`Unknown style "${style}". Valid styles: ${VALID_STYLES.join(", ")}`);
217
+ }
218
+ const resolved = resolveSymbol(symbolName, style);
219
+ const existing = context.sketch.getSymbols();
220
+ if (existing[symbolName]) {
221
+ return errorResult(`Symbol "${symbolName}" is already present in the sketch`);
222
+ }
223
+ const newSymbols = { ...existing, [symbolName]: resolved };
224
+ const existingComponents = context.sketch.getComponents();
225
+ if (!existingComponents["symbol-draw"]) {
226
+ const compMap = {};
227
+ for (const [name, value] of Object.entries(existingComponents)) {
228
+ if (typeof value === "string") {
229
+ compMap[name] = value;
230
+ } else if (value && typeof value === "object" && "version" in value) {
231
+ compMap[name] = value.version;
232
+ }
233
+ }
234
+ compMap["symbol-draw"] = "^1.0.0";
235
+ const resolved2 = resolveComponents(compMap, renderer);
236
+ const resolvedRecord = {};
237
+ for (const rc of resolved2) {
238
+ resolvedRecord[rc.name] = { version: rc.version, code: rc.code, exports: [...rc.exports] };
239
+ }
240
+ context.sketch.setComponents(resolvedRecord);
241
+ }
242
+ context.sketch.setSymbols(newSymbols);
243
+ context.sketch.setGenartVersion("1.3");
244
+ context.emitChange("sketch-updated");
245
+ const added = Object.keys(newSymbols).filter((k) => !existing[k]);
246
+ return textResult(JSON.stringify({
247
+ success: true,
248
+ added,
249
+ symbolCount: Object.keys(newSymbols).length
250
+ }));
251
+ }
252
+ };
253
+ var removeSymbolTool = {
254
+ name: "remove_symbol",
255
+ description: "Remove a symbol from a sketch. Warns if the algorithm references the symbol ID.",
256
+ inputSchema: {
257
+ type: "object",
258
+ required: ["symbol"],
259
+ properties: {
260
+ symbol: { type: "string", description: "Symbol ID to remove" }
261
+ }
262
+ },
263
+ async handler(input, context) {
264
+ const symbolName = input["symbol"];
265
+ const existing = context.sketch.getSymbols();
266
+ if (!existing[symbolName]) {
267
+ return errorResult(`Symbol "${symbolName}" is not present in the sketch`);
268
+ }
269
+ const newSymbols = { ...existing };
270
+ delete newSymbols[symbolName];
271
+ context.sketch.setSymbols(
272
+ Object.keys(newSymbols).length > 0 ? newSymbols : void 0
273
+ );
274
+ context.emitChange("sketch-updated");
275
+ return textResult(JSON.stringify({
276
+ success: true,
277
+ removed: symbolName,
278
+ symbolCount: Object.keys(newSymbols).length
279
+ }));
280
+ }
281
+ };
282
+ var createSymbolTool = {
283
+ name: "create_symbol",
284
+ description: "Create a custom AI-generated symbol with SVG path data. Validates path syntax and enforces a 10KB size limit. Caches it in the sketch file for use with drawSymbol().",
285
+ inputSchema: {
286
+ type: "object",
287
+ required: ["name", "category", "tags", "description", "paths", "viewBox", "style"],
288
+ properties: {
289
+ name: { type: "string", description: "Human-readable symbol name (e.g. 'Weeping Willow')" },
290
+ id: { type: "string", description: "Symbol ID (kebab-case, auto-generated from name if omitted)" },
291
+ category: { type: "string", enum: ["nature", "architecture", "people", "vehicles", "objects", "animals", "abstract", "celestial", "flora", "weather"], description: "Symbol category" },
292
+ tags: { type: "array", items: { type: "string" }, description: "Search tags (e.g. ['tree', 'weeping', 'willow', 'nature'])" },
293
+ description: { type: "string", description: "Short description of the symbol" },
294
+ paths: {
295
+ type: "array",
296
+ items: {
297
+ type: "object",
298
+ properties: {
299
+ d: { type: "string", description: "SVG path d attribute" },
300
+ fill: { type: "string" },
301
+ stroke: { type: "string" },
302
+ strokeWidth: { type: "number" },
303
+ role: { type: "string", description: "Semantic role (e.g. 'trunk', 'canopy')" }
304
+ },
305
+ required: ["d"]
306
+ },
307
+ description: "Array of SVG path objects"
308
+ },
309
+ viewBox: { type: "string", description: "SVG viewBox (e.g. '0 0 100 100')" },
310
+ style: { type: "string", enum: ["geometric", "organic", "silhouette", "sketch"], description: "Visual style of these paths" }
311
+ }
312
+ },
313
+ async handler(input, context) {
314
+ const paths = input["paths"];
315
+ const viewBox = input["viewBox"];
316
+ const errors = validateSymbol(paths, viewBox);
317
+ if (errors.length > 0) {
318
+ return errorResult(`Symbol validation failed:
319
+ ${errors.join("\n")}`);
320
+ }
321
+ const style = input["style"] ?? "geometric";
322
+ if (!VALID_STYLES.includes(style)) {
323
+ return errorResult(`Unknown style "${style}". Valid styles: ${VALID_STYLES.join(", ")}`);
324
+ }
325
+ const name = input["name"];
326
+ const id = input["id"] ?? name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
327
+ const symbolDef = {
328
+ id,
329
+ name,
330
+ style,
331
+ paths,
332
+ viewBox,
333
+ custom: true
334
+ };
335
+ const renderer = context.sketch.getRenderer();
336
+ if (!JS_RENDERERS.has(renderer)) {
337
+ return errorResult(
338
+ `Symbols require a JS-based renderer. Renderer "${renderer}" does not support symbols.`
339
+ );
340
+ }
341
+ const existing = context.sketch.getSymbols();
342
+ const newSymbols = { ...existing, [id]: symbolDef };
343
+ context.sketch.setSymbols(newSymbols);
344
+ context.sketch.setGenartVersion("1.3");
345
+ context.emitChange("sketch-updated");
346
+ return textResult(JSON.stringify({
347
+ success: true,
348
+ symbol: symbolDef,
349
+ tip: `Use drawSymbol(ctx, "${id}", x, y, width, height) to render this symbol in your algorithm.`
350
+ }));
351
+ }
352
+ };
353
+ var fetchSymbolTool = {
354
+ name: "fetch_symbol",
355
+ description: "Search Iconify for professional icons (275k+ from Phosphor, Lucide, Tabler, MDI, etc.) or embed one into a sketch. Two modes: (1) Search \u2014 provide query to get a list of iconifyIds; (2) Embed \u2014 provide iconifyId (e.g. 'ph:cat') to fetch SVG, parse paths, and embed as a SketchSymbolDef. Approved prefixes: ph, lucide, tabler, heroicons, bi, mdi, ri, carbon, fluent.",
356
+ inputSchema: {
357
+ type: "object",
358
+ properties: {
359
+ query: { type: "string", description: "Keyword to search Iconify (e.g. 'cat', 'arrow left', 'sun'). Returns a list of iconifyIds \u2014 no SVG fetched yet." },
360
+ iconifyId: { type: "string", description: "Iconify icon ID to embed (e.g. 'ph:cat', 'lucide:arrow-left'). Fetches SVG, parses paths, embeds in sketch." },
361
+ prefix: { type: "string", description: "Limit search to a specific icon set (e.g. 'ph', 'lucide', 'tabler'). Ignored when using iconifyId." },
362
+ limit: { type: "number", description: "Maximum search results to return (default: 10)" }
363
+ }
364
+ },
365
+ async handler(input, context) {
366
+ const query = input["query"];
367
+ const iconifyId = input["iconifyId"];
368
+ if (query && !iconifyId) {
369
+ const prefixes = input["prefix"] ? [input["prefix"]] : void 0;
370
+ const results = await searchIconify(query, input["limit"] ?? 10, prefixes);
371
+ return textResult(JSON.stringify({
372
+ mode: "search",
373
+ results,
374
+ tip: "Call fetch_symbol with iconifyId to embed one of these icons into a sketch"
375
+ }));
376
+ }
377
+ if (!iconifyId) {
378
+ return errorResult(
379
+ "Provide either query (to search) or iconifyId (to embed). Approved prefixes: " + Object.keys(SAFE_PREFIXES).join(", ")
380
+ );
381
+ }
382
+ let iconData;
383
+ try {
384
+ iconData = await fetchAndParseIcon(iconifyId);
385
+ } catch (err) {
386
+ const msg = err instanceof Error ? err.message : String(err);
387
+ if (msg.includes("not in the approved list")) {
388
+ return textResult(JSON.stringify({ warning: msg }));
389
+ }
390
+ return errorResult(msg);
391
+ }
392
+ const symbolId = iconData.iconifyId.replace(":", "-");
393
+ const symbolDef = {
394
+ id: symbolId,
395
+ name: iconData.name,
396
+ paths: iconData.paths,
397
+ viewBox: iconData.viewBox,
398
+ iconifyId: iconData.iconifyId,
399
+ license: iconData.license
400
+ };
401
+ const renderer = context.sketch.getRenderer();
402
+ if (!JS_RENDERERS.has(renderer)) {
403
+ return errorResult(
404
+ `Symbols require a JS-based renderer. Renderer "${renderer}" does not support symbols.`
405
+ );
406
+ }
407
+ const existing = context.sketch.getSymbols();
408
+ const newSymbols = { ...existing, [symbolId]: symbolDef };
409
+ context.sketch.setSymbols(newSymbols);
410
+ const notice = ICONIFY_NOTICES[iconData.prefix];
411
+ const existingNotices = context.sketch.getThirdParty();
412
+ const alreadyPresent = existingNotices.some(
413
+ (n) => n.name === notice?.name
414
+ );
415
+ if (notice && !alreadyPresent) {
416
+ context.sketch.setThirdParty([
417
+ ...existingNotices,
418
+ notice
419
+ ]);
420
+ }
421
+ context.sketch.setGenartVersion("1.3");
422
+ context.emitChange("sketch-updated");
423
+ return textResult(JSON.stringify({
424
+ mode: "embedded",
425
+ symbolId,
426
+ iconifyId: iconData.iconifyId,
427
+ license: iconData.license,
428
+ viewBox: iconData.viewBox,
429
+ pathCount: iconData.paths.length,
430
+ tip: `drawSymbol(ctx, "${symbolId}", x, y, width, height)`
431
+ }));
432
+ }
433
+ };
434
+ var placeSymbolTool = {
435
+ name: "place_symbol",
436
+ description: "Place a symbol as a design layer on the canvas. Creates a symbols:symbol layer with embedded SVG path data for visual composition.",
437
+ inputSchema: {
438
+ type: "object",
439
+ required: ["symbolId", "viewBox", "paths"],
440
+ properties: {
441
+ symbolId: { type: "string", description: "Symbol ID (e.g. 'ph-cat')" },
442
+ iconifyId: { type: "string", description: "Original Iconify ID (e.g. 'ph:cat')" },
443
+ viewBox: { type: "string", description: "SVG viewBox (e.g. '0 0 256 256')" },
444
+ paths: {
445
+ type: "array",
446
+ items: {
447
+ type: "object",
448
+ properties: {
449
+ d: { type: "string", description: "SVG path d attribute" },
450
+ fill: { type: "string" },
451
+ stroke: { type: "string" },
452
+ strokeWidth: { type: "number" },
453
+ role: { type: "string" }
454
+ },
455
+ required: ["d"]
456
+ },
457
+ description: "Array of SVG path objects"
458
+ },
459
+ x: { type: "number", description: "X position (default: 0)" },
460
+ y: { type: "number", description: "Y position (default: 0)" },
461
+ width: { type: "number", description: "Layer width (default: 100)" },
462
+ height: { type: "number", description: "Layer height (default: 100)" }
463
+ }
464
+ },
465
+ async handler(input, context) {
466
+ const symbolId = input["symbolId"];
467
+ const iconifyId = input["iconifyId"] ?? "";
468
+ const viewBox = input["viewBox"];
469
+ const paths = input["paths"];
470
+ const x = input["x"] ?? 0;
471
+ const y = input["y"] ?? 0;
472
+ const width = input["width"] ?? 100;
473
+ const height = input["height"] ?? 100;
474
+ const id = `symbol-${symbolId}-${Date.now()}`;
475
+ context.layers.add({
476
+ id,
477
+ type: "symbols:symbol",
478
+ name: symbolId,
479
+ visible: true,
480
+ locked: false,
481
+ opacity: 1,
482
+ blendMode: "normal",
483
+ transform: {
484
+ x,
485
+ y,
486
+ width,
487
+ height,
488
+ rotation: 0,
489
+ scaleX: 1,
490
+ scaleY: 1,
491
+ anchorX: 0.5,
492
+ anchorY: 0.5
493
+ },
494
+ properties: {
495
+ symbolId,
496
+ iconifyId,
497
+ viewBox,
498
+ paths: JSON.stringify(paths),
499
+ fillOverride: "",
500
+ strokeOverride: "",
501
+ preserveAspect: true
502
+ }
503
+ });
504
+ context.emitChange("layer-added");
505
+ return textResult(JSON.stringify({
506
+ layerId: id,
507
+ symbolId,
508
+ position: { x, y },
509
+ size: { width, height }
510
+ }));
511
+ }
512
+ };
513
+ var symbolMcpTools = [
514
+ searchSymbolsTool,
515
+ listSymbolCategoriesTool,
516
+ addSymbolTool,
517
+ removeSymbolTool,
518
+ createSymbolTool,
519
+ fetchSymbolTool,
520
+ placeSymbolTool
521
+ ];
522
+
523
+ // src/index.ts
524
+ var symbolsPlugin = {
525
+ id: "symbols",
526
+ name: "Symbols & Icons",
527
+ version: "0.1.0",
528
+ tier: "free",
529
+ description: "Vector symbol library with Iconify integration. Search, fetch, and place symbols on the design canvas or embed in algorithms.",
530
+ layerTypes: [symbolLayerType],
531
+ tools: [],
532
+ exportHandlers: [],
533
+ mcpTools: symbolMcpTools,
534
+ async initialize(_context) {
535
+ },
536
+ dispose() {
537
+ }
538
+ };
539
+ var index_default = symbolsPlugin;
540
+ export {
541
+ index_default as default,
542
+ symbolLayerType,
543
+ symbolMcpTools,
544
+ symbolsPlugin
545
+ };
546
+ //# sourceMappingURL=index.js.map