@genart-dev/mcp-server 0.1.2 → 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/index.cjs CHANGED
@@ -657,10 +657,40 @@ async function createSketch(state, input) {
657
657
  const adapter = registry4.resolve(rendererType);
658
658
  algorithm = adapter.getAlgorithmTemplate();
659
659
  }
660
+ let resolvedComponents;
661
+ if (input.components && Object.keys(input.components).length > 0) {
662
+ const shorthand = {};
663
+ for (const [name, value] of Object.entries(input.components)) {
664
+ if (typeof value === "string") {
665
+ shorthand[name] = value;
666
+ } else if (value.version) {
667
+ shorthand[name] = value.version;
668
+ } else if (value.code) {
669
+ if (!resolvedComponents) resolvedComponents = {};
670
+ resolvedComponents[name] = {
671
+ ...value.version ? { version: value.version } : {},
672
+ code: value.code,
673
+ ...value.exports ? { exports: value.exports } : {}
674
+ };
675
+ }
676
+ }
677
+ if (Object.keys(shorthand).length > 0) {
678
+ const resolved = (0, import_core3.resolveComponents)(shorthand, rendererType);
679
+ if (!resolvedComponents) resolvedComponents = {};
680
+ for (const rc of resolved) {
681
+ resolvedComponents[rc.name] = {
682
+ version: rc.version,
683
+ code: rc.code,
684
+ exports: [...rc.exports]
685
+ };
686
+ }
687
+ }
688
+ }
660
689
  const seed = input.seed ?? Math.floor(Math.random() * 1e5);
661
690
  const ts = now2();
691
+ const hasComponents = resolvedComponents && Object.keys(resolvedComponents).length > 0;
662
692
  const sketch = {
663
- genart: "1.1",
693
+ genart: hasComponents ? "1.2" : "1.1",
664
694
  id: input.id,
665
695
  title: input.title,
666
696
  created: ts,
@@ -674,6 +704,7 @@ async function createSketch(state, input) {
674
704
  ...input.philosophy ? { philosophy: input.philosophy } : {},
675
705
  ...input.themes && input.themes.length > 0 ? { themes: input.themes } : {},
676
706
  ...input.skills && input.skills.length > 0 ? { skills: input.skills } : {},
707
+ ...hasComponents ? { components: resolvedComponents } : {},
677
708
  ...input.agent ? { agent: input.agent } : {},
678
709
  ...input.model ? { model: input.model } : {}
679
710
  };
@@ -857,10 +888,44 @@ async function updateAlgorithm(state, input) {
857
888
  );
858
889
  }
859
890
  }
891
+ let resolvedComponents;
892
+ if (input.components && Object.keys(input.components).length > 0) {
893
+ const renderer = def.renderer.type;
894
+ const shorthand = {};
895
+ for (const [name, value] of Object.entries(input.components)) {
896
+ if (typeof value === "string") {
897
+ shorthand[name] = value;
898
+ } else if (value.version) {
899
+ shorthand[name] = value.version;
900
+ } else if (value.code) {
901
+ if (!resolvedComponents) resolvedComponents = {};
902
+ resolvedComponents[name] = {
903
+ ...value.version ? { version: value.version } : {},
904
+ code: value.code,
905
+ ...value.exports ? { exports: value.exports } : {}
906
+ };
907
+ }
908
+ }
909
+ if (Object.keys(shorthand).length > 0) {
910
+ const resolved = (0, import_core3.resolveComponents)(shorthand, renderer);
911
+ if (!resolvedComponents) resolvedComponents = {};
912
+ for (const rc of resolved) {
913
+ resolvedComponents[rc.name] = {
914
+ version: rc.version,
915
+ code: rc.code,
916
+ exports: [...rc.exports]
917
+ };
918
+ }
919
+ }
920
+ }
921
+ const updated = ["algorithm"];
922
+ const hasNewComponents = resolvedComponents && Object.keys(resolvedComponents).length > 0;
923
+ if (hasNewComponents) updated.push("components");
860
924
  const newDef = {
861
925
  ...def,
862
926
  modified: now2(),
863
927
  algorithm: input.algorithm,
928
+ ...hasNewComponents ? { genart: "1.2", components: resolvedComponents } : {},
864
929
  ...input.agent ? { agent: input.agent } : {},
865
930
  ...input.model ? { model: input.model } : {}
866
931
  };
@@ -874,7 +939,7 @@ async function updateAlgorithm(state, input) {
874
939
  }
875
940
  state.emitMutation("sketch:updated", {
876
941
  id: input.sketchId,
877
- updated: ["algorithm"]
942
+ updated
878
943
  });
879
944
  return {
880
945
  success: true,
@@ -882,6 +947,7 @@ async function updateAlgorithm(state, input) {
882
947
  renderer: def.renderer.type,
883
948
  algorithmLength: input.algorithm.length,
884
949
  validationPassed,
950
+ ...hasNewComponents ? { componentsUpdated: true } : {},
885
951
  fileContent: json
886
952
  };
887
953
  }
@@ -2179,9 +2245,216 @@ ${guidelines}`,
2179
2245
  };
2180
2246
  }
2181
2247
 
2182
- // src/tools/capture.ts
2248
+ // src/tools/components.ts
2183
2249
  var import_promises7 = require("fs/promises");
2184
2250
  var import_core8 = require("@genart-dev/core");
2251
+ var VALID_RENDERERS2 = [
2252
+ "p5",
2253
+ "three",
2254
+ "glsl",
2255
+ "canvas2d",
2256
+ "svg"
2257
+ ];
2258
+ var RENDERER_TARGET = {
2259
+ p5: "js",
2260
+ three: "js",
2261
+ canvas2d: "js",
2262
+ svg: "js",
2263
+ glsl: "glsl"
2264
+ };
2265
+ async function listComponents(_state, input) {
2266
+ let entries = Object.values(import_core8.COMPONENT_REGISTRY);
2267
+ if (input.renderer) {
2268
+ const renderer = input.renderer;
2269
+ const target = RENDERER_TARGET[renderer];
2270
+ if (!target) {
2271
+ throw new Error(
2272
+ `Unknown renderer type: '${input.renderer}'. Valid types: ${VALID_RENDERERS2.join(", ")}`
2273
+ );
2274
+ }
2275
+ entries = entries.filter(
2276
+ (e) => e.target === target && (e.renderers.length === 0 || e.renderers.includes(renderer))
2277
+ );
2278
+ }
2279
+ if (input.category) {
2280
+ const cat = input.category;
2281
+ entries = entries.filter((e) => e.category === cat);
2282
+ }
2283
+ entries.sort((a, b) => {
2284
+ const catCmp = a.category.localeCompare(b.category);
2285
+ if (catCmp !== 0) return catCmp;
2286
+ return a.name.localeCompare(b.name);
2287
+ });
2288
+ const components = entries.map((e) => ({
2289
+ name: e.name,
2290
+ version: e.version,
2291
+ category: e.category,
2292
+ target: e.target,
2293
+ exports: [...e.exports],
2294
+ dependencies: [...e.dependencies],
2295
+ description: e.description
2296
+ }));
2297
+ return {
2298
+ count: components.length,
2299
+ components
2300
+ };
2301
+ }
2302
+ async function addComponent(state, input) {
2303
+ const loaded = state.requireSketch(input.sketchId);
2304
+ const def = loaded.definition;
2305
+ const renderer = def.renderer.type;
2306
+ const entry = import_core8.COMPONENT_REGISTRY[input.component];
2307
+ if (!entry) {
2308
+ throw new Error(`Unknown component: "${input.component}"`);
2309
+ }
2310
+ const target = RENDERER_TARGET[renderer];
2311
+ if (entry.target !== target) {
2312
+ throw new Error(
2313
+ `Component "${input.component}" has target "${entry.target}" but renderer "${renderer}" requires target "${target}"`
2314
+ );
2315
+ }
2316
+ if (entry.renderers.length > 0 && !entry.renderers.includes(renderer)) {
2317
+ throw new Error(
2318
+ `Component "${input.component}" is not compatible with renderer "${renderer}". Compatible: ${entry.renderers.join(", ")}`
2319
+ );
2320
+ }
2321
+ const existingComponents = {};
2322
+ if (def.components) {
2323
+ for (const [name, value] of Object.entries(def.components)) {
2324
+ if (typeof value === "string") {
2325
+ existingComponents[name] = value;
2326
+ } else if (value.version) {
2327
+ existingComponents[name] = value.version;
2328
+ }
2329
+ }
2330
+ }
2331
+ if (existingComponents[input.component]) {
2332
+ throw new Error(
2333
+ `Component "${input.component}" is already present in sketch "${input.sketchId}"`
2334
+ );
2335
+ }
2336
+ existingComponents[input.component] = input.version ?? "^1.0.0";
2337
+ const resolved = (0, import_core8.resolveComponents)(existingComponents, renderer);
2338
+ const resolvedRecord = {};
2339
+ for (const rc of resolved) {
2340
+ resolvedRecord[rc.name] = {
2341
+ version: rc.version,
2342
+ code: rc.code,
2343
+ exports: [...rc.exports]
2344
+ };
2345
+ }
2346
+ const previousNames = new Set(
2347
+ def.components ? Object.keys(def.components) : []
2348
+ );
2349
+ const added = resolved.map((rc) => rc.name).filter((name) => !previousNames.has(name));
2350
+ const newDef = {
2351
+ ...def,
2352
+ genart: "1.2",
2353
+ modified: (/* @__PURE__ */ new Date()).toISOString(),
2354
+ components: resolvedRecord
2355
+ };
2356
+ state.sketches.set(input.sketchId, {
2357
+ definition: newDef,
2358
+ path: loaded.path
2359
+ });
2360
+ const json = (0, import_core8.serializeGenart)(newDef);
2361
+ if (!state.remoteMode) {
2362
+ await (0, import_promises7.writeFile)(loaded.path, json, "utf-8");
2363
+ }
2364
+ state.emitMutation("sketch:updated", {
2365
+ id: input.sketchId,
2366
+ updated: ["components"]
2367
+ });
2368
+ return {
2369
+ success: true,
2370
+ sketchId: input.sketchId,
2371
+ components: resolvedRecord,
2372
+ added,
2373
+ fileContent: json
2374
+ };
2375
+ }
2376
+ async function removeComponent(state, input) {
2377
+ const loaded = state.requireSketch(input.sketchId);
2378
+ const def = loaded.definition;
2379
+ if (!def.components || !def.components[input.component]) {
2380
+ throw new Error(
2381
+ `Component "${input.component}" is not present in sketch "${input.sketchId}"`
2382
+ );
2383
+ }
2384
+ const remaining = { ...def.components };
2385
+ delete remaining[input.component];
2386
+ for (const [name, value] of Object.entries(remaining)) {
2387
+ const entry = import_core8.COMPONENT_REGISTRY[name];
2388
+ if (entry && entry.dependencies.includes(input.component)) {
2389
+ throw new Error(
2390
+ `Cannot remove "${input.component}": component "${name}" depends on it`
2391
+ );
2392
+ }
2393
+ }
2394
+ let warning;
2395
+ const removedValue = def.components[input.component];
2396
+ const exports2 = typeof removedValue === "string" ? import_core8.COMPONENT_REGISTRY[input.component]?.exports ?? [] : removedValue.exports ?? [];
2397
+ const usedExports = exports2.filter((exp) => def.algorithm.includes(exp));
2398
+ if (usedExports.length > 0) {
2399
+ warning = `Algorithm may reference these exports from "${input.component}": ${usedExports.join(", ")}. Review your algorithm after removal.`;
2400
+ }
2401
+ const removed = [input.component];
2402
+ const neededDeps = /* @__PURE__ */ new Set();
2403
+ for (const name of Object.keys(remaining)) {
2404
+ const entry = import_core8.COMPONENT_REGISTRY[name];
2405
+ if (entry) {
2406
+ collectTransitiveDeps(name, neededDeps);
2407
+ }
2408
+ }
2409
+ for (const name of Object.keys(remaining)) {
2410
+ if (!neededDeps.has(name) && !isDirectComponent(name, remaining)) {
2411
+ delete remaining[name];
2412
+ removed.push(name);
2413
+ }
2414
+ }
2415
+ const hasRemaining = Object.keys(remaining).length > 0;
2416
+ const newDef = {
2417
+ ...def,
2418
+ modified: (/* @__PURE__ */ new Date()).toISOString(),
2419
+ ...hasRemaining ? { components: remaining } : { components: void 0 }
2420
+ };
2421
+ state.sketches.set(input.sketchId, {
2422
+ definition: newDef,
2423
+ path: loaded.path
2424
+ });
2425
+ const json = (0, import_core8.serializeGenart)(newDef);
2426
+ if (!state.remoteMode) {
2427
+ await (0, import_promises7.writeFile)(loaded.path, json, "utf-8");
2428
+ }
2429
+ state.emitMutation("sketch:updated", {
2430
+ id: input.sketchId,
2431
+ updated: ["components"]
2432
+ });
2433
+ return {
2434
+ success: true,
2435
+ sketchId: input.sketchId,
2436
+ removed,
2437
+ ...warning ? { warning } : {},
2438
+ fileContent: json
2439
+ };
2440
+ }
2441
+ function collectTransitiveDeps(name, deps) {
2442
+ const entry = import_core8.COMPONENT_REGISTRY[name];
2443
+ if (!entry) return;
2444
+ deps.add(name);
2445
+ for (const dep of entry.dependencies) {
2446
+ if (!deps.has(dep)) {
2447
+ collectTransitiveDeps(dep, deps);
2448
+ }
2449
+ }
2450
+ }
2451
+ function isDirectComponent(name, components) {
2452
+ return name in components;
2453
+ }
2454
+
2455
+ // src/tools/capture.ts
2456
+ var import_promises8 = require("fs/promises");
2457
+ var import_core9 = require("@genart-dev/core");
2185
2458
 
2186
2459
  // src/capture/headless.ts
2187
2460
  var cachedModule = null;
@@ -2275,7 +2548,7 @@ async function captureHtmlMulti(options) {
2275
2548
  }
2276
2549
 
2277
2550
  // src/tools/capture.ts
2278
- var registry2 = (0, import_core8.createDefaultRegistry)();
2551
+ var registry2 = (0, import_core9.createDefaultRegistry)();
2279
2552
  function applyOverrides(sketch, overrides) {
2280
2553
  if (overrides.seed === void 0 && overrides.params === void 0) {
2281
2554
  return sketch;
@@ -2356,7 +2629,7 @@ async function buildScreenshotMetadata(state, multi, info) {
2356
2629
  previewPath: info.previewPath
2357
2630
  };
2358
2631
  if (!state.remoteMode) {
2359
- await (0, import_promises7.writeFile)(info.previewPath, multi.previewPng);
2632
+ await (0, import_promises8.writeFile)(info.previewPath, multi.previewPng);
2360
2633
  metadata.savedPreviewTo = info.previewPath;
2361
2634
  }
2362
2635
  return metadata;
@@ -2417,15 +2690,15 @@ async function captureBatch(state, input) {
2417
2690
 
2418
2691
  // src/tools/export.ts
2419
2692
  var import_fs = require("fs");
2420
- var import_promises8 = require("fs/promises");
2693
+ var import_promises9 = require("fs/promises");
2421
2694
  var import_path9 = require("path");
2422
2695
  var import_archiver = __toESM(require("archiver"), 1);
2423
- var import_core9 = require("@genart-dev/core");
2424
- var registry3 = (0, import_core9.createDefaultRegistry)();
2696
+ var import_core10 = require("@genart-dev/core");
2697
+ var registry3 = (0, import_core10.createDefaultRegistry)();
2425
2698
  async function validateOutputPath(outputPath) {
2426
2699
  const parentDir = (0, import_path9.dirname)(outputPath);
2427
2700
  try {
2428
- const s = await (0, import_promises8.stat)(parentDir);
2701
+ const s = await (0, import_promises9.stat)(parentDir);
2429
2702
  if (!s.isDirectory()) {
2430
2703
  throw new Error(`Parent directory does not exist: ${parentDir}`);
2431
2704
  }
@@ -2434,7 +2707,7 @@ async function validateOutputPath(outputPath) {
2434
2707
  throw new Error(`Parent directory does not exist: ${parentDir}`);
2435
2708
  }
2436
2709
  try {
2437
- await (0, import_promises8.stat)(outputPath);
2710
+ await (0, import_promises9.stat)(outputPath);
2438
2711
  throw new Error(
2439
2712
  `File already exists at ${outputPath}. Delete it first or use a different path.`
2440
2713
  );
@@ -2489,7 +2762,7 @@ async function exportHtml(sketch, outputPath) {
2489
2762
  const adapter = registry3.resolve(sketch.renderer.type);
2490
2763
  const html = adapter.generateStandaloneHTML(sketch);
2491
2764
  const content = Buffer.from(html, "utf-8");
2492
- await (0, import_promises8.writeFile)(outputPath, content);
2765
+ await (0, import_promises9.writeFile)(outputPath, content);
2493
2766
  return {
2494
2767
  success: true,
2495
2768
  sketchId: sketch.id,
@@ -2505,7 +2778,7 @@ async function exportPng(sketch, input) {
2505
2778
  const width = input.width ?? sketch.canvas.width;
2506
2779
  const height = input.height ?? sketch.canvas.height;
2507
2780
  const result = await captureHtml({ html, width, height });
2508
- await (0, import_promises8.writeFile)(input.outputPath, result.bytes);
2781
+ await (0, import_promises9.writeFile)(input.outputPath, result.bytes);
2509
2782
  return {
2510
2783
  success: true,
2511
2784
  sketchId: sketch.id,
@@ -2520,7 +2793,7 @@ async function exportSvg(sketch, input) {
2520
2793
  const height = input.height ?? sketch.canvas.height;
2521
2794
  if (sketch.renderer.type === "svg") {
2522
2795
  const content2 = Buffer.from(sketch.algorithm, "utf-8");
2523
- await (0, import_promises8.writeFile)(input.outputPath, content2);
2796
+ await (0, import_promises9.writeFile)(input.outputPath, content2);
2524
2797
  return {
2525
2798
  success: true,
2526
2799
  sketchId: sketch.id,
@@ -2542,7 +2815,7 @@ async function exportSvg(sketch, input) {
2542
2815
  href="data:image/png;base64,${b64}"/>
2543
2816
  </svg>`;
2544
2817
  const content = Buffer.from(svg, "utf-8");
2545
- await (0, import_promises8.writeFile)(input.outputPath, content);
2818
+ await (0, import_promises9.writeFile)(input.outputPath, content);
2546
2819
  return {
2547
2820
  success: true,
2548
2821
  sketchId: sketch.id,
@@ -2555,7 +2828,7 @@ async function exportSvg(sketch, input) {
2555
2828
  }
2556
2829
  async function exportAlgorithm(sketch, outputPath) {
2557
2830
  const content = Buffer.from(sketch.algorithm, "utf-8");
2558
- await (0, import_promises8.writeFile)(outputPath, content);
2831
+ await (0, import_promises9.writeFile)(outputPath, content);
2559
2832
  return {
2560
2833
  success: true,
2561
2834
  sketchId: sketch.id,
@@ -2570,7 +2843,7 @@ async function exportZip(sketch, input) {
2570
2843
  const width = input.width ?? sketch.canvas.width;
2571
2844
  const height = input.height ?? sketch.canvas.height;
2572
2845
  const html = adapter.generateStandaloneHTML(sketch);
2573
- const genartJson = (0, import_core9.serializeGenart)(sketch);
2846
+ const genartJson = (0, import_core10.serializeGenart)(sketch);
2574
2847
  const algorithm = sketch.algorithm;
2575
2848
  const algExt = algorithmExtension(sketch.renderer.type);
2576
2849
  const captureResult = await captureHtml({ html, width, height });
@@ -2587,7 +2860,7 @@ async function exportZip(sketch, input) {
2587
2860
  archive.append(genartJson, { name: `${sketch.id}.genart` });
2588
2861
  await archive.finalize();
2589
2862
  await finished;
2590
- const s = await (0, import_promises8.stat)(input.outputPath);
2863
+ const s = await (0, import_promises9.stat)(input.outputPath);
2591
2864
  return {
2592
2865
  success: true,
2593
2866
  sketchId: sketch.id,
@@ -2605,7 +2878,7 @@ async function exportZip(sketch, input) {
2605
2878
  }
2606
2879
 
2607
2880
  // src/resources/index.ts
2608
- var import_core10 = require("@genart-dev/core");
2881
+ var import_core11 = require("@genart-dev/core");
2609
2882
  function registerResources(server, state) {
2610
2883
  registerSkillsResource(server);
2611
2884
  registerCanvasPresetsResource(server);
@@ -2613,7 +2886,7 @@ function registerResources(server, state) {
2613
2886
  registerRenderersResource(server);
2614
2887
  }
2615
2888
  function registerSkillsResource(server) {
2616
- const skillRegistry = (0, import_core10.createDefaultSkillRegistry)();
2889
+ const skillRegistry = (0, import_core11.createDefaultSkillRegistry)();
2617
2890
  server.resource(
2618
2891
  "skills",
2619
2892
  "genart://skills",
@@ -2664,7 +2937,7 @@ function registerCanvasPresetsResource(server) {
2664
2937
  mimeType: "application/json",
2665
2938
  text: JSON.stringify(
2666
2939
  {
2667
- presets: import_core10.CANVAS_PRESETS.map((p) => ({
2940
+ presets: import_core11.CANVAS_PRESETS.map((p) => ({
2668
2941
  id: p.id,
2669
2942
  label: p.label,
2670
2943
  category: p.category,
@@ -2721,7 +2994,7 @@ function registerGalleryResource(server, state) {
2721
2994
  );
2722
2995
  }
2723
2996
  function registerRenderersResource(server) {
2724
- const registry4 = (0, import_core10.createDefaultRegistry)();
2997
+ const registry4 = (0, import_core11.createDefaultRegistry)();
2725
2998
  server.resource(
2726
2999
  "renderers",
2727
3000
  "genart://renderers",
@@ -3103,6 +3376,7 @@ function createServer(state) {
3103
3376
  );
3104
3377
  registerWorkspaceTools(server, state);
3105
3378
  registerSketchTools(server, state);
3379
+ registerComponentTools(server, state);
3106
3380
  registerSelectionTools(server, state);
3107
3381
  registerParameterTools(server, state);
3108
3382
  registerArrangementTools(server, state);
@@ -3206,7 +3480,7 @@ function registerWorkspaceTools(server, state) {
3206
3480
  function registerSketchTools(server, state) {
3207
3481
  server.tool(
3208
3482
  "create_sketch",
3209
- "Create a new .genart sketch file from metadata, parameters, and algorithm",
3483
+ '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.',
3210
3484
  {
3211
3485
  id: import_zod2.z.string().describe("URL-safe kebab-case identifier"),
3212
3486
  title: import_zod2.z.string().describe("Human-readable title"),
@@ -3244,6 +3518,16 @@ function registerSketchTools(server, state) {
3244
3518
  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)."),
3245
3519
  seed: import_zod2.z.number().optional().describe("Initial random seed (default: random)"),
3246
3520
  skills: import_zod2.z.array(import_zod2.z.string()).optional().describe("Design skill references"),
3521
+ components: import_zod2.z.record(
3522
+ import_zod2.z.union([
3523
+ import_zod2.z.string(),
3524
+ import_zod2.z.object({
3525
+ version: import_zod2.z.string().optional(),
3526
+ code: import_zod2.z.string().optional(),
3527
+ exports: import_zod2.z.array(import_zod2.z.string()).optional()
3528
+ })
3529
+ ])
3530
+ ).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.'),
3247
3531
  addToWorkspace: import_zod2.z.string().optional().describe("Path to workspace to add sketch to after creation"),
3248
3532
  agent: import_zod2.z.string().optional().describe("Your CLI agent name (e.g. 'claude-code', 'codex-cli', 'gemini-cli', 'opencode', 'kiro')"),
3249
3533
  model: import_zod2.z.string().optional().describe("Your AI model identifier (e.g. 'claude-opus-4-6', 'gpt-4o', 'gemini-2.5-pro')")
@@ -3323,11 +3607,21 @@ function registerSketchTools(server, state) {
3323
3607
  );
3324
3608
  server.tool(
3325
3609
  "update_algorithm",
3326
- "Replace the algorithm source code of a sketch",
3610
+ "Replace the algorithm source code of a sketch. If adding/changing components, pass them in the components field alongside the algorithm.",
3327
3611
  {
3328
3612
  sketchId: import_zod2.z.string().describe("ID of the sketch to update"),
3329
3613
  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)."),
3330
3614
  validate: import_zod2.z.boolean().optional().describe("Run renderer-specific validation before saving (default: true)"),
3615
+ components: import_zod2.z.record(
3616
+ import_zod2.z.union([
3617
+ import_zod2.z.string(),
3618
+ import_zod2.z.object({
3619
+ version: import_zod2.z.string().optional(),
3620
+ code: import_zod2.z.string().optional(),
3621
+ exports: import_zod2.z.array(import_zod2.z.string()).optional()
3622
+ })
3623
+ ])
3624
+ ).optional().describe("Component dependencies to resolve alongside the algorithm update. Use list_components to see available."),
3331
3625
  agent: import_zod2.z.string().optional().describe("Your CLI agent name (e.g. 'claude-code', 'codex-cli', 'gemini-cli', 'opencode', 'kiro')"),
3332
3626
  model: import_zod2.z.string().optional().describe("Your AI model identifier (e.g. 'claude-opus-4-6', 'gpt-4o', 'gemini-2.5-pro')")
3333
3627
  },
@@ -3423,6 +3717,76 @@ function registerSketchTools(server, state) {
3423
3717
  }
3424
3718
  );
3425
3719
  }
3720
+ function registerComponentTools(server, state) {
3721
+ server.tool(
3722
+ "list_components",
3723
+ "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.",
3724
+ {
3725
+ renderer: import_zod2.z.enum(["p5", "three", "glsl", "canvas2d", "svg"]).optional().describe("Filter by renderer compatibility"),
3726
+ category: import_zod2.z.enum([
3727
+ "randomness",
3728
+ "noise",
3729
+ "math",
3730
+ "easing",
3731
+ "color",
3732
+ "vector",
3733
+ "geometry",
3734
+ "grid",
3735
+ "particle",
3736
+ "physics",
3737
+ "distribution",
3738
+ "pattern",
3739
+ "sdf",
3740
+ "transform",
3741
+ "animation",
3742
+ "string",
3743
+ "data-structure",
3744
+ "imaging"
3745
+ ]).optional().describe("Filter by component category")
3746
+ },
3747
+ async (args) => {
3748
+ try {
3749
+ const result = await listComponents(state, args);
3750
+ return jsonResult(result);
3751
+ } catch (e) {
3752
+ return toolError(e instanceof Error ? e.message : String(e));
3753
+ }
3754
+ }
3755
+ );
3756
+ server.tool(
3757
+ "add_component",
3758
+ "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.",
3759
+ {
3760
+ sketchId: import_zod2.z.string().describe("ID of the sketch to add the component to"),
3761
+ component: import_zod2.z.string().describe("Component name (e.g. 'prng', 'noise-2d', 'glsl-noise')"),
3762
+ version: import_zod2.z.string().optional().describe("Version range (default: '^1.0.0')")
3763
+ },
3764
+ async (args) => {
3765
+ try {
3766
+ const result = await addComponent(state, args);
3767
+ return jsonResult(result);
3768
+ } catch (e) {
3769
+ return toolError(e instanceof Error ? e.message : String(e));
3770
+ }
3771
+ }
3772
+ );
3773
+ server.tool(
3774
+ "remove_component",
3775
+ "Remove a component dependency from a sketch. Checks for dependent components and warns if the algorithm references the component's exports.",
3776
+ {
3777
+ sketchId: import_zod2.z.string().describe("ID of the sketch to remove the component from"),
3778
+ component: import_zod2.z.string().describe("Component name to remove")
3779
+ },
3780
+ async (args) => {
3781
+ try {
3782
+ const result = await removeComponent(state, args);
3783
+ return jsonResult(result);
3784
+ } catch (e) {
3785
+ return toolError(e instanceof Error ? e.message : String(e));
3786
+ }
3787
+ }
3788
+ );
3789
+ }
3426
3790
  function registerSelectionTools(server, state) {
3427
3791
  server.tool(
3428
3792
  "get_selection",