@pinepaper.studio/mcp-server 1.4.3 → 1.5.1

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.
Files changed (74) hide show
  1. package/README.md +89 -0
  2. package/dist/__tests__/fixtures/mock-generators.d.ts +147 -0
  3. package/dist/__tests__/fixtures/mock-generators.d.ts.map +1 -1
  4. package/dist/__tests__/fixtures/mock-generators.js +85 -0
  5. package/dist/__tests__/fixtures/mock-generators.js.map +1 -1
  6. package/dist/agent/export-manager.d.ts +77 -0
  7. package/dist/agent/export-manager.d.ts.map +1 -0
  8. package/dist/agent/export-manager.js +465 -0
  9. package/dist/agent/export-manager.js.map +1 -0
  10. package/dist/agent/index.d.ts +9 -0
  11. package/dist/agent/index.d.ts.map +1 -0
  12. package/dist/agent/index.js +12 -0
  13. package/dist/agent/index.js.map +1 -0
  14. package/dist/agent/session-manager.d.ts +109 -0
  15. package/dist/agent/session-manager.d.ts.map +1 -0
  16. package/dist/agent/session-manager.js +311 -0
  17. package/dist/agent/session-manager.js.map +1 -0
  18. package/dist/agent/types.d.ts +385 -0
  19. package/dist/agent/types.d.ts.map +1 -0
  20. package/dist/agent/types.js +141 -0
  21. package/dist/agent/types.js.map +1 -0
  22. package/dist/browser/index.d.ts +1 -1
  23. package/dist/browser/index.d.ts.map +1 -1
  24. package/dist/browser/index.js +1 -1
  25. package/dist/browser/index.js.map +1 -1
  26. package/dist/browser/puppeteer-controller.d.ts +89 -5
  27. package/dist/browser/puppeteer-controller.d.ts.map +1 -1
  28. package/dist/browser/puppeteer-controller.js +412 -11
  29. package/dist/browser/puppeteer-controller.js.map +1 -1
  30. package/dist/cli.js +61 -65
  31. package/dist/cli.js.map +1 -1
  32. package/dist/index.d.ts +6 -0
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +2769 -150
  35. package/dist/index.js.map +1 -1
  36. package/dist/metrics/types.d.ts +3 -1
  37. package/dist/metrics/types.d.ts.map +1 -1
  38. package/dist/prompts/index.d.ts +30 -0
  39. package/dist/prompts/index.d.ts.map +1 -0
  40. package/dist/prompts/index.js +163 -0
  41. package/dist/prompts/index.js.map +1 -0
  42. package/dist/tools/compact-descriptions.d.ts +9 -0
  43. package/dist/tools/compact-descriptions.d.ts.map +1 -0
  44. package/dist/tools/compact-descriptions.js +291 -0
  45. package/dist/tools/compact-descriptions.js.map +1 -0
  46. package/dist/tools/definitions.d.ts +14 -0
  47. package/dist/tools/definitions.d.ts.map +1 -1
  48. package/dist/tools/definitions.js +4954 -1552
  49. package/dist/tools/definitions.js.map +1 -1
  50. package/dist/tools/handlers.d.ts.map +1 -1
  51. package/dist/tools/handlers.js +1302 -19
  52. package/dist/tools/handlers.js.map +1 -1
  53. package/dist/tools/index.d.ts +4 -1
  54. package/dist/tools/index.d.ts.map +1 -1
  55. package/dist/tools/index.js +2 -1
  56. package/dist/tools/index.js.map +1 -1
  57. package/dist/tools/minimal-descriptions.d.ts +9 -0
  58. package/dist/tools/minimal-descriptions.d.ts.map +1 -0
  59. package/dist/tools/minimal-descriptions.js +158 -0
  60. package/dist/tools/minimal-descriptions.js.map +1 -0
  61. package/dist/tools/toolkits.d.ts +51 -0
  62. package/dist/tools/toolkits.d.ts.map +1 -0
  63. package/dist/tools/toolkits.js +336 -0
  64. package/dist/tools/toolkits.js.map +1 -0
  65. package/dist/types/code-generator.d.ts +193 -1
  66. package/dist/types/code-generator.d.ts.map +1 -1
  67. package/dist/types/code-generator.js +2167 -26
  68. package/dist/types/code-generator.js.map +1 -1
  69. package/dist/types/schemas.d.ts +5278 -1141
  70. package/dist/types/schemas.d.ts.map +1 -1
  71. package/dist/types/schemas.js +863 -5
  72. package/dist/types/schemas.js.map +1 -1
  73. package/manifest.json +94 -2
  74. package/package.json +1 -1
@@ -4,9 +4,26 @@
4
4
  * Converts MCP tool inputs to executable PinePaper JavaScript code.
5
5
  * This code is designed to run in the browser context where window.PinePaper is available.
6
6
  */
7
+ import { generateP5DrawCode } from '../tools/p5-compat/p5-helpers.js';
7
8
  import { CreateItemInputSchema, ModifyItemInputSchema, AddRelationInputSchema, AnimateItemInputSchema, KeyframeAnimateInputSchema, ExecuteGeneratorInputSchema, ApplyEffectInputSchema, SetBackgroundColorInputSchema, SetCanvasSizeInputSchema, CreateGlossySphereInputSchema, CreateDiagonalStripesInputSchema, BatchCreateInputSchema, BatchModifyInputSchema, CreateGridInputSchema, CreateSceneInputSchema,
8
9
  // Diagram types
9
- CreateDiagramShapeInputSchema, ConnectInputSchema, ConnectPortsInputSchema, AddPortsInputSchema, AutoLayoutInputSchema, GetDiagramShapesInputSchema, UpdateConnectorInputSchema, RemoveConnectorInputSchema, DiagramModeInputSchema, } from './schemas.js';
10
+ CreateDiagramShapeInputSchema, ConnectInputSchema, ConnectPortsInputSchema, AddPortsInputSchema, AutoLayoutInputSchema, GetDiagramShapesInputSchema, UpdateConnectorInputSchema, RemoveConnectorInputSchema, DiagramModeInputSchema,
11
+ // Agent flow mode types
12
+ AgentStartJobInputSchema, AgentEndJobInputSchema, AgentResetInputSchema, AgentBatchExecuteInputSchema, AgentExportInputSchema, AgentAnalyzeInputSchema,
13
+ // Interactive/Trigger types
14
+ AddTriggerInputSchema, RemoveTriggerInputSchema, QueryTriggersInputSchema,
15
+ // Quiz types
16
+ CreateQuizInputSchema, GetQuizStateInputSchema, ResetQuizInputSchema,
17
+ // Letter collage types
18
+ CreateLetterCollageInputSchema, AnimateLetterCollageInputSchema,
19
+ // Map types
20
+ LoadMapInputSchema, HighlightRegionsInputSchema, UnhighlightRegionsInputSchema, ApplyDataColorsInputSchema, AddMarkerInputSchema, AddMapLabelsInputSchema, PanMapInputSchema, ZoomMapInputSchema, ImportCustomMapInputSchema, GetRegionAtPointInputSchema,
21
+ // Map animation/CSV types
22
+ AnimateMapRegionsInputSchema, AnimateMapWaveInputSchema, StopMapAnimationsInputSchema, ExportMapRegionCSVInputSchema, ImportMapRegionCSVInputSchema, SelectMapRegionsInputSchema, DeselectMapRegionsInputSchema,
23
+ // Custom relation/code types
24
+ RegisterCustomRelationInputSchema, ExecuteCustomCodeInputSchema,
25
+ // Mask types
26
+ ApplyAnimatedMaskInputSchema, ApplyCustomMaskInputSchema, RemoveMaskInputSchema, } from './schemas.js';
10
27
  // =============================================================================
11
28
  // HELPER FUNCTIONS
12
29
  // =============================================================================
@@ -108,6 +125,8 @@ const item = app.create('${itemType}', ${JSON.stringify(params, null, 2)});`;
108
125
  if (opacity !== undefined) {
109
126
  code += `\nitem.opacity = ${opacity};`;
110
127
  }
128
+ // Ensure item is visible above backgrounds/generators
129
+ code += `\nif (item.bringToFront) item.bringToFront();`;
111
130
  code += `
112
131
  const itemId = item.data.registryId;
113
132
  app.historyManager.saveState();
@@ -122,11 +141,8 @@ app.historyManager.saveState();
122
141
  function generateModifyItemCode(itemId, properties) {
123
142
  return `
124
143
  // Modify item ${itemId}
125
- const item = app.getItemById('${itemId}');
126
- if (!item) {
127
- throw new Error('Item not found: ${itemId}');
128
- }
129
- app.modify(item, ${JSON.stringify(properties, null, 2)});
144
+ app.select('${itemId}');
145
+ app.modify(${JSON.stringify(properties, null, 2)});
130
146
  app.historyManager.saveState();
131
147
 
132
148
  // Return success
@@ -246,18 +262,7 @@ function generateKeyframeAnimateCode(itemId, keyframes, duration, loop = false)
246
262
  const calculatedDuration = duration || Math.max(...keyframes.map(k => k.time));
247
263
  return `
248
264
  // Apply keyframe animation to ${itemId}
249
- const item = app.getItemById('${itemId}');
250
- if (!item) {
251
- throw new Error('Item not found: ${itemId}');
252
- }
253
-
254
- app.modify(item, {
255
- animationType: 'keyframe',
256
- keyframes: ${keyframesJson}
257
- });
258
-
259
- // Start playback
260
- app.playKeyframeTimeline(${calculatedDuration}, ${loop});
265
+ app.addAnimation('${itemId}', ${keyframesJson}, { duration: ${calculatedDuration}, loop: ${loop} });
261
266
 
262
267
  ({ success: true, itemId: '${itemId}', duration: ${calculatedDuration}, loop: ${loop} });
263
268
  `.trim();
@@ -268,9 +273,10 @@ app.playKeyframeTimeline(${calculatedDuration}, ${loop});
268
273
  function generateExecuteGeneratorCode(generatorName, params) {
269
274
  return `
270
275
  // Execute ${generatorName} generator
271
- await app.executeGenerator('${generatorName}', ${JSON.stringify(params, null, 2)});
272
-
273
- ({ success: true, generator: '${generatorName}' });
276
+ (async function() {
277
+ await app.executeGenerator('${generatorName}', ${JSON.stringify(params, null, 2)});
278
+ return { success: true, generator: '${generatorName}' };
279
+ })();
274
280
  `.trim();
275
281
  }
276
282
  /**
@@ -374,9 +380,12 @@ app.setBackgroundColor('${color}');
374
380
  * Template for canvas size
375
381
  */
376
382
  function generateSetCanvasSizeCode(width, height, preset) {
383
+ const sizeArg = preset
384
+ ? `'${preset}'`
385
+ : `{ width: ${width}, height: ${height} }`;
377
386
  return `
378
387
  // Set canvas size
379
- app.setCanvasSize(${width}, ${height}${preset ? `, '${preset}'` : ''});
388
+ app.setCanvasSize(${sizeArg});
380
389
  app.historyManager.saveState();
381
390
  ({ success: true, width: ${width}, height: ${height} });
382
391
  `.trim();
@@ -587,7 +596,7 @@ export class PinePaperCodeGenerator {
587
596
  */
588
597
  generateAddRelation(input) {
589
598
  const validated = AddRelationInputSchema.parse(input);
590
- return generateAddRelationCode(validated.sourceId, validated.targetId, validated.relationType, validated.params);
599
+ return generateAddRelationCode(validated.sourceId, validated.targetId || validated.sourceId, validated.relationType, validated.params);
591
600
  }
592
601
  /**
593
602
  * Generate code for removing a relation
@@ -915,8 +924,6 @@ app.historyManager.saveState();
915
924
  * Generate code for executing p5.js-style drawing code
916
925
  */
917
926
  generateP5Draw(code) {
918
- // Import the p5 helpers generator
919
- const { generateP5DrawCode } = require('../tools/p5-compat/p5-helpers.js');
920
927
  return generateP5DrawCode(code);
921
928
  }
922
929
  /**
@@ -1033,6 +1040,7 @@ if (typeof app.generators !== 'undefined' && app.generators['${backgroundGenerat
1033
1040
  }
1034
1041
 
1035
1042
  const itemId = app.registerItem(item, '${name}', { source: 'mcp-scene' });
1043
+ if (item.bringToFront) item.bringToFront();
1036
1044
  nameToId['${name}'] = itemId;
1037
1045
  results.items.push({ name: '${name}', itemId, type: '${itemType}' });
1038
1046
  })();
@@ -1071,7 +1079,7 @@ if (typeof app.generators !== 'undefined' && app.generators['${backgroundGenerat
1071
1079
  const targetItem = app.itemRegistry.get(targetId);
1072
1080
  if (targetItem) {
1073
1081
  const params = ${paramsStr};
1074
- app.animationManager.addAnimation(targetItem, '${type}', params);
1082
+ app.animate(targetItem, { animationType: '${type}', ...params });
1075
1083
  results.animations.push({ target: '${target}', type: '${type}' });
1076
1084
  }
1077
1085
  }
@@ -1324,6 +1332,2139 @@ throw new Error('Unknown diagram mode action: ${action}');
1324
1332
  `.trim();
1325
1333
  }
1326
1334
  }
1335
+ // ===========================================================================
1336
+ // AGENT FLOW MODE CODE GENERATORS
1337
+ // ===========================================================================
1338
+ /**
1339
+ * Generate code for starting an agent job
1340
+ */
1341
+ generateAgentStartJob(input) {
1342
+ const validated = AgentStartJobInputSchema.parse(input);
1343
+ const { name, screenshotPolicy, canvasPreset, clearCanvas } = validated;
1344
+ const nameStr = name ? `'${name.replace(/'/g, "\\'")}'` : 'null';
1345
+ const policyStr = screenshotPolicy || 'on_complete';
1346
+ const shouldClear = clearCanvas !== false;
1347
+ let code = `
1348
+ // Start agent job
1349
+ (async function() {
1350
+ const jobOptions = {
1351
+ name: ${nameStr},
1352
+ screenshotPolicy: '${policyStr}',
1353
+ agentMode: true
1354
+ };
1355
+ `;
1356
+ if (shouldClear) {
1357
+ code += `
1358
+ // Clear canvas
1359
+ if (app.clearCanvas) {
1360
+ app.clearCanvas();
1361
+ } else {
1362
+ if (app.textItemGroup) app.textItemGroup.removeChildren();
1363
+ if (app.patternGroup) app.patternGroup.removeChildren();
1364
+ if (app.itemRegistry) app.itemRegistry.clear();
1365
+ if (app.relationRegistry) app.relationRegistry.clear();
1366
+ }
1367
+ `;
1368
+ }
1369
+ if (canvasPreset) {
1370
+ code += `
1371
+ // Set canvas size to preset
1372
+ app.setCanvasSize('${canvasPreset}');
1373
+ // Allow canvas resize to take effect
1374
+ await new Promise(r => setTimeout(r, 50));
1375
+ `;
1376
+ }
1377
+ code += `
1378
+ // Return job context with canvas size
1379
+ const _cs = app.getCanvasSize ? app.getCanvasSize() : { width: 800, height: 600 };
1380
+ return {
1381
+ success: true,
1382
+ jobStarted: true,
1383
+ name: ${nameStr},
1384
+ screenshotPolicy: '${policyStr}',
1385
+ canvasPreset: ${canvasPreset ? `'${canvasPreset}'` : 'null'},
1386
+ canvasCleared: ${shouldClear},
1387
+ canvasSize: { width: _cs.width || 800, height: _cs.height || 600 }
1388
+ };
1389
+ })();
1390
+ `;
1391
+ return code.trim();
1392
+ }
1393
+ /**
1394
+ * Generate code for ending an agent job
1395
+ */
1396
+ generateAgentEndJob(input) {
1397
+ const validated = AgentEndJobInputSchema.parse(input);
1398
+ const { takeScreenshot, analyzeContent } = validated;
1399
+ const shouldAnalyze = analyzeContent !== false;
1400
+ let code = `
1401
+ // End agent job
1402
+ (async function() {
1403
+ const result = {
1404
+ success: true,
1405
+ jobEnded: true
1406
+ };
1407
+ `;
1408
+ if (shouldAnalyze) {
1409
+ code += `
1410
+ // Analyze content
1411
+ const analysis = {
1412
+ hasAnimations: false,
1413
+ animationTypes: [],
1414
+ colorComplexity: 'simple',
1415
+ itemCount: 0,
1416
+ canvasSize: { width: 0, height: 0 },
1417
+ hasRelations: false,
1418
+ relationTypes: [],
1419
+ hasGradients: false,
1420
+ hasShadows: false,
1421
+ hasText: false,
1422
+ hasImages: false,
1423
+ };
1424
+
1425
+ // Get canvas size
1426
+ if (app.canvasEl) {
1427
+ analysis.canvasSize = { width: app.canvasEl.width, height: app.canvasEl.height };
1428
+ } else if (paper.view) {
1429
+ analysis.canvasSize = { width: paper.view.size.width, height: paper.view.size.height };
1430
+ }
1431
+
1432
+ // Count items and analyze
1433
+ const items = app.itemRegistry ? app.itemRegistry.getAll() : [];
1434
+ analysis.itemCount = items.length;
1435
+
1436
+ const animationSet = new Set();
1437
+ const relationSet = new Set();
1438
+
1439
+ items.forEach(entry => {
1440
+ const item = entry.item;
1441
+ const data = item.data || {};
1442
+
1443
+ if (entry.type === 'text' || item.className === 'PointText') analysis.hasText = true;
1444
+ if (item.className === 'Raster') analysis.hasImages = true;
1445
+ if (data.animationType) {
1446
+ analysis.hasAnimations = true;
1447
+ animationSet.add(data.animationType);
1448
+ }
1449
+ // Detect keyframe animations (added via app.addAnimation with keyframes array)
1450
+ if (data.keyframes || data.animation || data.keyframeAnimation) {
1451
+ analysis.hasAnimations = true;
1452
+ animationSet.add('keyframe');
1453
+ }
1454
+ if (item.fillColor && item.fillColor.gradient) analysis.hasGradients = true;
1455
+ if (item.shadowColor || item.shadowBlur) analysis.hasShadows = true;
1456
+ });
1457
+
1458
+ // Check animation manager for registered animations
1459
+ if (app.animationManager) {
1460
+ const animations = app.animationManager.getAll ? app.animationManager.getAll() : (app.animationManager.animations || []);
1461
+ if (animations.length > 0) {
1462
+ analysis.hasAnimations = true;
1463
+ animationSet.add('managed');
1464
+ }
1465
+ }
1466
+ // Check timeline for active animations
1467
+ if (app.timeline && (app.timeline.isPlaying || app.timeline.animations?.length > 0)) {
1468
+ analysis.hasAnimations = true;
1469
+ animationSet.add('timeline');
1470
+ }
1471
+
1472
+ analysis.animationTypes = Array.from(animationSet);
1473
+
1474
+ // Check relations
1475
+ if (app.relationRegistry) {
1476
+ const relations = app.relationRegistry.getAll ? app.relationRegistry.getAll() : [];
1477
+ analysis.hasRelations = relations.length > 0;
1478
+ relations.forEach(rel => {
1479
+ if (rel.relationType) relationSet.add(rel.relationType);
1480
+ });
1481
+ analysis.relationTypes = Array.from(relationSet);
1482
+ if (analysis.hasRelations) analysis.hasAnimations = true;
1483
+ }
1484
+
1485
+ // Determine color complexity
1486
+ if (analysis.hasGradients) {
1487
+ analysis.colorComplexity = 'gradient';
1488
+ } else if (analysis.itemCount > 20 || analysis.hasShadows) {
1489
+ analysis.colorComplexity = 'complex';
1490
+ }
1491
+
1492
+ result.analysis = analysis;
1493
+
1494
+ // Generate recommendations
1495
+ const recommendations = [];
1496
+ if (analysis.hasAnimations) {
1497
+ if (analysis.hasGradients || analysis.colorComplexity === 'complex') {
1498
+ recommendations.push({ platform: 'web', format: 'webm', confidence: 0.9, reason: 'Animated content with gradients' });
1499
+ recommendations.push({ platform: 'instagram', format: 'mp4', confidence: 0.85, reason: 'Social media video' });
1500
+ } else {
1501
+ recommendations.push({ platform: 'web', format: 'svg', confidence: 0.95, reason: 'Simple animations as SVG' });
1502
+ recommendations.push({ platform: 'twitter', format: 'gif', confidence: 0.8, reason: 'Social media GIF' });
1503
+ }
1504
+ } else {
1505
+ if (analysis.colorComplexity === 'simple' && !analysis.hasImages) {
1506
+ recommendations.push({ platform: 'web', format: 'svg', confidence: 0.95, reason: 'Vector graphics' });
1507
+ } else {
1508
+ recommendations.push({ platform: 'instagram', format: 'png', confidence: 0.9, reason: 'High quality static' });
1509
+ }
1510
+ }
1511
+ result.recommendations = recommendations;
1512
+ `;
1513
+ }
1514
+ if (takeScreenshot) {
1515
+ code += `
1516
+ // Capture screenshot
1517
+ const canvas = document.querySelector('canvas');
1518
+ if (canvas) {
1519
+ result.screenshot = canvas.toDataURL('image/png');
1520
+ }
1521
+ `;
1522
+ }
1523
+ code += `
1524
+ return result;
1525
+ })();
1526
+ `;
1527
+ return code.trim();
1528
+ }
1529
+ /**
1530
+ * Generate code for fast canvas reset
1531
+ */
1532
+ generateAgentReset(input) {
1533
+ const validated = AgentResetInputSchema.parse(input);
1534
+ const { canvasPreset, backgroundColor, preserveBackground } = validated;
1535
+ let code = `
1536
+ // Fast canvas reset
1537
+ (async function() {
1538
+ `;
1539
+ if (!preserveBackground) {
1540
+ code += `
1541
+ // Clear canvas completely
1542
+ if (app.clearCanvas) {
1543
+ app.clearCanvas();
1544
+ } else {
1545
+ if (app.textItemGroup) app.textItemGroup.removeChildren();
1546
+ if (app.patternGroup) app.patternGroup.removeChildren();
1547
+ if (app.itemRegistry) app.itemRegistry.clear();
1548
+ if (app.relationRegistry) app.relationRegistry.clear();
1549
+ }
1550
+ `;
1551
+ }
1552
+ else {
1553
+ code += `
1554
+ // Clear items but preserve background
1555
+ if (app.textItemGroup) app.textItemGroup.removeChildren();
1556
+ if (app.itemRegistry) app.itemRegistry.clear();
1557
+ if (app.relationRegistry) app.relationRegistry.clear();
1558
+ `;
1559
+ }
1560
+ if (canvasPreset) {
1561
+ code += `
1562
+ // Set canvas to preset
1563
+ app.setCanvasSize('${canvasPreset}');
1564
+ // Allow canvas resize to take effect
1565
+ await new Promise(r => setTimeout(r, 50));
1566
+ `;
1567
+ }
1568
+ if (backgroundColor && !preserveBackground) {
1569
+ code += `
1570
+ // Set background color
1571
+ app.setBackgroundColor('${backgroundColor}');
1572
+ `;
1573
+ }
1574
+ code += `
1575
+ // Save state
1576
+ if (app.historyManager) app.historyManager.saveState();
1577
+ const _cs = app.getCanvasSize ? app.getCanvasSize() : { width: 800, height: 600 };
1578
+ return { success: true, reset: true, canvasPreset: ${canvasPreset ? `'${canvasPreset}'` : 'null'}, canvasSize: { width: _cs.width || 800, height: _cs.height || 600 } };
1579
+ })();
1580
+ `;
1581
+ return code.trim();
1582
+ }
1583
+ /**
1584
+ * Generate code for batch execute
1585
+ */
1586
+ generateAgentBatchExecute(input) {
1587
+ const validated = AgentBatchExecuteInputSchema.parse(input);
1588
+ const { operations, atomic } = validated;
1589
+ const isAtomic = atomic !== false;
1590
+ let code = `
1591
+ // Batch execute ${operations.length} operations
1592
+ (async function() {
1593
+ const results = [];
1594
+ const itemIds = [];
1595
+ let success = true;
1596
+
1597
+ try {
1598
+ `;
1599
+ operations.forEach((op, index) => {
1600
+ const opCode = this.generateBatchOperationCode(op, index);
1601
+ code += `
1602
+ // Operation ${index}: ${op.type}
1603
+ try {
1604
+ const result${index} = await (async () => {
1605
+ ${opCode}
1606
+ })();
1607
+ results.push({ index: ${index}, success: true, result: result${index} });
1608
+ if (result${index} && result${index}.itemId) {
1609
+ itemIds.push(result${index}.itemId);
1610
+ }
1611
+ } catch (opError) {
1612
+ results.push({ index: ${index}, success: false, error: opError.message });
1613
+ ${isAtomic ? 'throw opError;' : 'success = false;'}
1614
+ }
1615
+ `;
1616
+ });
1617
+ code += `
1618
+ // Save state
1619
+ if (app.historyManager) app.historyManager.saveState();
1620
+
1621
+ const _cs = app.getCanvasSize ? app.getCanvasSize() : { width: 800, height: 600 };
1622
+ return { success, itemIds, results, operationCount: ${operations.length}, canvasSize: { width: _cs.width || 800, height: _cs.height || 600 } };
1623
+ } catch (e) {
1624
+ const _cs = app.getCanvasSize ? app.getCanvasSize() : { width: 800, height: 600 };
1625
+ return { success: false, error: e.message, itemIds, results, operationCount: ${operations.length}, canvasSize: { width: _cs.width || 800, height: _cs.height || 600 } };
1626
+ }
1627
+ })();
1628
+ `;
1629
+ return code.trim();
1630
+ }
1631
+ /**
1632
+ * Generate code for a single batch operation
1633
+ */
1634
+ generateBatchOperationCode(op, index) {
1635
+ switch (op.type) {
1636
+ case 'create':
1637
+ const pos = op.position || { x: 400, y: 300 };
1638
+ const props = JSON.stringify(op.properties || {});
1639
+ return `
1640
+ const item = app.create('${op.itemType}', { position: { x: ${pos.x}, y: ${pos.y} }, ...${props} });
1641
+ const itemId = item.data && item.data.id ? item.data.id : app.registerItem(item, '${op.itemType}', { source: 'mcp-batch' });
1642
+ // Ensure item is visible above backgrounds/generators
1643
+ if (item.bringToFront) item.bringToFront();
1644
+ return { itemId };
1645
+ `;
1646
+ case 'modify':
1647
+ const itemRef = op.itemId?.startsWith('$')
1648
+ ? `itemIds[${op.itemId.substring(1)}]`
1649
+ : `'${op.itemId}'`;
1650
+ const modifyProps = JSON.stringify(op.properties || {});
1651
+ return `
1652
+ const targetId = ${itemRef};
1653
+ app.select(targetId);
1654
+ app.modify(${modifyProps});
1655
+ return { itemId: targetId, modified: true };
1656
+ `;
1657
+ case 'animate':
1658
+ const animItemRef = op.itemId?.startsWith('$')
1659
+ ? `itemIds[${op.itemId.substring(1)}]`
1660
+ : `'${op.itemId}'`;
1661
+ const animOpts = JSON.stringify(op.animationOptions || {});
1662
+ return `
1663
+ const targetId = ${animItemRef};
1664
+ const item = app.getItemById(targetId);
1665
+ if (!item) throw new Error('Item not found: ' + targetId);
1666
+ app.animate(item, { animationType: '${op.animationType}', ...${animOpts} });
1667
+ return { itemId: targetId, animationType: '${op.animationType}' };
1668
+ `;
1669
+ case 'relation':
1670
+ const srcRef = op.sourceId?.startsWith('$')
1671
+ ? `itemIds[${op.sourceId.substring(1)}]`
1672
+ : `'${op.sourceId}'`;
1673
+ const tgtRef = op.targetId?.startsWith('$')
1674
+ ? `itemIds[${op.targetId.substring(1)}]`
1675
+ : `'${op.targetId}'`;
1676
+ const relOpts = JSON.stringify(op.relationOptions || {});
1677
+ return `
1678
+ const sourceId = ${srcRef};
1679
+ const targetId = ${tgtRef};
1680
+ const source = app.getItemById(sourceId);
1681
+ const target = app.getItemById(targetId);
1682
+ if (!source) throw new Error('Source not found: ' + sourceId);
1683
+ if (!target) throw new Error('Target not found: ' + targetId);
1684
+ const relationId = app.addRelation(source, target, '${op.relationType}', ${relOpts});
1685
+ return { relationId, sourceId, targetId, relationType: '${op.relationType}' };
1686
+ `;
1687
+ case 'delete':
1688
+ const delRef = op.itemId?.startsWith('$')
1689
+ ? `itemIds[${op.itemId.substring(1)}]`
1690
+ : `'${op.itemId}'`;
1691
+ return `
1692
+ const targetId = ${delRef};
1693
+ const item = app.getItemById(targetId);
1694
+ if (!item) throw new Error('Item not found: ' + targetId);
1695
+ item.remove();
1696
+ if (app.itemRegistry) app.itemRegistry.unregister(targetId);
1697
+ return { itemId: targetId, deleted: true };
1698
+ `;
1699
+ case 'set_background':
1700
+ const bgColor = op.backgroundColor || op.properties?.color || '#000000';
1701
+ return `
1702
+ app.setBackgroundColor('${bgColor}');
1703
+ return { success: true, backgroundColor: '${bgColor}' };
1704
+ `;
1705
+ case 'execute_generator':
1706
+ const genName = op.generatorName || 'drawSunburst';
1707
+ const genParams = JSON.stringify(op.generatorParams || {});
1708
+ return `
1709
+ await app.executeGenerator('${genName}', ${genParams});
1710
+ return { success: true, generator: '${genName}' };
1711
+ `;
1712
+ case 'set_canvas_size': {
1713
+ const w = op.width || 1080;
1714
+ const h = op.height || 1080;
1715
+ const sizeArg = op.preset ? `'${op.preset}'` : `{ width: ${w}, height: ${h} }`;
1716
+ return `
1717
+ app.setCanvasSize(${sizeArg});
1718
+ await new Promise(r => setTimeout(r, 50));
1719
+ return { success: true, width: ${w}, height: ${h} };
1720
+ `;
1721
+ }
1722
+ case 'keyframe_animate': {
1723
+ const kfItemRef = op.itemId?.startsWith('$')
1724
+ ? `itemIds[${op.itemId.substring(1)}]`
1725
+ : `'${op.itemId}'`;
1726
+ const kfJson = JSON.stringify(op.keyframes || []);
1727
+ const kfDuration = op.duration || (op.keyframes?.length ? Math.max(...op.keyframes.map(k => k.time)) : 5);
1728
+ const kfLoop = op.loop ?? false;
1729
+ return `
1730
+ const targetId = ${kfItemRef};
1731
+ app.addAnimation(targetId, ${kfJson}, { duration: ${kfDuration}, loop: ${kfLoop} });
1732
+ return { itemId: targetId, duration: ${kfDuration}, loop: ${kfLoop} };
1733
+ `;
1734
+ }
1735
+ case 'apply_mask': {
1736
+ const maskItemRef = op.itemId?.startsWith('$')
1737
+ ? `itemIds[${op.itemId.substring(1)}]`
1738
+ : `'${op.itemId}'`;
1739
+ const maskOpts = JSON.stringify(op.maskOptions || {});
1740
+ return `
1741
+ const targetId = ${maskItemRef};
1742
+ if (!app.maskSystem) throw new Error('Mask system not available');
1743
+ const result = app.maskSystem.applyAnimatedMask(targetId, {
1744
+ preset: ${op.maskPreset ? `'${op.maskPreset}'` : 'null'},
1745
+ maskType: ${op.maskType ? `'${op.maskType}'` : 'null'},
1746
+ options: ${maskOpts},
1747
+ maskOptions: ${maskOpts}
1748
+ });
1749
+ return result;
1750
+ `;
1751
+ }
1752
+ case 'apply_effect': {
1753
+ const effItemRef = op.itemId?.startsWith('$')
1754
+ ? `itemIds[${op.itemId.substring(1)}]`
1755
+ : `'${op.itemId}'`;
1756
+ const effParams = JSON.stringify(op.effectParams || {});
1757
+ return `
1758
+ const targetId = ${effItemRef};
1759
+ const item = app.getItemById(targetId);
1760
+ if (!item) throw new Error('Item not found: ' + targetId);
1761
+ app.applyEffect(item, '${op.effectType || 'sparkle'}', ${effParams});
1762
+ return { itemId: targetId, effectType: '${op.effectType || 'sparkle'}' };
1763
+ `;
1764
+ }
1765
+ case 'play_timeline': {
1766
+ const ptAction = op.action || 'play';
1767
+ const ptDuration = op.duration || 5;
1768
+ const ptLoop = op.loop ?? false;
1769
+ if (ptAction === 'play') {
1770
+ return `
1771
+ app.playKeyframeTimeline(${ptDuration}, ${ptLoop});
1772
+ return { success: true, action: 'play', duration: ${ptDuration}, loop: ${ptLoop} };
1773
+ `;
1774
+ }
1775
+ else if (ptAction === 'stop') {
1776
+ return `
1777
+ app.stopKeyframeTimeline();
1778
+ return { success: true, action: 'stop' };
1779
+ `;
1780
+ }
1781
+ else {
1782
+ return `
1783
+ app.setPlaybackTime(${op.time || 0});
1784
+ return { success: true, action: 'seek', time: ${op.time || 0} };
1785
+ `;
1786
+ }
1787
+ }
1788
+ default:
1789
+ return `throw new Error('Unknown operation type: ${op.type}');`;
1790
+ }
1791
+ }
1792
+ /**
1793
+ * Generate code for smart export
1794
+ */
1795
+ generateAgentExport(input) {
1796
+ const validated = AgentExportInputSchema.parse(input);
1797
+ const { platform, format, quality } = validated;
1798
+ const qualityLevel = quality || 'standard';
1799
+ // Quality settings
1800
+ const qualitySettings = {
1801
+ draft: { compression: 0.6, fps: 15, dpi: 72 },
1802
+ standard: { compression: 0.85, fps: 30, dpi: 150 },
1803
+ high: { compression: 0.95, fps: 60, dpi: 300 },
1804
+ }[qualityLevel];
1805
+ // Platform presets
1806
+ const platformPresets = {
1807
+ 'instagram': { width: 1080, height: 1080, staticFormat: 'png', animatedFormat: 'mp4' },
1808
+ 'instagram-story': { width: 1080, height: 1920, staticFormat: 'png', animatedFormat: 'mp4' },
1809
+ 'tiktok': { width: 1080, height: 1920, staticFormat: 'png', animatedFormat: 'mp4' },
1810
+ 'youtube': { width: 1920, height: 1080, staticFormat: 'png', animatedFormat: 'mp4' },
1811
+ 'youtube-thumbnail': { width: 1280, height: 720, staticFormat: 'png', animatedFormat: 'png' },
1812
+ 'twitter': { width: 1200, height: 675, staticFormat: 'png', animatedFormat: 'gif' },
1813
+ 'linkedin': { width: 1200, height: 627, staticFormat: 'png', animatedFormat: 'gif' },
1814
+ 'web': { width: 800, height: 600, staticFormat: 'svg', animatedFormat: 'svg' },
1815
+ 'print-a4': { width: 2480, height: 3508, staticFormat: 'pdf', animatedFormat: 'pdf' },
1816
+ 'print-letter': { width: 2550, height: 3300, staticFormat: 'pdf', animatedFormat: 'pdf' },
1817
+ };
1818
+ const preset = platformPresets[platform] || platformPresets['web'];
1819
+ // Resolve "auto" format to the platform's recommended format
1820
+ const exportFormat = (!format || format === 'auto') ? preset.staticFormat : format;
1821
+ return `
1822
+ // Smart export for ${platform}
1823
+ (async function() {
1824
+ const platform = '${platform}';
1825
+ const format = '${exportFormat}';
1826
+ const quality = '${qualityLevel}';
1827
+ const settings = ${JSON.stringify(qualitySettings)};
1828
+ const dimensions = ${JSON.stringify({ width: preset.width, height: preset.height })};
1829
+
1830
+ let result = { success: false, platform, format, quality };
1831
+
1832
+ try {
1833
+ switch (format) {
1834
+ case 'svg':
1835
+ const svgString = app.exportAnimatedSVG ? app.exportAnimatedSVG() : app.exportSVG();
1836
+ result = {
1837
+ success: true,
1838
+ platform,
1839
+ format: 'svg',
1840
+ data: svgString,
1841
+ mimeType: 'image/svg+xml',
1842
+ size: new Blob([svgString]).size,
1843
+ hasAnimations: svgString.includes('<animate') || svgString.includes('@keyframes')
1844
+ };
1845
+ break;
1846
+
1847
+ case 'png':
1848
+ if (app.exportEngine && app.exportEngine.exportPNG) {
1849
+ const dataUrl = await app.exportEngine.exportPNG({ dpi: settings.dpi });
1850
+ result = {
1851
+ success: true,
1852
+ platform,
1853
+ format: 'png',
1854
+ data: dataUrl,
1855
+ mimeType: 'image/png',
1856
+ size: Math.round(dataUrl.length * 0.75)
1857
+ };
1858
+ } else {
1859
+ const canvas = document.querySelector('canvas');
1860
+ if (canvas) {
1861
+ const dataUrl = canvas.toDataURL('image/png');
1862
+ result = {
1863
+ success: true,
1864
+ platform,
1865
+ format: 'png',
1866
+ data: dataUrl,
1867
+ mimeType: 'image/png',
1868
+ size: Math.round(dataUrl.length * 0.75)
1869
+ };
1870
+ }
1871
+ }
1872
+ break;
1873
+
1874
+ case 'gif':
1875
+ case 'mp4':
1876
+ case 'webm': {
1877
+ const videoMimeType = { mp4: 'video/mp4', webm: 'video/webm', gif: 'image/gif' }[format];
1878
+ // Use _quickExportVideo (delegates to VideoExporter.export internally)
1879
+ if (app.exportEngine && app.exportEngine._quickExportVideo) {
1880
+ const videoResult = await app.exportEngine._quickExportVideo(format, { fps: settings.fps, duration: 5 }, false);
1881
+ if (videoResult && videoResult.blob) {
1882
+ const reader = new FileReader();
1883
+ const dataUrl = await new Promise(resolve => {
1884
+ reader.onloadend = () => resolve(reader.result);
1885
+ reader.readAsDataURL(videoResult.blob);
1886
+ });
1887
+ result = { success: true, platform, format, data: dataUrl, mimeType: videoMimeType, size: videoResult.blob.size };
1888
+ } else {
1889
+ result = { success: false, error: format.toUpperCase() + ' export returned no data' };
1890
+ }
1891
+ } else if (app.exportEngine && app.exportEngine.videoExporter) {
1892
+ // Fallback: call VideoExporter.export directly
1893
+ const blob = await app.exportEngine.videoExporter.export({ format, fps: settings.fps, duration: 5 });
1894
+ const reader = new FileReader();
1895
+ const dataUrl = await new Promise(resolve => {
1896
+ reader.onloadend = () => resolve(reader.result);
1897
+ reader.readAsDataURL(blob);
1898
+ });
1899
+ result = { success: true, platform, format, data: dataUrl, mimeType: videoMimeType, size: blob.size };
1900
+ } else {
1901
+ result = { success: false, error: format.toUpperCase() + ' export not available' };
1902
+ }
1903
+ break;
1904
+ }
1905
+
1906
+ case 'pdf':
1907
+ if (app.exportEngine && app.exportEngine.exportPDF) {
1908
+ const blob = await app.exportEngine.exportPDF({ dpi: settings.dpi });
1909
+ const reader = new FileReader();
1910
+ const dataUrl = await new Promise(resolve => {
1911
+ reader.onloadend = () => resolve(reader.result);
1912
+ reader.readAsDataURL(blob);
1913
+ });
1914
+ result = { success: true, platform, format: 'pdf', data: dataUrl, mimeType: 'application/pdf', size: blob.size, dpi: settings.dpi };
1915
+ } else {
1916
+ result = { success: false, error: 'PDF export failed' };
1917
+ }
1918
+ break;
1919
+
1920
+ default:
1921
+ result = { success: false, error: 'Unknown format: ' + format };
1922
+ }
1923
+ } catch (e) {
1924
+ result = { success: false, error: e.message, platform, format };
1925
+ }
1926
+
1927
+ return result;
1928
+ })();
1929
+ `.trim();
1930
+ }
1931
+ /**
1932
+ * Generate code for content analysis
1933
+ */
1934
+ generateAgentAnalyze(input) {
1935
+ AgentAnalyzeInputSchema.parse(input);
1936
+ return `
1937
+ // Analyze canvas content
1938
+ (function() {
1939
+ const analysis = {
1940
+ hasAnimations: false,
1941
+ animationTypes: [],
1942
+ colorComplexity: 'simple',
1943
+ itemCount: 0,
1944
+ canvasSize: { width: 0, height: 0 },
1945
+ hasRelations: false,
1946
+ relationTypes: [],
1947
+ hasGradients: false,
1948
+ hasShadows: false,
1949
+ hasText: false,
1950
+ hasImages: false,
1951
+ };
1952
+
1953
+ // Get canvas size
1954
+ if (app.canvasEl) {
1955
+ analysis.canvasSize = { width: app.canvasEl.width, height: app.canvasEl.height };
1956
+ } else if (paper.view) {
1957
+ analysis.canvasSize = { width: paper.view.size.width, height: paper.view.size.height };
1958
+ }
1959
+
1960
+ // Count items and analyze types
1961
+ const items = app.itemRegistry ? app.itemRegistry.getAll() : [];
1962
+ analysis.itemCount = items.length;
1963
+
1964
+ const animationSet = new Set();
1965
+ const relationSet = new Set();
1966
+ let hasGradient = false;
1967
+ let hasShadow = false;
1968
+
1969
+ items.forEach(entry => {
1970
+ const item = entry.item;
1971
+ const data = item.data || {};
1972
+
1973
+ if (entry.type === 'text' || item.className === 'PointText') analysis.hasText = true;
1974
+ if (item.className === 'Raster') analysis.hasImages = true;
1975
+
1976
+ if (data.animationType) {
1977
+ analysis.hasAnimations = true;
1978
+ animationSet.add(data.animationType);
1979
+ }
1980
+ // Detect keyframe animations (added via app.addAnimation with keyframes array)
1981
+ if (data.keyframes || data.animation || data.keyframeAnimation) {
1982
+ analysis.hasAnimations = true;
1983
+ animationSet.add('keyframe');
1984
+ }
1985
+
1986
+ if (item.fillColor && item.fillColor.gradient) hasGradient = true;
1987
+ if (item.strokeColor && item.strokeColor.gradient) hasGradient = true;
1988
+ if (item.shadowColor || item.shadowBlur) hasShadow = true;
1989
+ });
1990
+
1991
+ // Check animation manager for registered animations
1992
+ if (app.animationManager) {
1993
+ const animations = app.animationManager.getAll ? app.animationManager.getAll() : (app.animationManager.animations || []);
1994
+ if (animations.length > 0) {
1995
+ analysis.hasAnimations = true;
1996
+ animationSet.add('managed');
1997
+ }
1998
+ }
1999
+ // Check timeline for active animations
2000
+ if (app.timeline && (app.timeline.isPlaying || app.timeline.animations?.length > 0)) {
2001
+ analysis.hasAnimations = true;
2002
+ animationSet.add('timeline');
2003
+ }
2004
+
2005
+ analysis.animationTypes = Array.from(animationSet);
2006
+ analysis.hasGradients = hasGradient;
2007
+ analysis.hasShadows = hasShadow;
2008
+
2009
+ // Check relations
2010
+ if (app.relationRegistry) {
2011
+ const relations = app.relationRegistry.getAll ? app.relationRegistry.getAll() : [];
2012
+ analysis.hasRelations = relations.length > 0;
2013
+ relations.forEach(rel => {
2014
+ if (rel.relationType) relationSet.add(rel.relationType);
2015
+ });
2016
+ analysis.relationTypes = Array.from(relationSet);
2017
+ if (analysis.hasRelations) analysis.hasAnimations = true;
2018
+ }
2019
+
2020
+ // Determine color complexity
2021
+ if (hasGradient) {
2022
+ analysis.colorComplexity = 'gradient';
2023
+ } else if (analysis.itemCount > 20 || hasShadow) {
2024
+ analysis.colorComplexity = 'complex';
2025
+ }
2026
+
2027
+ // Generate recommendations
2028
+ const recommendations = [];
2029
+ const { width, height } = analysis.canvasSize;
2030
+ const aspectRatio = width / height;
2031
+
2032
+ if (analysis.hasAnimations) {
2033
+ if (analysis.hasGradients || analysis.colorComplexity === 'complex') {
2034
+ recommendations.push({ platform: 'web', format: 'webm', confidence: 0.9, reason: 'Animated content with gradients renders best as WebM' });
2035
+ recommendations.push({ platform: 'instagram', format: 'mp4', confidence: 0.85, reason: 'MP4 for social media compatibility' });
2036
+ } else {
2037
+ recommendations.push({ platform: 'web', format: 'svg', confidence: 0.95, reason: 'Simple animations export well as animated SVG' });
2038
+ recommendations.push({ platform: 'twitter', format: 'gif', confidence: 0.8, reason: 'GIF for social media', warnings: ['Limited to 256 colors'] });
2039
+ }
2040
+ } else {
2041
+ if (analysis.colorComplexity === 'simple' && !analysis.hasImages) {
2042
+ recommendations.push({ platform: 'web', format: 'svg', confidence: 0.95, reason: 'Vector graphics best as SVG for scalability' });
2043
+ } else {
2044
+ recommendations.push({ platform: 'instagram', format: 'png', confidence: 0.9, reason: 'High quality static export as PNG' });
2045
+ }
2046
+ }
2047
+
2048
+ // Platform suggestions based on aspect ratio
2049
+ const suggestedPlatforms = [];
2050
+ if (Math.abs(aspectRatio - 1) < 0.1) suggestedPlatforms.push('instagram');
2051
+ if (aspectRatio < 0.7) suggestedPlatforms.push('instagram-story', 'tiktok');
2052
+ if (aspectRatio > 1.5) suggestedPlatforms.push('youtube', 'twitter', 'linkedin');
2053
+ suggestedPlatforms.push('web');
2054
+
2055
+ return {
2056
+ success: true,
2057
+ analysis,
2058
+ recommendations,
2059
+ suggestedPlatforms
2060
+ };
2061
+ })();
2062
+ `.trim();
2063
+ }
2064
+ // ===========================================================================
2065
+ // INTERACTIVE TRIGGER CODE GENERATORS
2066
+ // ===========================================================================
2067
+ /**
2068
+ * Generate code for adding a trigger
2069
+ */
2070
+ generateAddTrigger(input) {
2071
+ const validated = AddTriggerInputSchema.parse(input);
2072
+ const { itemId, event, actions, condition, timelineOffset } = validated;
2073
+ const actionsStr = JSON.stringify(actions);
2074
+ const conditionStr = condition ? `'${condition.replace(/'/g, "\\'")}'` : 'null';
2075
+ const offsetStr = timelineOffset !== undefined ? timelineOffset : 'null';
2076
+ return `
2077
+ // Add trigger to ${itemId}
2078
+ const item = app.getItemById('${itemId}');
2079
+ if (!item) throw new Error('Item not found: ${itemId}');
2080
+
2081
+ const trigger = {
2082
+ event: '${event}',
2083
+ actions: ${actionsStr},
2084
+ condition: ${conditionStr},
2085
+ timelineOffset: ${offsetStr}
2086
+ };
2087
+
2088
+ // Initialize triggers array if needed
2089
+ if (!item.data) item.data = {};
2090
+ if (!item.data.triggers) item.data.triggers = [];
2091
+
2092
+ // Add trigger
2093
+ item.data.triggers.push(trigger);
2094
+
2095
+ // Register event handler
2096
+ if (app.triggerManager) {
2097
+ app.triggerManager.registerTrigger(item, trigger);
2098
+ }
2099
+
2100
+ app.historyManager.saveState();
2101
+ ({ success: true, itemId: '${itemId}', event: '${event}', triggerId: item.data.triggers.length - 1 });
2102
+ `.trim();
2103
+ }
2104
+ /**
2105
+ * Generate code for removing a trigger
2106
+ */
2107
+ generateRemoveTrigger(input) {
2108
+ const validated = RemoveTriggerInputSchema.parse(input);
2109
+ const { itemId, event, removeAll } = validated;
2110
+ if (removeAll) {
2111
+ return `
2112
+ // Remove all triggers from ${itemId}
2113
+ const item = app.getItemById('${itemId}');
2114
+ if (!item) throw new Error('Item not found: ${itemId}');
2115
+
2116
+ const removedCount = item.data?.triggers?.length || 0;
2117
+ if (item.data) item.data.triggers = [];
2118
+
2119
+ if (app.triggerManager) {
2120
+ app.triggerManager.unregisterAll(item);
2121
+ }
2122
+
2123
+ app.historyManager.saveState();
2124
+ ({ success: true, itemId: '${itemId}', removedCount, removeAll: true });
2125
+ `.trim();
2126
+ }
2127
+ return `
2128
+ // Remove ${event} trigger from ${itemId}
2129
+ const item = app.getItemById('${itemId}');
2130
+ if (!item) throw new Error('Item not found: ${itemId}');
2131
+
2132
+ let removedCount = 0;
2133
+ if (item.data?.triggers) {
2134
+ const before = item.data.triggers.length;
2135
+ item.data.triggers = item.data.triggers.filter(t => t.event !== '${event}');
2136
+ removedCount = before - item.data.triggers.length;
2137
+ }
2138
+
2139
+ if (app.triggerManager) {
2140
+ app.triggerManager.unregisterEvent(item, '${event}');
2141
+ }
2142
+
2143
+ app.historyManager.saveState();
2144
+ ({ success: true, itemId: '${itemId}', event: '${event}', removedCount });
2145
+ `.trim();
2146
+ }
2147
+ /**
2148
+ * Generate code for querying triggers
2149
+ */
2150
+ generateQueryTriggers(input) {
2151
+ const validated = QueryTriggersInputSchema.parse(input);
2152
+ const { itemId, event } = validated;
2153
+ if (itemId) {
2154
+ return `
2155
+ // Query triggers for ${itemId}
2156
+ const item = app.getItemById('${itemId}');
2157
+ if (!item) throw new Error('Item not found: ${itemId}');
2158
+
2159
+ let triggers = item.data?.triggers || [];
2160
+ ${event ? `triggers = triggers.filter(t => t.event === '${event}');` : ''}
2161
+
2162
+ ({ success: true, itemId: '${itemId}', triggers, count: triggers.length });
2163
+ `.trim();
2164
+ }
2165
+ return `
2166
+ // Query all triggers
2167
+ const allTriggers = [];
2168
+ const items = app.itemRegistry ? app.itemRegistry.getAll() : [];
2169
+
2170
+ items.forEach(entry => {
2171
+ const item = entry.item;
2172
+ if (item.data?.triggers?.length > 0) {
2173
+ let triggers = item.data.triggers;
2174
+ ${event ? `triggers = triggers.filter(t => t.event === '${event}');` : ''}
2175
+ triggers.forEach((trigger, idx) => {
2176
+ allTriggers.push({
2177
+ itemId: entry.id,
2178
+ triggerId: idx,
2179
+ ...trigger
2180
+ });
2181
+ });
2182
+ }
2183
+ });
2184
+
2185
+ ({ success: true, triggers: allTriggers, count: allTriggers.length${event ? `, event: '${event}'` : ''} });
2186
+ `.trim();
2187
+ }
2188
+ // ===========================================================================
2189
+ // QUIZ CODE GENERATORS
2190
+ // ===========================================================================
2191
+ /**
2192
+ * Generate code for creating a quiz
2193
+ */
2194
+ generateCreateQuiz(input) {
2195
+ const validated = CreateQuizInputSchema.parse(input);
2196
+ const { title, questions, passingScore, showScore, allowRetry, shuffleQuestions, shuffleOptions } = validated;
2197
+ const quizConfig = JSON.stringify({
2198
+ title: title || 'Untitled Quiz',
2199
+ questions,
2200
+ passingScore: passingScore || 70,
2201
+ showScore: showScore !== false,
2202
+ allowRetry: allowRetry !== false,
2203
+ shuffleQuestions: shuffleQuestions || false,
2204
+ shuffleOptions: shuffleOptions || false,
2205
+ }, null, 2);
2206
+ return `
2207
+ // Create quiz
2208
+ const quizConfig = ${quizConfig};
2209
+
2210
+ // Initialize quiz manager if needed
2211
+ if (!app.quizManager) {
2212
+ app.quizManager = {
2213
+ quizzes: [],
2214
+ activeQuiz: null,
2215
+ createQuiz: function(config) {
2216
+ const quiz = {
2217
+ id: 'quiz_' + Date.now(),
2218
+ ...config,
2219
+ state: {
2220
+ currentQuestion: 0,
2221
+ answers: [],
2222
+ score: 0,
2223
+ maxScore: config.questions.reduce((sum, q) => sum + q.points, 0),
2224
+ complete: false
2225
+ }
2226
+ };
2227
+ this.quizzes.push(quiz);
2228
+ this.activeQuiz = quiz;
2229
+ return quiz;
2230
+ }
2231
+ };
2232
+ }
2233
+
2234
+ const quiz = app.quizManager.createQuiz(quizConfig);
2235
+ app.historyManager.saveState();
2236
+
2237
+ ({
2238
+ success: true,
2239
+ quizId: quiz.id,
2240
+ title: quiz.title,
2241
+ questionCount: quiz.questions.length,
2242
+ maxScore: quiz.state.maxScore,
2243
+ passingScore: quiz.passingScore
2244
+ });
2245
+ `.trim();
2246
+ }
2247
+ /**
2248
+ * Generate code for getting quiz state
2249
+ */
2250
+ generateGetQuizState(input) {
2251
+ const validated = GetQuizStateInputSchema.parse(input);
2252
+ const { quizId } = validated;
2253
+ const quizRef = quizId
2254
+ ? `app.quizManager.quizzes.find(q => q.id === '${quizId}')`
2255
+ : 'app.quizManager?.activeQuiz';
2256
+ return `
2257
+ // Get quiz state
2258
+ if (!app.quizManager) {
2259
+ return { success: false, error: 'No quiz manager initialized' };
2260
+ }
2261
+
2262
+ const quiz = ${quizRef};
2263
+ if (!quiz) {
2264
+ return { success: false, error: 'Quiz not found' };
2265
+ }
2266
+
2267
+ ({
2268
+ success: true,
2269
+ quizId: quiz.id,
2270
+ title: quiz.title,
2271
+ currentQuestion: quiz.state.currentQuestion,
2272
+ totalQuestions: quiz.questions.length,
2273
+ score: quiz.state.score,
2274
+ maxScore: quiz.state.maxScore,
2275
+ answers: quiz.state.answers,
2276
+ complete: quiz.state.complete,
2277
+ passed: quiz.state.complete ? (quiz.state.score / quiz.state.maxScore * 100) >= quiz.passingScore : null
2278
+ });
2279
+ `.trim();
2280
+ }
2281
+ /**
2282
+ * Generate code for resetting a quiz
2283
+ */
2284
+ generateResetQuiz(input) {
2285
+ const validated = ResetQuizInputSchema.parse(input);
2286
+ const { quizId } = validated;
2287
+ const quizRef = quizId
2288
+ ? `app.quizManager.quizzes.find(q => q.id === '${quizId}')`
2289
+ : 'app.quizManager?.activeQuiz';
2290
+ return `
2291
+ // Reset quiz
2292
+ if (!app.quizManager) {
2293
+ return { success: false, error: 'No quiz manager initialized' };
2294
+ }
2295
+
2296
+ const quiz = ${quizRef};
2297
+ if (!quiz) {
2298
+ return { success: false, error: 'Quiz not found' };
2299
+ }
2300
+
2301
+ // Reset state
2302
+ quiz.state = {
2303
+ currentQuestion: 0,
2304
+ answers: [],
2305
+ score: 0,
2306
+ maxScore: quiz.questions.reduce((sum, q) => sum + q.points, 0),
2307
+ complete: false
2308
+ };
2309
+
2310
+ app.historyManager.saveState();
2311
+
2312
+ ({ success: true, quizId: quiz.id, reset: true });
2313
+ `.trim();
2314
+ }
2315
+ // ===========================================================================
2316
+ // LETTER COLLAGE CODE GENERATORS
2317
+ // ===========================================================================
2318
+ /**
2319
+ * Generate code to create a letter collage
2320
+ */
2321
+ generateCreateLetterCollage(input) {
2322
+ const validated = CreateLetterCollageInputSchema.parse(input);
2323
+ const { text, style = 'tile', palette, position, fontSize = 48, fontFamily = 'Inter, sans-serif', spacing = 1.1, gradientPalette, gradientDirection = 'vertical', cornerRadius = 4, shadowEnabled = true, } = validated;
2324
+ // Build options object
2325
+ const options = {
2326
+ style,
2327
+ fontSize,
2328
+ fontFamily,
2329
+ spacing,
2330
+ cornerRadius,
2331
+ shadowEnabled,
2332
+ };
2333
+ if (palette)
2334
+ options.palette = palette;
2335
+ if (position)
2336
+ options.position = position;
2337
+ if (gradientPalette)
2338
+ options.gradientPalette = gradientPalette;
2339
+ if (gradientDirection)
2340
+ options.gradientDirection = gradientDirection;
2341
+ return `
2342
+ // Create letter collage: "${text}"
2343
+ (function() {
2344
+ const text = ${JSON.stringify(text)};
2345
+ const options = ${JSON.stringify(options, null, 2)};
2346
+
2347
+ if (!app.createLetterCollage) {
2348
+ return { success: false, error: 'Letter collage feature not available' };
2349
+ }
2350
+
2351
+ const result = app.createLetterCollage(text, options);
2352
+
2353
+ if (app.historyManager) {
2354
+ app.historyManager.saveState('create_letter_collage');
2355
+ }
2356
+
2357
+ return result;
2358
+ })();
2359
+ `.trim();
2360
+ }
2361
+ /**
2362
+ * Generate code to animate a letter collage
2363
+ */
2364
+ generateAnimateLetterCollage(input) {
2365
+ const validated = AnimateLetterCollageInputSchema.parse(input);
2366
+ const { collageId, animationType, staggerDelay = 0.1, animationSpeed = 1, } = validated;
2367
+ return `
2368
+ // Animate letter collage: ${collageId}
2369
+ (function() {
2370
+ const collageId = ${JSON.stringify(collageId)};
2371
+ const animationType = ${JSON.stringify(animationType)};
2372
+ const staggerDelay = ${staggerDelay};
2373
+ const animationSpeed = ${animationSpeed};
2374
+
2375
+ if (!app.animateLetterCollage) {
2376
+ return { success: false, error: 'Letter collage animation not available' };
2377
+ }
2378
+
2379
+ const result = app.animateLetterCollage(collageId, {
2380
+ animationType,
2381
+ staggerDelay,
2382
+ animationSpeed
2383
+ });
2384
+
2385
+ return result;
2386
+ })();
2387
+ `.trim();
2388
+ }
2389
+ /**
2390
+ * Generate code to get letter collage options
2391
+ */
2392
+ generateGetLetterCollageOptions() {
2393
+ return `
2394
+ // Get letter collage options
2395
+ (function() {
2396
+ if (!app.getLetterCollageOptions) {
2397
+ // Return static options if function not available
2398
+ return {
2399
+ success: true,
2400
+ styles: ['tile', 'magazine', 'paperCut', 'fold', 'gradient', 'image'],
2401
+ tilePalettes: {
2402
+ game: ['wordle', 'scrabble'],
2403
+ vibrant: ['candy', 'neon', 'rainbow'],
2404
+ soft: ['pastel', 'cotton'],
2405
+ natural: ['earth', 'ocean', 'forest', 'sunset'],
2406
+ professional: ['corporate', 'minimal', 'slate'],
2407
+ seasonal: ['christmas', 'halloween', 'spring'],
2408
+ magazine: ['magazine', 'newspaper', 'vintage'],
2409
+ paperCraft: ['paperCraft', 'origami', 'craftPaper']
2410
+ },
2411
+ gradientPalettes: ['rainbow', 'sunset', 'ocean', 'fire', 'gold', 'rose', 'ice', 'cyberpunk', 'neonGlow', 'purplePink'],
2412
+ gradientDirections: ['vertical', 'horizontal', 'diagonal', 'radial']
2413
+ };
2414
+ }
2415
+
2416
+ return app.getLetterCollageOptions();
2417
+ })();
2418
+ `.trim();
2419
+ }
2420
+ // ===========================================================================
2421
+ // CANVAS PRESETS CODE GENERATORS
2422
+ // ===========================================================================
2423
+ /**
2424
+ * Generate code to get canvas presets
2425
+ */
2426
+ generateGetCanvasPresets() {
2427
+ return `
2428
+ // Get canvas presets
2429
+ (function() {
2430
+ if (app.getCanvasPresets) {
2431
+ return app.getCanvasPresets();
2432
+ }
2433
+
2434
+ // Return static presets if function not available
2435
+ return {
2436
+ success: true,
2437
+ presets: [
2438
+ { key: 'youtube-thumbnail', name: 'YouTube Thumbnail', width: 1280, height: 720, aspectRatio: '16:9', category: 'video' },
2439
+ { key: 'youtube-short', name: 'YouTube Shorts', width: 1080, height: 1920, aspectRatio: '9:16', category: 'video' },
2440
+ { key: 'tiktok', name: 'TikTok', width: 1080, height: 1920, aspectRatio: '9:16', category: 'video' },
2441
+ { key: 'instagram-story', name: 'Instagram Story', width: 1080, height: 1920, aspectRatio: '9:16', category: 'social' },
2442
+ { key: 'instagram-post', name: 'Instagram Post (Square)', width: 1080, height: 1080, aspectRatio: '1:1', category: 'social' },
2443
+ { key: 'instagram-landscape', name: 'Instagram Landscape', width: 1080, height: 566, aspectRatio: '1.91:1', category: 'social' },
2444
+ { key: 'instagram-portrait', name: 'Instagram Portrait', width: 1080, height: 1350, aspectRatio: '4:5', category: 'social' },
2445
+ { key: 'facebook-post', name: 'Facebook Post', width: 1200, height: 630, aspectRatio: '1.91:1', category: 'social' },
2446
+ { key: 'facebook-cover', name: 'Facebook Cover', width: 820, height: 312, aspectRatio: '2.63:1', category: 'social' },
2447
+ { key: 'facebook-story', name: 'Facebook Story', width: 1080, height: 1920, aspectRatio: '9:16', category: 'social' },
2448
+ { key: 'twitter-post', name: 'Twitter/X Post', width: 1200, height: 675, aspectRatio: '16:9', category: 'social' },
2449
+ { key: 'twitter-header', name: 'Twitter/X Header', width: 1500, height: 500, aspectRatio: '3:1', category: 'social' },
2450
+ { key: 'linkedin-post', name: 'LinkedIn Post', width: 1200, height: 627, aspectRatio: '1.91:1', category: 'social' },
2451
+ { key: 'linkedin-banner', name: 'LinkedIn Banner', width: 1584, height: 396, aspectRatio: '4:1', category: 'social' },
2452
+ { key: 'pinterest-pin', name: 'Pinterest Pin', width: 1000, height: 1500, aspectRatio: '2:3', category: 'social' },
2453
+ { key: 'presentation-16x9', name: 'Presentation 16:9', width: 1920, height: 1080, aspectRatio: '16:9', category: 'presentation' },
2454
+ { key: 'presentation-4x3', name: 'Presentation 4:3', width: 1024, height: 768, aspectRatio: '4:3', category: 'presentation' },
2455
+ { key: 'hd-720p', name: 'HD 720p', width: 1280, height: 720, aspectRatio: '16:9', category: 'video' },
2456
+ { key: 'full-hd-1080p', name: 'Full HD 1080p', width: 1920, height: 1080, aspectRatio: '16:9', category: 'video' },
2457
+ { key: 'default', name: 'Default', width: 800, height: 600, aspectRatio: '4:3', category: 'general' }
2458
+ ]
2459
+ };
2460
+ })();
2461
+ `.trim();
2462
+ }
2463
+ // ===========================================================================
2464
+ // MAP CODE GENERATORS
2465
+ // ===========================================================================
2466
+ /**
2467
+ * Generate code to load a geographic map
2468
+ */
2469
+ generateLoadMap(input) {
2470
+ const validated = LoadMapInputSchema.parse(input);
2471
+ const { mapId, ...mapOptions } = validated;
2472
+ const optionsStr = Object.keys(mapOptions).length > 0 ? JSON.stringify(mapOptions) : '{}';
2473
+ return `
2474
+ // Load geographic map
2475
+ (async function() {
2476
+ if (!app.mapSystem) {
2477
+ return { success: false, error: 'Map system not available' };
2478
+ }
2479
+
2480
+ try {
2481
+ const result = await app.mapSystem.loadMap('${mapId}', ${optionsStr});
2482
+ return {
2483
+ success: true,
2484
+ mapId: result.mapId || '${mapId}',
2485
+ regions: result.regions?.length || 0,
2486
+ bounds: result.bounds,
2487
+ center: result.center
2488
+ };
2489
+ } catch (error) {
2490
+ return { success: false, error: error.message };
2491
+ }
2492
+ })();
2493
+ `.trim();
2494
+ }
2495
+ /**
2496
+ * Generate code to highlight map regions
2497
+ */
2498
+ generateHighlightRegions(input) {
2499
+ const validated = HighlightRegionsInputSchema.parse(input);
2500
+ const regionIds = JSON.stringify(validated.regionIds);
2501
+ const optionsStr = validated.options ? JSON.stringify(validated.options) : '{}';
2502
+ return `
2503
+ // Highlight map regions
2504
+ (function() {
2505
+ if (!app.mapSystem) {
2506
+ return { success: false, error: 'Map system not available' };
2507
+ }
2508
+
2509
+ try {
2510
+ app.mapSystem.highlightRegions(${regionIds}, ${optionsStr});
2511
+ return { success: true, highlighted: ${regionIds} };
2512
+ } catch (error) {
2513
+ return { success: false, error: error.message };
2514
+ }
2515
+ })();
2516
+ `.trim();
2517
+ }
2518
+ /**
2519
+ * Generate code to unhighlight map regions
2520
+ */
2521
+ generateUnhighlightRegions(input) {
2522
+ const validated = UnhighlightRegionsInputSchema.parse(input);
2523
+ const regionIds = validated.regionIds === 'all' ? "'all'" : JSON.stringify(validated.regionIds);
2524
+ return `
2525
+ // Unhighlight map regions
2526
+ (function() {
2527
+ if (!app.mapSystem) {
2528
+ return { success: false, error: 'Map system not available' };
2529
+ }
2530
+
2531
+ try {
2532
+ app.mapSystem.unhighlightRegions(${regionIds});
2533
+ return { success: true };
2534
+ } catch (error) {
2535
+ return { success: false, error: error.message };
2536
+ }
2537
+ })();
2538
+ `.trim();
2539
+ }
2540
+ /**
2541
+ * Generate code to apply data-driven colors (choropleth)
2542
+ */
2543
+ generateApplyDataColors(input) {
2544
+ const validated = ApplyDataColorsInputSchema.parse(input);
2545
+ const dataStr = JSON.stringify(validated.data);
2546
+ const optionsStr = validated.options ? JSON.stringify(validated.options) : '{}';
2547
+ return `
2548
+ // Apply choropleth data colors
2549
+ (function() {
2550
+ if (!app.mapSystem) {
2551
+ return { success: false, error: 'Map system not available' };
2552
+ }
2553
+
2554
+ try {
2555
+ app.mapSystem.applyDataColors(${dataStr}, ${optionsStr});
2556
+ return { success: true, regionsColored: ${Object.keys(validated.data).length} };
2557
+ } catch (error) {
2558
+ return { success: false, error: error.message };
2559
+ }
2560
+ })();
2561
+ `.trim();
2562
+ }
2563
+ /**
2564
+ * Generate code to add a map marker
2565
+ */
2566
+ generateAddMarker(input) {
2567
+ const validated = AddMarkerInputSchema.parse(input);
2568
+ const options = {
2569
+ lat: validated.lat,
2570
+ lon: validated.lon,
2571
+ };
2572
+ if (validated.label)
2573
+ options.label = validated.label;
2574
+ if (validated.color)
2575
+ options.color = validated.color;
2576
+ if (validated.size)
2577
+ options.size = validated.size;
2578
+ if (validated.pulse !== undefined)
2579
+ options.pulse = validated.pulse;
2580
+ if (validated.shape)
2581
+ options.shape = validated.shape;
2582
+ return `
2583
+ // Add map marker
2584
+ (function() {
2585
+ if (!app.mapSystem) {
2586
+ return { success: false, error: 'Map system not available' };
2587
+ }
2588
+
2589
+ try {
2590
+ const marker = app.mapSystem.addMarker(${JSON.stringify(options)});
2591
+ return { success: true, markerId: marker?.id || 'marker_added' };
2592
+ } catch (error) {
2593
+ return { success: false, error: error.message };
2594
+ }
2595
+ })();
2596
+ `.trim();
2597
+ }
2598
+ /**
2599
+ * Generate code to add map labels
2600
+ */
2601
+ generateAddMapLabels(input) {
2602
+ const validated = AddMapLabelsInputSchema.parse(input);
2603
+ const regionsStr = validated.regions ? JSON.stringify(validated.regions) : 'null';
2604
+ const optionsStr = validated.options ? JSON.stringify(validated.options) : '{}';
2605
+ return `
2606
+ // Add map labels
2607
+ (function() {
2608
+ if (!app.mapSystem) {
2609
+ return { success: false, error: 'Map system not available' };
2610
+ }
2611
+
2612
+ try {
2613
+ app.mapSystem.addLabels({ regions: ${regionsStr}, ...${optionsStr} });
2614
+ return { success: true };
2615
+ } catch (error) {
2616
+ return { success: false, error: error.message };
2617
+ }
2618
+ })();
2619
+ `.trim();
2620
+ }
2621
+ /**
2622
+ * Generate code to pan the map
2623
+ */
2624
+ generatePanMap(input) {
2625
+ const validated = PanMapInputSchema.parse(input);
2626
+ const options = {};
2627
+ if (validated.animate !== undefined)
2628
+ options.animate = validated.animate;
2629
+ if (validated.duration !== undefined)
2630
+ options.duration = validated.duration;
2631
+ return `
2632
+ // Pan map to coordinates
2633
+ (function() {
2634
+ if (!app.mapSystem) {
2635
+ return { success: false, error: 'Map system not available' };
2636
+ }
2637
+
2638
+ try {
2639
+ app.mapSystem.panTo(${validated.lat}, ${validated.lon}${Object.keys(options).length > 0 ? ', ' + JSON.stringify(options) : ''});
2640
+ return { success: true, panTo: [${validated.lat}, ${validated.lon}] };
2641
+ } catch (error) {
2642
+ return { success: false, error: error.message };
2643
+ }
2644
+ })();
2645
+ `.trim();
2646
+ }
2647
+ /**
2648
+ * Generate code to zoom the map
2649
+ */
2650
+ generateZoomMap(input) {
2651
+ const validated = ZoomMapInputSchema.parse(input);
2652
+ const options = {};
2653
+ if (validated.animate !== undefined)
2654
+ options.animate = validated.animate;
2655
+ if (validated.duration !== undefined)
2656
+ options.duration = validated.duration;
2657
+ return `
2658
+ // Set map zoom level
2659
+ (function() {
2660
+ if (!app.mapSystem) {
2661
+ return { success: false, error: 'Map system not available' };
2662
+ }
2663
+
2664
+ try {
2665
+ app.mapSystem.zoomTo(${validated.level}${Object.keys(options).length > 0 ? ', ' + JSON.stringify(options) : ''});
2666
+ return { success: true, zoomLevel: ${validated.level} };
2667
+ } catch (error) {
2668
+ return { success: false, error: error.message };
2669
+ }
2670
+ })();
2671
+ `.trim();
2672
+ }
2673
+ /**
2674
+ * Generate code to export map configuration
2675
+ */
2676
+ generateExportMap() {
2677
+ return `
2678
+ // Export map configuration
2679
+ (function() {
2680
+ if (!app.mapSystem) {
2681
+ return { success: false, error: 'Map system not available' };
2682
+ }
2683
+
2684
+ try {
2685
+ const mapData = app.mapSystem.exportMap();
2686
+ return { success: true, ...mapData };
2687
+ } catch (error) {
2688
+ return { success: false, error: error.message };
2689
+ }
2690
+ })();
2691
+ `.trim();
2692
+ }
2693
+ /**
2694
+ * Generate code to import a custom map
2695
+ */
2696
+ generateImportCustomMap(input) {
2697
+ const validated = ImportCustomMapInputSchema.parse(input);
2698
+ const optionsStr = validated.options ? JSON.stringify(validated.options) : '{}';
2699
+ if (validated.url) {
2700
+ return `
2701
+ // Import custom map from URL
2702
+ (async function() {
2703
+ if (!app.mapSystem) {
2704
+ return { success: false, error: 'Map system not available' };
2705
+ }
2706
+
2707
+ try {
2708
+ const result = await app.mapSystem.importCustomMap('${validated.url}', ${optionsStr});
2709
+ return { success: true, ...result };
2710
+ } catch (error) {
2711
+ return { success: false, error: error.message };
2712
+ }
2713
+ })();
2714
+ `.trim();
2715
+ }
2716
+ else if (validated.geoJson) {
2717
+ return `
2718
+ // Import custom GeoJSON map
2719
+ (async function() {
2720
+ if (!app.mapSystem) {
2721
+ return { success: false, error: 'Map system not available' };
2722
+ }
2723
+
2724
+ try {
2725
+ const result = await app.mapSystem.importCustomMap(${JSON.stringify(validated.geoJson)}, ${optionsStr});
2726
+ return { success: true, ...result };
2727
+ } catch (error) {
2728
+ return { success: false, error: error.message };
2729
+ }
2730
+ })();
2731
+ `.trim();
2732
+ }
2733
+ return `
2734
+ // Import custom map - no source provided
2735
+ (function() {
2736
+ return { success: false, error: 'Either url or geoJson must be provided' };
2737
+ })();
2738
+ `.trim();
2739
+ }
2740
+ /**
2741
+ * Generate code to get region at a point (hit testing)
2742
+ */
2743
+ generateGetRegionAtPoint(input) {
2744
+ const validated = GetRegionAtPointInputSchema.parse(input);
2745
+ return `
2746
+ // Get region at canvas point
2747
+ (function() {
2748
+ if (!app.mapSystem) {
2749
+ return { success: false, error: 'Map system not available' };
2750
+ }
2751
+
2752
+ try {
2753
+ const region = app.mapSystem.getRegionAtPoint({ x: ${validated.x}, y: ${validated.y} });
2754
+ if (region) {
2755
+ return { success: true, regionId: region.id, regionName: region.name, properties: region.properties };
2756
+ }
2757
+ return { success: true, regionId: null, message: 'No region at this point' };
2758
+ } catch (error) {
2759
+ return { success: false, error: error.message };
2760
+ }
2761
+ })();
2762
+ `.trim();
2763
+ }
2764
+ // ===========================================================================
2765
+ // MAP ANIMATION/CSV CODE GENERATORS
2766
+ // ===========================================================================
2767
+ /**
2768
+ * Generate code to animate map regions with keyframes
2769
+ */
2770
+ generateAnimateMapRegions(input) {
2771
+ const validated = AnimateMapRegionsInputSchema.parse(input);
2772
+ const regionsStr = JSON.stringify(validated.regions);
2773
+ return `
2774
+ // Animate map regions with keyframes
2775
+ (function() {
2776
+ if (!app.mapSystem) {
2777
+ return { success: false, error: 'Map system not available' };
2778
+ }
2779
+
2780
+ try {
2781
+ const result = app.mapSystem.animateRegions({
2782
+ duration: ${validated.duration || 5},
2783
+ loop: ${validated.loop !== false},
2784
+ regions: ${regionsStr}
2785
+ });
2786
+ return { success: true, animatedRegions: Object.keys(${regionsStr}), duration: ${validated.duration || 5}, errors: result?.errors || [] };
2787
+ } catch (error) {
2788
+ return { success: false, error: error.message };
2789
+ }
2790
+ })();
2791
+ `.trim();
2792
+ }
2793
+ /**
2794
+ * Generate code to create wave animation across map regions
2795
+ */
2796
+ generateAnimateMapWave(input) {
2797
+ const validated = AnimateMapWaveInputSchema.parse(input);
2798
+ const colorsStr = validated.colors ? JSON.stringify(validated.colors) : '["#ef4444", "#fbbf24", "#22c55e", "#3b82f6"]';
2799
+ return `
2800
+ // Create wave animation across map regions
2801
+ (function() {
2802
+ if (!app.mapSystem) {
2803
+ return { success: false, error: 'Map system not available' };
2804
+ }
2805
+
2806
+ try {
2807
+ const result = app.mapSystem.animateWave({
2808
+ duration: ${validated.duration || 10},
2809
+ loop: ${validated.loop !== false},
2810
+ colors: ${colorsStr},
2811
+ waveDirection: '${validated.waveDirection || 'horizontal'}'
2812
+ });
2813
+ return { success: true, animatedRegions: result?.animatedRegions || [], totalRegions: result?.totalRegions || 0 };
2814
+ } catch (error) {
2815
+ return { success: false, error: error.message };
2816
+ }
2817
+ })();
2818
+ `.trim();
2819
+ }
2820
+ /**
2821
+ * Generate code to stop map animations
2822
+ */
2823
+ generateStopMapAnimations(input) {
2824
+ const validated = StopMapAnimationsInputSchema.parse(input);
2825
+ const regionsStr = validated.regions ? JSON.stringify(validated.regions) : 'null';
2826
+ return `
2827
+ // Stop map region animations
2828
+ (function() {
2829
+ if (!app.mapSystem) {
2830
+ return { success: false, error: 'Map system not available' };
2831
+ }
2832
+
2833
+ try {
2834
+ app.mapSystem.stopAnimations({
2835
+ regions: ${regionsStr},
2836
+ resetColors: ${validated.resetColors !== false}
2837
+ });
2838
+ return { success: true };
2839
+ } catch (error) {
2840
+ return { success: false, error: error.message };
2841
+ }
2842
+ })();
2843
+ `.trim();
2844
+ }
2845
+ /**
2846
+ * Generate code to get animated map regions
2847
+ */
2848
+ generateGetAnimatedMapRegions() {
2849
+ return `
2850
+ // Get animated map regions
2851
+ (function() {
2852
+ if (!app.mapSystem) {
2853
+ return { success: false, error: 'Map system not available' };
2854
+ }
2855
+
2856
+ try {
2857
+ const result = app.mapSystem.getAnimatedRegions();
2858
+ return { animatedRegions: result?.animatedRegions || [], count: result?.count || 0 };
2859
+ } catch (error) {
2860
+ return { success: false, error: error.message };
2861
+ }
2862
+ })();
2863
+ `.trim();
2864
+ }
2865
+ /**
2866
+ * Generate code to export map region data as CSV
2867
+ */
2868
+ generateExportMapRegionCSV(input) {
2869
+ const validated = ExportMapRegionCSVInputSchema.parse(input);
2870
+ return `
2871
+ // Export map region data as CSV
2872
+ (function() {
2873
+ if (!app.mapSystem) {
2874
+ return { success: false, error: 'Map system not available' };
2875
+ }
2876
+
2877
+ try {
2878
+ const result = app.mapSystem.exportRegionCSV({
2879
+ includeHighlighted: ${validated.includeHighlighted !== false},
2880
+ includeSelected: ${validated.includeSelected !== false},
2881
+ includeColors: ${validated.includeColors !== false},
2882
+ download: ${validated.download === true},
2883
+ filename: '${validated.filename || 'map-regions.csv'}'
2884
+ });
2885
+ return { success: true, csv: result?.csv || '', regionCount: result?.regionCount || 0 };
2886
+ } catch (error) {
2887
+ return { success: false, error: error.message };
2888
+ }
2889
+ })();
2890
+ `.trim();
2891
+ }
2892
+ /**
2893
+ * Generate code to import CSV data to update map regions
2894
+ */
2895
+ generateImportMapRegionCSV(input) {
2896
+ const validated = ImportMapRegionCSVInputSchema.parse(input);
2897
+ // Escape the CSV text for JavaScript string
2898
+ const escapedCsv = validated.csvText.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$/g, '\\$');
2899
+ return `
2900
+ // Import CSV data to update map regions
2901
+ (function() {
2902
+ if (!app.mapSystem) {
2903
+ return { success: false, error: 'Map system not available' };
2904
+ }
2905
+
2906
+ try {
2907
+ const result = app.mapSystem.importRegionCSV({
2908
+ csvText: \`${escapedCsv}\`,
2909
+ applyColors: ${validated.applyColors !== false},
2910
+ applyHighlight: ${validated.applyHighlight !== false},
2911
+ applySelection: ${validated.applySelection !== false}
2912
+ });
2913
+ return { success: true, updatedRegions: result?.updatedRegions || 0, notFound: result?.notFound || [], errors: result?.errors || [] };
2914
+ } catch (error) {
2915
+ return { success: false, error: error.message };
2916
+ }
2917
+ })();
2918
+ `.trim();
2919
+ }
2920
+ /**
2921
+ * Generate code to select map regions
2922
+ */
2923
+ generateSelectMapRegions(input) {
2924
+ const validated = SelectMapRegionsInputSchema.parse(input);
2925
+ const regionIds = JSON.stringify(validated.regionIds);
2926
+ return `
2927
+ // Select map regions
2928
+ (function() {
2929
+ if (!app.mapSystem) {
2930
+ return { success: false, error: 'Map system not available' };
2931
+ }
2932
+
2933
+ try {
2934
+ app.mapSystem.selectRegions(${regionIds});
2935
+ return { success: true, selected: ${regionIds} };
2936
+ } catch (error) {
2937
+ return { success: false, error: error.message };
2938
+ }
2939
+ })();
2940
+ `.trim();
2941
+ }
2942
+ /**
2943
+ * Generate code to deselect map regions
2944
+ */
2945
+ generateDeselectMapRegions(input) {
2946
+ const validated = DeselectMapRegionsInputSchema.parse(input);
2947
+ const regionIds = validated.regionIds ? JSON.stringify(validated.regionIds) : 'null';
2948
+ return `
2949
+ // Deselect map regions
2950
+ (function() {
2951
+ if (!app.mapSystem) {
2952
+ return { success: false, error: 'Map system not available' };
2953
+ }
2954
+
2955
+ try {
2956
+ app.mapSystem.deselectRegions(${regionIds});
2957
+ return { success: true };
2958
+ } catch (error) {
2959
+ return { success: false, error: error.message };
2960
+ }
2961
+ })();
2962
+ `.trim();
2963
+ }
2964
+ /**
2965
+ * Generate code to get highlighted map regions
2966
+ */
2967
+ generateGetHighlightedMapRegions() {
2968
+ return `
2969
+ // Get highlighted map regions
2970
+ (function() {
2971
+ if (!app.mapSystem) {
2972
+ return { success: false, error: 'Map system not available' };
2973
+ }
2974
+
2975
+ try {
2976
+ const result = app.mapSystem.getHighlightedRegions();
2977
+ return { highlighted: result?.highlighted || [], count: result?.count || 0 };
2978
+ } catch (error) {
2979
+ return { success: false, error: error.message };
2980
+ }
2981
+ })();
2982
+ `.trim();
2983
+ }
2984
+ /**
2985
+ * Generate code to export the complete scene state
2986
+ */
2987
+ generateExportScene() {
2988
+ return `
2989
+ // Export complete scene state
2990
+ (function() {
2991
+ try {
2992
+ const items = [];
2993
+ const relations = [];
2994
+ const decorative = [];
2995
+
2996
+ // Collect items from registry
2997
+ if (app.itemRegistry) {
2998
+ for (const [id, item] of app.itemRegistry.entries()) {
2999
+ const itemData = {
3000
+ id: id,
3001
+ type: item.data?.type || item.className?.toLowerCase() || 'unknown',
3002
+ position: item.position ? { x: item.position.x, y: item.position.y } : null,
3003
+ bounds: item.bounds ? {
3004
+ x: item.bounds.x,
3005
+ y: item.bounds.y,
3006
+ width: item.bounds.width,
3007
+ height: item.bounds.height
3008
+ } : null,
3009
+ properties: {}
3010
+ };
3011
+
3012
+ // Collect common properties
3013
+ if (item.fillColor) itemData.properties.fillColor = item.fillColor.toCSS ? item.fillColor.toCSS() : item.fillColor;
3014
+ if (item.strokeColor) itemData.properties.strokeColor = item.strokeColor.toCSS ? item.strokeColor.toCSS() : item.strokeColor;
3015
+ if (item.strokeWidth) itemData.properties.strokeWidth = item.strokeWidth;
3016
+ if (item.opacity !== undefined) itemData.properties.opacity = item.opacity;
3017
+ if (item.rotation) itemData.properties.rotation = item.rotation;
3018
+ if (item.data?.content) itemData.properties.content = item.data.content;
3019
+
3020
+ if (item.data?.isDecorative) {
3021
+ decorative.push(itemData);
3022
+ } else {
3023
+ items.push(itemData);
3024
+ }
3025
+ }
3026
+ }
3027
+
3028
+ // Collect relations
3029
+ if (app.getRelations) {
3030
+ const activeRelations = app.getRelations();
3031
+ for (const rel of activeRelations) {
3032
+ relations.push({
3033
+ sourceId: rel.sourceId,
3034
+ targetId: rel.targetId,
3035
+ type: rel.type,
3036
+ params: rel.params
3037
+ });
3038
+ }
3039
+ }
3040
+
3041
+ // Get background color and canvas size
3042
+ const backgroundColor = app.getBackgroundColor ? app.getBackgroundColor() : null;
3043
+ const canvasSize = app.getCanvasSize ? app.getCanvasSize() : { width: 800, height: 600 };
3044
+
3045
+ return {
3046
+ success: true,
3047
+ items: items,
3048
+ relations: relations,
3049
+ decorative: decorative,
3050
+ backgroundColor: backgroundColor,
3051
+ canvasSize: canvasSize,
3052
+ itemCount: items.length,
3053
+ relationCount: relations.length
3054
+ };
3055
+ } catch (error) {
3056
+ return { success: false, error: error.message };
3057
+ }
3058
+ })();
3059
+ `.trim();
3060
+ }
3061
+ /**
3062
+ * Generate code to export map as GeoJSON with current styling
3063
+ */
3064
+ generateExportMapGeoJson(options = {}) {
3065
+ const includeStyles = options.includeStyles !== false;
3066
+ const includeMetadata = options.includeMetadata !== false;
3067
+ const selectedOnly = options.selectedOnly || false;
3068
+ const download = options.download || false;
3069
+ const filename = options.filename || 'map-export.geojson';
3070
+ return `
3071
+ // Export map as GeoJSON
3072
+ (function() {
3073
+ if (!app.mapSystem) {
3074
+ return { success: false, error: 'Map system not available' };
3075
+ }
3076
+
3077
+ try {
3078
+ const result = app.mapSystem.exportGeoJSON({
3079
+ includeStyles: ${includeStyles},
3080
+ includeMetadata: ${includeMetadata},
3081
+ selectedOnly: ${selectedOnly},
3082
+ download: ${download},
3083
+ filename: ${JSON.stringify(filename)}
3084
+ });
3085
+ return result;
3086
+ } catch (error) {
3087
+ return { success: false, error: error.message };
3088
+ }
3089
+ })();
3090
+ `.trim();
3091
+ }
3092
+ /**
3093
+ * Generate code to export original map GeoJSON (unmodified boundaries)
3094
+ */
3095
+ generateExportOriginalMapGeoJson(options = {}) {
3096
+ const download = options.download || false;
3097
+ const filename = options.filename || 'map-source.geojson';
3098
+ return `
3099
+ // Export original map GeoJSON
3100
+ (function() {
3101
+ if (!app.mapSystem) {
3102
+ return { success: false, error: 'Map system not available' };
3103
+ }
3104
+
3105
+ try {
3106
+ const result = app.mapSystem.exportOriginalGeoJSON({
3107
+ download: ${download},
3108
+ filename: ${JSON.stringify(filename)}
3109
+ });
3110
+ return result;
3111
+ } catch (error) {
3112
+ return { success: false, error: error.message };
3113
+ }
3114
+ })();
3115
+ `.trim();
3116
+ }
3117
+ /**
3118
+ * Generate code to get map source info
3119
+ */
3120
+ generateGetMapSourceInfo() {
3121
+ return `
3122
+ // Get map source info
3123
+ (function() {
3124
+ if (!app.mapSystem) {
3125
+ return { success: false, error: 'Map system not available' };
3126
+ }
3127
+
3128
+ try {
3129
+ const result = app.mapSystem.getSourceInfo();
3130
+ return result;
3131
+ } catch (error) {
3132
+ return { success: false, error: error.message };
3133
+ }
3134
+ })();
3135
+ `.trim();
3136
+ }
3137
+ /**
3138
+ * Generate code to register a Paper.js item
3139
+ */
3140
+ generateRegisterItem(input) {
3141
+ const itemJsonStr = JSON.stringify(input.itemJson);
3142
+ const propertiesStr = input.properties ? JSON.stringify(input.properties) : '{}';
3143
+ return `
3144
+ // Register Paper.js item
3145
+ (function() {
3146
+ try {
3147
+ // Import the Paper.js item from JSON
3148
+ const item = paper.project.importJSON(${itemJsonStr});
3149
+ if (!item) {
3150
+ return { success: false, error: 'Failed to import item from JSON' };
3151
+ }
3152
+
3153
+ // Register in item registry
3154
+ const itemId = app.itemRegistry ? 'item_' + (app.itemRegistry.size + 1) : 'item_1';
3155
+ item.data = item.data || {};
3156
+ item.data.registryId = itemId;
3157
+ item.data.type = ${JSON.stringify(input.itemType)};
3158
+
3159
+ // Merge custom properties
3160
+ const customProps = ${propertiesStr};
3161
+ Object.assign(item.data, customProps);
3162
+
3163
+ // Add to registry
3164
+ if (app.itemRegistry) {
3165
+ app.itemRegistry.set(itemId, item);
3166
+ }
3167
+
3168
+ // Add to appropriate group
3169
+ if (app.textItemGroup) {
3170
+ app.textItemGroup.addChild(item);
3171
+ }
3172
+
3173
+ return {
3174
+ success: true,
3175
+ itemId: itemId,
3176
+ type: ${JSON.stringify(input.itemType)},
3177
+ position: item.position ? { x: item.position.x, y: item.position.y } : null,
3178
+ bounds: item.bounds ? {
3179
+ x: item.bounds.x,
3180
+ y: item.bounds.y,
3181
+ width: item.bounds.width,
3182
+ height: item.bounds.height
3183
+ } : null
3184
+ };
3185
+ } catch (error) {
3186
+ return { success: false, error: error.message };
3187
+ }
3188
+ })();
3189
+ `.trim();
3190
+ }
3191
+ // =============================================================================
3192
+ // CUSTOM RELATION & CODE GENERATORS
3193
+ // =============================================================================
3194
+ generateRegisterCustomRelation(input) {
3195
+ const validated = RegisterCustomRelationInputSchema.parse(input);
3196
+ const paramsStr = validated.params ? JSON.stringify(validated.params) : '{}';
3197
+ const templatesStr = validated.templates ? JSON.stringify(validated.templates) : '[]';
3198
+ return `
3199
+ // Register custom relation: ${validated.name}
3200
+ (function() {
3201
+ try {
3202
+ app.registerRelationRule('${validated.name}', {
3203
+ description: ${JSON.stringify(validated.description || '')},
3204
+ params: ${paramsStr},
3205
+ compute: (ctx) => {
3206
+ const { fromPosition, toPosition, params, delta, time } = ctx;
3207
+ ${validated.computeFunction}
3208
+ },
3209
+ apply: (item, target, computed, params) => {
3210
+ ${validated.applyFunction}
3211
+ },
3212
+ templates: ${templatesStr},
3213
+ continuous: ${validated.continuous !== false},
3214
+ priority: ${validated.priority || 0}
3215
+ });
3216
+ return { success: true, relationName: '${validated.name}', message: 'Custom relation registered successfully' };
3217
+ } catch (error) {
3218
+ return { success: false, error: error.message };
3219
+ }
3220
+ })();
3221
+ `.trim();
3222
+ }
3223
+ generateExecuteCustomCode(input) {
3224
+ const validated = ExecuteCustomCodeInputSchema.parse(input);
3225
+ const description = validated.description || 'Execute custom code';
3226
+ return `
3227
+ // ${description}
3228
+ (function() {
3229
+ try {
3230
+ ${validated.code}
3231
+ } catch (error) {
3232
+ return { success: false, error: error.message };
3233
+ }
3234
+ })();
3235
+ `.trim();
3236
+ }
3237
+ // =============================================================================
3238
+ // MASK GENERATORS
3239
+ // =============================================================================
3240
+ generateApplyAnimatedMask(input) {
3241
+ const validated = ApplyAnimatedMaskInputSchema.parse(input);
3242
+ const optionsStr = validated.options ? JSON.stringify(validated.options) : '{}';
3243
+ const keyframesStr = validated.keyframes ? JSON.stringify(validated.keyframes) : 'null';
3244
+ const maskOptionsStr = validated.maskOptions ? JSON.stringify(validated.maskOptions) : '{}';
3245
+ return `
3246
+ // Apply animated mask to ${validated.itemId}
3247
+ (function() {
3248
+ if (!app.maskSystem) {
3249
+ return { success: false, error: 'Mask system not available' };
3250
+ }
3251
+
3252
+ try {
3253
+ const result = app.maskSystem.applyAnimatedMask('${validated.itemId}', {
3254
+ preset: ${validated.preset ? `'${validated.preset}'` : 'null'},
3255
+ maskType: ${validated.maskType ? `'${validated.maskType}'` : 'null'},
3256
+ keyframes: ${keyframesStr},
3257
+ options: ${optionsStr},
3258
+ maskOptions: ${maskOptionsStr}
3259
+ });
3260
+ return result;
3261
+ } catch (error) {
3262
+ return { success: false, error: error.message };
3263
+ }
3264
+ })();
3265
+ `.trim();
3266
+ }
3267
+ generateApplyCustomMask(input) {
3268
+ const validated = ApplyCustomMaskInputSchema.parse(input);
3269
+ const keyframesStr = JSON.stringify(validated.keyframes);
3270
+ const maskOptionsStr = validated.maskOptions ? JSON.stringify(validated.maskOptions) : '{}';
3271
+ return `
3272
+ // Apply custom mask to ${validated.itemId}
3273
+ (function() {
3274
+ if (!app.maskSystem) {
3275
+ return { success: false, error: 'Mask system not available' };
3276
+ }
3277
+
3278
+ try {
3279
+ const result = app.maskSystem.applyCustomMask('${validated.itemId}', {
3280
+ maskType: '${validated.maskType}',
3281
+ keyframes: ${keyframesStr},
3282
+ maskOptions: ${maskOptionsStr}
3283
+ });
3284
+ return result;
3285
+ } catch (error) {
3286
+ return { success: false, error: error.message };
3287
+ }
3288
+ })();
3289
+ `.trim();
3290
+ }
3291
+ generateRemoveMask(input) {
3292
+ const validated = RemoveMaskInputSchema.parse(input);
3293
+ return `
3294
+ // Remove mask from ${validated.itemId}
3295
+ (function() {
3296
+ if (!app.maskSystem) {
3297
+ return { success: false, error: 'Mask system not available' };
3298
+ }
3299
+
3300
+ try {
3301
+ const result = app.maskSystem.removeMask('${validated.itemId}');
3302
+ return { success: true, itemId: '${validated.itemId}', message: 'Mask removed' };
3303
+ } catch (error) {
3304
+ return { success: false, error: error.message };
3305
+ }
3306
+ })();
3307
+ `.trim();
3308
+ }
3309
+ generateGetAnimatableProperties() {
3310
+ return `
3311
+ // Get animatable properties for mask types
3312
+ (function() {
3313
+ return {
3314
+ rectangle: {
3315
+ properties: ['x', 'y', 'width', 'height', 'rotation', 'opacity'],
3316
+ description: 'Rectangular mask with position and size'
3317
+ },
3318
+ circle: {
3319
+ properties: ['x', 'y', 'radius', 'scale', 'opacity'],
3320
+ description: 'Circular mask with center and radius'
3321
+ },
3322
+ ellipse: {
3323
+ properties: ['x', 'y', 'radiusX', 'radiusY', 'rotation', 'scale', 'opacity'],
3324
+ description: 'Elliptical mask with separate radii'
3325
+ },
3326
+ star: {
3327
+ properties: ['x', 'y', 'radius', 'scale', 'rotation', 'opacity'],
3328
+ description: 'Star mask with configurable points'
3329
+ },
3330
+ triangle: {
3331
+ properties: ['x', 'y', 'scale', 'rotation', 'opacity'],
3332
+ description: 'Triangular mask'
3333
+ },
3334
+ hexagon: {
3335
+ properties: ['x', 'y', 'scale', 'rotation', 'opacity'],
3336
+ description: 'Hexagonal mask'
3337
+ },
3338
+ heart: {
3339
+ properties: ['x', 'y', 'scale', 'rotation', 'opacity'],
3340
+ description: 'Heart-shaped mask'
3341
+ },
3342
+ rounded: {
3343
+ properties: ['x', 'y', 'width', 'height', 'cornerRadius', 'rotation', 'opacity'],
3344
+ description: 'Rounded rectangle mask'
3345
+ }
3346
+ };
3347
+ })();
3348
+ `.trim();
3349
+ }
3350
+ generateGetAvailableEasings() {
3351
+ return `
3352
+ // Get available easing functions
3353
+ (function() {
3354
+ return {
3355
+ easings: ['linear', 'easeIn', 'easeOut', 'easeInOut', 'bounce', 'elastic'],
3356
+ descriptions: {
3357
+ linear: 'Constant speed',
3358
+ easeIn: 'Slow start, fast end',
3359
+ easeOut: 'Fast start, slow end',
3360
+ easeInOut: 'Slow start and end',
3361
+ bounce: 'Bouncing effect at end',
3362
+ elastic: 'Spring-like overshoot'
3363
+ }
3364
+ };
3365
+ })();
3366
+ `.trim();
3367
+ }
3368
+ generateGetMaskTypes() {
3369
+ return `
3370
+ // Get available mask types
3371
+ (function() {
3372
+ return {
3373
+ maskTypes: ['rectangle', 'circle', 'ellipse', 'star', 'triangle', 'hexagon', 'heart', 'rounded', 'custom']
3374
+ };
3375
+ })();
3376
+ `.trim();
3377
+ }
3378
+ generateGetMaskAnimations() {
3379
+ return `
3380
+ // Get available mask animation presets
3381
+ (function() {
3382
+ return {
3383
+ animations: [
3384
+ 'wipeLeft', 'wipeRight', 'wipeUp', 'wipeDown',
3385
+ 'iris', 'irisOut',
3386
+ 'star', 'heart',
3387
+ 'curtainHorizontal', 'curtainVertical', 'cinematic',
3388
+ 'diagonalWipe',
3389
+ 'revealUp', 'revealDown'
3390
+ ]
3391
+ };
3392
+ })();
3393
+ `.trim();
3394
+ }
3395
+ generateApplyTemplate(input) {
3396
+ const { templateId, category, listOnly } = input;
3397
+ if (listOnly || !templateId) {
3398
+ // List mode: return available templates, optionally filtered by category
3399
+ const categoryFilter = category ? `'${category}'` : 'null';
3400
+ return `
3401
+ // List available templates
3402
+ (function() {
3403
+ if (!app.templateManager) {
3404
+ return { error: 'Template manager not available. Make sure PinePaper Studio is loaded.' };
3405
+ }
3406
+ const allTemplates = app.templateManager.getAllTemplates();
3407
+ const category = ${categoryFilter};
3408
+ const filtered = category
3409
+ ? allTemplates.filter(t => t.category === category)
3410
+ : allTemplates;
3411
+ return {
3412
+ templates: filtered.map(t => ({
3413
+ id: t.id,
3414
+ name: t.name,
3415
+ category: t.category,
3416
+ description: t.description || ''
3417
+ })),
3418
+ count: filtered.length,
3419
+ categories: [...new Set(allTemplates.map(t => t.category))]
3420
+ };
3421
+ })();
3422
+ `.trim();
3423
+ }
3424
+ // Load mode: apply a specific template
3425
+ return `
3426
+ // Apply template: ${templateId}
3427
+ (async function() {
3428
+ if (!app.templateManager) {
3429
+ return { error: 'Template manager not available. Make sure PinePaper Studio is loaded.' };
3430
+ }
3431
+ try {
3432
+ await app.templateManager.loadTemplate('${templateId}', true);
3433
+ return { success: true, templateId: '${templateId}', message: 'Template applied successfully. Canvas has been replaced with template content.' };
3434
+ } catch (e) {
3435
+ return { error: 'Failed to apply template: ' + e.message };
3436
+ }
3437
+ })();
3438
+ `.trim();
3439
+ }
3440
+ generateImportImage(input) {
3441
+ const { url, position, maxWidth, maxHeight, mask } = input;
3442
+ const positionOpts = position ? `, position: { x: ${position.x}, y: ${position.y} }` : '';
3443
+ const maxWidthOpt = maxWidth !== undefined ? `, maxWidth: ${maxWidth}` : '';
3444
+ const maxHeightOpt = maxHeight !== undefined ? `, maxHeight: ${maxHeight}` : '';
3445
+ return `
3446
+ // Import image from URL
3447
+ (async function() {
3448
+ if (!app.imageTools) {
3449
+ return { error: 'Image tools not available. Make sure PinePaper Studio is loaded.' };
3450
+ }
3451
+ try {
3452
+ const entry = await app.imageTools.uploadFromURL('${url}');
3453
+ const opts = {${positionOpts}${maxWidthOpt}${maxHeightOpt} };
3454
+ const raster = await app.imageTools.placeImage(entry.id, Object.keys(opts).length > 0 ? opts : undefined);
3455
+ ${mask ? ` app.imageTools.applyMask(raster, '${mask}');\n` : ''} const itemId = raster.data?.itemId || raster.name || raster.id;
3456
+ return {
3457
+ success: true,
3458
+ itemId: itemId,
3459
+ message: 'Image imported and placed on canvas.',
3460
+ bounds: { x: raster.bounds.x, y: raster.bounds.y, width: raster.bounds.width, height: raster.bounds.height }${mask ? `,\n mask: '${mask}'` : ''}
3461
+ };
3462
+ } catch (e) {
3463
+ return { error: 'Failed to import image: ' + e.message };
3464
+ }
3465
+ })();
3466
+ `.trim();
3467
+ }
1327
3468
  }
1328
3469
  // Export singleton instance
1329
3470
  export const codeGenerator = new PinePaperCodeGenerator();