editmamei 0.19.0 → 0.22.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.
- package/NOTICES.md +17 -1
- package/README.md +9 -8
- package/dist/api/extendscript/_helpers.js +1 -1
- 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/cli/config.js +3 -3
- package/dist/cli/help.js +4 -0
- package/dist/cli/install.js +2 -2
- package/dist/cli/report.js +11 -0
- package/dist/cli/router.js +20 -0
- package/dist/core/server.js +8 -8
- package/dist/core/settings.js +2 -2
- package/dist/core/tool-groups.js +92 -82
- package/dist/core/tool-tiers.js +91 -81
- package/dist/detection/landmark-detection-client.js +8 -0
- package/dist/detection/mesh-eye-correction.js +126 -0
- package/dist/detection/sam-segmenter.js +83 -0
- package/dist/diagnostics/collect.js +169 -0
- package/dist/index.js +2 -0
- package/dist/license/entitlement.js +52 -0
- package/dist/modules/ce/index.js +6 -4
- package/dist/perception/grounding-anchors.js +37 -0
- package/dist/perception/grounding-corners.js +93 -0
- package/dist/perception/grounding-edge-trace.js +90 -0
- package/dist/perception/grounding-extrema.js +41 -0
- package/dist/perception/grounding-gate.js +154 -0
- package/dist/perception/grounding-geometry.js +132 -0
- package/dist/perception/grounding-grid.js +34 -0
- package/dist/perception/grounding-landmarks.js +40 -0
- package/dist/perception/grounding-locate.js +341 -0
- package/dist/perception/grounding-resolver.js +202 -0
- package/dist/perception/grounding-review-crop.js +85 -0
- package/dist/perception/grounding-warp.js +182 -0
- package/dist/perception/sky-mask-transfer.js +50 -18
- package/dist/skills/editmamei-skill.zip +0 -0
- package/dist/tools/adjustment-tools.js +3 -3
- package/dist/tools/brush-tools.js +111 -23
- package/dist/tools/channel-compose-tools.js +241 -0
- package/dist/tools/detection-tools.js +1 -1
- package/dist/tools/diagnostics-tools.js +66 -0
- package/dist/tools/document-tools.js +6 -6
- package/dist/tools/filter-tools.js +476 -19
- package/dist/tools/group-tools.js +10 -10
- package/dist/tools/guide-tools.js +4 -4
- package/dist/tools/history-tools.js +4 -4
- package/dist/tools/image-placement-tools.js +1 -1
- package/dist/tools/image-tools.js +62 -19
- package/dist/tools/inspect-tools.js +2 -2
- package/dist/tools/layer-ordering-tools.js +1 -1
- package/dist/tools/layer-properties-tools.js +42 -23
- package/dist/tools/layer-tools.js +10 -10
- package/dist/tools/layer-transform-tools.js +80 -41
- package/dist/tools/overview-tools.js +42 -42
- package/dist/tools/path-tools.js +53 -10
- package/dist/tools/portrait-tools.js +3 -3
- package/dist/tools/preview-tools.js +7 -82
- package/dist/tools/retouch-tools.js +93 -25
- package/dist/tools/scene-tools.js +13 -13
- package/dist/tools/selection-tools.js +319 -40
- package/dist/tools/shape-tools.js +216 -0
- package/dist/tools/text-tools.js +1 -1
- package/dist/tools/transform-canvas-tools.js +2 -2
- package/dist/tools/vector-mask-tools.js +23 -8
- package/dist/utils/log-buffer.js +41 -0
- package/dist/utils/logger.js +12 -1
- package/dist/utils/session-log-reader.js +27 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/dist/preview/coordinate-grid.js +0 -250
- package/dist/tools/object-tools.js +0 -193
- package/dist/tools/text-on-object-tools.js +0 -225
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { runScript } from '../utils/run-script.js';
|
|
2
|
+
import { validateArgs } from '../utils/validate.js';
|
|
3
|
+
const BLEND_MODES = [
|
|
4
|
+
'normal',
|
|
5
|
+
'multiply',
|
|
6
|
+
'screen',
|
|
7
|
+
'overlay',
|
|
8
|
+
'darken',
|
|
9
|
+
'lighten',
|
|
10
|
+
'soft_light',
|
|
11
|
+
'hard_light',
|
|
12
|
+
'difference',
|
|
13
|
+
'exclusion',
|
|
14
|
+
'subtract',
|
|
15
|
+
'add',
|
|
16
|
+
];
|
|
17
|
+
const blendFragment = {
|
|
18
|
+
type: 'string',
|
|
19
|
+
enum: [...BLEND_MODES],
|
|
20
|
+
description: 'Calculation blend mode: how the source combines with the target. multiply (darken/texture), screen (lighten), overlay/soft_light/hard_light (contrast), difference/exclusion/subtract/add (channel math for masks).',
|
|
21
|
+
};
|
|
22
|
+
const applyImageSchema = {
|
|
23
|
+
type: 'object',
|
|
24
|
+
properties: {
|
|
25
|
+
source_layer: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
description: "Which layer to pull the source from: 'merged' (the flattened composite, default) or an exact layer name.",
|
|
28
|
+
default: 'merged',
|
|
29
|
+
},
|
|
30
|
+
source_channel: {
|
|
31
|
+
type: 'string',
|
|
32
|
+
enum: ['rgb', 'red', 'green', 'blue', 'alpha'],
|
|
33
|
+
description: "Source channel: 'rgb' (the composite, default), a single 'red'/'green'/'blue' channel, or 'alpha' (then set source_alpha_name).",
|
|
34
|
+
default: 'rgb',
|
|
35
|
+
},
|
|
36
|
+
source_alpha_name: {
|
|
37
|
+
type: 'string',
|
|
38
|
+
description: "Required when source_channel='alpha': the name of the alpha channel to read.",
|
|
39
|
+
},
|
|
40
|
+
blend: blendFragment,
|
|
41
|
+
opacity: {
|
|
42
|
+
type: 'integer',
|
|
43
|
+
description: 'Blend opacity 0-100. Default 100 (full strength).',
|
|
44
|
+
minimum: 0,
|
|
45
|
+
maximum: 100,
|
|
46
|
+
default: 100,
|
|
47
|
+
},
|
|
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
|
+
},
|
|
53
|
+
},
|
|
54
|
+
required: ['blend'],
|
|
55
|
+
};
|
|
56
|
+
const calculationsSchema = {
|
|
57
|
+
type: 'object',
|
|
58
|
+
properties: {
|
|
59
|
+
source1_layer: {
|
|
60
|
+
type: 'string',
|
|
61
|
+
description: "Source 1 layer: 'merged' (default) or an exact layer name.",
|
|
62
|
+
default: 'merged',
|
|
63
|
+
},
|
|
64
|
+
source1_channel: {
|
|
65
|
+
type: 'string',
|
|
66
|
+
enum: ['red', 'green', 'blue', 'alpha'],
|
|
67
|
+
description: "Source 1 channel: a single 'red'/'green'/'blue' channel, or 'alpha' (+ name).",
|
|
68
|
+
default: 'red',
|
|
69
|
+
},
|
|
70
|
+
source1_alpha_name: {
|
|
71
|
+
type: 'string',
|
|
72
|
+
description: "Required when source1_channel='alpha'.",
|
|
73
|
+
},
|
|
74
|
+
source2_layer: {
|
|
75
|
+
type: 'string',
|
|
76
|
+
description: "Source 2 layer: 'merged' (default) or an exact layer name.",
|
|
77
|
+
default: 'merged',
|
|
78
|
+
},
|
|
79
|
+
source2_channel: {
|
|
80
|
+
type: 'string',
|
|
81
|
+
enum: ['red', 'green', 'blue', 'alpha'],
|
|
82
|
+
description: "Source 2 channel: a single 'red'/'green'/'blue' channel, or 'alpha' (+ name).",
|
|
83
|
+
default: 'red',
|
|
84
|
+
},
|
|
85
|
+
source2_alpha_name: {
|
|
86
|
+
type: 'string',
|
|
87
|
+
description: "Required when source2_channel='alpha'.",
|
|
88
|
+
},
|
|
89
|
+
blend: blendFragment,
|
|
90
|
+
opacity: {
|
|
91
|
+
type: 'integer',
|
|
92
|
+
description: 'Blend opacity 0-100. Default 100.',
|
|
93
|
+
minimum: 0,
|
|
94
|
+
maximum: 100,
|
|
95
|
+
default: 100,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
required: ['blend'],
|
|
99
|
+
};
|
|
100
|
+
export function createChannelComposeTools(connection, snippetClient) {
|
|
101
|
+
return [
|
|
102
|
+
{
|
|
103
|
+
tool: {
|
|
104
|
+
name: 'ps_apply_image',
|
|
105
|
+
description: 'Composite a source layer + channel onto the ACTIVE layer using a calculation blend mode (Image ▸ Apply Image). **Reach for this** for luminosity blends, frequency-separation re-blends, texture overlays, or pulling one channel into another. Source = a layer (merged or named) and a channel (rgb/red/green/blue/alpha). Bakes into pixels, so by default it runs on a DUPLICATE of the active layer (set apply_to_active_layer=true to bake in place). The active layer must be a normal pixel layer. (AM-only; verified live on PS 27.2.0.)',
|
|
106
|
+
inputSchema: applyImageSchema,
|
|
107
|
+
outputSchema: {
|
|
108
|
+
type: 'object',
|
|
109
|
+
properties: {
|
|
110
|
+
applied: { type: 'boolean' },
|
|
111
|
+
source_channel: { type: 'string' },
|
|
112
|
+
source_layer: { type: 'string' },
|
|
113
|
+
blend: { type: 'string' },
|
|
114
|
+
opacity: { type: 'number' },
|
|
115
|
+
target_was_copy: { type: 'boolean' },
|
|
116
|
+
target_layer_name: { type: 'string' },
|
|
117
|
+
context: { type: 'object' },
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
annotations: {
|
|
121
|
+
title: 'Apply Image',
|
|
122
|
+
destructiveHint: true,
|
|
123
|
+
idempotentHint: false,
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
handler: async (args) => applyImage(connection, snippetClient, args),
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
tool: {
|
|
130
|
+
name: 'ps_calculations',
|
|
131
|
+
description: 'Blend TWO sources (each a layer + single channel) into a NEW alpha channel via a calculation blend mode (Image ▸ Calculations). **Reach for this** to build advanced selections/masks from channel math — e.g. difference of two channels to isolate edges, or multiply two channels for a luminosity mask. Non-destructive: adds a new channel (delete it to revert); the result is loadable as a selection via ps_selection_channel op=load. (AM-only; verified live on PS 27.2.0.)',
|
|
132
|
+
inputSchema: calculationsSchema,
|
|
133
|
+
outputSchema: {
|
|
134
|
+
type: 'object',
|
|
135
|
+
properties: {
|
|
136
|
+
calculated: { type: 'boolean' },
|
|
137
|
+
new_channel_name: { type: 'string' },
|
|
138
|
+
channel_count: { type: 'number' },
|
|
139
|
+
blend: { type: 'string' },
|
|
140
|
+
opacity: { type: 'number' },
|
|
141
|
+
context: { type: 'object' },
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
annotations: {
|
|
145
|
+
title: 'Calculations',
|
|
146
|
+
destructiveHint: false,
|
|
147
|
+
idempotentHint: false,
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
handler: async (args) => calculations(connection, snippetClient, args),
|
|
151
|
+
},
|
|
152
|
+
];
|
|
153
|
+
}
|
|
154
|
+
async function applyImage(connection, snippetClient, rawArgs) {
|
|
155
|
+
try {
|
|
156
|
+
const args = validateArgs(applyImageSchema, rawArgs);
|
|
157
|
+
const params = {
|
|
158
|
+
sourceLayer: args.source_layer ?? 'merged',
|
|
159
|
+
sourceChannel: args.source_channel ?? 'rgb',
|
|
160
|
+
blend: args.blend,
|
|
161
|
+
opacity: args.opacity ?? 100,
|
|
162
|
+
applyToActiveLayer: args.apply_to_active_layer ?? false,
|
|
163
|
+
};
|
|
164
|
+
if (args.source_alpha_name !== undefined)
|
|
165
|
+
params.sourceAlphaName = args.source_alpha_name;
|
|
166
|
+
if (params.sourceChannel === 'alpha' && !params.sourceAlphaName) {
|
|
167
|
+
throw new Error("source_channel='alpha' requires source_alpha_name");
|
|
168
|
+
}
|
|
169
|
+
const script = await snippetClient.build('applyImage', params);
|
|
170
|
+
const result = (await runScript(connection, script));
|
|
171
|
+
result.source_channel =
|
|
172
|
+
params.sourceChannel === 'alpha' ? params.sourceAlphaName : params.sourceChannel;
|
|
173
|
+
result.blend = params.blend;
|
|
174
|
+
return {
|
|
175
|
+
content: [
|
|
176
|
+
{
|
|
177
|
+
type: 'text',
|
|
178
|
+
text: `Apply Image: ${String(result.source_channel ?? '')} of "${String(result.source_layer ?? '')}" (${String(result.blend ?? '')} @ ${String(result.opacity ?? '')}%) onto "${String(result.target_layer_name ?? '')}".`,
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
structuredContent: result,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
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
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
async function calculations(connection, snippetClient, rawArgs) {
|
|
197
|
+
try {
|
|
198
|
+
const args = validateArgs(calculationsSchema, rawArgs);
|
|
199
|
+
const params = {
|
|
200
|
+
source1Layer: args.source1_layer ?? 'merged',
|
|
201
|
+
source1Channel: args.source1_channel ?? 'red',
|
|
202
|
+
source2Layer: args.source2_layer ?? 'merged',
|
|
203
|
+
source2Channel: args.source2_channel ?? 'red',
|
|
204
|
+
blend: args.blend,
|
|
205
|
+
opacity: args.opacity ?? 100,
|
|
206
|
+
};
|
|
207
|
+
if (args.source1_alpha_name !== undefined)
|
|
208
|
+
params.source1AlphaName = args.source1_alpha_name;
|
|
209
|
+
if (args.source2_alpha_name !== undefined)
|
|
210
|
+
params.source2AlphaName = args.source2_alpha_name;
|
|
211
|
+
if (params.source1Channel === 'alpha' && !params.source1AlphaName) {
|
|
212
|
+
throw new Error("source1_channel='alpha' requires source1_alpha_name");
|
|
213
|
+
}
|
|
214
|
+
if (params.source2Channel === 'alpha' && !params.source2AlphaName) {
|
|
215
|
+
throw new Error("source2_channel='alpha' requires source2_alpha_name");
|
|
216
|
+
}
|
|
217
|
+
const script = await snippetClient.build('calculations', params);
|
|
218
|
+
const result = (await runScript(connection, script));
|
|
219
|
+
result.blend = params.blend;
|
|
220
|
+
return {
|
|
221
|
+
content: [
|
|
222
|
+
{
|
|
223
|
+
type: 'text',
|
|
224
|
+
text: `Calculations → new channel "${String(result.new_channel_name ?? '')}" (${String(result.blend ?? '')} @ ${String(result.opacity ?? '')}%).`,
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
structuredContent: result,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
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
|
+
};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
@@ -161,7 +161,7 @@ export function createDetectionTools(connection, _snippetClient, client = new On
|
|
|
161
161
|
return [
|
|
162
162
|
{
|
|
163
163
|
tool: {
|
|
164
|
-
name: '
|
|
164
|
+
name: 'ps_detect',
|
|
165
165
|
description: 'Locate faces and/or COCO-80 objects (person, dog, car, chair, sofa, …) in the active document using LOCAL on-device computer vision — the image is never sent anywhere. Returns labeled bounding boxes in DOCUMENT-pixel space plus an annotated preview for visual confirmation. Use this to gain semantic scene awareness ("there are 2 people and a dog") and real coordinates before any spatially-targeted edit — far more reliable than estimating positions from a preview. `target` selects faces / objects / both. Read-only: renders a throwaway duplicate, never modifies the working document. Boxes are [x1, y1, x2, y2]. Validate surprising results against the annotated image before acting on them.',
|
|
166
166
|
inputSchema: detectSchema,
|
|
167
167
|
outputSchema: {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { collectDiagnostics, writeDiagnosticBundle, ISSUES_URL } from '../diagnostics/collect.js';
|
|
2
|
+
const reportSchema = {
|
|
3
|
+
type: 'object',
|
|
4
|
+
properties: {
|
|
5
|
+
note: {
|
|
6
|
+
type: 'string',
|
|
7
|
+
description: 'Optional short description of the problem (what went wrong, what you were doing). Embedded verbatim after sanitization. No file contents or paths needed.',
|
|
8
|
+
},
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
export function createDiagnosticsTools(_connection, _snippetClient, deps = {}) {
|
|
12
|
+
const collect = deps.collect ?? collectDiagnostics;
|
|
13
|
+
const writeBundle = deps.write ?? writeDiagnosticBundle;
|
|
14
|
+
return [
|
|
15
|
+
{
|
|
16
|
+
tool: {
|
|
17
|
+
name: 'ps_report_problem',
|
|
18
|
+
description: "Collect an ANONYMIZED diagnostic bundle and write it to the user's Downloads folder so they can attach it to a bug report — use when Editmamei misbehaves (won't connect, a tool keeps failing, unexpected results). The bundle holds recent server logs, system info (Editmamei/OS/Photoshop versions), and a content-free summary of recent tool calls (name, success, duration, error class). It contains NO image content, NO tool arguments, and file paths reduced to basenames. Does not touch Photoshop; writes one JSON file. After calling, tell the user the file path and that they can attach it to a new issue at " +
|
|
19
|
+
ISSUES_URL +
|
|
20
|
+
'.',
|
|
21
|
+
inputSchema: reportSchema,
|
|
22
|
+
outputSchema: {
|
|
23
|
+
type: 'object',
|
|
24
|
+
properties: {
|
|
25
|
+
path: { type: 'string' },
|
|
26
|
+
bytes: { type: 'number' },
|
|
27
|
+
server_log_lines: { type: 'number' },
|
|
28
|
+
recent_session_count: { type: 'number' },
|
|
29
|
+
desktop_log_included: { type: 'boolean' },
|
|
30
|
+
issues_url: { type: 'string' },
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
annotations: {
|
|
34
|
+
title: 'Report a problem',
|
|
35
|
+
readOnlyHint: false,
|
|
36
|
+
destructiveHint: false,
|
|
37
|
+
idempotentHint: false,
|
|
38
|
+
openWorldHint: true,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
handler: async (args) => {
|
|
42
|
+
const note = typeof args.note === 'string' ? args.note : undefined;
|
|
43
|
+
const bundle = await collect({ note });
|
|
44
|
+
const { path, bytes } = await writeBundle(bundle);
|
|
45
|
+
return {
|
|
46
|
+
content: [
|
|
47
|
+
{
|
|
48
|
+
type: 'text',
|
|
49
|
+
text: `Wrote an anonymized diagnostic bundle to:\n ${path}\n\n` +
|
|
50
|
+
`It contains recent logs + system info — no images, no full file paths, no tool arguments. ` +
|
|
51
|
+
`Attach this file to a new issue at ${ISSUES_URL} so the maintainers can debug.`,
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
structuredContent: {
|
|
55
|
+
path,
|
|
56
|
+
bytes,
|
|
57
|
+
server_log_lines: bundle.server_log.length,
|
|
58
|
+
recent_session_count: bundle.recent_sessions.length,
|
|
59
|
+
desktop_log_included: bundle.desktop_log.length > 0,
|
|
60
|
+
issues_url: ISSUES_URL,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
}
|
|
@@ -152,8 +152,8 @@ export function createDocumentTools(connection, snippetClient) {
|
|
|
152
152
|
return [
|
|
153
153
|
{
|
|
154
154
|
tool: {
|
|
155
|
-
name: '
|
|
156
|
-
description: 'Create a new empty Photoshop document with given dimensions, resolution, and color mode. Returns the new document id and name plus active context. Use this when starting from scratch; prefer
|
|
155
|
+
name: 'ps_create_document',
|
|
156
|
+
description: 'Create a new empty Photoshop document with given dimensions, resolution, and color mode. Returns the new document id and name plus active context. Use this when starting from scratch; prefer ps_open_document to load an existing file.',
|
|
157
157
|
inputSchema: createDocumentSchema,
|
|
158
158
|
outputSchema: {
|
|
159
159
|
type: 'object',
|
|
@@ -172,7 +172,7 @@ export function createDocumentTools(connection, snippetClient) {
|
|
|
172
172
|
},
|
|
173
173
|
{
|
|
174
174
|
tool: {
|
|
175
|
-
name: '
|
|
175
|
+
name: 'ps_close_document',
|
|
176
176
|
description: 'Close the active Photoshop document. Destructive if save=false and the document has unsaved changes. Returns the closed document name plus a fresh context block (which document, if any, is active afterwards).',
|
|
177
177
|
inputSchema: closeDocumentSchema,
|
|
178
178
|
outputSchema: {
|
|
@@ -194,7 +194,7 @@ export function createDocumentTools(connection, snippetClient) {
|
|
|
194
194
|
},
|
|
195
195
|
{
|
|
196
196
|
tool: {
|
|
197
|
-
name: '
|
|
197
|
+
name: 'ps_open_document',
|
|
198
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.',
|
|
199
199
|
inputSchema: openDocumentSchema,
|
|
200
200
|
outputSchema: {
|
|
@@ -223,7 +223,7 @@ export function createDocumentTools(connection, snippetClient) {
|
|
|
223
223
|
},
|
|
224
224
|
{
|
|
225
225
|
tool: {
|
|
226
|
-
name: '
|
|
226
|
+
name: 'ps_save_psd',
|
|
227
227
|
description: 'Save the active document as a layered PSD to the given path. Saves as a copy, so the working document is unmodified and unrenamed. Use this in the pipeline to land an editable PSD into the Edit folder.',
|
|
228
228
|
inputSchema: savePsdSchema,
|
|
229
229
|
outputSchema: {
|
|
@@ -248,7 +248,7 @@ export function createDocumentTools(connection, snippetClient) {
|
|
|
248
248
|
},
|
|
249
249
|
{
|
|
250
250
|
tool: {
|
|
251
|
-
name: '
|
|
251
|
+
name: 'ps_export',
|
|
252
252
|
description: 'Export the active document to a flattened image file — JPEG or PNG, chosen via `format`. Operates on a duplicate, so the working document is unchanged. Optionally downscales so the long edge equals long_edge_px. JPEG: quality 0-100 (Save-As scale), optional sRGB convert + profile embed. PNG: transparent background (alpha preserved) or flattened onto white, compression 0-9.',
|
|
253
253
|
inputSchema: exportInputSchema,
|
|
254
254
|
outputSchema: {
|