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.
- package/README.md +8 -6
- package/dist/api/extendscript/_helpers.js +97 -13
- package/dist/api/photoshop-api.js +4 -3
- package/dist/api/snippet-client.js +38 -2
- package/dist/bin/editmamei-core-darwin-arm64 +0 -0
- package/dist/bin/editmamei-core-darwin-x64 +0 -0
- package/dist/bin/editmamei-core-win-x64.exe +0 -0
- package/dist/core/server.js +56 -180
- package/dist/delivery/client.js +50 -1
- package/dist/delivery/provision.js +18 -1
- package/dist/delivery/signing.js +38 -1
- package/dist/delivery/store.js +149 -16
- package/dist/detection/detect-active-doc.js +41 -10
- package/dist/detection/detection-client.js +3 -3
- package/dist/detection/face-detector.js +2 -2
- package/dist/detection/geometry.js +14 -0
- package/dist/detection/landmark-detection-client.js +3 -3
- package/dist/detection/mesh-face.js +2 -3
- package/dist/detection/object-detector.js +2 -2
- package/dist/detection/runtime.js +14 -3
- package/dist/detection/sam-segmenter.js +69 -26
- package/dist/kernel/kernel.js +5 -9
- package/dist/kernel/module-lifecycle.js +181 -0
- package/dist/license/entitlement.js +13 -3
- package/dist/license/store.js +8 -1
- package/dist/modules/ce/index.js +1 -1
- package/dist/perception/facets.js +19 -1
- package/dist/perception/grounding-anchors.js +2 -12
- package/dist/perception/grounding-locate.js +1 -9
- package/dist/perception/grounding-review-crop.js +4 -29
- package/dist/perception/overlay.js +62 -0
- package/dist/perception/pixel-identity.js +41 -0
- package/dist/perception/region-precompute.js +85 -17
- package/dist/perception/scene-model.js +93 -50
- package/dist/perception/select-recipes.js +107 -66
- package/dist/perception/sky-ground-flood.js +0 -5
- package/dist/perception/sky-mask-transfer.js +72 -13
- package/dist/platform/connection.js +42 -11
- package/dist/platform/executor-queue.js +68 -0
- package/dist/platform/macos-detector.js +2 -1
- package/dist/platform/macos-executor.js +18 -67
- package/dist/platform/run-child.js +6 -3
- package/dist/platform/windows-detector.js +14 -7
- package/dist/platform/windows-executor.js +9 -64
- package/dist/skills/editmamei-skill.zip +0 -0
- package/dist/tools/adjustment-tools.js +78 -130
- package/dist/tools/brush-tools.js +3 -14
- package/dist/tools/channel-compose-tools.js +4 -23
- package/dist/tools/detection-tools.js +59 -47
- package/dist/tools/document-tools.js +121 -131
- package/dist/tools/filter-tools.js +489 -730
- package/dist/tools/group-tools.js +90 -156
- package/dist/tools/guide-tools.js +52 -83
- package/dist/tools/history-tools.js +29 -63
- package/dist/tools/image-placement-tools.js +18 -37
- package/dist/tools/image-tools.js +23 -51
- package/dist/tools/layer-ordering-tools.js +2 -9
- package/dist/tools/layer-properties-tools.js +245 -384
- package/dist/tools/layer-tools.js +115 -181
- package/dist/tools/layer-transform-tools.js +41 -106
- package/dist/tools/metadata-tools.js +2 -9
- package/dist/tools/path-tools.js +3 -14
- package/dist/tools/portrait-tools.js +2 -5
- package/dist/tools/preview-tools.js +53 -65
- package/dist/tools/retouch-tools.js +32 -70
- package/dist/tools/scene-tools.js +94 -73
- package/dist/tools/selection-tools.js +306 -563
- package/dist/tools/shape-tools.js +18 -10
- package/dist/tools/text-tools.js +24 -57
- package/dist/tools/transform-canvas-tools.js +21 -54
- package/dist/tools/vector-mask-tools.js +2 -9
- package/dist/utils/jpeg-quality.js +1 -0
- package/dist/utils/logger.js +3 -0
- package/dist/utils/operation-timeouts.js +7 -0
- package/dist/utils/run-script.js +7 -2
- package/dist/utils/session-log.js +134 -12
- package/dist/utils/template-storage.js +5 -6
- package/dist/utils/tool-helpers.js +37 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { runScript } from '../utils/run-script.js';
|
|
2
2
|
import { validateArgs } from '../utils/validate.js';
|
|
3
|
+
import { toolErrorResult, applyToActiveLayerProp } from '../utils/tool-helpers.js';
|
|
3
4
|
const BLEND_MODES = [
|
|
4
5
|
'normal',
|
|
5
6
|
'multiply',
|
|
@@ -45,11 +46,7 @@ const applyImageSchema = {
|
|
|
45
46
|
maximum: 100,
|
|
46
47
|
default: 100,
|
|
47
48
|
},
|
|
48
|
-
apply_to_active_layer:
|
|
49
|
-
type: 'boolean',
|
|
50
|
-
description: 'If false (default) the composite is baked onto a DUPLICATE of the active layer (original preserved — delete the copy to revert). Set true to bake destructively in place.',
|
|
51
|
-
default: false,
|
|
52
|
-
},
|
|
49
|
+
apply_to_active_layer: applyToActiveLayerProp('the composite'),
|
|
53
50
|
},
|
|
54
51
|
required: ['blend'],
|
|
55
52
|
};
|
|
@@ -182,15 +179,7 @@ async function applyImage(connection, snippetClient, rawArgs) {
|
|
|
182
179
|
};
|
|
183
180
|
}
|
|
184
181
|
catch (error) {
|
|
185
|
-
return
|
|
186
|
-
content: [
|
|
187
|
-
{
|
|
188
|
-
type: 'text',
|
|
189
|
-
text: `Error in ps_apply_image: ${error instanceof Error ? error.message : String(error)}`,
|
|
190
|
-
},
|
|
191
|
-
],
|
|
192
|
-
isError: true,
|
|
193
|
-
};
|
|
182
|
+
return toolErrorResult('Error in ps_apply_image', error);
|
|
194
183
|
}
|
|
195
184
|
}
|
|
196
185
|
async function calculations(connection, snippetClient, rawArgs) {
|
|
@@ -228,14 +217,6 @@ async function calculations(connection, snippetClient, rawArgs) {
|
|
|
228
217
|
};
|
|
229
218
|
}
|
|
230
219
|
catch (error) {
|
|
231
|
-
return
|
|
232
|
-
content: [
|
|
233
|
-
{
|
|
234
|
-
type: 'text',
|
|
235
|
-
text: `Error in ps_calculations: ${error instanceof Error ? error.message : String(error)}`,
|
|
236
|
-
},
|
|
237
|
-
],
|
|
238
|
-
isError: true,
|
|
239
|
-
};
|
|
220
|
+
return toolErrorResult('Error in ps_calculations', error);
|
|
240
221
|
}
|
|
241
222
|
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { encode } from 'jpeg-js';
|
|
2
2
|
import { validateArgs } from '../utils/validate.js';
|
|
3
3
|
import { detectActiveDoc } from '../detection/detect-active-doc.js';
|
|
4
|
+
import { ANNOTATED_PREVIEW_JPEG_QUALITY } from '../utils/jpeg-quality.js';
|
|
4
5
|
import { OnnxDetectionClient, } from '../detection/detection-client.js';
|
|
6
|
+
import { toolErrorResult } from '../utils/tool-helpers.js';
|
|
7
|
+
import { computePixelIdentity, docKeyFrom, samePixelIdentity, } from '../perception/pixel-identity.js';
|
|
8
|
+
import { ANNOTATION_RGB, annotationThickness, drawBoxOutline, } from '../perception/overlay.js';
|
|
5
9
|
const detectSchema = {
|
|
6
10
|
type: 'object',
|
|
7
11
|
properties: {
|
|
@@ -46,36 +50,15 @@ const detectSchema = {
|
|
|
46
50
|
},
|
|
47
51
|
},
|
|
48
52
|
};
|
|
49
|
-
function drawBoxes(
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
if (x < 0 || y < 0 || x >= w || y >= h)
|
|
55
|
-
return;
|
|
56
|
-
const i = (y * w + x) * 4;
|
|
57
|
-
data[i] = r;
|
|
58
|
-
data[i + 1] = g;
|
|
59
|
-
data[i + 2] = b;
|
|
60
|
-
data[i + 3] = 255;
|
|
61
|
-
};
|
|
53
|
+
export function drawBoxes(img, boxes) {
|
|
54
|
+
const { width: w, height: h } = img;
|
|
55
|
+
const data = Uint8Array.from(img.data);
|
|
56
|
+
const out = { data, width: w, height: h };
|
|
57
|
+
const thickness = annotationThickness(out);
|
|
62
58
|
for (const { bbox, rgb } of boxes) {
|
|
63
|
-
|
|
64
|
-
const y1 = Math.round(bbox[1]);
|
|
65
|
-
const x2 = Math.round(bbox[2]);
|
|
66
|
-
const y2 = Math.round(bbox[3]);
|
|
67
|
-
for (let x = x1; x <= x2; x++)
|
|
68
|
-
for (let k = 0; k < thickness; k++) {
|
|
69
|
-
setPx(x, y1 + k, rgb[0], rgb[1], rgb[2]);
|
|
70
|
-
setPx(x, y2 - k, rgb[0], rgb[1], rgb[2]);
|
|
71
|
-
}
|
|
72
|
-
for (let y = y1; y <= y2; y++)
|
|
73
|
-
for (let k = 0; k < thickness; k++) {
|
|
74
|
-
setPx(x1 + k, y, rgb[0], rgb[1], rgb[2]);
|
|
75
|
-
setPx(x2 - k, y, rgb[0], rgb[1], rgb[2]);
|
|
76
|
-
}
|
|
59
|
+
drawBoxOutline(out, bbox, rgb, thickness);
|
|
77
60
|
}
|
|
78
|
-
return encode({ data, width: w, height: h },
|
|
61
|
+
return encode({ data, width: w, height: h }, ANNOTATED_PREVIEW_JPEG_QUALITY).data;
|
|
79
62
|
}
|
|
80
63
|
function summarizeObjects(objects) {
|
|
81
64
|
if (!objects || objects.length === 0)
|
|
@@ -85,13 +68,27 @@ function summarizeObjects(objects) {
|
|
|
85
68
|
counts.set(o.label, (counts.get(o.label) ?? 0) + 1);
|
|
86
69
|
return [...counts.entries()].map(([l, n]) => (n > 1 ? `${l}×${n}` : l)).join(', ');
|
|
87
70
|
}
|
|
88
|
-
|
|
71
|
+
let detectCache = null;
|
|
72
|
+
export function __clearDetectCache() {
|
|
73
|
+
detectCache = null;
|
|
74
|
+
}
|
|
75
|
+
async function detect(connection, client, rawArgs, detectDeps) {
|
|
89
76
|
try {
|
|
90
77
|
const args = validateArgs(detectSchema, rawArgs);
|
|
91
78
|
const target = args.target ?? 'both';
|
|
92
79
|
const annotate = args.annotate ?? true;
|
|
93
80
|
const wantFaces = target === 'faces' || target === 'both';
|
|
94
81
|
const wantObjects = target === 'objects' || target === 'both';
|
|
82
|
+
const detectKey = JSON.stringify({
|
|
83
|
+
faces: wantFaces,
|
|
84
|
+
objects: wantObjects,
|
|
85
|
+
maxDimension: args.max_dimension ?? null,
|
|
86
|
+
faceThreshold: args.face_threshold ?? null,
|
|
87
|
+
objectThreshold: args.object_threshold ?? null,
|
|
88
|
+
maxObjects: args.max_objects ?? null,
|
|
89
|
+
});
|
|
90
|
+
let hit = null;
|
|
91
|
+
let pendingIdentity = null;
|
|
95
92
|
const det = await detectActiveDoc(connection, client, {
|
|
96
93
|
faces: wantFaces,
|
|
97
94
|
objects: wantObjects,
|
|
@@ -99,22 +96,41 @@ async function detect(connection, client, rawArgs) {
|
|
|
99
96
|
faceThreshold: args.face_threshold,
|
|
100
97
|
objectThreshold: args.object_threshold,
|
|
101
98
|
maxObjects: args.max_objects,
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
shouldDetect: ({ decoded, context, docWidth, docHeight }) => {
|
|
100
|
+
if (!decoded)
|
|
101
|
+
return true;
|
|
102
|
+
const identity = computePixelIdentity(decoded, docKeyFrom(context, docWidth, docHeight));
|
|
103
|
+
if (detectCache &&
|
|
104
|
+
detectCache.optionsKey === detectKey &&
|
|
105
|
+
samePixelIdentity(detectCache.identity, identity)) {
|
|
106
|
+
hit = detectCache;
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
pendingIdentity = identity;
|
|
110
|
+
return true;
|
|
111
|
+
},
|
|
112
|
+
}, detectDeps);
|
|
113
|
+
const mapped = hit ? hit.result : det.result;
|
|
114
|
+
const rawDetections = hit ? hit.raw : det.raw;
|
|
115
|
+
if (!hit && pendingIdentity) {
|
|
116
|
+
detectCache = {
|
|
117
|
+
identity: pendingIdentity,
|
|
118
|
+
optionsKey: detectKey,
|
|
119
|
+
result: det.result,
|
|
120
|
+
raw: det.raw,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
104
123
|
const content = [];
|
|
105
|
-
if (annotate) {
|
|
124
|
+
if (annotate && det.decoded) {
|
|
106
125
|
try {
|
|
107
126
|
const boxes = [
|
|
108
|
-
...(
|
|
109
|
-
|
|
110
|
-
rgb: [0, 220, 255],
|
|
111
|
-
})),
|
|
112
|
-
...(det.raw.objects ?? []).map((o) => ({
|
|
127
|
+
...(rawDetections.faces ?? []).map((f) => ({ bbox: f.bbox, rgb: ANNOTATION_RGB.face })),
|
|
128
|
+
...(rawDetections.objects ?? []).map((o) => ({
|
|
113
129
|
bbox: o.bbox,
|
|
114
|
-
rgb:
|
|
130
|
+
rgb: ANNOTATION_RGB.object,
|
|
115
131
|
})),
|
|
116
132
|
];
|
|
117
|
-
const annotated = drawBoxes(det.
|
|
133
|
+
const annotated = drawBoxes(det.decoded, boxes);
|
|
118
134
|
content.push({
|
|
119
135
|
type: 'image',
|
|
120
136
|
data: annotated.toString('base64'),
|
|
@@ -150,14 +166,10 @@ async function detect(connection, client, rawArgs) {
|
|
|
150
166
|
};
|
|
151
167
|
}
|
|
152
168
|
catch (error) {
|
|
153
|
-
|
|
154
|
-
return {
|
|
155
|
-
content: [{ type: 'text', text: `Error during detection: ${msg}` }],
|
|
156
|
-
isError: true,
|
|
157
|
-
};
|
|
169
|
+
return toolErrorResult('Error during detection', error);
|
|
158
170
|
}
|
|
159
171
|
}
|
|
160
|
-
export function createDetectionTools(connection, _snippetClient, client = new OnnxDetectionClient()) {
|
|
172
|
+
export function createDetectionTools(connection, _snippetClient, client = new OnnxDetectionClient(), detectDeps) {
|
|
161
173
|
return [
|
|
162
174
|
{
|
|
163
175
|
tool: {
|
|
@@ -203,7 +215,7 @@ export function createDetectionTools(connection, _snippetClient, client = new On
|
|
|
203
215
|
idempotentHint: true,
|
|
204
216
|
},
|
|
205
217
|
},
|
|
206
|
-
handler: async (args) => detect(connection, client, args),
|
|
218
|
+
handler: async (args) => detect(connection, client, args, detectDeps),
|
|
207
219
|
},
|
|
208
220
|
];
|
|
209
221
|
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { runScript } from '../utils/run-script.js';
|
|
2
2
|
import { validateArgs } from '../utils/validate.js';
|
|
3
|
+
import { OPEN_DOCUMENT_TIMEOUT_MS, OPEN_DOCUMENT_REPROBE_TIMEOUT_MS, } from '../utils/operation-timeouts.js';
|
|
4
|
+
import { toolErrorResult, runSnippetTool } from '../utils/tool-helpers.js';
|
|
5
|
+
import { purgeSceneChannels } from '../perception/region-precompute.js';
|
|
6
|
+
import { Logger } from '../utils/logger.js';
|
|
7
|
+
const logger = new Logger('document-tools');
|
|
3
8
|
const createDocumentSchema = {
|
|
4
9
|
type: 'object',
|
|
5
10
|
properties: {
|
|
@@ -68,6 +73,11 @@ const savePsdSchema = {
|
|
|
68
73
|
description: 'Include flattened composite for compatibility with other apps. Default true.',
|
|
69
74
|
default: true,
|
|
70
75
|
},
|
|
76
|
+
keep_scene_channels: {
|
|
77
|
+
type: 'boolean',
|
|
78
|
+
description: "Keep the managed scene:* alpha channels ps_read_scene precomputes. Default false — they are DERIVED masks (rebuilt by the next scene read) and each is a full-resolution channel, so baking them into the .psd bloats it badly (~51MB per channel on a 51MP document). Set true only if you want the saved file to carry the masks. The result reports scene_channels_purged either way, so it's never silent.",
|
|
79
|
+
default: false,
|
|
80
|
+
},
|
|
71
81
|
},
|
|
72
82
|
required: ['output_path'],
|
|
73
83
|
};
|
|
@@ -195,7 +205,7 @@ export function createDocumentTools(connection, snippetClient) {
|
|
|
195
205
|
{
|
|
196
206
|
tool: {
|
|
197
207
|
name: 'ps_open_document',
|
|
198
|
-
description: 'Open a file from disk into Photoshop with all dialogs suppressed (uses last-used Camera Raw settings for raw/HEIC). Returns document name, dimensions, color mode, and whether the source was a raw format. Use this in the pipeline to load Inbox files for editing.',
|
|
208
|
+
description: 'Open a file from disk into Photoshop with all dialogs suppressed (uses last-used Camera Raw settings for raw/HEIC). Returns document name, dimensions, color mode, and whether the source was a raw format. Use this in the pipeline to load Inbox files for editing. If the file is ALREADY open, its existing document is activated rather than opened a second time — `already_open: true` says so, and any edits made to it are still there (Photoshop would otherwise open a duplicate with a fresh Background, which silently strands prior work).',
|
|
199
209
|
inputSchema: openDocumentSchema,
|
|
200
210
|
outputSchema: {
|
|
201
211
|
type: 'object',
|
|
@@ -208,6 +218,7 @@ export function createDocumentTools(connection, snippetClient) {
|
|
|
208
218
|
color_mode: { type: 'string' },
|
|
209
219
|
bits_per_channel: { type: 'number' },
|
|
210
220
|
is_raw_source: { type: 'boolean' },
|
|
221
|
+
already_open: { type: 'boolean' },
|
|
211
222
|
file_path: { type: 'string' },
|
|
212
223
|
context: { type: 'object' },
|
|
213
224
|
},
|
|
@@ -297,50 +308,34 @@ async function exportImage(connection, snippetClient, rawArgs) {
|
|
|
297
308
|
}
|
|
298
309
|
}
|
|
299
310
|
async function createDocument(connection, snippetClient, rawArgs) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
},
|
|
329
|
-
],
|
|
330
|
-
structuredContent: result,
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
catch (error) {
|
|
334
|
-
return {
|
|
335
|
-
content: [
|
|
336
|
-
{
|
|
337
|
-
type: 'text',
|
|
338
|
-
text: `Error creating document: ${error instanceof Error ? error.message : String(error)}`,
|
|
339
|
-
},
|
|
340
|
-
],
|
|
341
|
-
isError: true,
|
|
342
|
-
};
|
|
343
|
-
}
|
|
311
|
+
return runSnippetTool({
|
|
312
|
+
connection,
|
|
313
|
+
snippetClient,
|
|
314
|
+
rawArgs,
|
|
315
|
+
schema: createDocumentSchema,
|
|
316
|
+
snippet: 'newDocument',
|
|
317
|
+
errorPrefix: 'Error creating document',
|
|
318
|
+
params: (args) => {
|
|
319
|
+
const colorMode = args.color_mode;
|
|
320
|
+
const colorModeMap = {
|
|
321
|
+
RGB: 'NewDocumentMode.RGB',
|
|
322
|
+
CMYK: 'NewDocumentMode.CMYK',
|
|
323
|
+
Grayscale: 'NewDocumentMode.GRAYSCALE',
|
|
324
|
+
};
|
|
325
|
+
const mappedMode = colorModeMap[colorMode];
|
|
326
|
+
if (!mappedMode) {
|
|
327
|
+
throw new Error(`colorModeMap missing entry for "${colorMode}" — schema enum and ` +
|
|
328
|
+
`internal mapping have drifted. Update src/tools/document-tools.ts.`);
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
width: args.width,
|
|
332
|
+
height: args.height,
|
|
333
|
+
resolution: args.resolution,
|
|
334
|
+
colorMode: mappedMode,
|
|
335
|
+
};
|
|
336
|
+
},
|
|
337
|
+
successText: (_result, args) => `Document created: ${args.width}x${args.height}px at ${args.resolution}dpi (${args.color_mode})`,
|
|
338
|
+
});
|
|
344
339
|
}
|
|
345
340
|
async function closeDocument(connection, snippetClient, rawArgs) {
|
|
346
341
|
try {
|
|
@@ -364,24 +359,30 @@ async function closeDocument(connection, snippetClient, rawArgs) {
|
|
|
364
359
|
};
|
|
365
360
|
}
|
|
366
361
|
catch (error) {
|
|
367
|
-
return
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
};
|
|
362
|
+
return toolErrorResult('Error closing document', error);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
function isScriptTimeoutError(message) {
|
|
366
|
+
return /Script execution timeout|AppleEvent timed out|(?:^|[\s(])-1712(?:[\s)]|$)/i.test(message);
|
|
367
|
+
}
|
|
368
|
+
async function reprobeOpenDocument(connection, snippetClient, filePath) {
|
|
369
|
+
try {
|
|
370
|
+
const probeScript = await snippetClient.build('probeOpenDocument', { filePath });
|
|
371
|
+
const result = (await runScript(connection, probeScript, OPEN_DOCUMENT_REPROBE_TIMEOUT_MS));
|
|
372
|
+
return result.success ? result : null;
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
return null;
|
|
376
376
|
}
|
|
377
377
|
}
|
|
378
378
|
async function openDocumentPipeline(connection, snippetClient, rawArgs) {
|
|
379
|
+
let filePath;
|
|
379
380
|
try {
|
|
380
381
|
const args = validateArgs(openDocumentSchema, rawArgs);
|
|
381
|
-
|
|
382
|
+
filePath = args.file_path;
|
|
382
383
|
const suppressDialogs = args.suppress_dialogs;
|
|
383
384
|
const script = await snippetClient.build('openDocumentPipeline', { filePath, suppressDialogs });
|
|
384
|
-
const result = await runScript(connection, script);
|
|
385
|
+
const result = await runScript(connection, script, OPEN_DOCUMENT_TIMEOUT_MS);
|
|
385
386
|
return {
|
|
386
387
|
content: [
|
|
387
388
|
{
|
|
@@ -393,45 +394,54 @@ async function openDocumentPipeline(connection, snippetClient, rawArgs) {
|
|
|
393
394
|
};
|
|
394
395
|
}
|
|
395
396
|
catch (error) {
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
397
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
398
|
+
if (filePath !== undefined && isScriptTimeoutError(message)) {
|
|
399
|
+
const reprobed = await reprobeOpenDocument(connection, snippetClient, filePath);
|
|
400
|
+
if (reprobed) {
|
|
401
|
+
return {
|
|
402
|
+
content: [
|
|
403
|
+
{
|
|
404
|
+
type: 'text',
|
|
405
|
+
text: `Document opened (the open exceeded the ${OPEN_DOCUMENT_TIMEOUT_MS}ms budget and was ` +
|
|
406
|
+
`reported as a timeout, but a post-timeout check confirmed it actually completed):\n` +
|
|
407
|
+
JSON.stringify(reprobed, null, 2),
|
|
408
|
+
},
|
|
409
|
+
],
|
|
410
|
+
structuredContent: reprobed,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return toolErrorResult('Error opening document', error);
|
|
405
415
|
}
|
|
406
416
|
}
|
|
407
417
|
async function savePsdPipeline(connection, snippetClient, rawArgs) {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
type: 'text',
|
|
418
|
-
text: `PSD saved:\n${JSON.stringify(result, null, 2)}`,
|
|
419
|
-
},
|
|
420
|
-
],
|
|
421
|
-
structuredContent: result,
|
|
422
|
-
};
|
|
418
|
+
let purged = 0;
|
|
419
|
+
if (rawArgs.keep_scene_channels !== true) {
|
|
420
|
+
try {
|
|
421
|
+
purged = await purgeSceneChannels(connection);
|
|
422
|
+
}
|
|
423
|
+
catch (err) {
|
|
424
|
+
logger.debug(`save_psd: scene-channel purge failed, saving anyway — ` +
|
|
425
|
+
`${err instanceof Error ? err.message : String(err)}`);
|
|
426
|
+
}
|
|
423
427
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
428
|
+
const res = await runSnippetTool({
|
|
429
|
+
connection,
|
|
430
|
+
snippetClient,
|
|
431
|
+
rawArgs,
|
|
432
|
+
schema: savePsdSchema,
|
|
433
|
+
snippet: 'savePsdAsCopy',
|
|
434
|
+
errorPrefix: 'Error saving PSD',
|
|
435
|
+
params: (args) => ({
|
|
436
|
+
outputPath: args.output_path,
|
|
437
|
+
maximizeCompat: args.maximize_compatibility,
|
|
438
|
+
}),
|
|
439
|
+
successText: (result) => `PSD saved:\n${JSON.stringify(result, null, 2)}`,
|
|
440
|
+
});
|
|
441
|
+
if (!res.isError && typeof res.structuredContent === 'object' && res.structuredContent !== null) {
|
|
442
|
+
res.structuredContent.scene_channels_purged = purged;
|
|
434
443
|
}
|
|
444
|
+
return res;
|
|
435
445
|
}
|
|
436
446
|
async function exportJpegPipeline(connection, snippetClient, rawArgs) {
|
|
437
447
|
try {
|
|
@@ -459,48 +469,28 @@ async function exportJpegPipeline(connection, snippetClient, rawArgs) {
|
|
|
459
469
|
};
|
|
460
470
|
}
|
|
461
471
|
catch (error) {
|
|
462
|
-
return
|
|
463
|
-
content: [
|
|
464
|
-
{
|
|
465
|
-
type: 'text',
|
|
466
|
-
text: `Error exporting JPEG: ${error instanceof Error ? error.message : String(error)}`,
|
|
467
|
-
},
|
|
468
|
-
],
|
|
469
|
-
isError: true,
|
|
470
|
-
};
|
|
472
|
+
return toolErrorResult('Error exporting JPEG', error);
|
|
471
473
|
}
|
|
472
474
|
}
|
|
473
475
|
async function exportPngPipeline(connection, snippetClient, rawArgs) {
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
}
|
|
495
|
-
catch (error) {
|
|
496
|
-
return {
|
|
497
|
-
content: [
|
|
498
|
-
{
|
|
499
|
-
type: 'text',
|
|
500
|
-
text: `Error exporting PNG: ${error instanceof Error ? error.message : String(error)}`,
|
|
501
|
-
},
|
|
502
|
-
],
|
|
503
|
-
isError: true,
|
|
504
|
-
};
|
|
505
|
-
}
|
|
476
|
+
return runSnippetTool({
|
|
477
|
+
connection,
|
|
478
|
+
snippetClient,
|
|
479
|
+
rawArgs,
|
|
480
|
+
schema: exportPngSchema,
|
|
481
|
+
snippet: 'exportPngPipeline',
|
|
482
|
+
errorPrefix: 'Error exporting PNG',
|
|
483
|
+
params: (args) => {
|
|
484
|
+
const longEdgePx = args.long_edge_px;
|
|
485
|
+
const params = {
|
|
486
|
+
outputPath: args.output_path,
|
|
487
|
+
transparentBg: args.transparent_background,
|
|
488
|
+
compression: args.compression,
|
|
489
|
+
};
|
|
490
|
+
if (longEdgePx !== undefined)
|
|
491
|
+
params.longEdgePx = longEdgePx;
|
|
492
|
+
return params;
|
|
493
|
+
},
|
|
494
|
+
successText: (result) => `PNG exported:\n${JSON.stringify(result, null, 2)}`,
|
|
495
|
+
});
|
|
506
496
|
}
|