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