@pinepaper.studio/mcp-server 1.2.2 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +109 -2
  2. package/dist/assets/asset-manager.d.ts +75 -0
  3. package/dist/assets/asset-manager.d.ts.map +1 -0
  4. package/dist/assets/asset-manager.js +156 -0
  5. package/dist/assets/asset-manager.js.map +1 -0
  6. package/dist/assets/index.d.ts +12 -0
  7. package/dist/assets/index.d.ts.map +1 -0
  8. package/dist/assets/index.js +12 -0
  9. package/dist/assets/index.js.map +1 -0
  10. package/dist/assets/repositories/fontawesome.d.ts +43 -0
  11. package/dist/assets/repositories/fontawesome.d.ts.map +1 -0
  12. package/dist/assets/repositories/fontawesome.js +163 -0
  13. package/dist/assets/repositories/fontawesome.js.map +1 -0
  14. package/dist/assets/repositories/iconify.d.ts +44 -0
  15. package/dist/assets/repositories/iconify.d.ts.map +1 -0
  16. package/dist/assets/repositories/iconify.js +208 -0
  17. package/dist/assets/repositories/iconify.js.map +1 -0
  18. package/dist/assets/repositories/openclipart.d.ts +41 -0
  19. package/dist/assets/repositories/openclipart.d.ts.map +1 -0
  20. package/dist/assets/repositories/openclipart.js +138 -0
  21. package/dist/assets/repositories/openclipart.js.map +1 -0
  22. package/dist/assets/repositories/svgrepo.d.ts +61 -0
  23. package/dist/assets/repositories/svgrepo.d.ts.map +1 -0
  24. package/dist/assets/repositories/svgrepo.js +276 -0
  25. package/dist/assets/repositories/svgrepo.js.map +1 -0
  26. package/dist/assets/types.d.ts +85 -0
  27. package/dist/assets/types.d.ts.map +1 -0
  28. package/dist/assets/types.js +7 -0
  29. package/dist/assets/types.js.map +1 -0
  30. package/dist/execution/canvas-state.d.ts +65 -0
  31. package/dist/execution/canvas-state.d.ts.map +1 -0
  32. package/dist/execution/canvas-state.js +165 -0
  33. package/dist/execution/canvas-state.js.map +1 -0
  34. package/dist/execution/index.d.ts +8 -0
  35. package/dist/execution/index.d.ts.map +1 -0
  36. package/dist/execution/index.js +8 -0
  37. package/dist/execution/index.js.map +1 -0
  38. package/dist/metrics/index.d.ts +8 -0
  39. package/dist/metrics/index.d.ts.map +1 -0
  40. package/dist/metrics/index.js +8 -0
  41. package/dist/metrics/index.js.map +1 -0
  42. package/dist/metrics/performance-tracker.d.ts +96 -0
  43. package/dist/metrics/performance-tracker.d.ts.map +1 -0
  44. package/dist/metrics/performance-tracker.js +270 -0
  45. package/dist/metrics/performance-tracker.js.map +1 -0
  46. package/dist/metrics/types.d.ts +86 -0
  47. package/dist/metrics/types.d.ts.map +1 -0
  48. package/dist/metrics/types.js +7 -0
  49. package/dist/metrics/types.js.map +1 -0
  50. package/dist/tools/definitions.d.ts.map +1 -1
  51. package/dist/tools/definitions.js +476 -0
  52. package/dist/tools/definitions.js.map +1 -1
  53. package/dist/tools/handlers.d.ts.map +1 -1
  54. package/dist/tools/handlers.js +301 -37
  55. package/dist/tools/handlers.js.map +1 -1
  56. package/dist/tools/p5-compat/index.d.ts +8 -0
  57. package/dist/tools/p5-compat/index.d.ts.map +1 -0
  58. package/dist/tools/p5-compat/index.js +8 -0
  59. package/dist/tools/p5-compat/index.js.map +1 -0
  60. package/dist/tools/p5-compat/p5-helpers.d.ts +25 -0
  61. package/dist/tools/p5-compat/p5-helpers.d.ts.map +1 -0
  62. package/dist/tools/p5-compat/p5-helpers.js +399 -0
  63. package/dist/tools/p5-compat/p5-helpers.js.map +1 -0
  64. package/dist/types/code-generator.d.ts +4 -0
  65. package/dist/types/code-generator.d.ts.map +1 -1
  66. package/dist/types/code-generator.js +8 -0
  67. package/dist/types/code-generator.js.map +1 -1
  68. package/dist/types/schemas.d.ts +77 -0
  69. package/dist/types/schemas.d.ts.map +1 -1
  70. package/dist/types/schemas.js +34 -0
  71. package/dist/types/schemas.js.map +1 -1
  72. package/manifest.json +18 -2
  73. package/package.json +1 -1
@@ -6,9 +6,11 @@
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, 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, GetPerformanceMetricsInputSchema, SearchAssetsInputSchema, ImportAssetInputSchema, P5DrawInputSchema, ErrorCodes, } from '../types/schemas.js';
10
10
  import { ZodError } from 'zod';
11
11
  import { getBrowserController, } from '../browser/puppeteer-controller.js';
12
+ import { getPerformanceTracker } from '../metrics/index.js';
13
+ import { formatErrorContext, captureCanvasState } from '../execution/index.js';
12
14
  /**
13
15
  * Get screenshot mode from environment variable.
14
16
  * Defaults to 'on_request' for optimal performance.
@@ -72,15 +74,24 @@ function screenshotResult(screenshot) {
72
74
  ],
73
75
  };
74
76
  }
75
- function errorResult(code, message, details) {
77
+ function errorResult(code, message, details, context) {
78
+ // Build error object
79
+ const errorObj = {
80
+ success: false,
81
+ error: { code, message, details },
82
+ };
83
+ // Add formatted context if available
84
+ if (context) {
85
+ const contextText = formatErrorContext(context);
86
+ if (contextText) {
87
+ errorObj.context = contextText;
88
+ }
89
+ }
76
90
  return {
77
91
  content: [
78
92
  {
79
93
  type: 'text',
80
- text: JSON.stringify({
81
- success: false,
82
- error: { code, message, details },
83
- }, null, 2),
94
+ text: JSON.stringify(errorObj, null, 2),
84
95
  },
85
96
  ],
86
97
  isError: true,
@@ -105,22 +116,83 @@ function handleValidationError(error, i18n) {
105
116
  *
106
117
  * Default is 'on_request' for optimal performance per best practices.
107
118
  */
108
- async function executeOrGenerate(code, description, options) {
119
+ async function executeOrGenerate(code, description, options, toolName) {
109
120
  const { executeInBrowser, browserController, screenshotMode } = options;
110
121
  const effectiveScreenshotMode = screenshotMode ?? getScreenshotMode();
122
+ const tracker = getPerformanceTracker();
123
+ // Start total timer
124
+ const timerId = `${toolName}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
125
+ tracker.startTimer(`${timerId}_total`);
111
126
  if (!executeInBrowser) {
127
+ const totalDuration = tracker.endTimer(`${timerId}_total`);
128
+ tracker.recordMetric({
129
+ toolName,
130
+ phase: 'total',
131
+ duration: totalDuration,
132
+ timestamp: Date.now(),
133
+ success: true,
134
+ });
112
135
  return successResult(code, description);
113
136
  }
114
137
  const controller = browserController || getBrowserController();
115
138
  if (!controller.connected) {
139
+ const totalDuration = tracker.endTimer(`${timerId}_total`);
140
+ tracker.recordMetric({
141
+ toolName,
142
+ phase: 'total',
143
+ duration: totalDuration,
144
+ timestamp: Date.now(),
145
+ success: true,
146
+ metadata: { browserConnected: false },
147
+ });
116
148
  return successResult(code, `${description}\n\n⚠️ Browser not connected. Call pinepaper_browser_connect first to execute code live.`);
117
149
  }
150
+ // Track browser execution time
151
+ tracker.startTimer(`${timerId}_browser_execution`);
118
152
  // Only take screenshot if mode is 'always'
119
153
  // 'on_request' mode means screenshots only via pinepaper_browser_screenshot
120
154
  const shouldTakeScreenshot = effectiveScreenshotMode === 'always';
155
+ // Track screenshot time separately if taking screenshot
156
+ if (shouldTakeScreenshot) {
157
+ tracker.startTimer(`${timerId}_screenshot`);
158
+ }
121
159
  const result = await controller.executeCode(code, shouldTakeScreenshot);
160
+ const browserDuration = tracker.endTimer(`${timerId}_browser_execution`);
161
+ tracker.recordMetric({
162
+ toolName,
163
+ phase: 'browser_execution',
164
+ duration: browserDuration,
165
+ timestamp: Date.now(),
166
+ success: result.success,
167
+ error: result.error,
168
+ });
169
+ if (shouldTakeScreenshot) {
170
+ const screenshotDuration = tracker.endTimer(`${timerId}_screenshot`);
171
+ tracker.recordMetric({
172
+ toolName,
173
+ phase: 'screenshot',
174
+ duration: screenshotDuration,
175
+ timestamp: Date.now(),
176
+ success: !!result.screenshot,
177
+ });
178
+ }
179
+ // Record total duration
180
+ const totalDuration = tracker.endTimer(`${timerId}_total`);
181
+ tracker.recordMetric({
182
+ toolName,
183
+ phase: 'total',
184
+ duration: totalDuration,
185
+ timestamp: Date.now(),
186
+ success: result.success,
187
+ error: result.error,
188
+ });
122
189
  if (!result.success) {
123
- return errorResult(ErrorCodes.EXECUTION_ERROR, result.error || 'Failed to execute code in browser', { code });
190
+ // Capture canvas state for error context
191
+ const canvasState = await captureCanvasState(controller);
192
+ return errorResult(ErrorCodes.EXECUTION_ERROR, result.error || 'Failed to execute code in browser', { code }, {
193
+ toolName,
194
+ canvasState: canvasState || undefined,
195
+ });
124
196
  }
125
197
  return executedResult(code, result.result, result.screenshot, description);
126
198
  }
@@ -135,24 +207,82 @@ function getLocalizedSuccessMessage(i18n, key, params) {
135
207
  return i18n.getSuccess(key, params);
136
208
  }
137
209
  // =============================================================================
210
+ // PERFORMANCE TRACKING HELPER
211
+ // =============================================================================
212
+ /**
213
+ * Execute a tool handler with automatic performance tracking for validation and code generation
214
+ */
215
+ async function executeToolWithTracking(toolName, args, options, validator, codeGenerator, description) {
216
+ const tracker = getPerformanceTracker();
217
+ const baseTimerId = `${toolName}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
218
+ // Track validation
219
+ tracker.startTimer(`${baseTimerId}_validation`);
220
+ const input = validator();
221
+ const validationDuration = tracker.endTimer(`${baseTimerId}_validation`);
222
+ tracker.recordMetric({
223
+ toolName,
224
+ phase: 'validation',
225
+ duration: validationDuration,
226
+ timestamp: Date.now(),
227
+ success: true,
228
+ });
229
+ // Track code generation
230
+ tracker.startTimer(`${baseTimerId}_code_generation`);
231
+ const code = codeGenerator(input);
232
+ const codeGenDuration = tracker.endTimer(`${baseTimerId}_code_generation`);
233
+ tracker.recordMetric({
234
+ toolName,
235
+ phase: 'code_generation',
236
+ duration: codeGenDuration,
237
+ timestamp: Date.now(),
238
+ success: true,
239
+ });
240
+ // Get final description
241
+ const finalDescription = typeof description === 'function' ? description(input) : description;
242
+ // Execute (will handle browser execution and screenshot timing)
243
+ return executeOrGenerate(code, finalDescription, options, toolName);
244
+ }
245
+ // =============================================================================
138
246
  // TOOL HANDLERS
139
247
  // =============================================================================
140
248
  export async function handleToolCall(toolName, args, options = {}) {
141
249
  const { i18n } = options;
250
+ const tracker = getPerformanceTracker();
251
+ const baseTimerId = `${toolName}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
142
252
  try {
143
253
  switch (toolName) {
144
254
  // -----------------------------------------------------------------------
145
255
  // ITEM TOOLS
146
256
  // -----------------------------------------------------------------------
147
257
  case 'pinepaper_create_item': {
258
+ // Track validation phase
259
+ tracker.startTimer(`${baseTimerId}_validation`);
148
260
  const input = CreateItemInputSchema.parse(args);
261
+ const validationDuration = tracker.endTimer(`${baseTimerId}_validation`);
262
+ tracker.recordMetric({
263
+ toolName,
264
+ phase: 'validation',
265
+ duration: validationDuration,
266
+ timestamp: Date.now(),
267
+ success: true,
268
+ });
269
+ // Track code generation phase
270
+ tracker.startTimer(`${baseTimerId}_code_generation`);
149
271
  const code = codeGenerator.generateCreateItem(input);
272
+ const codeGenDuration = tracker.endTimer(`${baseTimerId}_code_generation`);
273
+ tracker.recordMetric({
274
+ toolName,
275
+ phase: 'code_generation',
276
+ duration: codeGenDuration,
277
+ timestamp: Date.now(),
278
+ success: true,
279
+ });
150
280
  const description = getLocalizedSuccessMessage(i18n, 'itemCreated', {
151
281
  itemType: input.itemType,
152
282
  x: input.position.x,
153
283
  y: input.position.y,
154
284
  });
155
- return executeOrGenerate(code, description, options);
285
+ return executeOrGenerate(code, description, options, toolName);
156
286
  }
157
287
  case 'pinepaper_modify_item': {
158
288
  const input = ModifyItemInputSchema.parse(args);
@@ -160,7 +290,7 @@ export async function handleToolCall(toolName, args, options = {}) {
160
290
  const description = getLocalizedSuccessMessage(i18n, 'itemModified', {
161
291
  itemId: input.itemId,
162
292
  });
163
- return executeOrGenerate(code, description, options);
293
+ return executeOrGenerate(code, description, options, 'pinepaper_modify_item');
164
294
  }
165
295
  case 'pinepaper_delete_item': {
166
296
  const input = DeleteItemInputSchema.parse(args);
@@ -168,7 +298,7 @@ export async function handleToolCall(toolName, args, options = {}) {
168
298
  const description = getLocalizedSuccessMessage(i18n, 'itemDeleted', {
169
299
  itemId: input.itemId,
170
300
  });
171
- return executeOrGenerate(code, description, options);
301
+ return executeOrGenerate(code, description, options, 'pinepaper_delete_item');
172
302
  }
173
303
  // -----------------------------------------------------------------------
174
304
  // HIGH-LEVEL DESIGN TOOLS
@@ -177,13 +307,13 @@ export async function handleToolCall(toolName, args, options = {}) {
177
307
  const input = CreateGlossySphereInputSchema.parse(args);
178
308
  const code = codeGenerator.generateCreateGlossySphere(input);
179
309
  const description = `Creates a 3D glossy sphere with ${input.baseColor} color at (${input.position.x}, ${input.position.y})`;
180
- return executeOrGenerate(code, description, options);
310
+ return executeOrGenerate(code, description, options, 'pinepaper_create_glossy_sphere');
181
311
  }
182
312
  case 'pinepaper_create_diagonal_stripes': {
183
313
  const input = CreateDiagonalStripesInputSchema.parse(args);
184
314
  const code = codeGenerator.generateCreateDiagonalStripes(input);
185
315
  const description = `Creates diagonal stripes pattern at (${input.position.x}, ${input.position.y}) with ${input.colors.length} colors`;
186
- return executeOrGenerate(code, description, options);
316
+ return executeOrGenerate(code, description, options, 'pinepaper_create_diagonal_stripes');
187
317
  }
188
318
  // -----------------------------------------------------------------------
189
319
  // BATCH OPERATION TOOLS
@@ -192,19 +322,19 @@ export async function handleToolCall(toolName, args, options = {}) {
192
322
  const input = BatchCreateInputSchema.parse(args);
193
323
  const code = codeGenerator.generateBatchCreate(input);
194
324
  const description = `Batch creates ${input.items.length} items with single history save`;
195
- return executeOrGenerate(code, description, options);
325
+ return executeOrGenerate(code, description, options, 'pinepaper_batch_create');
196
326
  }
197
327
  case 'pinepaper_batch_modify': {
198
328
  const input = BatchModifyInputSchema.parse(args);
199
329
  const code = codeGenerator.generateBatchModify(input);
200
330
  const description = `Batch modifies ${input.modifications.length} items with single history save`;
201
- return executeOrGenerate(code, description, options);
331
+ return executeOrGenerate(code, description, options, 'pinepaper_batch_modify');
202
332
  }
203
333
  case 'pinepaper_create_grid': {
204
334
  const input = CreateGridInputSchema.parse(args);
205
335
  const code = codeGenerator.generateCreateGrid(input);
206
336
  const description = `Creates a ${input.cols}x${input.rows} grid${input.animated ? ' with wave animation' : ''}`;
207
- return executeOrGenerate(code, description, options);
337
+ return executeOrGenerate(code, description, options, 'pinepaper_create_grid');
208
338
  }
209
339
  // -----------------------------------------------------------------------
210
340
  // RELATION TOOLS
@@ -217,7 +347,7 @@ export async function handleToolCall(toolName, args, options = {}) {
217
347
  sourceId: input.sourceId,
218
348
  targetId: input.targetId,
219
349
  });
220
- return executeOrGenerate(code, description, options);
350
+ return executeOrGenerate(code, description, options, 'pinepaper_add_relation');
221
351
  }
222
352
  case 'pinepaper_remove_relation': {
223
353
  const input = RemoveRelationInputSchema.parse(args);
@@ -226,12 +356,12 @@ export async function handleToolCall(toolName, args, options = {}) {
226
356
  sourceId: input.sourceId,
227
357
  targetId: input.targetId,
228
358
  });
229
- return executeOrGenerate(code, description, options);
359
+ return executeOrGenerate(code, description, options, 'pinepaper_remove_relation');
230
360
  }
231
361
  case 'pinepaper_query_relations': {
232
362
  const input = QueryRelationsInputSchema.parse(args);
233
363
  const code = codeGenerator.generateQueryRelations(input.itemId, input.relationType, input.direction);
234
- return executeOrGenerate(code, `Queries ${input.direction || 'all'} relations for ${input.itemId}`, options);
364
+ return executeOrGenerate(code, `Queries ${input.direction || 'all'} relations for ${input.itemId}`, options, 'pinepaper_query_relations');
235
365
  }
236
366
  // -----------------------------------------------------------------------
237
367
  // ANIMATION TOOLS
@@ -243,17 +373,17 @@ export async function handleToolCall(toolName, args, options = {}) {
243
373
  animationType: input.animationType,
244
374
  itemId: input.itemId,
245
375
  });
246
- return executeOrGenerate(code, description, options);
376
+ return executeOrGenerate(code, description, options, 'pinepaper_animate');
247
377
  }
248
378
  case 'pinepaper_keyframe_animate': {
249
379
  const input = KeyframeAnimateInputSchema.parse(args);
250
380
  const code = codeGenerator.generateKeyframeAnimate(input);
251
- return executeOrGenerate(code, `Applies keyframe animation with ${input.keyframes.length} keyframes to ${input.itemId}`, options);
381
+ return executeOrGenerate(code, `Applies keyframe animation with ${input.keyframes.length} keyframes to ${input.itemId}`, options, 'pinepaper_keyframe_animate');
252
382
  }
253
383
  case 'pinepaper_play_timeline': {
254
384
  const input = PlayTimelineInputSchema.parse(args);
255
385
  const code = codeGenerator.generatePlayTimeline(input.action, input.duration, input.loop, input.time);
256
- return executeOrGenerate(code, `Timeline action: ${input.action}`, options);
386
+ return executeOrGenerate(code, `Timeline action: ${input.action}`, options, 'pinepaper_play_timeline');
257
387
  }
258
388
  // -----------------------------------------------------------------------
259
389
  // GENERATOR TOOLS
@@ -264,11 +394,11 @@ export async function handleToolCall(toolName, args, options = {}) {
264
394
  const description = getLocalizedSuccessMessage(i18n, 'generatorExecuted', {
265
395
  generatorName: input.generatorName,
266
396
  });
267
- return executeOrGenerate(code, description, options);
397
+ return executeOrGenerate(code, description, options, 'pinepaper_execute_generator');
268
398
  }
269
399
  case 'pinepaper_list_generators': {
270
400
  const code = codeGenerator.generateListGenerators();
271
- return executeOrGenerate(code, 'Lists all available generators', options);
401
+ return executeOrGenerate(code, 'Lists all available generators', options, 'pinepaper_list_generators');
272
402
  }
273
403
  // -----------------------------------------------------------------------
274
404
  // EFFECT TOOLS
@@ -280,7 +410,7 @@ export async function handleToolCall(toolName, args, options = {}) {
280
410
  effectType: input.effectType,
281
411
  itemId: input.itemId,
282
412
  });
283
- return executeOrGenerate(code, description, options);
413
+ return executeOrGenerate(code, description, options, 'pinepaper_apply_effect');
284
414
  }
285
415
  // -----------------------------------------------------------------------
286
416
  // QUERY TOOLS
@@ -288,11 +418,11 @@ export async function handleToolCall(toolName, args, options = {}) {
288
418
  case 'pinepaper_get_items': {
289
419
  const input = GetItemsInputSchema.parse(args);
290
420
  const code = codeGenerator.generateGetItems(input.filter);
291
- return executeOrGenerate(code, 'Gets items from canvas', options);
421
+ return executeOrGenerate(code, 'Gets items from canvas', options, 'pinepaper_get_items');
292
422
  }
293
423
  case 'pinepaper_get_relation_stats': {
294
424
  const code = codeGenerator.generateGetRelationStats();
295
- return executeOrGenerate(code, 'Gets relation statistics', options);
425
+ return executeOrGenerate(code, 'Gets relation statistics', options, 'pinepaper_get_relation_stats');
296
426
  }
297
427
  // -----------------------------------------------------------------------
298
428
  // CANVAS TOOLS
@@ -303,7 +433,7 @@ export async function handleToolCall(toolName, args, options = {}) {
303
433
  const description = getLocalizedSuccessMessage(i18n, 'backgroundSet', {
304
434
  color: input.color,
305
435
  });
306
- return executeOrGenerate(code, description, options);
436
+ return executeOrGenerate(code, description, options, 'pinepaper_set_background_color');
307
437
  }
308
438
  case 'pinepaper_set_canvas_size': {
309
439
  const input = SetCanvasSizeInputSchema.parse(args);
@@ -312,16 +442,16 @@ export async function handleToolCall(toolName, args, options = {}) {
312
442
  width: input.width,
313
443
  height: input.height,
314
444
  });
315
- return executeOrGenerate(code, description, options);
445
+ return executeOrGenerate(code, description, options, 'pinepaper_set_canvas_size');
316
446
  }
317
447
  case 'pinepaper_get_canvas_size': {
318
448
  const code = codeGenerator.generateGetCanvasSize();
319
- return executeOrGenerate(code, 'Gets current canvas dimensions', options);
449
+ return executeOrGenerate(code, 'Gets current canvas dimensions', options, 'pinepaper_get_canvas_size');
320
450
  }
321
451
  case 'pinepaper_clear_canvas': {
322
452
  const code = codeGenerator.generateClearCanvas();
323
453
  const description = 'Clears all items from the canvas, including any welcome template items';
324
- return executeOrGenerate(code, description, options);
454
+ return executeOrGenerate(code, description, options, 'pinepaper_clear_canvas');
325
455
  }
326
456
  case 'pinepaper_refresh_page': {
327
457
  const controller = options.browserController || getBrowserController();
@@ -365,7 +495,7 @@ You can now start creating new items on a clean canvas.`,
365
495
  const input = ImportSVGInputSchema.parse(args);
366
496
  const code = codeGenerator.generateImportSVG(input.svgString, input.url, input.position, input.scale);
367
497
  const description = 'Imports SVG onto the canvas';
368
- return executeOrGenerate(code, description, options);
498
+ return executeOrGenerate(code, description, options, 'pinepaper_import_svg');
369
499
  }
370
500
  // -----------------------------------------------------------------------
371
501
  // FILTER TOOLS
@@ -374,7 +504,7 @@ You can now start creating new items on a clean canvas.`,
374
504
  const input = AddFilterInputSchema.parse(args);
375
505
  const code = codeGenerator.generateAddFilter(input.filterType, input.params);
376
506
  const description = `Adds ${input.filterType} filter to the canvas`;
377
- return executeOrGenerate(code, description, options);
507
+ return executeOrGenerate(code, description, options, 'pinepaper_add_filter');
378
508
  }
379
509
  // -----------------------------------------------------------------------
380
510
  // EXPORT TOOLS
@@ -384,7 +514,7 @@ You can now start creating new items on a clean canvas.`,
384
514
  const description = getLocalizedSuccessMessage(i18n, 'exported', {
385
515
  format: 'SVG',
386
516
  });
387
- return executeOrGenerate(code, description, options);
517
+ return executeOrGenerate(code, description, options, 'pinepaper_export_svg');
388
518
  }
389
519
  case 'pinepaper_export_training_data': {
390
520
  const input = ExportTrainingDataInputSchema.parse(args);
@@ -392,7 +522,7 @@ You can now start creating new items on a clean canvas.`,
392
522
  const description = getLocalizedSuccessMessage(i18n, 'exported', {
393
523
  format: input.format?.toUpperCase() || 'JSON',
394
524
  });
395
- return executeOrGenerate(code, description, options);
525
+ return executeOrGenerate(code, description, options, 'pinepaper_export_training_data');
396
526
  }
397
527
  // -----------------------------------------------------------------------
398
528
  // BROWSER CONTROL TOOLS
@@ -476,7 +606,12 @@ Browser is ready. You can now use other pinepaper tools to create and animate gr
476
606
  }
477
607
  const screenshot = await controller.takeScreenshot();
478
608
  if (!screenshot) {
479
- return errorResult('SCREENSHOT_FAILED', 'Failed to capture screenshot');
609
+ // Capture canvas state for debugging
610
+ const canvasState = await captureCanvasState(controller);
611
+ return errorResult('SCREENSHOT_FAILED', 'Failed to capture screenshot', undefined, {
612
+ toolName: 'pinepaper_browser_screenshot',
613
+ canvasState: canvasState || undefined,
614
+ });
480
615
  }
481
616
  return screenshotResult(screenshot);
482
617
  }
@@ -495,6 +630,115 @@ Browser is ready. You can now use other pinepaper tools to create and animate gr
495
630
  };
496
631
  }
497
632
  // -----------------------------------------------------------------------
633
+ // PERFORMANCE TOOLS
634
+ // -----------------------------------------------------------------------
635
+ case 'pinepaper_get_performance_metrics': {
636
+ const input = GetPerformanceMetricsInputSchema.parse(args);
637
+ const perfTracker = getPerformanceTracker();
638
+ // Build filter
639
+ const filter = {
640
+ toolName: input.toolName,
641
+ phase: input.phase,
642
+ since: input.since,
643
+ limit: input.limit ?? 100,
644
+ };
645
+ // Get format (default to 'summary')
646
+ const format = input.format ?? 'summary';
647
+ // Export metrics in requested format
648
+ const output = perfTracker.exportMetrics(format, filter);
649
+ // Get tracker stats
650
+ const trackerStats = perfTracker.getStats();
651
+ return {
652
+ content: [
653
+ {
654
+ type: 'text',
655
+ text: format === 'csv' || format === 'summary'
656
+ ? output
657
+ : JSON.stringify({
658
+ metrics: output,
659
+ trackerInfo: trackerStats,
660
+ }, null, 2),
661
+ },
662
+ ],
663
+ };
664
+ }
665
+ // -----------------------------------------------------------------------
666
+ // ASSET TOOLS
667
+ // -----------------------------------------------------------------------
668
+ case 'pinepaper_search_assets': {
669
+ const input = SearchAssetsInputSchema.parse(args);
670
+ const { getAssetManager } = await import('../assets/index.js');
671
+ const assetManager = getAssetManager();
672
+ try {
673
+ const results = await assetManager.search(input.query, input.repository || 'all', input.limit || 10);
674
+ return {
675
+ content: [
676
+ {
677
+ type: 'text',
678
+ text: JSON.stringify({
679
+ success: true,
680
+ query: input.query,
681
+ repository: input.repository || 'all',
682
+ count: results.length,
683
+ results: results.map((r) => ({
684
+ id: r.id,
685
+ repository: r.repository,
686
+ title: r.title,
687
+ description: r.description,
688
+ previewUrl: r.previewUrl,
689
+ license: r.license,
690
+ tags: r.tags,
691
+ })),
692
+ }, null, 2),
693
+ },
694
+ ],
695
+ };
696
+ }
697
+ catch (error) {
698
+ return errorResult(ErrorCodes.EXECUTION_ERROR, error instanceof Error ? error.message : 'Failed to search assets', { query: input.query });
699
+ }
700
+ }
701
+ case 'pinepaper_import_asset': {
702
+ const input = ImportAssetInputSchema.parse(args);
703
+ const { getAssetManager } = await import('../assets/index.js');
704
+ const assetManager = getAssetManager();
705
+ try {
706
+ // Validate that either assetId or url is provided
707
+ if (!input.assetId && !input.url) {
708
+ return errorResult(ErrorCodes.INVALID_PARAMS, 'Either assetId or url must be provided');
709
+ }
710
+ let svg;
711
+ let metadata;
712
+ if (input.assetId) {
713
+ // Download from asset manager
714
+ const result = await assetManager.download(input.assetId);
715
+ svg = result.svg;
716
+ metadata = result.metadata;
717
+ }
718
+ else if (input.url) {
719
+ // For URL imports, we'd need to fetch the SVG
720
+ // For now, delegate to pinepaper_import_svg
721
+ return errorResult(ErrorCodes.INVALID_PARAMS, 'URL imports not yet implemented - use pinepaper_import_svg with url parameter instead', { url: input.url });
722
+ }
723
+ // Import the SVG onto canvas using existing import_svg tool
724
+ const code = codeGenerator.generateImportSVG(svg, undefined, input.position, input.scale);
725
+ const description = `Imported asset: ${metadata.title} (${metadata.license.name})`;
726
+ return executeOrGenerate(code, description, options, 'pinepaper_import_asset');
727
+ }
728
+ catch (error) {
729
+ return errorResult(ErrorCodes.EXECUTION_ERROR, error instanceof Error ? error.message : 'Failed to import asset', { assetId: input.assetId, url: input.url });
730
+ }
731
+ }
732
+ // -----------------------------------------------------------------------
733
+ // P5.JS COMPATIBILITY
734
+ // -----------------------------------------------------------------------
735
+ case 'pinepaper_p5_draw': {
736
+ const input = P5DrawInputSchema.parse(args);
737
+ const code = codeGenerator.generateP5Draw(input.code);
738
+ const description = 'Executed p5.js-style drawing code';
739
+ return executeOrGenerate(code, description, options, 'pinepaper_p5_draw');
740
+ }
741
+ // -----------------------------------------------------------------------
498
742
  // UNKNOWN TOOL
499
743
  // -----------------------------------------------------------------------
500
744
  default: {
@@ -535,6 +779,9 @@ Browser is ready. You can now use other pinepaper tools to create and animate gr
535
779
  'pinepaper_browser_disconnect',
536
780
  'pinepaper_browser_screenshot',
537
781
  'pinepaper_browser_status',
782
+ 'pinepaper_get_performance_metrics',
783
+ 'pinepaper_search_assets',
784
+ 'pinepaper_import_asset',
538
785
  ],
539
786
  });
540
787
  }
@@ -544,18 +791,35 @@ Browser is ready. You can now use other pinepaper tools to create and animate gr
544
791
  if (error instanceof ZodError) {
545
792
  return handleValidationError(error, i18n);
546
793
  }
794
+ // Try to capture canvas state for error context (if browser is connected)
795
+ let canvasState = null;
796
+ try {
797
+ const controller = options.browserController || getBrowserController();
798
+ if (controller.connected) {
799
+ canvasState = await captureCanvasState(controller);
800
+ }
801
+ }
802
+ catch (stateError) {
803
+ // Ignore errors from canvas state capture
804
+ }
547
805
  if (error instanceof Error) {
548
806
  const message = i18n
549
807
  ? i18n.getError('executionError', { message: error.message })
550
808
  : error.message;
551
809
  return errorResult(ErrorCodes.EXECUTION_ERROR, message, {
552
810
  stack: error.stack,
811
+ }, {
812
+ toolName,
813
+ canvasState: canvasState || undefined,
553
814
  });
554
815
  }
555
816
  const message = i18n
556
817
  ? i18n.getError('executionError', { message: 'Unknown error occurred' })
557
818
  : 'Unknown error occurred';
558
- return errorResult(ErrorCodes.EXECUTION_ERROR, message, { error });
819
+ return errorResult(ErrorCodes.EXECUTION_ERROR, message, { error }, {
820
+ toolName,
821
+ canvasState: canvasState || undefined,
822
+ });
559
823
  }
560
824
  }
561
825
  //# sourceMappingURL=handlers.js.map