@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
@@ -6,11 +6,37 @@
6
6
  * Supports browser execution via Puppeteer.
7
7
  */
8
8
  import { codeGenerator } from '../types/code-generator.js';
9
- import { CreateItemInputSchema, ModifyItemInputSchema, DeleteItemInputSchema, AddRelationInputSchema, RemoveRelationInputSchema, QueryRelationsInputSchema, AnimateItemInputSchema, KeyframeAnimateInputSchema, ExecuteGeneratorInputSchema, ApplyEffectInputSchema, GetItemsInputSchema, PlayTimelineInputSchema, SetBackgroundColorInputSchema, SetCanvasSizeInputSchema, ExportTrainingDataInputSchema, ImportSVGInputSchema, AddFilterInputSchema, CreateGlossySphereInputSchema, CreateDiagonalStripesInputSchema, BatchCreateInputSchema, BatchModifyInputSchema, CreateGridInputSchema, CreateSceneInputSchema, GetPerformanceMetricsInputSchema, SearchAssetsInputSchema, ImportAssetInputSchema, P5DrawInputSchema, ErrorCodes, } from '../types/schemas.js';
9
+ import { CreateItemInputSchema, ModifyItemInputSchema, DeleteItemInputSchema, AddRelationInputSchema, RemoveRelationInputSchema, QueryRelationsInputSchema, AnimateItemInputSchema, KeyframeAnimateInputSchema, ExecuteGeneratorInputSchema, ApplyEffectInputSchema, GetItemsInputSchema, PlayTimelineInputSchema, SetBackgroundColorInputSchema, SetCanvasSizeInputSchema, ExportTrainingDataInputSchema, ImportSVGInputSchema, AddFilterInputSchema, CreateGlossySphereInputSchema, CreateDiagonalStripesInputSchema, BatchCreateInputSchema, BatchModifyInputSchema, CreateGridInputSchema, CreateSceneInputSchema, GetPerformanceMetricsInputSchema, SearchAssetsInputSchema, ImportAssetInputSchema, P5DrawInputSchema,
10
+ // Diagram schemas
11
+ CreateDiagramShapeInputSchema, ConnectInputSchema, ConnectPortsInputSchema, AddPortsInputSchema, AutoLayoutInputSchema, GetDiagramShapesInputSchema, UpdateConnectorInputSchema, RemoveConnectorInputSchema, DiagramModeInputSchema,
12
+ // Agent flow mode schemas
13
+ AgentStartJobInputSchema, AgentEndJobInputSchema, AgentResetInputSchema, AgentBatchExecuteInputSchema, AgentExportInputSchema, AgentAnalyzeInputSchema,
14
+ // Interactive/Trigger schemas
15
+ AddTriggerInputSchema, RemoveTriggerInputSchema, QueryTriggersInputSchema,
16
+ // Quiz schemas
17
+ CreateQuizInputSchema, GetQuizStateInputSchema, ResetQuizInputSchema,
18
+ // Letter collage schemas
19
+ CreateLetterCollageInputSchema, AnimateLetterCollageInputSchema,
20
+ // Map schemas
21
+ LoadMapInputSchema, HighlightRegionsInputSchema, UnhighlightRegionsInputSchema, ApplyDataColorsInputSchema, AddMarkerInputSchema, AddMapLabelsInputSchema, PanMapInputSchema, ZoomMapInputSchema, ImportCustomMapInputSchema, GetRegionAtPointInputSchema,
22
+ // Map animation/CSV schemas
23
+ AnimateMapRegionsInputSchema, AnimateMapWaveInputSchema, StopMapAnimationsInputSchema, ExportMapRegionCSVInputSchema, ImportMapRegionCSVInputSchema, SelectMapRegionsInputSchema, DeselectMapRegionsInputSchema,
24
+ // Custom relation/code schemas
25
+ RegisterCustomRelationInputSchema, ExecuteCustomCodeInputSchema,
26
+ // Mask schemas
27
+ ApplyAnimatedMaskInputSchema, ApplyCustomMaskInputSchema, RemoveMaskInputSchema,
28
+ // Template schemas
29
+ ApplyTemplateInputSchema,
30
+ // Image import schemas
31
+ ImportImageInputSchema, ErrorCodes, } from '../types/schemas.js';
10
32
  import { ZodError } from 'zod';
33
+ import { writeFile, mkdir } from 'node:fs/promises';
34
+ import { tmpdir } from 'node:os';
35
+ import { join } from 'node:path';
11
36
  import { getBrowserController, } from '../browser/puppeteer-controller.js';
12
37
  import { getPerformanceTracker } from '../metrics/index.js';
13
38
  import { formatErrorContext, captureCanvasState } from '../execution/index.js';
39
+ import { getSessionManager } from '../agent/session-manager.js';
14
40
  /**
15
41
  * Get screenshot mode from environment variable.
16
42
  * Defaults to 'on_request' for optimal performance.
@@ -23,6 +49,37 @@ export function getScreenshotMode() {
23
49
  // Default to 'on_request' for better performance (best practice)
24
50
  return 'on_request';
25
51
  }
52
+ // =============================================================================
53
+ // EXPORT FILE SAVE HELPERS
54
+ // =============================================================================
55
+ function getExportDir() {
56
+ return process.env.PINEPAPER_EXPORT_DIR || join(tmpdir(), 'pinepaper-exports');
57
+ }
58
+ const ALWAYS_SAVE_FORMATS = new Set(['mp4', 'webm', 'gif', 'pdf']);
59
+ const SAVE_THRESHOLD_BYTES = 500_000; // ~500KB base64 ≈ 375KB decoded
60
+ function getFileExtension(format) {
61
+ const extMap = { mp4: 'mp4', webm: 'webm', gif: 'gif', pdf: 'pdf', png: 'png', svg: 'svg' };
62
+ return extMap[format] || format;
63
+ }
64
+ async function saveExportToFile(data, format, platform) {
65
+ const exportDir = getExportDir();
66
+ await mkdir(exportDir, { recursive: true });
67
+ const ext = getFileExtension(format);
68
+ const timestamp = Date.now();
69
+ const fileName = `pinepaper_${platform}_${timestamp}.${ext}`;
70
+ const filePath = join(exportDir, fileName);
71
+ if (data.startsWith('data:')) {
72
+ const base64Data = data.split(',')[1];
73
+ const buffer = Buffer.from(base64Data, 'base64');
74
+ await writeFile(filePath, buffer);
75
+ return { filePath, fileSize: buffer.length };
76
+ }
77
+ else {
78
+ // Plain text (SVG)
79
+ await writeFile(filePath, data, 'utf-8');
80
+ return { filePath, fileSize: Buffer.byteLength(data, 'utf-8') };
81
+ }
82
+ }
26
83
  /**
27
84
  * Get execution mode from environment variable.
28
85
  * Defaults to 'puppeteer' for live execution.
@@ -151,7 +208,32 @@ async function executeOrGenerate(code, description, options, toolName) {
151
208
  success: true,
152
209
  metadata: { executionMode: 'code' },
153
210
  });
154
- return successResult(code, `${description}\n\n📋 **Code Generation Mode**: Copy the code above and paste it into the PinePaper Studio console to execute.`);
211
+ return {
212
+ content: [
213
+ {
214
+ type: 'text',
215
+ text: `✅ **Code Generated Successfully**
216
+
217
+ **Tool:** ${toolName}
218
+ **Action:** ${description}
219
+
220
+ ## Generated Code
221
+
222
+ \`\`\`javascript
223
+ ${code}
224
+ \`\`\`
225
+
226
+ ## How to Execute
227
+
228
+ 1. Open **PinePaper Studio**: https://pinepaper.studio/editor?agent=1&mode=agent
229
+ 2. Click the **Code Console** button (</> icon) in the toolbar
230
+ 3. Paste the code above into the console
231
+ 4. Click **Run** to execute
232
+
233
+ The code is ready to use. Each subsequent tool call will also generate code you can paste.`,
234
+ },
235
+ ],
236
+ };
155
237
  }
156
238
  if (!executeInBrowser) {
157
239
  const totalDuration = tracker.endTimer(`${timerId}_total`);
@@ -165,17 +247,72 @@ async function executeOrGenerate(code, description, options, toolName) {
165
247
  return successResult(code, description);
166
248
  }
167
249
  const controller = browserController || getBrowserController();
250
+ // Auto-connect in agent mode if not connected
168
251
  if (!controller.connected) {
169
- const totalDuration = tracker.endTimer(`${timerId}_total`);
170
- tracker.recordMetric({
171
- toolName,
172
- phase: 'total',
173
- duration: totalDuration,
174
- timestamp: Date.now(),
175
- success: true,
176
- metadata: { browserConnected: false },
252
+ console.error('[PinePaper] Auto-connecting browser in agent mode...');
253
+ try {
254
+ await controller.connect(); // Will use connectAgent() due to enforced agentMode
255
+ console.error('[PinePaper] Browser auto-connected successfully');
256
+ }
257
+ catch (connectError) {
258
+ const totalDuration = tracker.endTimer(`${timerId}_total`);
259
+ tracker.recordMetric({
260
+ toolName,
261
+ phase: 'total',
262
+ duration: totalDuration,
263
+ timestamp: Date.now(),
264
+ success: false,
265
+ metadata: { browserConnected: false, autoConnectFailed: true, fallbackToCode: true },
266
+ });
267
+ // FALLBACK: Return code for manual paste instead of failing
268
+ const errorMessage = connectError instanceof Error ? connectError.message : 'Unknown error';
269
+ console.error(`[PinePaper] Browser connection failed: ${errorMessage}. Falling back to code-only mode.`);
270
+ return {
271
+ content: [
272
+ {
273
+ type: 'text',
274
+ text: `⚠️ **Browser Connection Failed** - Falling back to code mode
275
+
276
+ **Error:** ${errorMessage}
277
+
278
+ **Don't worry!** You can still use PinePaper by copying the code below and pasting it into the PinePaper Code Console.
279
+
280
+ ## How to use the code manually:
281
+
282
+ 1. Open **PinePaper Studio**: https://pinepaper.studio/editor?agent=1&mode=agent
283
+ 2. Click the **Code Console** button (</> icon) in the toolbar
284
+ 3. Paste the code below into the console
285
+ 4. Click **Run** to execute
286
+
287
+ ---
288
+
289
+ ## Generated Code for: ${toolName}
290
+
291
+ \`\`\`javascript
292
+ ${code}
293
+ \`\`\`
294
+
295
+ ---
296
+
297
+ **${description}**
298
+
299
+ 💡 **Tip:** If browser connection keeps failing, check:
300
+ - Is Chrome/Chromium installed?
301
+ - Are there firewall restrictions?
302
+ - Try setting \`headless: false\` in browser_connect to see the browser window`,
303
+ },
304
+ ],
305
+ };
306
+ }
307
+ }
308
+ // Auto-start agent session if not already active (enforced agent mode)
309
+ const sessionManager = getSessionManager();
310
+ if (!sessionManager.hasActiveJob()) {
311
+ sessionManager.startJob({
312
+ name: 'auto_session',
313
+ screenshotPolicy: 'on_complete',
177
314
  });
178
- return successResult(code, `${description}\n\n⚠️ Browser not connected. Call pinepaper_browser_connect first to execute code live.`);
315
+ console.error('[PinePaper] Auto-started agent session for enforced agent mode');
179
316
  }
180
317
  // Track browser execution time
181
318
  tracker.startTimer(`${timerId}_browser_execution`);
@@ -316,6 +453,14 @@ export async function handleToolCall(toolName, args, options = {}) {
316
453
  }
317
454
  case 'pinepaper_modify_item': {
318
455
  const input = ModifyItemInputSchema.parse(args);
456
+ // Guard: reject animation properties — they don't work via modify
457
+ const animProps = ['animationType', 'animationSpeed', 'keyframes', 'animation'];
458
+ const passedAnimProps = animProps.filter(p => p in (input.properties || {}));
459
+ if (passedAnimProps.length > 0) {
460
+ return errorResult('INVALID_PROPERTIES', `Animation properties (${passedAnimProps.join(', ')}) cannot be set via pinepaper_modify_item — they will be silently ignored. ` +
461
+ `Use pinepaper_animate for loop animations (pulse, rotate, bounce, fade, wobble, slide) ` +
462
+ `or pinepaper_keyframe_animate for timed/sequenced animations with precise control.`);
463
+ }
319
464
  const code = codeGenerator.generateModifyItem(input);
320
465
  const description = getLocalizedSuccessMessage(i18n, 'itemModified', {
321
466
  itemId: input.itemId,
@@ -384,7 +529,7 @@ export async function handleToolCall(toolName, args, options = {}) {
384
529
  const description = getLocalizedSuccessMessage(i18n, 'relationAdded', {
385
530
  relationType: input.relationType,
386
531
  sourceId: input.sourceId,
387
- targetId: input.targetId,
532
+ targetId: input.targetId || 'self',
388
533
  });
389
534
  return executeOrGenerate(code, description, options, 'pinepaper_add_relation');
390
535
  }
@@ -402,6 +547,16 @@ export async function handleToolCall(toolName, args, options = {}) {
402
547
  const code = codeGenerator.generateQueryRelations(input.itemId, input.relationType, input.direction);
403
548
  return executeOrGenerate(code, `Queries ${input.direction || 'all'} relations for ${input.itemId}`, options, 'pinepaper_query_relations');
404
549
  }
550
+ case 'pinepaper_register_custom_relation': {
551
+ const input = RegisterCustomRelationInputSchema.parse(args);
552
+ const code = codeGenerator.generateRegisterCustomRelation(input);
553
+ return executeOrGenerate(code, `Registers custom relation type: ${input.name}`, options, 'pinepaper_register_custom_relation');
554
+ }
555
+ case 'pinepaper_execute_custom_code': {
556
+ const input = ExecuteCustomCodeInputSchema.parse(args);
557
+ const code = codeGenerator.generateExecuteCustomCode(input);
558
+ return executeOrGenerate(code, input.description || 'Executes custom JavaScript code', options, 'pinepaper_execute_custom_code');
559
+ }
405
560
  // -----------------------------------------------------------------------
406
561
  // ANIMATION TOOLS
407
562
  // -----------------------------------------------------------------------
@@ -452,6 +607,204 @@ export async function handleToolCall(toolName, args, options = {}) {
452
607
  return executeOrGenerate(code, description, options, 'pinepaper_apply_effect');
453
608
  }
454
609
  // -----------------------------------------------------------------------
610
+ // MASK TOOLS
611
+ // -----------------------------------------------------------------------
612
+ case 'pinepaper_apply_animated_mask': {
613
+ const input = ApplyAnimatedMaskInputSchema.parse(args);
614
+ const code = codeGenerator.generateApplyAnimatedMask(input);
615
+ const mode = input.preset && input.keyframes ? 'hybrid' : input.preset ? 'preset' : 'custom';
616
+ return executeOrGenerate(code, `Applies ${mode} animated mask to ${input.itemId}`, options, 'pinepaper_apply_animated_mask');
617
+ }
618
+ case 'pinepaper_apply_custom_mask': {
619
+ const input = ApplyCustomMaskInputSchema.parse(args);
620
+ const code = codeGenerator.generateApplyCustomMask(input);
621
+ return executeOrGenerate(code, `Applies custom ${input.maskType} mask with ${input.keyframes.length} keyframes to ${input.itemId}`, options, 'pinepaper_apply_custom_mask');
622
+ }
623
+ case 'pinepaper_remove_mask': {
624
+ const input = RemoveMaskInputSchema.parse(args);
625
+ const code = codeGenerator.generateRemoveMask(input);
626
+ return executeOrGenerate(code, `Removes mask from ${input.itemId}`, options, 'pinepaper_remove_mask');
627
+ }
628
+ case 'pinepaper_get_animatable_properties': {
629
+ const code = codeGenerator.generateGetAnimatableProperties();
630
+ return executeOrGenerate(code, 'Gets animatable properties for mask types', options, 'pinepaper_get_animatable_properties');
631
+ }
632
+ case 'pinepaper_get_available_easings': {
633
+ const code = codeGenerator.generateGetAvailableEasings();
634
+ return executeOrGenerate(code, 'Gets available easing functions', options, 'pinepaper_get_available_easings');
635
+ }
636
+ case 'pinepaper_get_mask_types': {
637
+ const code = codeGenerator.generateGetMaskTypes();
638
+ return executeOrGenerate(code, 'Gets available mask types', options, 'pinepaper_get_mask_types');
639
+ }
640
+ case 'pinepaper_get_mask_animations': {
641
+ const code = codeGenerator.generateGetMaskAnimations();
642
+ return executeOrGenerate(code, 'Gets available mask animation presets', options, 'pinepaper_get_mask_animations');
643
+ }
644
+ // -----------------------------------------------------------------------
645
+ // CAMERA TOOLS
646
+ // -----------------------------------------------------------------------
647
+ case 'pinepaper_camera_animate': {
648
+ const keyframes = args.keyframes;
649
+ const duration = args.duration;
650
+ const loop = args.loop ?? false;
651
+ const delay = args.delay ?? 0;
652
+ const keyframesStr = JSON.stringify(keyframes);
653
+ const code = `app.camera && app.camera.animate ? app.camera.animate(${keyframesStr}, ${duration}, ${loop}, ${delay}) : app.addRelation('camera', 'camera', 'camera_animates', { keyframes: ${keyframesStr}, duration: ${duration}, loop: ${loop}, delay: ${delay} });`;
654
+ return executeOrGenerate(code, `Animates camera with ${keyframes.length} keyframes over ${duration}s`, options, 'pinepaper_camera_animate');
655
+ }
656
+ case 'pinepaper_camera_zoom': {
657
+ const direction = args.direction;
658
+ const level = args.level ?? (direction === 'in' ? 2 : 0.5);
659
+ const duration = args.duration ?? 0.5;
660
+ const method = direction === 'in' ? 'zoomIn' : 'zoomOut';
661
+ const code = `app.camera && app.camera.${method} ? app.camera.${method}(${level}, ${duration}) : null;`;
662
+ return executeOrGenerate(code, `Camera zoom ${direction} to ${level}x`, options, 'pinepaper_camera_zoom');
663
+ }
664
+ case 'pinepaper_camera_pan': {
665
+ const direction = args.direction;
666
+ const amount = args.amount ?? 100;
667
+ const x = args.x;
668
+ const y = args.y;
669
+ const duration = args.duration ?? 0.5;
670
+ let code;
671
+ let description;
672
+ if (x !== undefined && y !== undefined) {
673
+ code = `app.camera && app.camera.panTo ? app.camera.panTo(${x}, ${y}, ${duration}) : null;`;
674
+ description = `Camera pan to (${x}, ${y})`;
675
+ }
676
+ else if (direction) {
677
+ const methodMap = { left: 'panLeft', right: 'panRight', up: 'panUp', down: 'panDown' };
678
+ const method = methodMap[direction];
679
+ code = `app.camera && app.camera.${method} ? app.camera.${method}(${amount}, ${duration}) : null;`;
680
+ description = `Camera pan ${direction} by ${amount}px`;
681
+ }
682
+ else {
683
+ code = `// No direction or coordinates specified`;
684
+ description = 'Camera pan (no parameters)';
685
+ }
686
+ return executeOrGenerate(code, description, options, 'pinepaper_camera_pan');
687
+ }
688
+ case 'pinepaper_camera_move_to': {
689
+ const x = args.x;
690
+ const y = args.y;
691
+ const zoom = args.zoom;
692
+ const duration = args.duration ?? 0.5;
693
+ const code = `app.camera && app.camera.moveTo ? app.camera.moveTo(${x}, ${y}, ${zoom}, ${duration}) : null;`;
694
+ return executeOrGenerate(code, `Camera move to (${x}, ${y}) at ${zoom}x zoom`, options, 'pinepaper_camera_move_to');
695
+ }
696
+ case 'pinepaper_camera_reset': {
697
+ const duration = args.duration ?? 0.5;
698
+ const code = `app.camera && app.camera.reset ? app.camera.reset(${duration}) : null;`;
699
+ return executeOrGenerate(code, 'Reset camera to default state', options, 'pinepaper_camera_reset');
700
+ }
701
+ case 'pinepaper_camera_stop': {
702
+ const code = `app.camera && app.camera.stop ? app.camera.stop() : null;`;
703
+ return executeOrGenerate(code, 'Stop camera animation', options, 'pinepaper_camera_stop');
704
+ }
705
+ case 'pinepaper_camera_state': {
706
+ const code = `app.camera && app.camera.getState ? app.camera.getState() : { zoom: 1, center: [400, 300], isAnimating: false };`;
707
+ return executeOrGenerate(code, 'Get current camera state', options, 'pinepaper_camera_state');
708
+ }
709
+ // -----------------------------------------------------------------------
710
+ // FONT TOOLS
711
+ // -----------------------------------------------------------------------
712
+ case 'pinepaper_font_show_studio': {
713
+ const code = `app.fontStudio && app.fontStudio.show ? app.fontStudio.show() : null;`;
714
+ return executeOrGenerate(code, 'Opens Font Studio UI', options, 'pinepaper_font_show_studio');
715
+ }
716
+ case 'pinepaper_font_set_name': {
717
+ const { name } = args;
718
+ const code = `app.fontStudio.setName(${JSON.stringify(name)});`;
719
+ return executeOrGenerate(code, `Set font name to "${name}"`, options, 'pinepaper_font_set_name');
720
+ }
721
+ case 'pinepaper_font_get_required_chars': {
722
+ const { set } = args;
723
+ const setArg = set ? JSON.stringify(set) : '"minimum"';
724
+ const code = `app.fontStudio.getRequiredChars(${setArg});`;
725
+ return executeOrGenerate(code, `Get required characters (${set || 'minimum'} set)`, options, 'pinepaper_font_get_required_chars');
726
+ }
727
+ case 'pinepaper_font_get_status': {
728
+ const code = `app.fontStudio.getStatus();`;
729
+ return executeOrGenerate(code, 'Get font completion status', options, 'pinepaper_font_get_status');
730
+ }
731
+ case 'pinepaper_font_create_glyph': {
732
+ const { character, pathId } = args;
733
+ const code = `app.fontStudio.createGlyph(${JSON.stringify(character)}, ${JSON.stringify(pathId)});`;
734
+ return executeOrGenerate(code, `Create glyph for "${character}" from path ${pathId}`, options, 'pinepaper_font_create_glyph');
735
+ }
736
+ case 'pinepaper_font_create_space': {
737
+ const { width } = args;
738
+ const code = width !== undefined
739
+ ? `app.fontStudio.createSpace(${width});`
740
+ : `app.fontStudio.createSpace();`;
741
+ return executeOrGenerate(code, `Create space glyph${width ? ` (width: ${width})` : ''}`, options, 'pinepaper_font_create_space');
742
+ }
743
+ case 'pinepaper_font_remove_glyph': {
744
+ const { character } = args;
745
+ const code = `app.fontStudio.removeGlyph(${JSON.stringify(character)});`;
746
+ return executeOrGenerate(code, `Remove glyph for "${character}"`, options, 'pinepaper_font_remove_glyph');
747
+ }
748
+ case 'pinepaper_font_set_metrics': {
749
+ const metrics = args;
750
+ const code = `app.fontStudio.setMetrics(${JSON.stringify(metrics)});`;
751
+ return executeOrGenerate(code, 'Set font metrics', options, 'pinepaper_font_set_metrics');
752
+ }
753
+ case 'pinepaper_font_export': {
754
+ const { download } = args;
755
+ const code = download === false
756
+ ? `app.fontStudio.export({ download: false });`
757
+ : `app.fontStudio.export();`;
758
+ return executeOrGenerate(code, 'Export font as OTF', options, 'pinepaper_font_export');
759
+ }
760
+ case 'pinepaper_font_load_into_document': {
761
+ const code = `app.fontStudio.loadIntoDocument();`;
762
+ return executeOrGenerate(code, 'Load font into document', options, 'pinepaper_font_load_into_document');
763
+ }
764
+ case 'pinepaper_font_export_data': {
765
+ const { download } = args;
766
+ const code = download === false
767
+ ? `app.fontStudio.exportData({ download: false });`
768
+ : `app.fontStudio.exportData();`;
769
+ return executeOrGenerate(code, 'Export font data as JSON', options, 'pinepaper_font_export_data');
770
+ }
771
+ case 'pinepaper_font_import_data': {
772
+ const { data } = args;
773
+ const code = `app.fontStudio.importData(${JSON.stringify(data)});`;
774
+ return executeOrGenerate(code, 'Import font data from JSON', options, 'pinepaper_font_import_data');
775
+ }
776
+ case 'pinepaper_font_clear': {
777
+ const code = `app.fontStudio.clear();`;
778
+ return executeOrGenerate(code, 'Clear all glyphs and reset font', options, 'pinepaper_font_clear');
779
+ }
780
+ case 'pinepaper_font_remove_overlap': {
781
+ const { pathId } = args;
782
+ const code = `app.fontStudio.removeOverlap(${JSON.stringify(pathId)});`;
783
+ return executeOrGenerate(code, `Remove overlaps from path ${pathId}`, options, 'pinepaper_font_remove_overlap');
784
+ }
785
+ case 'pinepaper_font_correct_direction': {
786
+ const { pathId } = args;
787
+ const code = `app.fontStudio.correctDirection(${JSON.stringify(pathId)});`;
788
+ return executeOrGenerate(code, `Correct path direction for ${pathId}`, options, 'pinepaper_font_correct_direction');
789
+ }
790
+ case 'pinepaper_font_cleanup_path': {
791
+ const { pathId, removeOverlap, correctDirection, smooth, smoothTolerance } = args;
792
+ const optsObj = {};
793
+ if (removeOverlap !== undefined)
794
+ optsObj.removeOverlap = removeOverlap;
795
+ if (correctDirection !== undefined)
796
+ optsObj.correctDirection = correctDirection;
797
+ if (smooth !== undefined)
798
+ optsObj.smooth = smooth;
799
+ if (smoothTolerance !== undefined)
800
+ optsObj.smoothTolerance = smoothTolerance;
801
+ const hasOpts = Object.keys(optsObj).length > 0;
802
+ const code = hasOpts
803
+ ? `app.fontStudio.cleanupPath(${JSON.stringify(pathId)}, ${JSON.stringify(optsObj)});`
804
+ : `app.fontStudio.cleanupPath(${JSON.stringify(pathId)});`;
805
+ return executeOrGenerate(code, `Cleanup path ${pathId}`, options, 'pinepaper_font_cleanup_path');
806
+ }
807
+ // -----------------------------------------------------------------------
455
808
  // QUERY TOOLS
456
809
  // -----------------------------------------------------------------------
457
810
  case 'pinepaper_get_items': {
@@ -563,19 +916,24 @@ You can now start creating new items on a clean canvas.`,
563
916
  });
564
917
  return executeOrGenerate(code, description, options, 'pinepaper_export_training_data');
565
918
  }
919
+ case 'pinepaper_export_scene': {
920
+ const code = codeGenerator.generateExportScene();
921
+ return executeOrGenerate(code, 'Exports complete scene state', options, 'pinepaper_export_scene');
922
+ }
566
923
  // -----------------------------------------------------------------------
567
924
  // BROWSER CONTROL TOOLS
568
925
  // -----------------------------------------------------------------------
569
926
  case 'pinepaper_browser_connect': {
570
927
  const url = args.url || 'https://pinepaper.studio';
571
- const headless = args.headless ?? false;
928
+ // Default to headless: true for enforced agent mode
929
+ const headless = args.headless ?? true;
572
930
  const controller = getBrowserController({ studioUrl: url, headless });
573
931
  if (controller.connected) {
574
932
  return {
575
933
  content: [
576
934
  {
577
935
  type: 'text',
578
- text: `Already connected to PinePaper Studio at ${controller.studioUrl}`,
936
+ text: `Already connected to PinePaper Studio at ${controller.actualUrl}. Do NOT call this tool again — proceed directly with pinepaper_agent_start_job.`,
579
937
  },
580
938
  ],
581
939
  };
@@ -591,8 +949,9 @@ You can now start creating new items on a clean canvas.`,
591
949
  const content = [
592
950
  {
593
951
  type: 'text',
594
- text: `✅ Connected to PinePaper Studio at ${url}
952
+ text: `✅ Connected to PinePaper Studio at ${controller.actualUrl}
595
953
 
954
+ **Agent mode enabled** (agent=1&mode=agent parameters added automatically)
596
955
  **Current canvas size: ${canvasSize.width}x${canvasSize.height}**
597
956
 
598
957
  ⚠️ IMPORTANT NOTES:
@@ -613,7 +972,39 @@ Browser is ready. You can now use other pinepaper tools to create and animate gr
613
972
  return { content };
614
973
  }
615
974
  catch (error) {
616
- return errorResult('BROWSER_CONNECTION_ERROR', error instanceof Error ? error.message : 'Failed to connect to browser', { url });
975
+ const errorMessage = error instanceof Error ? error.message : 'Failed to connect to browser';
976
+ // Provide helpful fallback instructions instead of just failing
977
+ return {
978
+ content: [
979
+ {
980
+ type: 'text',
981
+ text: `⚠️ **Browser Connection Failed**
982
+
983
+ **Error:** ${errorMessage}
984
+
985
+ ## Don't worry! You can still use PinePaper manually:
986
+
987
+ ### Option 1: Manual Browser Mode
988
+ 1. Open **PinePaper Studio** in your browser: https://pinepaper.studio/editor?agent=1&mode=agent
989
+ 2. Continue using PinePaper tools - the MCP server will generate code for each action
990
+ 3. Copy the generated code and paste it into the **Code Console** (</> icon in toolbar)
991
+
992
+ ### Option 2: Troubleshoot Browser Connection
993
+ - **Check Chrome/Chromium**: Puppeteer requires Chrome or Chromium installed
994
+ - **Try headless: false**: Call \`pinepaper_browser_connect\` with \`headless: false\` to see the browser window
995
+ - **Check permissions**: Ensure the process has permission to launch browsers
996
+ - **Firewall**: Check if firewall is blocking browser launch
997
+
998
+ ### What happens now?
999
+ All PinePaper tools will work in **code-only mode**:
1000
+ - Each tool generates JavaScript code
1001
+ - You copy the code and paste it in PinePaper's Code Console
1002
+ - The code executes and creates your graphics
1003
+
1004
+ **The MCP server is fully functional** - just without automatic browser control.`,
1005
+ },
1006
+ ],
1007
+ };
617
1008
  }
618
1009
  }
619
1010
  case 'pinepaper_browser_disconnect': {
@@ -662,7 +1053,8 @@ Browser is ready. You can now use other pinepaper tools to create and animate gr
662
1053
  type: 'text',
663
1054
  text: JSON.stringify({
664
1055
  connected: controller.connected,
665
- studioUrl: controller.studioUrl,
1056
+ studioUrl: controller.actualUrl,
1057
+ agentMode: controller.agentMode,
666
1058
  }, null, 2),
667
1059
  },
668
1060
  ],
@@ -835,6 +1227,483 @@ Browser is ready. You can now use other pinepaper tools to create and animate gr
835
1227
  return executeOrGenerate(code, description, options, 'pinepaper_p5_draw');
836
1228
  }
837
1229
  // -----------------------------------------------------------------------
1230
+ // DIAGRAM TOOLS
1231
+ // -----------------------------------------------------------------------
1232
+ case 'pinepaper_create_diagram_shape': {
1233
+ const input = CreateDiagramShapeInputSchema.parse(args);
1234
+ const code = codeGenerator.generateCreateDiagramShape(input);
1235
+ const description = `Creates ${input.shapeType} diagram shape${input.label ? ` with label "${input.label}"` : ''}`;
1236
+ return executeOrGenerate(code, description, options, 'pinepaper_create_diagram_shape');
1237
+ }
1238
+ case 'pinepaper_connect': {
1239
+ const input = ConnectInputSchema.parse(args);
1240
+ const code = codeGenerator.generateConnect(input);
1241
+ const description = `Connects ${input.sourceItemId} → ${input.targetItemId} with ${input.routing || 'orthogonal'} routing`;
1242
+ return executeOrGenerate(code, description, options, 'pinepaper_connect');
1243
+ }
1244
+ case 'pinepaper_connect_ports': {
1245
+ const input = ConnectPortsInputSchema.parse(args);
1246
+ const code = codeGenerator.generateConnectPorts(input);
1247
+ const description = `Connects ${input.sourceItemId}:${input.sourcePort} → ${input.targetItemId}:${input.targetPort}`;
1248
+ return executeOrGenerate(code, description, options, 'pinepaper_connect_ports');
1249
+ }
1250
+ case 'pinepaper_add_ports': {
1251
+ const input = AddPortsInputSchema.parse(args);
1252
+ const code = codeGenerator.generateAddPorts(input);
1253
+ const description = `Adds ${input.portType || 'standard'} ports to ${input.itemId}`;
1254
+ return executeOrGenerate(code, description, options, 'pinepaper_add_ports');
1255
+ }
1256
+ case 'pinepaper_auto_layout': {
1257
+ const input = AutoLayoutInputSchema.parse(args);
1258
+ const code = codeGenerator.generateAutoLayout(input);
1259
+ const description = `Applies ${input.layoutType} auto-layout to diagram items`;
1260
+ return executeOrGenerate(code, description, options, 'pinepaper_auto_layout');
1261
+ }
1262
+ case 'pinepaper_get_diagram_shapes': {
1263
+ const input = GetDiagramShapesInputSchema.parse(args);
1264
+ const code = codeGenerator.generateGetDiagramShapes(input);
1265
+ const description = `Gets available diagram shapes${input.category ? ` in category "${input.category}"` : ''}`;
1266
+ return executeOrGenerate(code, description, options, 'pinepaper_get_diagram_shapes');
1267
+ }
1268
+ case 'pinepaper_update_connector': {
1269
+ const input = UpdateConnectorInputSchema.parse(args);
1270
+ const code = codeGenerator.generateUpdateConnector(input);
1271
+ const description = `Updates connector ${input.connectorId}`;
1272
+ return executeOrGenerate(code, description, options, 'pinepaper_update_connector');
1273
+ }
1274
+ case 'pinepaper_remove_connector': {
1275
+ const input = RemoveConnectorInputSchema.parse(args);
1276
+ const code = codeGenerator.generateRemoveConnector(input);
1277
+ const description = `Removes connector ${input.connectorId}`;
1278
+ return executeOrGenerate(code, description, options, 'pinepaper_remove_connector');
1279
+ }
1280
+ case 'pinepaper_diagram_mode': {
1281
+ const input = DiagramModeInputSchema.parse(args);
1282
+ const code = codeGenerator.generateDiagramMode(input);
1283
+ const description = `Diagram mode: ${input.action}${input.mode ? ` (${input.mode})` : ''}`;
1284
+ return executeOrGenerate(code, description, options, 'pinepaper_diagram_mode');
1285
+ }
1286
+ // -----------------------------------------------------------------------
1287
+ // AGENT FLOW MODE TOOLS
1288
+ // -----------------------------------------------------------------------
1289
+ case 'pinepaper_agent_start_job': {
1290
+ const input = AgentStartJobInputSchema.parse(args);
1291
+ // Guard against pipeline restarts — warn loudly if a job is already active
1292
+ const session = getSessionManager();
1293
+ if (session.hasActiveJob()) {
1294
+ const currentJob = session.getCurrentJob();
1295
+ const itemCount = currentJob?.itemsCreated.length ?? 0;
1296
+ if (itemCount > 0) {
1297
+ // Job has items — this is almost certainly a pipeline restart bug
1298
+ console.error(`[PinePaper] WARNING: Starting new job while ${itemCount} items exist from active job ${currentJob?.jobId}`);
1299
+ const code = codeGenerator.generateAgentStartJob(input);
1300
+ const result = await executeOrGenerate(code, `Restarted job (previous had ${itemCount} items)`, options, 'pinepaper_agent_start_job');
1301
+ // Prepend a strong warning to the result text
1302
+ if (result.content && result.content.length > 0 && result.content[0].type === 'text') {
1303
+ result.content[0].text = `⚠️ WARNING: A job was already active with ${itemCount} items created. Starting a new job will create DUPLICATE items on the canvas. If you already created items, do NOT recreate them — continue from where you left off or call pinepaper_agent_end_job instead.\n\n${result.content[0].text}`;
1304
+ }
1305
+ return result;
1306
+ }
1307
+ }
1308
+ const code = codeGenerator.generateAgentStartJob(input);
1309
+ const description = `Started agent job${input.name ? ` "${input.name}"` : ''} with ${input.screenshotPolicy || 'on_complete'} screenshot policy`;
1310
+ return executeOrGenerate(code, description, options, 'pinepaper_agent_start_job');
1311
+ }
1312
+ case 'pinepaper_agent_end_job': {
1313
+ const input = AgentEndJobInputSchema.parse(args);
1314
+ const code = codeGenerator.generateAgentEndJob(input);
1315
+ const description = 'Ended agent job with summary and recommendations';
1316
+ // In code mode or non-browser mode, return as usual
1317
+ const endJobExecMode = options.executionMode ?? getExecutionMode();
1318
+ if (endJobExecMode === 'code' || !options.executeInBrowser) {
1319
+ return executeOrGenerate(code, description, options, 'pinepaper_agent_end_job');
1320
+ }
1321
+ // Execute in browser and intercept result to save large screenshots to disk
1322
+ const endJobController = options.browserController || getBrowserController();
1323
+ if (!endJobController.connected) {
1324
+ return executeOrGenerate(code, description, options, 'pinepaper_agent_end_job');
1325
+ }
1326
+ // Auto-start agent session if not active
1327
+ const endJobSessionMgr = getSessionManager();
1328
+ if (!endJobSessionMgr.hasActiveJob()) {
1329
+ endJobSessionMgr.startJob({ name: 'auto_session', screenshotPolicy: 'on_complete' });
1330
+ }
1331
+ const endJobBrowserResult = await endJobController.executeCode(code, false);
1332
+ if (!endJobBrowserResult.success) {
1333
+ const canvasState = await captureCanvasState(endJobController);
1334
+ return errorResult(ErrorCodes.EXECUTION_ERROR, endJobBrowserResult.error || 'End job failed', { code }, { toolName: 'pinepaper_agent_end_job', canvasState: canvasState || undefined });
1335
+ }
1336
+ const endJobResult = endJobBrowserResult.result;
1337
+ // Check if result contains a large screenshot data URL
1338
+ if (endJobResult?.screenshot && typeof endJobResult.screenshot === 'string' && endJobResult.screenshot.length > SAVE_THRESHOLD_BYTES) {
1339
+ try {
1340
+ const { filePath, fileSize } = await saveExportToFile(endJobResult.screenshot, 'png', 'screenshot');
1341
+ const cleanResult = { ...endJobResult, screenshot: undefined, screenshotPath: filePath, screenshotSize: fileSize };
1342
+ // Take a smaller Puppeteer viewport screenshot to show the user inline
1343
+ const previewScreenshot = await endJobController.takeScreenshot();
1344
+ const content = [{
1345
+ type: 'text',
1346
+ text: `Executed PinePaper code:\n\n\`\`\`javascript\n${code}\n\`\`\`\n\nFull screenshot saved to: ${filePath} (${(fileSize / 1024).toFixed(1)} KB)\n\nResult: ${JSON.stringify(cleanResult, null, 2)}`,
1347
+ }];
1348
+ if (previewScreenshot) {
1349
+ content.push({
1350
+ type: 'image',
1351
+ data: previewScreenshot,
1352
+ mimeType: 'image/png',
1353
+ });
1354
+ }
1355
+ return { content };
1356
+ }
1357
+ catch (saveError) {
1358
+ console.error('[PinePaper] Failed to save end_job screenshot to file:', saveError);
1359
+ // Fall through to inline result but strip the screenshot to avoid oversized response
1360
+ const strippedResult = { ...endJobResult, screenshot: '[screenshot too large for inline — save failed]' };
1361
+ return executedResult(code, strippedResult, undefined, description);
1362
+ }
1363
+ }
1364
+ return executedResult(code, endJobResult, endJobBrowserResult.screenshot, description);
1365
+ }
1366
+ case 'pinepaper_agent_reset': {
1367
+ const input = AgentResetInputSchema.parse(args);
1368
+ const code = codeGenerator.generateAgentReset(input);
1369
+ const description = `Fast canvas reset${input.canvasPreset ? ` to ${input.canvasPreset} preset` : ''}`;
1370
+ return executeOrGenerate(code, description, options, 'pinepaper_agent_reset');
1371
+ }
1372
+ case 'pinepaper_agent_batch_execute': {
1373
+ const input = AgentBatchExecuteInputSchema.parse(args);
1374
+ const code = codeGenerator.generateAgentBatchExecute(input);
1375
+ const description = `Batch executed ${input.operations.length} operations${input.atomic !== false ? ' (atomic)' : ''}`;
1376
+ // Check for out-of-bounds items before execution
1377
+ let boundsWarning = '';
1378
+ const sizeOp = input.operations.find((op) => op.type === 'set_canvas_size');
1379
+ if (sizeOp) {
1380
+ const cw = sizeOp.width || 1920;
1381
+ const ch = sizeOp.height || 1080;
1382
+ const oobItems = [];
1383
+ input.operations.forEach((op, idx) => {
1384
+ if (op.type === 'create' && op.position) {
1385
+ const { x, y } = op.position;
1386
+ if (x < 0 || y < 0 || x > cw || y > ch) {
1387
+ oobItems.push(`op[${idx}] ${op.itemType || 'item'} at (${x},${y})`);
1388
+ }
1389
+ }
1390
+ });
1391
+ if (oobItems.length > 0) {
1392
+ boundsWarning = `\n\nWARNING: ${oobItems.length} item(s) positioned outside canvas bounds (${cw}x${ch}): ${oobItems.join(', ')}. Items may be clipped or invisible.`;
1393
+ }
1394
+ }
1395
+ const batchResult = await executeOrGenerate(code, description, options, 'pinepaper_agent_batch_execute');
1396
+ // Append bounds warning if any
1397
+ if (boundsWarning && batchResult.content && batchResult.content.length > 0) {
1398
+ const firstContent = batchResult.content[0];
1399
+ if (firstContent.type === 'text') {
1400
+ firstContent.text += boundsWarning;
1401
+ }
1402
+ }
1403
+ return batchResult;
1404
+ }
1405
+ case 'pinepaper_agent_export': {
1406
+ const input = AgentExportInputSchema.parse(args);
1407
+ const code = codeGenerator.generateAgentExport(input);
1408
+ const description = `Smart export for ${input.platform} as ${input.format || 'auto'}`;
1409
+ // In code mode, return generated code as usual
1410
+ const effectiveExecMode = options.executionMode ?? getExecutionMode();
1411
+ if (effectiveExecMode === 'code' || !options.executeInBrowser) {
1412
+ return executeOrGenerate(code, description, options, 'pinepaper_agent_export');
1413
+ }
1414
+ // Execute in browser and intercept result for file saving
1415
+ const controller = options.browserController || getBrowserController();
1416
+ // Auto-connect if needed
1417
+ if (!controller.connected) {
1418
+ console.error('[PinePaper] Auto-connecting browser for export...');
1419
+ try {
1420
+ await controller.connect();
1421
+ }
1422
+ catch (connectError) {
1423
+ // Fall back to executeOrGenerate which handles the fallback gracefully
1424
+ return executeOrGenerate(code, description, options, 'pinepaper_agent_export');
1425
+ }
1426
+ }
1427
+ // Auto-start agent session if not active
1428
+ const exportSessionManager = getSessionManager();
1429
+ if (!exportSessionManager.hasActiveJob()) {
1430
+ exportSessionManager.startJob({ name: 'auto_session', screenshotPolicy: 'on_complete' });
1431
+ }
1432
+ const exportBrowserResult = await controller.executeCode(code, false);
1433
+ if (!exportBrowserResult.success) {
1434
+ const canvasState = await captureCanvasState(controller);
1435
+ return errorResult(ErrorCodes.EXECUTION_ERROR, exportBrowserResult.error || 'Export failed', { code }, { toolName: 'pinepaper_agent_export', canvasState: canvasState || undefined });
1436
+ }
1437
+ const exportResult = exportBrowserResult.result;
1438
+ const format = exportResult?.format || input.format || 'png';
1439
+ const data = exportResult?.data;
1440
+ const shouldSaveToFile = data && typeof data === 'string' && (ALWAYS_SAVE_FORMATS.has(format) ||
1441
+ data.length > SAVE_THRESHOLD_BYTES);
1442
+ if (shouldSaveToFile) {
1443
+ try {
1444
+ const { filePath, fileSize } = await saveExportToFile(data, format, input.platform || 'auto');
1445
+ const cleanResult = { ...exportResult, data: undefined, filePath, fileSize };
1446
+ return {
1447
+ content: [{
1448
+ type: 'text',
1449
+ text: `Export saved to file:\n\nFile: ${filePath}\nFormat: ${format}\nSize: ${(fileSize / 1024).toFixed(1)} KB\nPlatform: ${input.platform}\n\nResult: ${JSON.stringify(cleanResult, null, 2)}`,
1450
+ }],
1451
+ };
1452
+ }
1453
+ catch (saveError) {
1454
+ console.error('[PinePaper] Failed to save export to file:', saveError);
1455
+ // Strip the massive data to prevent oversized response
1456
+ const strippedResult = { ...exportResult, data: `[${format} export data — file save failed: ${saveError instanceof Error ? saveError.message : 'unknown error'}]` };
1457
+ return executedResult(code, strippedResult, exportBrowserResult.screenshot, description);
1458
+ }
1459
+ }
1460
+ // Small export — return inline (existing behavior)
1461
+ return executedResult(code, exportResult, exportBrowserResult.screenshot, description);
1462
+ }
1463
+ case 'pinepaper_agent_analyze': {
1464
+ const input = AgentAnalyzeInputSchema.parse(args);
1465
+ const code = codeGenerator.generateAgentAnalyze(input);
1466
+ const description = 'Analyzed canvas content for export recommendations';
1467
+ return executeOrGenerate(code, description, options, 'pinepaper_agent_analyze');
1468
+ }
1469
+ // -----------------------------------------------------------------------
1470
+ // INTERACTIVE TRIGGER TOOLS
1471
+ // -----------------------------------------------------------------------
1472
+ case 'pinepaper_add_trigger': {
1473
+ const input = AddTriggerInputSchema.parse(args);
1474
+ const code = codeGenerator.generateAddTrigger(input);
1475
+ const description = `Added ${input.event} trigger to ${input.itemId} with ${input.actions.length} actions`;
1476
+ return executeOrGenerate(code, description, options, 'pinepaper_add_trigger');
1477
+ }
1478
+ case 'pinepaper_remove_trigger': {
1479
+ const input = RemoveTriggerInputSchema.parse(args);
1480
+ const code = codeGenerator.generateRemoveTrigger(input);
1481
+ const description = input.removeAll
1482
+ ? `Removed all triggers from ${input.itemId}`
1483
+ : `Removed ${input.event} trigger from ${input.itemId}`;
1484
+ return executeOrGenerate(code, description, options, 'pinepaper_remove_trigger');
1485
+ }
1486
+ case 'pinepaper_query_triggers': {
1487
+ const input = QueryTriggersInputSchema.parse(args);
1488
+ const code = codeGenerator.generateQueryTriggers(input);
1489
+ const description = input.itemId
1490
+ ? `Queried triggers for ${input.itemId}`
1491
+ : 'Queried all triggers on canvas';
1492
+ return executeOrGenerate(code, description, options, 'pinepaper_query_triggers');
1493
+ }
1494
+ // -----------------------------------------------------------------------
1495
+ // QUIZ/LMS TOOLS
1496
+ // -----------------------------------------------------------------------
1497
+ case 'pinepaper_create_quiz': {
1498
+ const input = CreateQuizInputSchema.parse(args);
1499
+ const code = codeGenerator.generateCreateQuiz(input);
1500
+ const description = `Created quiz${input.title ? ` "${input.title}"` : ''} with ${input.questions.length} questions`;
1501
+ return executeOrGenerate(code, description, options, 'pinepaper_create_quiz');
1502
+ }
1503
+ case 'pinepaper_get_quiz_state': {
1504
+ const input = GetQuizStateInputSchema.parse(args);
1505
+ const code = codeGenerator.generateGetQuizState(input);
1506
+ const description = input.quizId
1507
+ ? `Got state for quiz ${input.quizId}`
1508
+ : 'Got state for active quiz';
1509
+ return executeOrGenerate(code, description, options, 'pinepaper_get_quiz_state');
1510
+ }
1511
+ case 'pinepaper_reset_quiz': {
1512
+ const input = ResetQuizInputSchema.parse(args);
1513
+ const code = codeGenerator.generateResetQuiz(input);
1514
+ const description = input.quizId
1515
+ ? `Reset quiz ${input.quizId}`
1516
+ : 'Reset active quiz';
1517
+ return executeOrGenerate(code, description, options, 'pinepaper_reset_quiz');
1518
+ }
1519
+ // -----------------------------------------------------------------------
1520
+ // LETTER COLLAGE TOOLS
1521
+ // -----------------------------------------------------------------------
1522
+ case 'pinepaper_create_letter_collage': {
1523
+ const input = CreateLetterCollageInputSchema.parse(args);
1524
+ const code = codeGenerator.generateCreateLetterCollage(input);
1525
+ const description = `Created ${input.style || 'tile'} letter collage: "${input.text}"`;
1526
+ return executeOrGenerate(code, description, options, 'pinepaper_create_letter_collage');
1527
+ }
1528
+ case 'pinepaper_animate_letter_collage': {
1529
+ const input = AnimateLetterCollageInputSchema.parse(args);
1530
+ const code = codeGenerator.generateAnimateLetterCollage(input);
1531
+ const description = `Animated collage ${input.collageId} with ${input.animationType}`;
1532
+ return executeOrGenerate(code, description, options, 'pinepaper_animate_letter_collage');
1533
+ }
1534
+ case 'pinepaper_get_letter_collage_options': {
1535
+ const code = codeGenerator.generateGetLetterCollageOptions();
1536
+ return executeOrGenerate(code, 'Gets available letter collage styles and palettes', options, 'pinepaper_get_letter_collage_options');
1537
+ }
1538
+ // -----------------------------------------------------------------------
1539
+ // CANVAS PRESETS TOOLS
1540
+ // -----------------------------------------------------------------------
1541
+ case 'pinepaper_get_canvas_presets': {
1542
+ const code = codeGenerator.generateGetCanvasPresets();
1543
+ return executeOrGenerate(code, 'Gets all available canvas presets', options, 'pinepaper_get_canvas_presets');
1544
+ }
1545
+ // -----------------------------------------------------------------------
1546
+ // MAP TOOLS
1547
+ // -----------------------------------------------------------------------
1548
+ case 'pinepaper_load_map': {
1549
+ const input = LoadMapInputSchema.parse(args);
1550
+ const code = codeGenerator.generateLoadMap(input);
1551
+ const description = `Loads ${input.mapId} map with ${input.projection || 'default'} projection`;
1552
+ return executeOrGenerate(code, description, options, 'pinepaper_load_map');
1553
+ }
1554
+ case 'pinepaper_highlight_regions': {
1555
+ const input = HighlightRegionsInputSchema.parse(args);
1556
+ const code = codeGenerator.generateHighlightRegions(input);
1557
+ const description = `Highlights ${input.regionIds.length} region(s) on the map`;
1558
+ return executeOrGenerate(code, description, options, 'pinepaper_highlight_regions');
1559
+ }
1560
+ case 'pinepaper_unhighlight_regions': {
1561
+ const input = UnhighlightRegionsInputSchema.parse(args);
1562
+ const code = codeGenerator.generateUnhighlightRegions(input);
1563
+ return executeOrGenerate(code, 'Removes region highlights', options, 'pinepaper_unhighlight_regions');
1564
+ }
1565
+ case 'pinepaper_apply_data_colors': {
1566
+ const input = ApplyDataColorsInputSchema.parse(args);
1567
+ const code = codeGenerator.generateApplyDataColors(input);
1568
+ const regionCount = Object.keys(input.data).length;
1569
+ const description = `Applies choropleth coloring to ${regionCount} region(s)`;
1570
+ return executeOrGenerate(code, description, options, 'pinepaper_apply_data_colors');
1571
+ }
1572
+ case 'pinepaper_add_marker': {
1573
+ const input = AddMarkerInputSchema.parse(args);
1574
+ const code = codeGenerator.generateAddMarker(input);
1575
+ const description = `Adds marker at [${input.lat}, ${input.lon}]${input.label ? `: ${input.label}` : ''}`;
1576
+ return executeOrGenerate(code, description, options, 'pinepaper_add_marker');
1577
+ }
1578
+ case 'pinepaper_add_map_labels': {
1579
+ const input = AddMapLabelsInputSchema.parse(args);
1580
+ const code = codeGenerator.generateAddMapLabels(input);
1581
+ return executeOrGenerate(code, 'Adds labels to map regions', options, 'pinepaper_add_map_labels');
1582
+ }
1583
+ case 'pinepaper_pan_map': {
1584
+ const input = PanMapInputSchema.parse(args);
1585
+ const code = codeGenerator.generatePanMap(input);
1586
+ const description = `Pans map to [${input.lat}, ${input.lon}]`;
1587
+ return executeOrGenerate(code, description, options, 'pinepaper_pan_map');
1588
+ }
1589
+ case 'pinepaper_zoom_map': {
1590
+ const input = ZoomMapInputSchema.parse(args);
1591
+ const code = codeGenerator.generateZoomMap(input);
1592
+ const description = `Sets map zoom level to ${input.level}`;
1593
+ return executeOrGenerate(code, description, options, 'pinepaper_zoom_map');
1594
+ }
1595
+ case 'pinepaper_export_map': {
1596
+ const code = codeGenerator.generateExportMap();
1597
+ return executeOrGenerate(code, 'Exports map configuration', options, 'pinepaper_export_map');
1598
+ }
1599
+ case 'pinepaper_import_custom_map': {
1600
+ const input = ImportCustomMapInputSchema.parse(args);
1601
+ const code = codeGenerator.generateImportCustomMap(input);
1602
+ const description = input.url ? `Imports custom map from URL` : 'Imports custom GeoJSON map';
1603
+ return executeOrGenerate(code, description, options, 'pinepaper_import_custom_map');
1604
+ }
1605
+ case 'pinepaper_get_region_at_point': {
1606
+ const input = GetRegionAtPointInputSchema.parse(args);
1607
+ const code = codeGenerator.generateGetRegionAtPoint(input);
1608
+ const description = `Gets region at point [${input.x}, ${input.y}]`;
1609
+ return executeOrGenerate(code, description, options, 'pinepaper_get_region_at_point');
1610
+ }
1611
+ // -----------------------------------------------------------------------
1612
+ // MAP ANIMATION/CSV TOOLS
1613
+ // -----------------------------------------------------------------------
1614
+ case 'pinepaper_animate_map_regions': {
1615
+ const input = AnimateMapRegionsInputSchema.parse(args);
1616
+ const code = codeGenerator.generateAnimateMapRegions(input);
1617
+ const regionCount = Object.keys(input.regions).length;
1618
+ const description = `Animates ${regionCount} map region(s) over ${input.duration || 5}s`;
1619
+ return executeOrGenerate(code, description, options, 'pinepaper_animate_map_regions');
1620
+ }
1621
+ case 'pinepaper_animate_map_wave': {
1622
+ const input = AnimateMapWaveInputSchema.parse(args);
1623
+ const code = codeGenerator.generateAnimateMapWave(input);
1624
+ const description = `Creates ${input.waveDirection || 'horizontal'} wave animation`;
1625
+ return executeOrGenerate(code, description, options, 'pinepaper_animate_map_wave');
1626
+ }
1627
+ case 'pinepaper_stop_map_animations': {
1628
+ const input = StopMapAnimationsInputSchema.parse(args);
1629
+ const code = codeGenerator.generateStopMapAnimations(input);
1630
+ return executeOrGenerate(code, 'Stops map region animations', options, 'pinepaper_stop_map_animations');
1631
+ }
1632
+ case 'pinepaper_get_animated_map_regions': {
1633
+ const code = codeGenerator.generateGetAnimatedMapRegions();
1634
+ return executeOrGenerate(code, 'Gets list of animated map regions', options, 'pinepaper_get_animated_map_regions');
1635
+ }
1636
+ case 'pinepaper_export_map_region_csv': {
1637
+ const input = ExportMapRegionCSVInputSchema.parse(args);
1638
+ const code = codeGenerator.generateExportMapRegionCSV(input);
1639
+ return executeOrGenerate(code, 'Exports map region data as CSV', options, 'pinepaper_export_map_region_csv');
1640
+ }
1641
+ case 'pinepaper_import_map_region_csv': {
1642
+ const input = ImportMapRegionCSVInputSchema.parse(args);
1643
+ const code = codeGenerator.generateImportMapRegionCSV(input);
1644
+ return executeOrGenerate(code, 'Imports CSV data to update map regions', options, 'pinepaper_import_map_region_csv');
1645
+ }
1646
+ case 'pinepaper_select_map_regions': {
1647
+ const input = SelectMapRegionsInputSchema.parse(args);
1648
+ const code = codeGenerator.generateSelectMapRegions(input);
1649
+ const description = `Selects ${input.regionIds.length} map region(s)`;
1650
+ return executeOrGenerate(code, description, options, 'pinepaper_select_map_regions');
1651
+ }
1652
+ case 'pinepaper_deselect_map_regions': {
1653
+ const input = DeselectMapRegionsInputSchema.parse(args);
1654
+ const code = codeGenerator.generateDeselectMapRegions(input);
1655
+ return executeOrGenerate(code, 'Deselects map regions', options, 'pinepaper_deselect_map_regions');
1656
+ }
1657
+ case 'pinepaper_get_highlighted_map_regions': {
1658
+ const code = codeGenerator.generateGetHighlightedMapRegions();
1659
+ return executeOrGenerate(code, 'Gets list of highlighted map regions', options, 'pinepaper_get_highlighted_map_regions');
1660
+ }
1661
+ case 'pinepaper_export_map_geojson': {
1662
+ const code = codeGenerator.generateExportMapGeoJson({
1663
+ includeStyles: args.includeStyles,
1664
+ includeMetadata: args.includeMetadata,
1665
+ selectedOnly: args.selectedOnly,
1666
+ download: args.download,
1667
+ filename: args.filename,
1668
+ });
1669
+ return executeOrGenerate(code, 'Exports map as GeoJSON', options, 'pinepaper_export_map_geojson');
1670
+ }
1671
+ case 'pinepaper_export_original_map_geojson': {
1672
+ const code = codeGenerator.generateExportOriginalMapGeoJson({
1673
+ download: args.download,
1674
+ filename: args.filename,
1675
+ });
1676
+ return executeOrGenerate(code, 'Exports original map GeoJSON', options, 'pinepaper_export_original_map_geojson');
1677
+ }
1678
+ case 'pinepaper_get_map_source_info': {
1679
+ const code = codeGenerator.generateGetMapSourceInfo();
1680
+ return executeOrGenerate(code, 'Gets map source information', options, 'pinepaper_get_map_source_info');
1681
+ }
1682
+ case 'pinepaper_register_item': {
1683
+ const code = codeGenerator.generateRegisterItem({
1684
+ itemJson: args.itemJson,
1685
+ itemType: args.itemType,
1686
+ properties: args.properties,
1687
+ });
1688
+ return executeOrGenerate(code, 'Registers Paper.js item', options, 'pinepaper_register_item');
1689
+ }
1690
+ // -----------------------------------------------------------------------
1691
+ // TEMPLATE TOOLS
1692
+ // -----------------------------------------------------------------------
1693
+ case 'pinepaper_apply_template': {
1694
+ const parsed = ApplyTemplateInputSchema.parse(args);
1695
+ const code = codeGenerator.generateApplyTemplate(parsed);
1696
+ return executeOrGenerate(code, 'Applies template', options, 'pinepaper_apply_template');
1697
+ }
1698
+ // -----------------------------------------------------------------------
1699
+ // IMAGE IMPORT TOOLS
1700
+ // -----------------------------------------------------------------------
1701
+ case 'pinepaper_import_image': {
1702
+ const parsed = ImportImageInputSchema.parse(args);
1703
+ const code = codeGenerator.generateImportImage(parsed);
1704
+ return executeOrGenerate(code, 'Imports image', options, 'pinepaper_import_image');
1705
+ }
1706
+ // -----------------------------------------------------------------------
838
1707
  // UNKNOWN TOOL
839
1708
  // -----------------------------------------------------------------------
840
1709
  default: {
@@ -854,12 +1723,28 @@ Browser is ready. You can now use other pinepaper tools to create and animate gr
854
1723
  'pinepaper_add_relation',
855
1724
  'pinepaper_remove_relation',
856
1725
  'pinepaper_query_relations',
1726
+ 'pinepaper_register_custom_relation',
1727
+ 'pinepaper_execute_custom_code',
857
1728
  'pinepaper_animate',
858
1729
  'pinepaper_keyframe_animate',
859
1730
  'pinepaper_play_timeline',
860
1731
  'pinepaper_execute_generator',
861
1732
  'pinepaper_list_generators',
862
1733
  'pinepaper_apply_effect',
1734
+ 'pinepaper_apply_animated_mask',
1735
+ 'pinepaper_apply_custom_mask',
1736
+ 'pinepaper_remove_mask',
1737
+ 'pinepaper_get_animatable_properties',
1738
+ 'pinepaper_get_available_easings',
1739
+ 'pinepaper_get_mask_types',
1740
+ 'pinepaper_get_mask_animations',
1741
+ 'pinepaper_camera_animate',
1742
+ 'pinepaper_camera_zoom',
1743
+ 'pinepaper_camera_pan',
1744
+ 'pinepaper_camera_move_to',
1745
+ 'pinepaper_camera_reset',
1746
+ 'pinepaper_camera_stop',
1747
+ 'pinepaper_camera_state',
863
1748
  'pinepaper_get_items',
864
1749
  'pinepaper_get_relation_stats',
865
1750
  'pinepaper_set_background_color',
@@ -871,6 +1756,7 @@ Browser is ready. You can now use other pinepaper tools to create and animate gr
871
1756
  'pinepaper_add_filter',
872
1757
  'pinepaper_export_svg',
873
1758
  'pinepaper_export_training_data',
1759
+ 'pinepaper_export_scene',
874
1760
  'pinepaper_browser_connect',
875
1761
  'pinepaper_browser_disconnect',
876
1762
  'pinepaper_browser_screenshot',
@@ -878,6 +1764,88 @@ Browser is ready. You can now use other pinepaper tools to create and animate gr
878
1764
  'pinepaper_get_performance_metrics',
879
1765
  'pinepaper_search_assets',
880
1766
  'pinepaper_import_asset',
1767
+ 'pinepaper_p5_draw',
1768
+ // Diagram tools
1769
+ 'pinepaper_create_diagram_shape',
1770
+ 'pinepaper_connect',
1771
+ 'pinepaper_connect_ports',
1772
+ 'pinepaper_add_ports',
1773
+ 'pinepaper_auto_layout',
1774
+ 'pinepaper_get_diagram_shapes',
1775
+ 'pinepaper_update_connector',
1776
+ 'pinepaper_remove_connector',
1777
+ 'pinepaper_diagram_mode',
1778
+ // Scene tools
1779
+ 'pinepaper_create_scene',
1780
+ // Agent flow mode tools
1781
+ 'pinepaper_agent_start_job',
1782
+ 'pinepaper_agent_end_job',
1783
+ 'pinepaper_agent_reset',
1784
+ 'pinepaper_agent_batch_execute',
1785
+ 'pinepaper_agent_export',
1786
+ 'pinepaper_agent_analyze',
1787
+ // Interactive trigger tools
1788
+ 'pinepaper_add_trigger',
1789
+ 'pinepaper_remove_trigger',
1790
+ 'pinepaper_query_triggers',
1791
+ // Quiz/LMS tools
1792
+ 'pinepaper_create_quiz',
1793
+ 'pinepaper_get_quiz_state',
1794
+ 'pinepaper_reset_quiz',
1795
+ // Letter collage tools
1796
+ 'pinepaper_create_letter_collage',
1797
+ 'pinepaper_animate_letter_collage',
1798
+ 'pinepaper_get_letter_collage_options',
1799
+ // Canvas presets tools
1800
+ 'pinepaper_get_canvas_presets',
1801
+ // Map tools
1802
+ 'pinepaper_load_map',
1803
+ 'pinepaper_highlight_regions',
1804
+ 'pinepaper_unhighlight_regions',
1805
+ 'pinepaper_apply_data_colors',
1806
+ 'pinepaper_add_marker',
1807
+ 'pinepaper_add_map_labels',
1808
+ 'pinepaper_pan_map',
1809
+ 'pinepaper_zoom_map',
1810
+ 'pinepaper_export_map',
1811
+ 'pinepaper_import_custom_map',
1812
+ 'pinepaper_get_region_at_point',
1813
+ // Map animation/CSV tools
1814
+ 'pinepaper_animate_map_regions',
1815
+ 'pinepaper_animate_map_wave',
1816
+ 'pinepaper_stop_map_animations',
1817
+ 'pinepaper_get_animated_map_regions',
1818
+ 'pinepaper_export_map_region_csv',
1819
+ 'pinepaper_import_map_region_csv',
1820
+ 'pinepaper_select_map_regions',
1821
+ 'pinepaper_deselect_map_regions',
1822
+ 'pinepaper_get_highlighted_map_regions',
1823
+ 'pinepaper_export_map_geojson',
1824
+ 'pinepaper_export_original_map_geojson',
1825
+ 'pinepaper_get_map_source_info',
1826
+ // Font tools
1827
+ 'pinepaper_font_show_studio',
1828
+ 'pinepaper_font_set_name',
1829
+ 'pinepaper_font_get_required_chars',
1830
+ 'pinepaper_font_get_status',
1831
+ 'pinepaper_font_create_glyph',
1832
+ 'pinepaper_font_create_space',
1833
+ 'pinepaper_font_remove_glyph',
1834
+ 'pinepaper_font_set_metrics',
1835
+ 'pinepaper_font_export',
1836
+ 'pinepaper_font_load_into_document',
1837
+ 'pinepaper_font_export_data',
1838
+ 'pinepaper_font_import_data',
1839
+ 'pinepaper_font_clear',
1840
+ 'pinepaper_font_remove_overlap',
1841
+ 'pinepaper_font_correct_direction',
1842
+ 'pinepaper_font_cleanup_path',
1843
+ // Template tools
1844
+ 'pinepaper_apply_template',
1845
+ // Image import tools
1846
+ 'pinepaper_import_image',
1847
+ // Paper.js direct access tools
1848
+ 'pinepaper_register_item',
881
1849
  ],
882
1850
  });
883
1851
  }