editmamei 0.22.4 → 0.24.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 (80) hide show
  1. package/README.md +8 -6
  2. package/dist/api/extendscript/_helpers.js +97 -13
  3. package/dist/api/photoshop-api.js +4 -3
  4. package/dist/api/snippet-client.js +38 -2
  5. package/dist/bin/editmamei-core-darwin-arm64 +0 -0
  6. package/dist/bin/editmamei-core-darwin-x64 +0 -0
  7. package/dist/bin/editmamei-core-win-x64.exe +0 -0
  8. package/dist/core/server.js +56 -180
  9. package/dist/delivery/client.js +50 -1
  10. package/dist/delivery/provision.js +18 -1
  11. package/dist/delivery/signing.js +38 -1
  12. package/dist/delivery/store.js +149 -16
  13. package/dist/detection/detect-active-doc.js +41 -10
  14. package/dist/detection/detection-client.js +3 -3
  15. package/dist/detection/face-detector.js +2 -2
  16. package/dist/detection/geometry.js +14 -0
  17. package/dist/detection/landmark-detection-client.js +3 -3
  18. package/dist/detection/mesh-face.js +2 -3
  19. package/dist/detection/object-detector.js +2 -2
  20. package/dist/detection/runtime.js +14 -3
  21. package/dist/detection/sam-segmenter.js +69 -26
  22. package/dist/kernel/kernel.js +5 -9
  23. package/dist/kernel/module-lifecycle.js +181 -0
  24. package/dist/license/entitlement.js +13 -3
  25. package/dist/license/store.js +8 -1
  26. package/dist/modules/ce/index.js +1 -1
  27. package/dist/perception/facets.js +19 -1
  28. package/dist/perception/grounding-anchors.js +2 -12
  29. package/dist/perception/grounding-locate.js +1 -9
  30. package/dist/perception/grounding-review-crop.js +4 -29
  31. package/dist/perception/overlay.js +62 -0
  32. package/dist/perception/pixel-identity.js +41 -0
  33. package/dist/perception/region-precompute.js +85 -17
  34. package/dist/perception/scene-model.js +93 -50
  35. package/dist/perception/select-recipes.js +107 -66
  36. package/dist/perception/sky-ground-flood.js +0 -5
  37. package/dist/perception/sky-mask-transfer.js +72 -13
  38. package/dist/platform/connection.js +42 -11
  39. package/dist/platform/executor-queue.js +68 -0
  40. package/dist/platform/macos-detector.js +2 -1
  41. package/dist/platform/macos-executor.js +18 -67
  42. package/dist/platform/run-child.js +6 -3
  43. package/dist/platform/windows-detector.js +14 -7
  44. package/dist/platform/windows-executor.js +9 -64
  45. package/dist/skills/editmamei-skill.zip +0 -0
  46. package/dist/tools/adjustment-tools.js +78 -130
  47. package/dist/tools/brush-tools.js +3 -14
  48. package/dist/tools/channel-compose-tools.js +4 -23
  49. package/dist/tools/detection-tools.js +59 -47
  50. package/dist/tools/document-tools.js +121 -131
  51. package/dist/tools/filter-tools.js +489 -730
  52. package/dist/tools/group-tools.js +90 -156
  53. package/dist/tools/guide-tools.js +52 -83
  54. package/dist/tools/history-tools.js +29 -63
  55. package/dist/tools/image-placement-tools.js +18 -37
  56. package/dist/tools/image-tools.js +23 -51
  57. package/dist/tools/layer-ordering-tools.js +2 -9
  58. package/dist/tools/layer-properties-tools.js +245 -384
  59. package/dist/tools/layer-tools.js +115 -181
  60. package/dist/tools/layer-transform-tools.js +41 -106
  61. package/dist/tools/metadata-tools.js +2 -9
  62. package/dist/tools/path-tools.js +3 -14
  63. package/dist/tools/portrait-tools.js +2 -5
  64. package/dist/tools/preview-tools.js +53 -65
  65. package/dist/tools/retouch-tools.js +32 -70
  66. package/dist/tools/scene-tools.js +94 -73
  67. package/dist/tools/selection-tools.js +306 -563
  68. package/dist/tools/shape-tools.js +18 -10
  69. package/dist/tools/text-tools.js +24 -57
  70. package/dist/tools/transform-canvas-tools.js +21 -54
  71. package/dist/tools/vector-mask-tools.js +2 -9
  72. package/dist/utils/jpeg-quality.js +1 -0
  73. package/dist/utils/logger.js +3 -0
  74. package/dist/utils/operation-timeouts.js +7 -0
  75. package/dist/utils/run-script.js +7 -2
  76. package/dist/utils/session-log.js +134 -12
  77. package/dist/utils/template-storage.js +5 -6
  78. package/dist/utils/tool-helpers.js +37 -0
  79. package/dist/version.js +1 -1
  80. package/package.json +1 -1
@@ -1,6 +1,11 @@
1
1
  import { runScript } from '../utils/run-script.js';
2
2
  import { GROUP_BLEND_MODES } from '../utils/blend-modes.js';
3
3
  import { validateArgs } from '../utils/validate.js';
4
+ import { toolErrorResult, runSnippetTool } from '../utils/tool-helpers.js';
5
+ const emptySchema = {
6
+ type: 'object',
7
+ properties: {},
8
+ };
4
9
  const deleteGroupSchema = {
5
10
  type: 'object',
6
11
  properties: {
@@ -27,6 +32,11 @@ const createGroupSchema = {
27
32
  items: { type: 'string' },
28
33
  description: 'Optional list of existing layer names to move into the new group. The first listed name ends up on top of the group stack. Layers not found are returned in `not_found`.',
29
34
  },
35
+ into_active_group: {
36
+ type: 'boolean',
37
+ description: "Photoshop's Mk-layerSection descriptor carries no placement target, so with a GROUP active it would natively nest the new group INSIDE that group. Default false hoists the new group back out so it lands above the active layer/group as a sibling, matching this tool's documented placement. Pass true to keep it nested inside the active group instead.",
38
+ default: false,
39
+ },
30
40
  },
31
41
  required: ['name'],
32
42
  };
@@ -78,7 +88,7 @@ export function createGroupTools(connection, snippetClient) {
78
88
  {
79
89
  tool: {
80
90
  name: 'ps_create_group',
81
- description: 'Create a new layer group (LayerSet) above the active layer with the given name. Optionally moves existing layers into it in one step. Non-destructive. Foundational for structured non-destructive editing — e.g. group all adjustment layers into an "edits" group so you can A/B toggle the whole stack via group visibility.',
91
+ description: `Create a new layer group (LayerSet) above the active layer with the given name — hoisted out of the active layer's group by default even though Photoshop's own Mk-layerSection placement rule would otherwise nest it INSIDE that group (pass into_active_group:true to keep that native nesting; this is why groups created one after another land as siblings rather than nested, so bottom-to-top group creation is safe). Optionally moves existing layers into it in one step. Non-destructive. Foundational for structured non-destructive editing — e.g. group all adjustment layers into an "edits" group so you can A/B toggle the whole stack via group visibility.`,
82
92
  inputSchema: createGroupSchema,
83
93
  outputSchema: {
84
94
  type: 'object',
@@ -87,6 +97,15 @@ export function createGroupTools(connection, snippetClient) {
87
97
  groupName: { type: 'string' },
88
98
  moved_count: { type: 'number' },
89
99
  not_found: { type: 'array', items: { type: 'string' } },
100
+ hoisted: {
101
+ type: 'boolean',
102
+ description: 'True when the new group had to be moved back out of the previously-active group to honor into_active_group:false (the default). False when it landed correctly on its own, or when the move-back itself failed — check the layer tree if this matters and hoisted is false.',
103
+ },
104
+ parent_path: {
105
+ type: ['array', 'null'],
106
+ items: { type: 'string' },
107
+ description: 'The containing-group name chain (outermost first), empty array at the document root.',
108
+ },
90
109
  context: { type: 'object' },
91
110
  },
92
111
  },
@@ -226,150 +245,81 @@ export function createGroupTools(connection, snippetClient) {
226
245
  },
227
246
  ];
228
247
  }
229
- async function createClippingMask(connection, snippetClient, _rawArgs) {
230
- try {
231
- const script = await snippetClient.build('createClippingMask');
232
- const result = await runScript(connection, script);
233
- return {
234
- content: [
235
- {
236
- type: 'text',
237
- text: `Clipped layer "${result.layerName ?? ''}" to the layer below.`,
238
- },
239
- ],
240
- structuredContent: result,
241
- };
242
- }
243
- catch (error) {
244
- return {
245
- content: [
246
- {
247
- type: 'text',
248
- text: `Error creating clipping mask: ${error instanceof Error ? error.message : String(error)}`,
249
- },
250
- ],
251
- isError: true,
252
- };
253
- }
248
+ async function createClippingMask(connection, snippetClient, rawArgs) {
249
+ return runSnippetTool({
250
+ connection,
251
+ snippetClient,
252
+ rawArgs,
253
+ schema: emptySchema,
254
+ snippet: 'createClippingMask',
255
+ errorPrefix: 'Error creating clipping mask',
256
+ successText: (result) => `Clipped layer "${result.layerName ?? ''}" to the layer below.`,
257
+ });
254
258
  }
255
- async function releaseClippingMask(connection, snippetClient, _rawArgs) {
256
- try {
257
- const script = await snippetClient.build('releaseClippingMask');
258
- const result = await runScript(connection, script);
259
- return {
260
- content: [
261
- {
262
- type: 'text',
263
- text: `Released clipping mask on layer "${result.layerName ?? ''}".`,
264
- },
265
- ],
266
- structuredContent: result,
267
- };
268
- }
269
- catch (error) {
270
- return {
271
- content: [
272
- {
273
- type: 'text',
274
- text: `Error releasing clipping mask: ${error instanceof Error ? error.message : String(error)}`,
275
- },
276
- ],
277
- isError: true,
278
- };
279
- }
259
+ async function releaseClippingMask(connection, snippetClient, rawArgs) {
260
+ return runSnippetTool({
261
+ connection,
262
+ snippetClient,
263
+ rawArgs,
264
+ schema: emptySchema,
265
+ snippet: 'releaseClippingMask',
266
+ errorPrefix: 'Error releasing clipping mask',
267
+ successText: (result) => `Released clipping mask on layer "${result.layerName ?? ''}".`,
268
+ });
280
269
  }
281
270
  async function createGroup(connection, snippetClient, rawArgs) {
282
- try {
283
- const args = validateArgs(createGroupSchema, rawArgs);
284
- const name = args.name;
285
- const layers = args.layers;
286
- const params = { name };
287
- if (layers !== undefined)
288
- params.layerNames = layers;
289
- const script = await snippetClient.build('createGroup', params);
290
- const result = await runScript(connection, script);
291
- return {
292
- content: [
293
- {
294
- type: 'text',
295
- text: `Group "${name}" created${layers && layers.length ? ` (moved ${result.moved_count ?? 0}/${layers.length} layers)` : ''}`,
296
- },
297
- ],
298
- structuredContent: result,
299
- };
300
- }
301
- catch (error) {
302
- return {
303
- content: [
304
- {
305
- type: 'text',
306
- text: `Error creating group: ${error instanceof Error ? error.message : String(error)}`,
307
- },
308
- ],
309
- isError: true,
310
- };
311
- }
271
+ return runSnippetTool({
272
+ connection,
273
+ snippetClient,
274
+ rawArgs,
275
+ schema: createGroupSchema,
276
+ snippet: 'createGroup',
277
+ errorPrefix: 'Error creating group',
278
+ params: (args) => {
279
+ const name = args.name;
280
+ const layers = args.layers;
281
+ const intoActiveGroup = args.into_active_group ?? false;
282
+ const params = { name, into_active_group: intoActiveGroup };
283
+ if (layers !== undefined)
284
+ params.layerNames = layers;
285
+ return params;
286
+ },
287
+ successText: (result, args) => {
288
+ const name = args.name;
289
+ const layers = args.layers;
290
+ return `Group "${name}" created${layers && layers.length ? ` (moved ${result.moved_count ?? 0}/${layers.length} layers)` : ''}`;
291
+ },
292
+ });
312
293
  }
313
294
  async function moveLayerToGroup(connection, snippetClient, rawArgs) {
314
- try {
315
- const args = validateArgs(moveLayerToGroupSchema, rawArgs);
316
- const layerName = args.layer_name;
317
- const groupName = args.group_name;
318
- const script = await snippetClient.build('moveLayerToGroup', { layerName, groupName });
319
- const result = await runScript(connection, script);
320
- return {
321
- content: [
322
- {
323
- type: 'text',
324
- text: `Layer "${layerName}" moved into group "${groupName}"`,
325
- },
326
- ],
327
- structuredContent: result,
328
- };
329
- }
330
- catch (error) {
331
- return {
332
- content: [
333
- {
334
- type: 'text',
335
- text: `Error moving layer to group: ${error instanceof Error ? error.message : String(error)}`,
336
- },
337
- ],
338
- isError: true,
339
- };
340
- }
295
+ return runSnippetTool({
296
+ connection,
297
+ snippetClient,
298
+ rawArgs,
299
+ schema: moveLayerToGroupSchema,
300
+ snippet: 'moveLayerToGroup',
301
+ errorPrefix: 'Error moving layer to group',
302
+ params: (args) => ({
303
+ layerName: args.layer_name,
304
+ groupName: args.group_name,
305
+ }),
306
+ successText: (_result, args) => `Layer "${args.layer_name}" moved into group "${args.group_name}"`,
307
+ });
341
308
  }
342
309
  async function setGroupBlendMode(connection, snippetClient, rawArgs) {
343
- try {
344
- const args = validateArgs(setGroupBlendModeSchema, rawArgs);
345
- const name = args.name;
346
- const blendMode = args.blend_mode;
347
- const script = await snippetClient.build('setGroupBlendMode', {
348
- groupName: name,
349
- blendMode,
350
- });
351
- const result = await runScript(connection, script);
352
- return {
353
- content: [
354
- {
355
- type: 'text',
356
- text: `Group "${name}" blend mode set to ${blendMode}`,
357
- },
358
- ],
359
- structuredContent: result,
360
- };
361
- }
362
- catch (error) {
363
- return {
364
- content: [
365
- {
366
- type: 'text',
367
- text: `Error setting group blend mode: ${error instanceof Error ? error.message : String(error)}`,
368
- },
369
- ],
370
- isError: true,
371
- };
372
- }
310
+ return runSnippetTool({
311
+ connection,
312
+ snippetClient,
313
+ rawArgs,
314
+ schema: setGroupBlendModeSchema,
315
+ snippet: 'setGroupBlendMode',
316
+ errorPrefix: 'Error setting group blend mode',
317
+ params: (args) => ({
318
+ groupName: args.name,
319
+ blendMode: args.blend_mode,
320
+ }),
321
+ successText: (_result, args) => `Group "${args.name}" blend mode set to ${args.blend_mode}`,
322
+ });
373
323
  }
374
324
  async function ungroup(connection, snippetClient, rawArgs) {
375
325
  try {
@@ -400,15 +350,7 @@ async function ungroup(connection, snippetClient, rawArgs) {
400
350
  };
401
351
  }
402
352
  catch (error) {
403
- return {
404
- content: [
405
- {
406
- type: 'text',
407
- text: `Error ungrouping: ${error instanceof Error ? error.message : String(error)}`,
408
- },
409
- ],
410
- isError: true,
411
- };
353
+ return toolErrorResult('Error ungrouping', error);
412
354
  }
413
355
  }
414
356
  async function deleteGroup(connection, snippetClient, rawArgs) {
@@ -440,14 +382,6 @@ async function deleteGroup(connection, snippetClient, rawArgs) {
440
382
  };
441
383
  }
442
384
  catch (error) {
443
- return {
444
- content: [
445
- {
446
- type: 'text',
447
- text: `Error deleting group: ${error instanceof Error ? error.message : String(error)}`,
448
- },
449
- ],
450
- isError: true,
451
- };
385
+ return toolErrorResult('Error deleting group', error);
452
386
  }
453
387
  }
@@ -1,5 +1,4 @@
1
- import { runScript } from '../utils/run-script.js';
2
- import { validateArgs } from '../utils/validate.js';
1
+ import { runSnippetTool } from '../utils/tool-helpers.js';
3
2
  const addGuideSchema = {
4
3
  type: 'object',
5
4
  properties: {
@@ -33,6 +32,10 @@ const guideLayoutSchema = {
33
32
  },
34
33
  },
35
34
  };
35
+ const clearGuidesSchema = {
36
+ type: 'object',
37
+ properties: {},
38
+ };
36
39
  const GUIDE_OPS = ['add', 'layout', 'clear'];
37
40
  const GUIDE_INPUT_SCHEMA = {
38
41
  type: 'object',
@@ -98,89 +101,55 @@ async function guides(connection, snippetClient, rawArgs) {
98
101
  }
99
102
  }
100
103
  async function addGuide(connection, snippetClient, rawArgs) {
101
- try {
102
- const args = validateArgs(addGuideSchema, rawArgs);
103
- const orientation = args.orientation;
104
- const position = args.position;
105
- const script = await snippetClient.build('addGuide', { orientation, position });
106
- const result = (await runScript(connection, script));
107
- return {
108
- content: [
109
- {
110
- type: 'text',
111
- text: `Added ${orientation} guide at ${position}px\nResult: ${JSON.stringify(result)}`,
112
- },
113
- ],
114
- structuredContent: result,
115
- };
116
- }
117
- catch (error) {
118
- return {
119
- content: [
120
- {
121
- type: 'text',
122
- text: `Error adding guide: ${error instanceof Error ? error.message : String(error)}`,
123
- },
124
- ],
125
- isError: true,
126
- };
127
- }
104
+ return runSnippetTool({
105
+ connection,
106
+ snippetClient,
107
+ rawArgs,
108
+ schema: addGuideSchema,
109
+ snippet: 'addGuide',
110
+ errorPrefix: 'Error adding guide',
111
+ params: (args) => ({
112
+ orientation: args.orientation,
113
+ position: args.position,
114
+ }),
115
+ successText: (result, args) => {
116
+ const orientation = args.orientation;
117
+ const position = args.position;
118
+ return `Added ${orientation} guide at ${position}px\nResult: ${JSON.stringify(result)}`;
119
+ },
120
+ });
128
121
  }
129
122
  async function addGuideLayout(connection, snippetClient, rawArgs) {
130
- try {
131
- const args = validateArgs(guideLayoutSchema, rawArgs);
132
- const columns = args.columns ?? 0;
133
- const rows = args.rows ?? 0;
134
- if (columns <= 0 && rows <= 0) {
135
- throw new Error('op=layout requires at least one of columns / rows to be ≥ 1.');
136
- }
137
- const script = await snippetClient.build('addGuideLayout', { columns, rows });
138
- const result = await runScript(connection, script);
139
- return {
140
- content: [
141
- {
142
- type: 'text',
143
- text: `Created guide layout (${columns} columns × ${rows} rows)\nResult: ${JSON.stringify(result)}`,
144
- },
145
- ],
146
- structuredContent: result,
147
- };
148
- }
149
- catch (error) {
150
- return {
151
- content: [
152
- {
153
- type: 'text',
154
- text: `Error creating guide layout: ${error instanceof Error ? error.message : String(error)}`,
155
- },
156
- ],
157
- isError: true,
158
- };
159
- }
123
+ return runSnippetTool({
124
+ connection,
125
+ snippetClient,
126
+ rawArgs,
127
+ schema: guideLayoutSchema,
128
+ snippet: 'addGuideLayout',
129
+ errorPrefix: 'Error creating guide layout',
130
+ params: (args) => {
131
+ const columns = args.columns ?? 0;
132
+ const rows = args.rows ?? 0;
133
+ if (columns <= 0 && rows <= 0) {
134
+ throw new Error('op=layout requires at least one of columns / rows to be ≥ 1.');
135
+ }
136
+ return { columns, rows };
137
+ },
138
+ successText: (result, args) => {
139
+ const columns = args.columns ?? 0;
140
+ const rows = args.rows ?? 0;
141
+ return `Created guide layout (${columns} columns × ${rows} rows)\nResult: ${JSON.stringify(result)}`;
142
+ },
143
+ });
160
144
  }
161
145
  async function clearGuides(connection, snippetClient) {
162
- try {
163
- const script = await snippetClient.build('clearGuides', {});
164
- const result = await runScript(connection, script);
165
- return {
166
- content: [
167
- {
168
- type: 'text',
169
- text: `Cleared all guides\nResult: ${JSON.stringify(result)}`,
170
- },
171
- ],
172
- structuredContent: result,
173
- };
174
- }
175
- catch (error) {
176
- return {
177
- content: [
178
- {
179
- type: 'text',
180
- text: `Error clearing guides: ${error instanceof Error ? error.message : String(error)}`,
181
- },
182
- ],
183
- isError: true,
184
- };
185
- }
146
+ return runSnippetTool({
147
+ connection,
148
+ snippetClient,
149
+ rawArgs: {},
150
+ schema: clearGuidesSchema,
151
+ snippet: 'clearGuides',
152
+ errorPrefix: 'Error clearing guides',
153
+ successText: (result) => `Cleared all guides\nResult: ${JSON.stringify(result)}`,
154
+ });
186
155
  }
@@ -1,5 +1,5 @@
1
1
  import { runScript } from '../utils/run-script.js';
2
- import { validateArgs } from '../utils/validate.js';
2
+ import { toolErrorResult, runSnippetTool } from '../utils/tool-helpers.js';
3
3
  const undoSchema = {
4
4
  type: 'object',
5
5
  properties: {
@@ -39,7 +39,7 @@ export function createHistoryTools(connection, snippetClient) {
39
39
  {
40
40
  tool: {
41
41
  name: 'ps_undo',
42
- description: 'Step backward in the document history (equivalent to Ctrl/Cmd+Z). WHEN TO REACH FOR THIS: after an experimental destructive bake (photoshop_merge_visible_layers, photoshop_flatten_image, photoshop_apply_*_blur, photoshop_apply_sharpen, photoshop_apply_noise) produced a wrong result; after a play_action whose scope you mispredicted; or to revert an entire branch of exploration. Non-destructive workflows (adjustment layers + masks) rarely need this — just tweak/delete the offending layer instead. Reversible via ps_redo as long as no new edit has been made since. Returns current history state, remaining steps, and document context.',
42
+ description: 'Step backward in the document history (equivalent to Ctrl/Cmd+Z). WHEN TO REACH FOR THIS: after an experimental destructive bake (ps_merge mode=visible/flatten, ps_apply_filter type=*_blur/sharpen/noise) produced a wrong result; after a play_action whose scope you mispredicted; or to revert an entire branch of exploration. Non-destructive workflows (adjustment layers + masks) rarely need this — just tweak/delete the offending layer instead. Reversible via ps_redo as long as no new edit has been made since. Returns current history state, remaining steps, and document context.',
43
43
  inputSchema: undoSchema,
44
44
  outputSchema: HISTORY_STEP_RESULT_SCHEMA,
45
45
  annotations: {
@@ -65,60 +65,34 @@ export function createHistoryTools(connection, snippetClient) {
65
65
  ];
66
66
  }
67
67
  async function undo(connection, snippetClient, rawArgs) {
68
- try {
69
- const args = validateArgs(undoSchema, rawArgs);
70
- const steps = args.steps;
71
- const script = await snippetClient.build('undo', { steps });
72
- const result = await runScript(connection, script);
73
- return {
74
- content: [
75
- {
76
- type: 'text',
77
- text: `Undo successful (${steps} step${steps > 1 ? 's' : ''})\nResult: ${JSON.stringify(result)}`,
78
- },
79
- ],
80
- structuredContent: result,
81
- };
82
- }
83
- catch (error) {
84
- return {
85
- content: [
86
- {
87
- type: 'text',
88
- text: `Error undoing: ${error instanceof Error ? error.message : String(error)}`,
89
- },
90
- ],
91
- isError: true,
92
- };
93
- }
68
+ return runSnippetTool({
69
+ connection,
70
+ snippetClient,
71
+ rawArgs,
72
+ schema: undoSchema,
73
+ snippet: 'undo',
74
+ errorPrefix: 'Error undoing',
75
+ params: (args) => ({ steps: args.steps }),
76
+ successText: (result, args) => {
77
+ const steps = args.steps;
78
+ return `Undo successful (${steps} step${steps > 1 ? 's' : ''})\nResult: ${JSON.stringify(result)}`;
79
+ },
80
+ });
94
81
  }
95
82
  async function redo(connection, snippetClient, rawArgs) {
96
- try {
97
- const args = validateArgs(redoSchema, rawArgs);
98
- const steps = args.steps;
99
- const script = await snippetClient.build('redo', { steps });
100
- const result = await runScript(connection, script);
101
- return {
102
- content: [
103
- {
104
- type: 'text',
105
- text: `Redo successful (${steps} step${steps > 1 ? 's' : ''})\nResult: ${JSON.stringify(result)}`,
106
- },
107
- ],
108
- structuredContent: result,
109
- };
110
- }
111
- catch (error) {
112
- return {
113
- content: [
114
- {
115
- type: 'text',
116
- text: `Error redoing: ${error instanceof Error ? error.message : String(error)}`,
117
- },
118
- ],
119
- isError: true,
120
- };
121
- }
83
+ return runSnippetTool({
84
+ connection,
85
+ snippetClient,
86
+ rawArgs,
87
+ schema: redoSchema,
88
+ snippet: 'redo',
89
+ errorPrefix: 'Error redoing',
90
+ params: (args) => ({ steps: args.steps }),
91
+ successText: (result, args) => {
92
+ const steps = args.steps;
93
+ return `Redo successful (${steps} step${steps > 1 ? 's' : ''})\nResult: ${JSON.stringify(result)}`;
94
+ },
95
+ });
122
96
  }
123
97
  export async function getHistory(connection, snippetClient) {
124
98
  try {
@@ -135,14 +109,6 @@ export async function getHistory(connection, snippetClient) {
135
109
  };
136
110
  }
137
111
  catch (error) {
138
- return {
139
- content: [
140
- {
141
- type: 'text',
142
- text: `Error getting history: ${error instanceof Error ? error.message : String(error)}`,
143
- },
144
- ],
145
- isError: true,
146
- };
112
+ return toolErrorResult('Error getting history', error);
147
113
  }
148
114
  }
@@ -1,5 +1,4 @@
1
- import { runScript } from '../utils/run-script.js';
2
- import { validateArgs } from '../utils/validate.js';
1
+ import { runSnippetTool } from '../utils/tool-helpers.js';
3
2
  const PS_COORD_MAX_PX = 300_000;
4
3
  const placeImageSchema = {
5
4
  type: 'object',
@@ -65,39 +64,21 @@ export function createImagePlacementTools(connection, snippetClient) {
65
64
  ];
66
65
  }
67
66
  async function placeImage(connection, snippetClient, rawArgs) {
68
- try {
69
- const args = validateArgs(placeImageSchema, rawArgs);
70
- const filePath = args.file_path;
71
- const x = args.x;
72
- const y = args.y;
73
- const widthPercent = args.width_percent;
74
- const heightPercent = args.height_percent;
75
- const params = { filePath, x, y };
76
- if (widthPercent !== undefined)
77
- params.widthPercent = widthPercent;
78
- if (heightPercent !== undefined)
79
- params.heightPercent = heightPercent;
80
- const script = await snippetClient.build('placeImage', params);
81
- const result = await runScript(connection, script);
82
- return {
83
- content: [
84
- {
85
- type: 'text',
86
- text: `Image placed successfully: ${filePath}\nPosition: (${x}, ${y})\nResult: ${JSON.stringify(result)}`,
87
- },
88
- ],
89
- structuredContent: result,
90
- };
91
- }
92
- catch (error) {
93
- return {
94
- content: [
95
- {
96
- type: 'text',
97
- text: `Error placing image: ${error instanceof Error ? error.message : String(error)}`,
98
- },
99
- ],
100
- isError: true,
101
- };
102
- }
67
+ return runSnippetTool({
68
+ connection,
69
+ snippetClient,
70
+ rawArgs,
71
+ schema: placeImageSchema,
72
+ snippet: 'placeImage',
73
+ errorPrefix: 'Error placing image',
74
+ params: (args) => {
75
+ const params = { filePath: args.file_path, x: args.x, y: args.y };
76
+ if (args.width_percent !== undefined)
77
+ params.widthPercent = args.width_percent;
78
+ if (args.height_percent !== undefined)
79
+ params.heightPercent = args.height_percent;
80
+ return params;
81
+ },
82
+ successText: (result, args) => `Image placed successfully: ${args.file_path}\nPosition: (${args.x}, ${args.y})\nResult: ${JSON.stringify(result)}`,
83
+ });
103
84
  }