@genart-dev/mcp-server 0.1.1 → 0.2.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/dist/lib.cjs CHANGED
@@ -470,10 +470,40 @@ async function createSketch(state, input) {
470
470
  const adapter = registry4.resolve(rendererType);
471
471
  algorithm = adapter.getAlgorithmTemplate();
472
472
  }
473
+ let resolvedComponents;
474
+ if (input.components && Object.keys(input.components).length > 0) {
475
+ const shorthand = {};
476
+ for (const [name, value] of Object.entries(input.components)) {
477
+ if (typeof value === "string") {
478
+ shorthand[name] = value;
479
+ } else if (value.version) {
480
+ shorthand[name] = value.version;
481
+ } else if (value.code) {
482
+ if (!resolvedComponents) resolvedComponents = {};
483
+ resolvedComponents[name] = {
484
+ ...value.version ? { version: value.version } : {},
485
+ code: value.code,
486
+ ...value.exports ? { exports: value.exports } : {}
487
+ };
488
+ }
489
+ }
490
+ if (Object.keys(shorthand).length > 0) {
491
+ const resolved = (0, import_core2.resolveComponents)(shorthand, rendererType);
492
+ if (!resolvedComponents) resolvedComponents = {};
493
+ for (const rc of resolved) {
494
+ resolvedComponents[rc.name] = {
495
+ version: rc.version,
496
+ code: rc.code,
497
+ exports: [...rc.exports]
498
+ };
499
+ }
500
+ }
501
+ }
473
502
  const seed = input.seed ?? Math.floor(Math.random() * 1e5);
474
503
  const ts = now2();
504
+ const hasComponents = resolvedComponents && Object.keys(resolvedComponents).length > 0;
475
505
  const sketch = {
476
- genart: "1.1",
506
+ genart: hasComponents ? "1.2" : "1.1",
477
507
  id: input.id,
478
508
  title: input.title,
479
509
  created: ts,
@@ -487,6 +517,7 @@ async function createSketch(state, input) {
487
517
  ...input.philosophy ? { philosophy: input.philosophy } : {},
488
518
  ...input.themes && input.themes.length > 0 ? { themes: input.themes } : {},
489
519
  ...input.skills && input.skills.length > 0 ? { skills: input.skills } : {},
520
+ ...hasComponents ? { components: resolvedComponents } : {},
490
521
  ...input.agent ? { agent: input.agent } : {},
491
522
  ...input.model ? { model: input.model } : {}
492
523
  };
@@ -670,10 +701,44 @@ async function updateAlgorithm(state, input) {
670
701
  );
671
702
  }
672
703
  }
704
+ let resolvedComponents;
705
+ if (input.components && Object.keys(input.components).length > 0) {
706
+ const renderer = def.renderer.type;
707
+ const shorthand = {};
708
+ for (const [name, value] of Object.entries(input.components)) {
709
+ if (typeof value === "string") {
710
+ shorthand[name] = value;
711
+ } else if (value.version) {
712
+ shorthand[name] = value.version;
713
+ } else if (value.code) {
714
+ if (!resolvedComponents) resolvedComponents = {};
715
+ resolvedComponents[name] = {
716
+ ...value.version ? { version: value.version } : {},
717
+ code: value.code,
718
+ ...value.exports ? { exports: value.exports } : {}
719
+ };
720
+ }
721
+ }
722
+ if (Object.keys(shorthand).length > 0) {
723
+ const resolved = (0, import_core2.resolveComponents)(shorthand, renderer);
724
+ if (!resolvedComponents) resolvedComponents = {};
725
+ for (const rc of resolved) {
726
+ resolvedComponents[rc.name] = {
727
+ version: rc.version,
728
+ code: rc.code,
729
+ exports: [...rc.exports]
730
+ };
731
+ }
732
+ }
733
+ }
734
+ const updated = ["algorithm"];
735
+ const hasNewComponents = resolvedComponents && Object.keys(resolvedComponents).length > 0;
736
+ if (hasNewComponents) updated.push("components");
673
737
  const newDef = {
674
738
  ...def,
675
739
  modified: now2(),
676
740
  algorithm: input.algorithm,
741
+ ...hasNewComponents ? { genart: "1.2", components: resolvedComponents } : {},
677
742
  ...input.agent ? { agent: input.agent } : {},
678
743
  ...input.model ? { model: input.model } : {}
679
744
  };
@@ -687,7 +752,7 @@ async function updateAlgorithm(state, input) {
687
752
  }
688
753
  state.emitMutation("sketch:updated", {
689
754
  id: input.sketchId,
690
- updated: ["algorithm"]
755
+ updated
691
756
  });
692
757
  return {
693
758
  success: true,
@@ -695,6 +760,7 @@ async function updateAlgorithm(state, input) {
695
760
  renderer: def.renderer.type,
696
761
  algorithmLength: input.algorithm.length,
697
762
  validationPassed,
763
+ ...hasNewComponents ? { componentsUpdated: true } : {},
698
764
  fileContent: json
699
765
  };
700
766
  }
@@ -1992,9 +2058,216 @@ ${guidelines}`,
1992
2058
  };
1993
2059
  }
1994
2060
 
1995
- // src/tools/capture.ts
2061
+ // src/tools/components.ts
1996
2062
  var import_promises5 = require("fs/promises");
1997
2063
  var import_core7 = require("@genart-dev/core");
2064
+ var VALID_RENDERERS2 = [
2065
+ "p5",
2066
+ "three",
2067
+ "glsl",
2068
+ "canvas2d",
2069
+ "svg"
2070
+ ];
2071
+ var RENDERER_TARGET = {
2072
+ p5: "js",
2073
+ three: "js",
2074
+ canvas2d: "js",
2075
+ svg: "js",
2076
+ glsl: "glsl"
2077
+ };
2078
+ async function listComponents(_state, input) {
2079
+ let entries = Object.values(import_core7.COMPONENT_REGISTRY);
2080
+ if (input.renderer) {
2081
+ const renderer = input.renderer;
2082
+ const target = RENDERER_TARGET[renderer];
2083
+ if (!target) {
2084
+ throw new Error(
2085
+ `Unknown renderer type: '${input.renderer}'. Valid types: ${VALID_RENDERERS2.join(", ")}`
2086
+ );
2087
+ }
2088
+ entries = entries.filter(
2089
+ (e) => e.target === target && (e.renderers.length === 0 || e.renderers.includes(renderer))
2090
+ );
2091
+ }
2092
+ if (input.category) {
2093
+ const cat = input.category;
2094
+ entries = entries.filter((e) => e.category === cat);
2095
+ }
2096
+ entries.sort((a, b) => {
2097
+ const catCmp = a.category.localeCompare(b.category);
2098
+ if (catCmp !== 0) return catCmp;
2099
+ return a.name.localeCompare(b.name);
2100
+ });
2101
+ const components = entries.map((e) => ({
2102
+ name: e.name,
2103
+ version: e.version,
2104
+ category: e.category,
2105
+ target: e.target,
2106
+ exports: [...e.exports],
2107
+ dependencies: [...e.dependencies],
2108
+ description: e.description
2109
+ }));
2110
+ return {
2111
+ count: components.length,
2112
+ components
2113
+ };
2114
+ }
2115
+ async function addComponent(state, input) {
2116
+ const loaded = state.requireSketch(input.sketchId);
2117
+ const def = loaded.definition;
2118
+ const renderer = def.renderer.type;
2119
+ const entry = import_core7.COMPONENT_REGISTRY[input.component];
2120
+ if (!entry) {
2121
+ throw new Error(`Unknown component: "${input.component}"`);
2122
+ }
2123
+ const target = RENDERER_TARGET[renderer];
2124
+ if (entry.target !== target) {
2125
+ throw new Error(
2126
+ `Component "${input.component}" has target "${entry.target}" but renderer "${renderer}" requires target "${target}"`
2127
+ );
2128
+ }
2129
+ if (entry.renderers.length > 0 && !entry.renderers.includes(renderer)) {
2130
+ throw new Error(
2131
+ `Component "${input.component}" is not compatible with renderer "${renderer}". Compatible: ${entry.renderers.join(", ")}`
2132
+ );
2133
+ }
2134
+ const existingComponents = {};
2135
+ if (def.components) {
2136
+ for (const [name, value] of Object.entries(def.components)) {
2137
+ if (typeof value === "string") {
2138
+ existingComponents[name] = value;
2139
+ } else if (value.version) {
2140
+ existingComponents[name] = value.version;
2141
+ }
2142
+ }
2143
+ }
2144
+ if (existingComponents[input.component]) {
2145
+ throw new Error(
2146
+ `Component "${input.component}" is already present in sketch "${input.sketchId}"`
2147
+ );
2148
+ }
2149
+ existingComponents[input.component] = input.version ?? "^1.0.0";
2150
+ const resolved = (0, import_core7.resolveComponents)(existingComponents, renderer);
2151
+ const resolvedRecord = {};
2152
+ for (const rc of resolved) {
2153
+ resolvedRecord[rc.name] = {
2154
+ version: rc.version,
2155
+ code: rc.code,
2156
+ exports: [...rc.exports]
2157
+ };
2158
+ }
2159
+ const previousNames = new Set(
2160
+ def.components ? Object.keys(def.components) : []
2161
+ );
2162
+ const added = resolved.map((rc) => rc.name).filter((name) => !previousNames.has(name));
2163
+ const newDef = {
2164
+ ...def,
2165
+ genart: "1.2",
2166
+ modified: (/* @__PURE__ */ new Date()).toISOString(),
2167
+ components: resolvedRecord
2168
+ };
2169
+ state.sketches.set(input.sketchId, {
2170
+ definition: newDef,
2171
+ path: loaded.path
2172
+ });
2173
+ const json = (0, import_core7.serializeGenart)(newDef);
2174
+ if (!state.remoteMode) {
2175
+ await (0, import_promises5.writeFile)(loaded.path, json, "utf-8");
2176
+ }
2177
+ state.emitMutation("sketch:updated", {
2178
+ id: input.sketchId,
2179
+ updated: ["components"]
2180
+ });
2181
+ return {
2182
+ success: true,
2183
+ sketchId: input.sketchId,
2184
+ components: resolvedRecord,
2185
+ added,
2186
+ fileContent: json
2187
+ };
2188
+ }
2189
+ async function removeComponent(state, input) {
2190
+ const loaded = state.requireSketch(input.sketchId);
2191
+ const def = loaded.definition;
2192
+ if (!def.components || !def.components[input.component]) {
2193
+ throw new Error(
2194
+ `Component "${input.component}" is not present in sketch "${input.sketchId}"`
2195
+ );
2196
+ }
2197
+ const remaining = { ...def.components };
2198
+ delete remaining[input.component];
2199
+ for (const [name, value] of Object.entries(remaining)) {
2200
+ const entry = import_core7.COMPONENT_REGISTRY[name];
2201
+ if (entry && entry.dependencies.includes(input.component)) {
2202
+ throw new Error(
2203
+ `Cannot remove "${input.component}": component "${name}" depends on it`
2204
+ );
2205
+ }
2206
+ }
2207
+ let warning;
2208
+ const removedValue = def.components[input.component];
2209
+ const exports2 = typeof removedValue === "string" ? import_core7.COMPONENT_REGISTRY[input.component]?.exports ?? [] : removedValue.exports ?? [];
2210
+ const usedExports = exports2.filter((exp) => def.algorithm.includes(exp));
2211
+ if (usedExports.length > 0) {
2212
+ warning = `Algorithm may reference these exports from "${input.component}": ${usedExports.join(", ")}. Review your algorithm after removal.`;
2213
+ }
2214
+ const removed = [input.component];
2215
+ const neededDeps = /* @__PURE__ */ new Set();
2216
+ for (const name of Object.keys(remaining)) {
2217
+ const entry = import_core7.COMPONENT_REGISTRY[name];
2218
+ if (entry) {
2219
+ collectTransitiveDeps(name, neededDeps);
2220
+ }
2221
+ }
2222
+ for (const name of Object.keys(remaining)) {
2223
+ if (!neededDeps.has(name) && !isDirectComponent(name, remaining)) {
2224
+ delete remaining[name];
2225
+ removed.push(name);
2226
+ }
2227
+ }
2228
+ const hasRemaining = Object.keys(remaining).length > 0;
2229
+ const newDef = {
2230
+ ...def,
2231
+ modified: (/* @__PURE__ */ new Date()).toISOString(),
2232
+ ...hasRemaining ? { components: remaining } : { components: void 0 }
2233
+ };
2234
+ state.sketches.set(input.sketchId, {
2235
+ definition: newDef,
2236
+ path: loaded.path
2237
+ });
2238
+ const json = (0, import_core7.serializeGenart)(newDef);
2239
+ if (!state.remoteMode) {
2240
+ await (0, import_promises5.writeFile)(loaded.path, json, "utf-8");
2241
+ }
2242
+ state.emitMutation("sketch:updated", {
2243
+ id: input.sketchId,
2244
+ updated: ["components"]
2245
+ });
2246
+ return {
2247
+ success: true,
2248
+ sketchId: input.sketchId,
2249
+ removed,
2250
+ ...warning ? { warning } : {},
2251
+ fileContent: json
2252
+ };
2253
+ }
2254
+ function collectTransitiveDeps(name, deps) {
2255
+ const entry = import_core7.COMPONENT_REGISTRY[name];
2256
+ if (!entry) return;
2257
+ deps.add(name);
2258
+ for (const dep of entry.dependencies) {
2259
+ if (!deps.has(dep)) {
2260
+ collectTransitiveDeps(dep, deps);
2261
+ }
2262
+ }
2263
+ }
2264
+ function isDirectComponent(name, components) {
2265
+ return name in components;
2266
+ }
2267
+
2268
+ // src/tools/capture.ts
2269
+ var import_promises6 = require("fs/promises");
2270
+ var import_core8 = require("@genart-dev/core");
1998
2271
 
1999
2272
  // src/capture/headless.ts
2000
2273
  var cachedModule = null;
@@ -2088,7 +2361,7 @@ async function captureHtmlMulti(options) {
2088
2361
  }
2089
2362
 
2090
2363
  // src/tools/capture.ts
2091
- var registry2 = (0, import_core7.createDefaultRegistry)();
2364
+ var registry2 = (0, import_core8.createDefaultRegistry)();
2092
2365
  function applyOverrides(sketch, overrides) {
2093
2366
  if (overrides.seed === void 0 && overrides.params === void 0) {
2094
2367
  return sketch;
@@ -2169,7 +2442,7 @@ async function buildScreenshotMetadata(state, multi, info) {
2169
2442
  previewPath: info.previewPath
2170
2443
  };
2171
2444
  if (!state.remoteMode) {
2172
- await (0, import_promises5.writeFile)(info.previewPath, multi.previewPng);
2445
+ await (0, import_promises6.writeFile)(info.previewPath, multi.previewPng);
2173
2446
  metadata.savedPreviewTo = info.previewPath;
2174
2447
  }
2175
2448
  return metadata;
@@ -2230,15 +2503,15 @@ async function captureBatch(state, input) {
2230
2503
 
2231
2504
  // src/tools/export.ts
2232
2505
  var import_fs = require("fs");
2233
- var import_promises6 = require("fs/promises");
2506
+ var import_promises7 = require("fs/promises");
2234
2507
  var import_path8 = require("path");
2235
2508
  var import_archiver = __toESM(require("archiver"), 1);
2236
- var import_core8 = require("@genart-dev/core");
2237
- var registry3 = (0, import_core8.createDefaultRegistry)();
2509
+ var import_core9 = require("@genart-dev/core");
2510
+ var registry3 = (0, import_core9.createDefaultRegistry)();
2238
2511
  async function validateOutputPath(outputPath) {
2239
2512
  const parentDir = (0, import_path8.dirname)(outputPath);
2240
2513
  try {
2241
- const s = await (0, import_promises6.stat)(parentDir);
2514
+ const s = await (0, import_promises7.stat)(parentDir);
2242
2515
  if (!s.isDirectory()) {
2243
2516
  throw new Error(`Parent directory does not exist: ${parentDir}`);
2244
2517
  }
@@ -2247,7 +2520,7 @@ async function validateOutputPath(outputPath) {
2247
2520
  throw new Error(`Parent directory does not exist: ${parentDir}`);
2248
2521
  }
2249
2522
  try {
2250
- await (0, import_promises6.stat)(outputPath);
2523
+ await (0, import_promises7.stat)(outputPath);
2251
2524
  throw new Error(
2252
2525
  `File already exists at ${outputPath}. Delete it first or use a different path.`
2253
2526
  );
@@ -2302,7 +2575,7 @@ async function exportHtml(sketch, outputPath) {
2302
2575
  const adapter = registry3.resolve(sketch.renderer.type);
2303
2576
  const html = adapter.generateStandaloneHTML(sketch);
2304
2577
  const content = Buffer.from(html, "utf-8");
2305
- await (0, import_promises6.writeFile)(outputPath, content);
2578
+ await (0, import_promises7.writeFile)(outputPath, content);
2306
2579
  return {
2307
2580
  success: true,
2308
2581
  sketchId: sketch.id,
@@ -2318,7 +2591,7 @@ async function exportPng(sketch, input) {
2318
2591
  const width = input.width ?? sketch.canvas.width;
2319
2592
  const height = input.height ?? sketch.canvas.height;
2320
2593
  const result = await captureHtml({ html, width, height });
2321
- await (0, import_promises6.writeFile)(input.outputPath, result.bytes);
2594
+ await (0, import_promises7.writeFile)(input.outputPath, result.bytes);
2322
2595
  return {
2323
2596
  success: true,
2324
2597
  sketchId: sketch.id,
@@ -2333,7 +2606,7 @@ async function exportSvg(sketch, input) {
2333
2606
  const height = input.height ?? sketch.canvas.height;
2334
2607
  if (sketch.renderer.type === "svg") {
2335
2608
  const content2 = Buffer.from(sketch.algorithm, "utf-8");
2336
- await (0, import_promises6.writeFile)(input.outputPath, content2);
2609
+ await (0, import_promises7.writeFile)(input.outputPath, content2);
2337
2610
  return {
2338
2611
  success: true,
2339
2612
  sketchId: sketch.id,
@@ -2355,7 +2628,7 @@ async function exportSvg(sketch, input) {
2355
2628
  href="data:image/png;base64,${b64}"/>
2356
2629
  </svg>`;
2357
2630
  const content = Buffer.from(svg, "utf-8");
2358
- await (0, import_promises6.writeFile)(input.outputPath, content);
2631
+ await (0, import_promises7.writeFile)(input.outputPath, content);
2359
2632
  return {
2360
2633
  success: true,
2361
2634
  sketchId: sketch.id,
@@ -2368,7 +2641,7 @@ async function exportSvg(sketch, input) {
2368
2641
  }
2369
2642
  async function exportAlgorithm(sketch, outputPath) {
2370
2643
  const content = Buffer.from(sketch.algorithm, "utf-8");
2371
- await (0, import_promises6.writeFile)(outputPath, content);
2644
+ await (0, import_promises7.writeFile)(outputPath, content);
2372
2645
  return {
2373
2646
  success: true,
2374
2647
  sketchId: sketch.id,
@@ -2383,7 +2656,7 @@ async function exportZip(sketch, input) {
2383
2656
  const width = input.width ?? sketch.canvas.width;
2384
2657
  const height = input.height ?? sketch.canvas.height;
2385
2658
  const html = adapter.generateStandaloneHTML(sketch);
2386
- const genartJson = (0, import_core8.serializeGenart)(sketch);
2659
+ const genartJson = (0, import_core9.serializeGenart)(sketch);
2387
2660
  const algorithm = sketch.algorithm;
2388
2661
  const algExt = algorithmExtension(sketch.renderer.type);
2389
2662
  const captureResult = await captureHtml({ html, width, height });
@@ -2400,7 +2673,7 @@ async function exportZip(sketch, input) {
2400
2673
  archive.append(genartJson, { name: `${sketch.id}.genart` });
2401
2674
  await archive.finalize();
2402
2675
  await finished;
2403
- const s = await (0, import_promises6.stat)(input.outputPath);
2676
+ const s = await (0, import_promises7.stat)(input.outputPath);
2404
2677
  return {
2405
2678
  success: true,
2406
2679
  sketchId: sketch.id,
@@ -2418,7 +2691,7 @@ async function exportZip(sketch, input) {
2418
2691
  }
2419
2692
 
2420
2693
  // src/resources/index.ts
2421
- var import_core9 = require("@genart-dev/core");
2694
+ var import_core10 = require("@genart-dev/core");
2422
2695
  function registerResources(server, state) {
2423
2696
  registerSkillsResource(server);
2424
2697
  registerCanvasPresetsResource(server);
@@ -2426,7 +2699,7 @@ function registerResources(server, state) {
2426
2699
  registerRenderersResource(server);
2427
2700
  }
2428
2701
  function registerSkillsResource(server) {
2429
- const skillRegistry = (0, import_core9.createDefaultSkillRegistry)();
2702
+ const skillRegistry = (0, import_core10.createDefaultSkillRegistry)();
2430
2703
  server.resource(
2431
2704
  "skills",
2432
2705
  "genart://skills",
@@ -2477,7 +2750,7 @@ function registerCanvasPresetsResource(server) {
2477
2750
  mimeType: "application/json",
2478
2751
  text: JSON.stringify(
2479
2752
  {
2480
- presets: import_core9.CANVAS_PRESETS.map((p) => ({
2753
+ presets: import_core10.CANVAS_PRESETS.map((p) => ({
2481
2754
  id: p.id,
2482
2755
  label: p.label,
2483
2756
  category: p.category,
@@ -2534,7 +2807,7 @@ function registerGalleryResource(server, state) {
2534
2807
  );
2535
2808
  }
2536
2809
  function registerRenderersResource(server) {
2537
- const registry4 = (0, import_core9.createDefaultRegistry)();
2810
+ const registry4 = (0, import_core10.createDefaultRegistry)();
2538
2811
  server.resource(
2539
2812
  "renderers",
2540
2813
  "genart://renderers",
@@ -2916,6 +3189,7 @@ function createServer(state) {
2916
3189
  );
2917
3190
  registerWorkspaceTools(server, state);
2918
3191
  registerSketchTools(server, state);
3192
+ registerComponentTools(server, state);
2919
3193
  registerSelectionTools(server, state);
2920
3194
  registerParameterTools(server, state);
2921
3195
  registerArrangementTools(server, state);
@@ -3019,7 +3293,7 @@ function registerWorkspaceTools(server, state) {
3019
3293
  function registerSketchTools(server, state) {
3020
3294
  server.tool(
3021
3295
  "create_sketch",
3022
- "Create a new .genart sketch file from metadata, parameters, and algorithm",
3296
+ 'Create a new .genart sketch file from metadata, parameters, and algorithm. IMPORTANT: Do not embed common utilities (PRNG, noise, easing, color math, vector ops) inline in the algorithm. Instead, declare them as components: { "prng": "^1.0.0", "noise-2d": "^1.0.0" }. Then use the exported functions directly in your algorithm (e.g., mulberry32, fbm2D). Use list_components to see all available components for the current renderer.',
3023
3297
  {
3024
3298
  id: import_zod2.z.string().describe("URL-safe kebab-case identifier"),
3025
3299
  title: import_zod2.z.string().describe("Human-readable title"),
@@ -3057,6 +3331,16 @@ function registerSketchTools(server, state) {
3057
3331
  algorithm: import_zod2.z.string().optional().describe("Algorithm source code (default: renderer template). For p5: must be `function sketch(p, state) { ... }` in instance mode. State provides: state.WIDTH, state.HEIGHT, state.SEED (number), state.PARAMS (keyed by param key), state.COLORS (keyed by color key, hex strings). Use p5 instance methods (p.createCanvas, p.background, etc)."),
3058
3332
  seed: import_zod2.z.number().optional().describe("Initial random seed (default: random)"),
3059
3333
  skills: import_zod2.z.array(import_zod2.z.string()).optional().describe("Design skill references"),
3334
+ components: import_zod2.z.record(
3335
+ import_zod2.z.union([
3336
+ import_zod2.z.string(),
3337
+ import_zod2.z.object({
3338
+ version: import_zod2.z.string().optional(),
3339
+ code: import_zod2.z.string().optional(),
3340
+ exports: import_zod2.z.array(import_zod2.z.string()).optional()
3341
+ })
3342
+ ])
3343
+ ).optional().describe('Component dependencies. Use list_components to see available. Keys are component names, values are semver ranges (e.g. "^1.0.0") or objects with version/code/exports.'),
3060
3344
  addToWorkspace: import_zod2.z.string().optional().describe("Path to workspace to add sketch to after creation"),
3061
3345
  agent: import_zod2.z.string().optional().describe("Your CLI agent name (e.g. 'claude-code', 'codex-cli', 'gemini-cli', 'opencode', 'kiro')"),
3062
3346
  model: import_zod2.z.string().optional().describe("Your AI model identifier (e.g. 'claude-opus-4-6', 'gpt-4o', 'gemini-2.5-pro')")
@@ -3136,11 +3420,21 @@ function registerSketchTools(server, state) {
3136
3420
  );
3137
3421
  server.tool(
3138
3422
  "update_algorithm",
3139
- "Replace the algorithm source code of a sketch",
3423
+ "Replace the algorithm source code of a sketch. If adding/changing components, pass them in the components field alongside the algorithm.",
3140
3424
  {
3141
3425
  sketchId: import_zod2.z.string().describe("ID of the sketch to update"),
3142
3426
  algorithm: import_zod2.z.string().describe("New algorithm source code. For p5: must be `function sketch(p, state) { ... }` in instance mode. State provides: state.WIDTH, state.HEIGHT, state.SEED, state.PARAMS (keyed by param key), state.COLORS (keyed by color key)."),
3143
3427
  validate: import_zod2.z.boolean().optional().describe("Run renderer-specific validation before saving (default: true)"),
3428
+ components: import_zod2.z.record(
3429
+ import_zod2.z.union([
3430
+ import_zod2.z.string(),
3431
+ import_zod2.z.object({
3432
+ version: import_zod2.z.string().optional(),
3433
+ code: import_zod2.z.string().optional(),
3434
+ exports: import_zod2.z.array(import_zod2.z.string()).optional()
3435
+ })
3436
+ ])
3437
+ ).optional().describe("Component dependencies to resolve alongside the algorithm update. Use list_components to see available."),
3144
3438
  agent: import_zod2.z.string().optional().describe("Your CLI agent name (e.g. 'claude-code', 'codex-cli', 'gemini-cli', 'opencode', 'kiro')"),
3145
3439
  model: import_zod2.z.string().optional().describe("Your AI model identifier (e.g. 'claude-opus-4-6', 'gpt-4o', 'gemini-2.5-pro')")
3146
3440
  },
@@ -3236,6 +3530,76 @@ function registerSketchTools(server, state) {
3236
3530
  }
3237
3531
  );
3238
3532
  }
3533
+ function registerComponentTools(server, state) {
3534
+ server.tool(
3535
+ "list_components",
3536
+ "List available reusable components from the registry, filtered by renderer and/or category. Components provide common utilities (PRNG, noise, easing, color math, etc.) that can be declared as dependencies instead of inlining code in the algorithm.",
3537
+ {
3538
+ renderer: import_zod2.z.enum(["p5", "three", "glsl", "canvas2d", "svg"]).optional().describe("Filter by renderer compatibility"),
3539
+ category: import_zod2.z.enum([
3540
+ "randomness",
3541
+ "noise",
3542
+ "math",
3543
+ "easing",
3544
+ "color",
3545
+ "vector",
3546
+ "geometry",
3547
+ "grid",
3548
+ "particle",
3549
+ "physics",
3550
+ "distribution",
3551
+ "pattern",
3552
+ "sdf",
3553
+ "transform",
3554
+ "animation",
3555
+ "string",
3556
+ "data-structure",
3557
+ "imaging"
3558
+ ]).optional().describe("Filter by component category")
3559
+ },
3560
+ async (args) => {
3561
+ try {
3562
+ const result = await listComponents(state, args);
3563
+ return jsonResult(result);
3564
+ } catch (e) {
3565
+ return toolError(e instanceof Error ? e.message : String(e));
3566
+ }
3567
+ }
3568
+ );
3569
+ server.tool(
3570
+ "add_component",
3571
+ "Add a component dependency to an existing sketch. Resolves the component and any transitive dependencies from the registry, validates renderer compatibility, and writes the resolved form to the sketch file.",
3572
+ {
3573
+ sketchId: import_zod2.z.string().describe("ID of the sketch to add the component to"),
3574
+ component: import_zod2.z.string().describe("Component name (e.g. 'prng', 'noise-2d', 'glsl-noise')"),
3575
+ version: import_zod2.z.string().optional().describe("Version range (default: '^1.0.0')")
3576
+ },
3577
+ async (args) => {
3578
+ try {
3579
+ const result = await addComponent(state, args);
3580
+ return jsonResult(result);
3581
+ } catch (e) {
3582
+ return toolError(e instanceof Error ? e.message : String(e));
3583
+ }
3584
+ }
3585
+ );
3586
+ server.tool(
3587
+ "remove_component",
3588
+ "Remove a component dependency from a sketch. Checks for dependent components and warns if the algorithm references the component's exports.",
3589
+ {
3590
+ sketchId: import_zod2.z.string().describe("ID of the sketch to remove the component from"),
3591
+ component: import_zod2.z.string().describe("Component name to remove")
3592
+ },
3593
+ async (args) => {
3594
+ try {
3595
+ const result = await removeComponent(state, args);
3596
+ return jsonResult(result);
3597
+ } catch (e) {
3598
+ return toolError(e instanceof Error ? e.message : String(e));
3599
+ }
3600
+ }
3601
+ );
3602
+ }
3239
3603
  function registerSelectionTools(server, state) {
3240
3604
  server.tool(
3241
3605
  "get_selection",
@@ -3673,7 +4037,7 @@ function registerKnowledgeTools(server, _state) {
3673
4037
 
3674
4038
  // src/state.ts
3675
4039
  var import_events = require("events");
3676
- var import_promises7 = require("fs/promises");
4040
+ var import_promises8 = require("fs/promises");
3677
4041
  var import_path9 = require("path");
3678
4042
 
3679
4043
  // src/sidecar.ts
@@ -3689,8 +4053,8 @@ function notifyMutation(type, payload) {
3689
4053
  }
3690
4054
 
3691
4055
  // src/state.ts
3692
- var import_core10 = require("@genart-dev/core");
3693
- var import_promises8 = require("fs/promises");
4056
+ var import_core11 = require("@genart-dev/core");
4057
+ var import_promises9 = require("fs/promises");
3694
4058
  var EditorState = class extends import_events.EventEmitter {
3695
4059
  /** Absolute path to the active .genart-workspace file, or null. */
3696
4060
  workspacePath = null;
@@ -3769,9 +4133,9 @@ var EditorState = class extends import_events.EventEmitter {
3769
4133
  if (this.basePath && !absPath.startsWith(this.basePath)) {
3770
4134
  throw new Error(`Path escapes sandbox: ${absPath}`);
3771
4135
  }
3772
- const raw = await (0, import_promises7.readFile)(absPath, "utf-8");
4136
+ const raw = await (0, import_promises8.readFile)(absPath, "utf-8");
3773
4137
  const json = JSON.parse(raw);
3774
- const ws = (0, import_core10.parseWorkspace)(json);
4138
+ const ws = (0, import_core11.parseWorkspace)(json);
3775
4139
  this.workspacePath = absPath;
3776
4140
  this.workspace = ws;
3777
4141
  this.sketches.clear();
@@ -3787,9 +4151,9 @@ var EditorState = class extends import_events.EventEmitter {
3787
4151
  if (this.basePath && !absPath.startsWith(this.basePath)) {
3788
4152
  throw new Error(`Path escapes sandbox: ${absPath}`);
3789
4153
  }
3790
- const raw = await (0, import_promises7.readFile)(absPath, "utf-8");
4154
+ const raw = await (0, import_promises8.readFile)(absPath, "utf-8");
3791
4155
  const json = JSON.parse(raw);
3792
- const definition = (0, import_core10.parseGenart)(json);
4156
+ const definition = (0, import_core11.parseGenart)(json);
3793
4157
  this.sketches.set(definition.id, { definition, path: absPath });
3794
4158
  this.emitMutation("sketch:loaded", { id: definition.id, path: absPath });
3795
4159
  return definition;
@@ -3824,18 +4188,18 @@ var EditorState = class extends import_events.EventEmitter {
3824
4188
  if (!this.workspace || !this.workspacePath) {
3825
4189
  throw new Error("No workspace is currently open");
3826
4190
  }
3827
- const json = (0, import_core10.serializeWorkspace)(this.workspace);
4191
+ const json = (0, import_core11.serializeWorkspace)(this.workspace);
3828
4192
  if (!this.remoteMode) {
3829
- await (0, import_promises8.writeFile)(this.workspacePath, json, "utf-8");
4193
+ await (0, import_promises9.writeFile)(this.workspacePath, json, "utf-8");
3830
4194
  }
3831
4195
  this.emitMutation("workspace:saved", { path: this.workspacePath });
3832
4196
  }
3833
4197
  /** Save a sketch to disk. */
3834
4198
  async saveSketch(id) {
3835
4199
  const loaded = this.requireSketch(id);
3836
- const json = (0, import_core10.serializeGenart)(loaded.definition);
4200
+ const json = (0, import_core11.serializeGenart)(loaded.definition);
3837
4201
  if (!this.remoteMode) {
3838
- await (0, import_promises8.writeFile)(loaded.path, json, "utf-8");
4202
+ await (0, import_promises9.writeFile)(loaded.path, json, "utf-8");
3839
4203
  }
3840
4204
  this.emitMutation("sketch:saved", { id, path: loaded.path });
3841
4205
  }