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