@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 +83 -0
- package/dist/index.cjs +566 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +546 -0
- package/dist/index.js.map +1 -0
- package/package.json +66 -0
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
|
+

|
|
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
|