@pinepaper.studio/mcp-server 1.2.1 → 1.3.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 (65) 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 +510 -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 +313 -34
  55. package/dist/tools/handlers.js.map +1 -1
  56. package/dist/types/code-generator.d.ts +13 -1
  57. package/dist/types/code-generator.d.ts.map +1 -1
  58. package/dist/types/code-generator.js +91 -1
  59. package/dist/types/code-generator.js.map +1 -1
  60. package/dist/types/schemas.d.ts +210 -47
  61. package/dist/types/schemas.d.ts.map +1 -1
  62. package/dist/types/schemas.js +59 -0
  63. package/dist/types/schemas.js.map +1 -1
  64. package/manifest.json +42 -2
  65. 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, 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, 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,34 @@ 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');
317
+ }
318
+ // -----------------------------------------------------------------------
319
+ // BATCH OPERATION TOOLS
320
+ // -----------------------------------------------------------------------
321
+ case 'pinepaper_batch_create': {
322
+ const input = BatchCreateInputSchema.parse(args);
323
+ const code = codeGenerator.generateBatchCreate(input);
324
+ const description = `Batch creates ${input.items.length} items with single history save`;
325
+ return executeOrGenerate(code, description, options, 'pinepaper_batch_create');
326
+ }
327
+ case 'pinepaper_batch_modify': {
328
+ const input = BatchModifyInputSchema.parse(args);
329
+ const code = codeGenerator.generateBatchModify(input);
330
+ const description = `Batch modifies ${input.modifications.length} items with single history save`;
331
+ return executeOrGenerate(code, description, options, 'pinepaper_batch_modify');
332
+ }
333
+ case 'pinepaper_create_grid': {
334
+ const input = CreateGridInputSchema.parse(args);
335
+ const code = codeGenerator.generateCreateGrid(input);
336
+ const description = `Creates a ${input.cols}x${input.rows} grid${input.animated ? ' with wave animation' : ''}`;
337
+ return executeOrGenerate(code, description, options, 'pinepaper_create_grid');
187
338
  }
188
339
  // -----------------------------------------------------------------------
189
340
  // RELATION TOOLS
@@ -196,7 +347,7 @@ export async function handleToolCall(toolName, args, options = {}) {
196
347
  sourceId: input.sourceId,
197
348
  targetId: input.targetId,
198
349
  });
199
- return executeOrGenerate(code, description, options);
350
+ return executeOrGenerate(code, description, options, 'pinepaper_add_relation');
200
351
  }
201
352
  case 'pinepaper_remove_relation': {
202
353
  const input = RemoveRelationInputSchema.parse(args);
@@ -205,12 +356,12 @@ export async function handleToolCall(toolName, args, options = {}) {
205
356
  sourceId: input.sourceId,
206
357
  targetId: input.targetId,
207
358
  });
208
- return executeOrGenerate(code, description, options);
359
+ return executeOrGenerate(code, description, options, 'pinepaper_remove_relation');
209
360
  }
210
361
  case 'pinepaper_query_relations': {
211
362
  const input = QueryRelationsInputSchema.parse(args);
212
363
  const code = codeGenerator.generateQueryRelations(input.itemId, input.relationType, input.direction);
213
- 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');
214
365
  }
215
366
  // -----------------------------------------------------------------------
216
367
  // ANIMATION TOOLS
@@ -222,17 +373,17 @@ export async function handleToolCall(toolName, args, options = {}) {
222
373
  animationType: input.animationType,
223
374
  itemId: input.itemId,
224
375
  });
225
- return executeOrGenerate(code, description, options);
376
+ return executeOrGenerate(code, description, options, 'pinepaper_animate');
226
377
  }
227
378
  case 'pinepaper_keyframe_animate': {
228
379
  const input = KeyframeAnimateInputSchema.parse(args);
229
380
  const code = codeGenerator.generateKeyframeAnimate(input);
230
- 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');
231
382
  }
232
383
  case 'pinepaper_play_timeline': {
233
384
  const input = PlayTimelineInputSchema.parse(args);
234
385
  const code = codeGenerator.generatePlayTimeline(input.action, input.duration, input.loop, input.time);
235
- return executeOrGenerate(code, `Timeline action: ${input.action}`, options);
386
+ return executeOrGenerate(code, `Timeline action: ${input.action}`, options, 'pinepaper_play_timeline');
236
387
  }
237
388
  // -----------------------------------------------------------------------
238
389
  // GENERATOR TOOLS
@@ -243,11 +394,11 @@ export async function handleToolCall(toolName, args, options = {}) {
243
394
  const description = getLocalizedSuccessMessage(i18n, 'generatorExecuted', {
244
395
  generatorName: input.generatorName,
245
396
  });
246
- return executeOrGenerate(code, description, options);
397
+ return executeOrGenerate(code, description, options, 'pinepaper_execute_generator');
247
398
  }
248
399
  case 'pinepaper_list_generators': {
249
400
  const code = codeGenerator.generateListGenerators();
250
- return executeOrGenerate(code, 'Lists all available generators', options);
401
+ return executeOrGenerate(code, 'Lists all available generators', options, 'pinepaper_list_generators');
251
402
  }
252
403
  // -----------------------------------------------------------------------
253
404
  // EFFECT TOOLS
@@ -259,7 +410,7 @@ export async function handleToolCall(toolName, args, options = {}) {
259
410
  effectType: input.effectType,
260
411
  itemId: input.itemId,
261
412
  });
262
- return executeOrGenerate(code, description, options);
413
+ return executeOrGenerate(code, description, options, 'pinepaper_apply_effect');
263
414
  }
264
415
  // -----------------------------------------------------------------------
265
416
  // QUERY TOOLS
@@ -267,11 +418,11 @@ export async function handleToolCall(toolName, args, options = {}) {
267
418
  case 'pinepaper_get_items': {
268
419
  const input = GetItemsInputSchema.parse(args);
269
420
  const code = codeGenerator.generateGetItems(input.filter);
270
- return executeOrGenerate(code, 'Gets items from canvas', options);
421
+ return executeOrGenerate(code, 'Gets items from canvas', options, 'pinepaper_get_items');
271
422
  }
272
423
  case 'pinepaper_get_relation_stats': {
273
424
  const code = codeGenerator.generateGetRelationStats();
274
- return executeOrGenerate(code, 'Gets relation statistics', options);
425
+ return executeOrGenerate(code, 'Gets relation statistics', options, 'pinepaper_get_relation_stats');
275
426
  }
276
427
  // -----------------------------------------------------------------------
277
428
  // CANVAS TOOLS
@@ -282,7 +433,7 @@ export async function handleToolCall(toolName, args, options = {}) {
282
433
  const description = getLocalizedSuccessMessage(i18n, 'backgroundSet', {
283
434
  color: input.color,
284
435
  });
285
- return executeOrGenerate(code, description, options);
436
+ return executeOrGenerate(code, description, options, 'pinepaper_set_background_color');
286
437
  }
287
438
  case 'pinepaper_set_canvas_size': {
288
439
  const input = SetCanvasSizeInputSchema.parse(args);
@@ -291,16 +442,16 @@ export async function handleToolCall(toolName, args, options = {}) {
291
442
  width: input.width,
292
443
  height: input.height,
293
444
  });
294
- return executeOrGenerate(code, description, options);
445
+ return executeOrGenerate(code, description, options, 'pinepaper_set_canvas_size');
295
446
  }
296
447
  case 'pinepaper_get_canvas_size': {
297
448
  const code = codeGenerator.generateGetCanvasSize();
298
- return executeOrGenerate(code, 'Gets current canvas dimensions', options);
449
+ return executeOrGenerate(code, 'Gets current canvas dimensions', options, 'pinepaper_get_canvas_size');
299
450
  }
300
451
  case 'pinepaper_clear_canvas': {
301
452
  const code = codeGenerator.generateClearCanvas();
302
453
  const description = 'Clears all items from the canvas, including any welcome template items';
303
- return executeOrGenerate(code, description, options);
454
+ return executeOrGenerate(code, description, options, 'pinepaper_clear_canvas');
304
455
  }
305
456
  case 'pinepaper_refresh_page': {
306
457
  const controller = options.browserController || getBrowserController();
@@ -344,7 +495,7 @@ You can now start creating new items on a clean canvas.`,
344
495
  const input = ImportSVGInputSchema.parse(args);
345
496
  const code = codeGenerator.generateImportSVG(input.svgString, input.url, input.position, input.scale);
346
497
  const description = 'Imports SVG onto the canvas';
347
- return executeOrGenerate(code, description, options);
498
+ return executeOrGenerate(code, description, options, 'pinepaper_import_svg');
348
499
  }
349
500
  // -----------------------------------------------------------------------
350
501
  // FILTER TOOLS
@@ -353,7 +504,7 @@ You can now start creating new items on a clean canvas.`,
353
504
  const input = AddFilterInputSchema.parse(args);
354
505
  const code = codeGenerator.generateAddFilter(input.filterType, input.params);
355
506
  const description = `Adds ${input.filterType} filter to the canvas`;
356
- return executeOrGenerate(code, description, options);
507
+ return executeOrGenerate(code, description, options, 'pinepaper_add_filter');
357
508
  }
358
509
  // -----------------------------------------------------------------------
359
510
  // EXPORT TOOLS
@@ -363,7 +514,7 @@ You can now start creating new items on a clean canvas.`,
363
514
  const description = getLocalizedSuccessMessage(i18n, 'exported', {
364
515
  format: 'SVG',
365
516
  });
366
- return executeOrGenerate(code, description, options);
517
+ return executeOrGenerate(code, description, options, 'pinepaper_export_svg');
367
518
  }
368
519
  case 'pinepaper_export_training_data': {
369
520
  const input = ExportTrainingDataInputSchema.parse(args);
@@ -371,7 +522,7 @@ You can now start creating new items on a clean canvas.`,
371
522
  const description = getLocalizedSuccessMessage(i18n, 'exported', {
372
523
  format: input.format?.toUpperCase() || 'JSON',
373
524
  });
374
- return executeOrGenerate(code, description, options);
525
+ return executeOrGenerate(code, description, options, 'pinepaper_export_training_data');
375
526
  }
376
527
  // -----------------------------------------------------------------------
377
528
  // BROWSER CONTROL TOOLS
@@ -455,7 +606,12 @@ Browser is ready. You can now use other pinepaper tools to create and animate gr
455
606
  }
456
607
  const screenshot = await controller.takeScreenshot();
457
608
  if (!screenshot) {
458
- 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
+ });
459
615
  }
460
616
  return screenshotResult(screenshot);
461
617
  }
@@ -474,6 +630,106 @@ Browser is ready. You can now use other pinepaper tools to create and animate gr
474
630
  };
475
631
  }
476
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
+ // -----------------------------------------------------------------------
477
733
  // UNKNOWN TOOL
478
734
  // -----------------------------------------------------------------------
479
735
  default: {
@@ -487,6 +743,9 @@ Browser is ready. You can now use other pinepaper tools to create and animate gr
487
743
  'pinepaper_delete_item',
488
744
  'pinepaper_create_glossy_sphere',
489
745
  'pinepaper_create_diagonal_stripes',
746
+ 'pinepaper_batch_create',
747
+ 'pinepaper_batch_modify',
748
+ 'pinepaper_create_grid',
490
749
  'pinepaper_add_relation',
491
750
  'pinepaper_remove_relation',
492
751
  'pinepaper_query_relations',
@@ -511,6 +770,9 @@ Browser is ready. You can now use other pinepaper tools to create and animate gr
511
770
  'pinepaper_browser_disconnect',
512
771
  'pinepaper_browser_screenshot',
513
772
  'pinepaper_browser_status',
773
+ 'pinepaper_get_performance_metrics',
774
+ 'pinepaper_search_assets',
775
+ 'pinepaper_import_asset',
514
776
  ],
515
777
  });
516
778
  }
@@ -520,18 +782,35 @@ Browser is ready. You can now use other pinepaper tools to create and animate gr
520
782
  if (error instanceof ZodError) {
521
783
  return handleValidationError(error, i18n);
522
784
  }
785
+ // Try to capture canvas state for error context (if browser is connected)
786
+ let canvasState = null;
787
+ try {
788
+ const controller = options.browserController || getBrowserController();
789
+ if (controller.connected) {
790
+ canvasState = await captureCanvasState(controller);
791
+ }
792
+ }
793
+ catch (stateError) {
794
+ // Ignore errors from canvas state capture
795
+ }
523
796
  if (error instanceof Error) {
524
797
  const message = i18n
525
798
  ? i18n.getError('executionError', { message: error.message })
526
799
  : error.message;
527
800
  return errorResult(ErrorCodes.EXECUTION_ERROR, message, {
528
801
  stack: error.stack,
802
+ }, {
803
+ toolName,
804
+ canvasState: canvasState || undefined,
529
805
  });
530
806
  }
531
807
  const message = i18n
532
808
  ? i18n.getError('executionError', { message: 'Unknown error occurred' })
533
809
  : 'Unknown error occurred';
534
- return errorResult(ErrorCodes.EXECUTION_ERROR, message, { error });
810
+ return errorResult(ErrorCodes.EXECUTION_ERROR, message, { error }, {
811
+ toolName,
812
+ canvasState: canvasState || undefined,
813
+ });
535
814
  }
536
815
  }
537
816
  //# sourceMappingURL=handlers.js.map