editmamei 0.20.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 +8 -7
- 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/install.js +2 -2
- package/dist/core/server.js +7 -7
- package/dist/core/settings.js +2 -2
- package/dist/core/tool-groups.js +91 -82
- package/dist/core/tool-tiers.js +91 -82
- 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/index.js +2 -0
- package/dist/license/entitlement.js +52 -0
- package/dist/modules/ce/index.js +4 -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 +1 -1
- 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/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
|
@@ -77,7 +77,7 @@ export function createGroupTools(connection, snippetClient) {
|
|
|
77
77
|
return [
|
|
78
78
|
{
|
|
79
79
|
tool: {
|
|
80
|
-
name: '
|
|
80
|
+
name: 'ps_create_group',
|
|
81
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.',
|
|
82
82
|
inputSchema: createGroupSchema,
|
|
83
83
|
outputSchema: {
|
|
@@ -99,7 +99,7 @@ export function createGroupTools(connection, snippetClient) {
|
|
|
99
99
|
},
|
|
100
100
|
{
|
|
101
101
|
tool: {
|
|
102
|
-
name: '
|
|
102
|
+
name: 'ps_move_layer_to_group',
|
|
103
103
|
description: 'Move a named layer into a named group. The layer is placed at the top of the group stack. Both layer_name and group_name are looked up recursively. Throws if either is not found, or if the layer IS the group itself.',
|
|
104
104
|
inputSchema: moveLayerToGroupSchema,
|
|
105
105
|
outputSchema: {
|
|
@@ -120,7 +120,7 @@ export function createGroupTools(connection, snippetClient) {
|
|
|
120
120
|
},
|
|
121
121
|
{
|
|
122
122
|
tool: {
|
|
123
|
-
name: '
|
|
123
|
+
name: 'ps_set_group_blend_mode',
|
|
124
124
|
description: "Set a group's blend mode. The default for new groups is PASSTHROUGH (adjustments inside affect layers below the group). Change to NORMAL when treating the group as a single composite — required for masking a stack of adjustments with one mask.",
|
|
125
125
|
inputSchema: setGroupBlendModeSchema,
|
|
126
126
|
outputSchema: {
|
|
@@ -141,7 +141,7 @@ export function createGroupTools(connection, snippetClient) {
|
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
tool: {
|
|
144
|
-
name: '
|
|
144
|
+
name: 'ps_ungroup',
|
|
145
145
|
description: 'DESTRUCTIVE structural change: Dissolve a group, leaving its contents at the parent level in their existing stack order. The group itself is removed. Requires confirm:true. Recoverable only via Edit > Undo.',
|
|
146
146
|
inputSchema: ungroupSchema,
|
|
147
147
|
outputSchema: {
|
|
@@ -164,8 +164,8 @@ export function createGroupTools(connection, snippetClient) {
|
|
|
164
164
|
},
|
|
165
165
|
{
|
|
166
166
|
tool: {
|
|
167
|
-
name: '
|
|
168
|
-
description: 'Clip the active layer to the layer directly below it (uses that layer as the alpha source). Non-destructive — the upper layer is unchanged; PS just paints it only where the layer below has pixels. Common for masking adjustment-layer effect to a single underlying layer, or constraining a texture/photo to a shape. Equivalent to the PS menu Layer > Create Clipping Mask (Ctrl+Alt+G). Reverse it with the PS menu Layer > Release Clipping Mask, or
|
|
167
|
+
name: 'ps_create_clipping_mask',
|
|
168
|
+
description: 'Clip the active layer to the layer directly below it (uses that layer as the alpha source). Non-destructive — the upper layer is unchanged; PS just paints it only where the layer below has pixels. Common for masking adjustment-layer effect to a single underlying layer, or constraining a texture/photo to a shape. Equivalent to the PS menu Layer > Create Clipping Mask (Ctrl+Alt+G). Reverse it with the PS menu Layer > Release Clipping Mask, or ps_undo. This is the standalone primitive — the add_adjustment_layer tool already accepts clip_to_below for the adjustment-layer-specific case.',
|
|
169
169
|
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
170
170
|
outputSchema: {
|
|
171
171
|
type: 'object',
|
|
@@ -184,8 +184,8 @@ export function createGroupTools(connection, snippetClient) {
|
|
|
184
184
|
},
|
|
185
185
|
{
|
|
186
186
|
tool: {
|
|
187
|
-
name: '
|
|
188
|
-
description: "Release the active layer's clipping mask (the inverse of
|
|
187
|
+
name: 'ps_release_clipping_mask',
|
|
188
|
+
description: "Release the active layer's clipping mask (the inverse of ps_create_clipping_mask). The layer returns to compositing against the whole canvas instead of just the layer below. Equivalent to PS menu Layer > Release Clipping Mask. Idempotent on a non-clipped layer (PS silently no-ops).",
|
|
189
189
|
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
190
190
|
outputSchema: {
|
|
191
191
|
type: 'object',
|
|
@@ -204,8 +204,8 @@ export function createGroupTools(connection, snippetClient) {
|
|
|
204
204
|
},
|
|
205
205
|
{
|
|
206
206
|
tool: {
|
|
207
|
-
name: '
|
|
208
|
-
description: 'DESTRUCTIVE: Delete a group AND ALL its contents (including nested groups and their layers). Requires confirm:true because the destructive scope is much larger than a single-layer delete. Recoverable only via Edit > Undo. To dissolve a group while keeping its contents, use
|
|
207
|
+
name: 'ps_delete_group',
|
|
208
|
+
description: 'DESTRUCTIVE: Delete a group AND ALL its contents (including nested groups and their layers). Requires confirm:true because the destructive scope is much larger than a single-layer delete. Recoverable only via Edit > Undo. To dissolve a group while keeping its contents, use ps_ungroup instead.',
|
|
209
209
|
inputSchema: deleteGroupSchema,
|
|
210
210
|
outputSchema: {
|
|
211
211
|
type: 'object',
|
|
@@ -10,7 +10,7 @@ const addGuideSchema = {
|
|
|
10
10
|
},
|
|
11
11
|
position: {
|
|
12
12
|
type: 'number',
|
|
13
|
-
description: 'Guide position in document pixels (the x for vertical, the y for horizontal).',
|
|
13
|
+
description: 'Guide position in document pixels (the x for a vertical guide, the y for a horizontal guide).',
|
|
14
14
|
minimum: 0,
|
|
15
15
|
},
|
|
16
16
|
},
|
|
@@ -51,8 +51,8 @@ export function createGuideTools(connection, snippetClient) {
|
|
|
51
51
|
return [
|
|
52
52
|
{
|
|
53
53
|
tool: {
|
|
54
|
-
name: '
|
|
55
|
-
description: 'Manage document guides —
|
|
54
|
+
name: 'ps_guides',
|
|
55
|
+
description: 'Manage document guides — non-printing layout aids for a HUMAN working in Photoshop. Guides are editor-only chrome: they do NOT render into ps_get_preview (the exported/flattened image), so they are a user authoring aid, not a model-perception tool — place them when a person will open the PSD and wants alignment/composition marks. Choose with `op`. `add` places one guide (orientation horizontal|vertical) at a `position` in px. `layout` creates an evenly-spaced grid (columns and/or rows — great for rule-of-thirds at 3×3). `clear` removes all guides.',
|
|
56
56
|
inputSchema: GUIDE_INPUT_SCHEMA,
|
|
57
57
|
outputSchema: {
|
|
58
58
|
type: 'object',
|
|
@@ -103,7 +103,7 @@ async function addGuide(connection, snippetClient, rawArgs) {
|
|
|
103
103
|
const orientation = args.orientation;
|
|
104
104
|
const position = args.position;
|
|
105
105
|
const script = await snippetClient.build('addGuide', { orientation, position });
|
|
106
|
-
const result = await runScript(connection, script);
|
|
106
|
+
const result = (await runScript(connection, script));
|
|
107
107
|
return {
|
|
108
108
|
content: [
|
|
109
109
|
{
|
|
@@ -38,8 +38,8 @@ export function createHistoryTools(connection, snippetClient) {
|
|
|
38
38
|
return [
|
|
39
39
|
{
|
|
40
40
|
tool: {
|
|
41
|
-
name: '
|
|
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
|
|
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.',
|
|
43
43
|
inputSchema: undoSchema,
|
|
44
44
|
outputSchema: HISTORY_STEP_RESULT_SCHEMA,
|
|
45
45
|
annotations: {
|
|
@@ -51,8 +51,8 @@ export function createHistoryTools(connection, snippetClient) {
|
|
|
51
51
|
},
|
|
52
52
|
{
|
|
53
53
|
tool: {
|
|
54
|
-
name: '
|
|
55
|
-
description: 'Step forward in the document history (equivalent to Ctrl/Cmd+Shift+Z). Only works if
|
|
54
|
+
name: 'ps_redo',
|
|
55
|
+
description: 'Step forward in the document history (equivalent to Ctrl/Cmd+Shift+Z). Only works if ps_undo was the last operation; making a fresh edit destroys the redo stack. Returns updated context.',
|
|
56
56
|
inputSchema: redoSchema,
|
|
57
57
|
outputSchema: HISTORY_STEP_RESULT_SCHEMA,
|
|
58
58
|
annotations: {
|
|
@@ -41,7 +41,7 @@ export function createImagePlacementTools(connection, snippetClient) {
|
|
|
41
41
|
return [
|
|
42
42
|
{
|
|
43
43
|
tool: {
|
|
44
|
-
name: '
|
|
44
|
+
name: 'ps_place_image',
|
|
45
45
|
description: 'Place an image file (JPEG, PNG, PSD, etc.) as a new Smart Object layer in the active document, optionally offset from center. Rasterize separately if you need to apply pixel-only operations. Open-world: reads from the filesystem. Returns the new layer name, bounds, and updated document context.',
|
|
46
46
|
inputSchema: placeImageSchema,
|
|
47
47
|
outputSchema: {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { runScript } from '../utils/run-script.js';
|
|
2
2
|
import { validateArgs } from '../utils/validate.js';
|
|
3
|
+
import { OnnxLandmarkDetectionClient } from '../detection/landmark-detection-client.js';
|
|
4
|
+
import { resolveExpectedPlacement, PLACEMENT_SCHEMA } from '../perception/grounding-locate.js';
|
|
3
5
|
const PS_MAX_DIM_PX = 300_000;
|
|
4
6
|
const resizeImageSchema = {
|
|
5
7
|
type: 'object',
|
|
@@ -22,32 +24,37 @@ const resizeImageSchema = {
|
|
|
22
24
|
const cropDocumentSchema = {
|
|
23
25
|
type: 'object',
|
|
24
26
|
properties: {
|
|
27
|
+
placement: {
|
|
28
|
+
...PLACEMENT_SCHEMA,
|
|
29
|
+
description: 'ANCHOR-RELATIONAL crop (preferred over guessing pixels): a REGION relation (inside/gap) → the crop is the ' +
|
|
30
|
+
'resolved region bounding box, verified by the gate. Crops ONLY if the gate PASSES. When set, ' +
|
|
31
|
+
'left/top/right/bottom are ignored. See ps_resolve_placement for the anchors + relation vocabulary.',
|
|
32
|
+
},
|
|
25
33
|
left: {
|
|
26
34
|
type: 'integer',
|
|
27
|
-
description: 'Left edge position in pixels.',
|
|
35
|
+
description: 'Left edge position in pixels (raw mode; ignored when placement is set).',
|
|
28
36
|
minimum: 0,
|
|
29
37
|
maximum: PS_MAX_DIM_PX,
|
|
30
38
|
},
|
|
31
39
|
top: {
|
|
32
40
|
type: 'integer',
|
|
33
|
-
description: 'Top edge position in pixels.',
|
|
41
|
+
description: 'Top edge position in pixels (raw mode; ignored when placement is set).',
|
|
34
42
|
minimum: 0,
|
|
35
43
|
maximum: PS_MAX_DIM_PX,
|
|
36
44
|
},
|
|
37
45
|
right: {
|
|
38
46
|
type: 'integer',
|
|
39
|
-
description: 'Right edge position in pixels.',
|
|
47
|
+
description: 'Right edge position in pixels (raw mode; ignored when placement is set).',
|
|
40
48
|
minimum: 1,
|
|
41
49
|
maximum: PS_MAX_DIM_PX,
|
|
42
50
|
},
|
|
43
51
|
bottom: {
|
|
44
52
|
type: 'integer',
|
|
45
|
-
description: 'Bottom edge position in pixels.',
|
|
53
|
+
description: 'Bottom edge position in pixels (raw mode; ignored when placement is set).',
|
|
46
54
|
minimum: 1,
|
|
47
55
|
maximum: PS_MAX_DIM_PX,
|
|
48
56
|
},
|
|
49
57
|
},
|
|
50
|
-
required: ['left', 'top', 'right', 'bottom'],
|
|
51
58
|
};
|
|
52
59
|
const convertImageModeSchema = {
|
|
53
60
|
type: 'object',
|
|
@@ -80,11 +87,11 @@ const convertImageModeSchema = {
|
|
|
80
87
|
},
|
|
81
88
|
required: ['mode'],
|
|
82
89
|
};
|
|
83
|
-
export function createImageTools(connection, snippetClient) {
|
|
90
|
+
export function createImageTools(connection, snippetClient, client = new OnnxLandmarkDetectionClient()) {
|
|
84
91
|
return [
|
|
85
92
|
{
|
|
86
93
|
tool: {
|
|
87
|
-
name: '
|
|
94
|
+
name: 'ps_resize_image',
|
|
88
95
|
description: 'Resize the entire document canvas (all layers) to the specified absolute dimensions. Destructive: downscaling permanently loses pixel data. Idempotent: same width/height always yield the same result. For aspect-preserving downscale during export, see photoshop_export_jpeg / photoshop_export_png which resize a duplicate. Returns updated document context.',
|
|
89
96
|
inputSchema: resizeImageSchema,
|
|
90
97
|
outputSchema: {
|
|
@@ -107,8 +114,8 @@ export function createImageTools(connection, snippetClient) {
|
|
|
107
114
|
},
|
|
108
115
|
{
|
|
109
116
|
tool: {
|
|
110
|
-
name: '
|
|
111
|
-
description: 'Crop the document to the
|
|
117
|
+
name: 'ps_crop_document',
|
|
118
|
+
description: 'Crop the document to a region — EITHER anchor-relational `placement` (preferred: a region relation like inside/gap → the crop is the resolved, gate-verified region bbox, no pixel-guessing) OR absolute pixel bounds (left/top/right/bottom in document space). Destructive: pixels outside the bounds are permanently lost. Idempotent for the same bounds. Returns updated document context.',
|
|
112
119
|
inputSchema: cropDocumentSchema,
|
|
113
120
|
outputSchema: {
|
|
114
121
|
type: 'object',
|
|
@@ -116,6 +123,16 @@ export function createImageTools(connection, snippetClient) {
|
|
|
116
123
|
cropped: { type: 'boolean' },
|
|
117
124
|
newWidth: { type: 'number' },
|
|
118
125
|
newHeight: { type: 'number' },
|
|
126
|
+
placement: {
|
|
127
|
+
type: 'object',
|
|
128
|
+
description: 'Present when anchor-relational placement was used: the resolved region + gate verdict.',
|
|
129
|
+
properties: {
|
|
130
|
+
target: { type: 'string' },
|
|
131
|
+
gate: { type: 'object' },
|
|
132
|
+
anchors: { type: 'object' },
|
|
133
|
+
geometry: { type: 'object' },
|
|
134
|
+
},
|
|
135
|
+
},
|
|
119
136
|
context: { type: 'object' },
|
|
120
137
|
},
|
|
121
138
|
},
|
|
@@ -125,11 +142,11 @@ export function createImageTools(connection, snippetClient) {
|
|
|
125
142
|
idempotentHint: true,
|
|
126
143
|
},
|
|
127
144
|
},
|
|
128
|
-
handler: async (args) => cropDocument(connection, snippetClient, args),
|
|
145
|
+
handler: async (args) => cropDocument(connection, snippetClient, client, args),
|
|
129
146
|
},
|
|
130
147
|
{
|
|
131
148
|
tool: {
|
|
132
|
-
name: '
|
|
149
|
+
name: 'ps_convert_image_mode',
|
|
133
150
|
description: 'Convert the document color mode (grayscale / rgb / cmyk / lab). Destructive and document-wide: the image is FLATTENED and, for grayscale, color is discarded (the confirmation prompt is auto-accepted). Use grayscale as the base for halftone / line-art workflows, or rgb to bring a CMYK/Lab doc back for normal editing. Returns updated document context.',
|
|
134
151
|
inputSchema: convertImageModeSchema,
|
|
135
152
|
outputSchema: {
|
|
@@ -218,23 +235,49 @@ async function convertImageMode(connection, snippetClient, rawArgs) {
|
|
|
218
235
|
};
|
|
219
236
|
}
|
|
220
237
|
}
|
|
221
|
-
async function cropDocument(connection, snippetClient, rawArgs) {
|
|
238
|
+
async function cropDocument(connection, snippetClient, client, rawArgs) {
|
|
222
239
|
try {
|
|
223
240
|
const args = validateArgs(cropDocumentSchema, rawArgs);
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
241
|
+
let left, top, right, bottom;
|
|
242
|
+
let placementInfo;
|
|
243
|
+
if (args.placement) {
|
|
244
|
+
const rp = await resolveExpectedPlacement(connection, client, args.placement, 'region', 'crop');
|
|
245
|
+
({ left, top, right, bottom } = rp.bbox);
|
|
246
|
+
placementInfo = { target: rp.target, anchors: rp.anchors, geometry: rp.bbox };
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
for (const k of ['left', 'top', 'right', 'bottom']) {
|
|
250
|
+
if (typeof args[k] !== 'number') {
|
|
251
|
+
throw new Error(`crop needs numeric left/top/right/bottom (missing ${k}), or a placement (anchors + a region relation).`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
left = args.left;
|
|
255
|
+
top = args.top;
|
|
256
|
+
right = args.right;
|
|
257
|
+
bottom = args.bottom;
|
|
258
|
+
}
|
|
228
259
|
const script = await snippetClient.build('cropDocument', { left, top, right, bottom });
|
|
229
|
-
const result = await runScript(connection, script);
|
|
260
|
+
const result = (await runScript(connection, script));
|
|
230
261
|
return {
|
|
231
262
|
content: [
|
|
232
263
|
{
|
|
233
264
|
type: 'text',
|
|
234
|
-
text:
|
|
265
|
+
text: placementInfo
|
|
266
|
+
? `Document cropped to [${left},${top},${right},${bottom}] (placement gate PASS)\nResult: ${JSON.stringify(result)}`
|
|
267
|
+
: `Document cropped\nResult: ${JSON.stringify(result)}`,
|
|
235
268
|
},
|
|
236
269
|
],
|
|
237
|
-
structuredContent:
|
|
270
|
+
structuredContent: placementInfo
|
|
271
|
+
? {
|
|
272
|
+
...result,
|
|
273
|
+
placement: {
|
|
274
|
+
target: placementInfo.target,
|
|
275
|
+
gate: { pass: true },
|
|
276
|
+
anchors: placementInfo.anchors,
|
|
277
|
+
geometry: placementInfo.geometry,
|
|
278
|
+
},
|
|
279
|
+
}
|
|
280
|
+
: result,
|
|
238
281
|
};
|
|
239
282
|
}
|
|
240
283
|
catch (error) {
|
|
@@ -23,8 +23,8 @@ export function createInspectTools(connection, snippetClient) {
|
|
|
23
23
|
return [
|
|
24
24
|
{
|
|
25
25
|
tool: {
|
|
26
|
-
name: '
|
|
27
|
-
description: 'Read-only document inspection — choose with `what` (metadata / layer_tree / history / selection_info). This is the assess/orientation surface: call it at the start of a workflow and whenever you need fresh state. For metadata, pass `sections` to subset (e.g. ["context"] for a cheap probe). For an IMAGE-based check use
|
|
26
|
+
name: 'ps_inspect',
|
|
27
|
+
description: 'Read-only document inspection — choose with `what` (metadata / layer_tree / history / selection_info). This is the assess/orientation surface: call it at the start of a workflow and whenever you need fresh state. For metadata, pass `sections` to subset (e.g. ["context"] for a cheap probe). For an IMAGE-based check use ps_get_preview; for NUMERIC verification use ps_get_histogram / ps_compare_regions / ps_get_layer_bounds_diff (these stay separate, named tools on purpose). Read-only and idempotent.',
|
|
28
28
|
inputSchema: INSPECT_INPUT_SCHEMA,
|
|
29
29
|
outputSchema: {
|
|
30
30
|
type: 'object',
|
|
@@ -35,7 +35,7 @@ export function createLayerOrderingTools(connection, snippetClient) {
|
|
|
35
35
|
return [
|
|
36
36
|
{
|
|
37
37
|
tool: {
|
|
38
|
-
name: '
|
|
38
|
+
name: 'ps_move_layer_to_position',
|
|
39
39
|
description: 'Move a layer to a position in the stack — the single ordering primitive (the per-direction helpers `_to_top` / `_to_bottom` / `_up` / `_down` were removed 2026-05-31; this tool covers all four cases). Position keywords: ABOVE/BELOW (requires target_layer_name — places the moved layer immediately above/below the target), TOP/BOTTOM (absolute — target_layer_name not needed). By default the ACTIVE layer is moved; pass layer_to_move=<name> to move a specific layer by name (recurses into groups). COMMON CASES: an adjustment layer just created landed below another adjustment that masks its effect → move it to TOP; a vignette darkens the wrong layers → move ABOVE the layer it should affect; a sky-replacement composite is showing through the foreground → move the foreground to TOP. Idempotent for a given (layer_to_move, target, position).',
|
|
40
40
|
inputSchema: moveLayerToPositionSchema,
|
|
41
41
|
outputSchema: LAYER_MOVE_RESULT_SCHEMA,
|
|
@@ -5,6 +5,18 @@ const emptySchema = {
|
|
|
5
5
|
type: 'object',
|
|
6
6
|
properties: {},
|
|
7
7
|
};
|
|
8
|
+
const convertToSmartObjectSchema = {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
mode: {
|
|
12
|
+
type: 'string',
|
|
13
|
+
enum: ['convert', 'new_via_copy'],
|
|
14
|
+
description: 'convert (default): wrap the active layer into a Smart Object in place. ' +
|
|
15
|
+
"new_via_copy: the active layer must ALREADY be a Smart Object — make a NEW Smart Object that is an INDEPENDENT copy (its own embedded source), unlinked from the original's shared source, so editing the copy's contents does not propagate back. (Differs from ps_duplicate_layer of a Smart Object, which keeps the shared source.)",
|
|
16
|
+
default: 'convert',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
};
|
|
8
20
|
const setLayerOpacitySchema = {
|
|
9
21
|
type: 'object',
|
|
10
22
|
properties: {
|
|
@@ -77,8 +89,8 @@ const addLayerStyleSchema = {
|
|
|
77
89
|
properties: {
|
|
78
90
|
style: {
|
|
79
91
|
type: 'string',
|
|
80
|
-
enum: ['drop_shadow', 'stroke', 'outer_glow'],
|
|
81
|
-
description: 'Which layer style to apply.',
|
|
92
|
+
enum: ['drop_shadow', 'stroke', 'outer_glow', 'inner_shadow', 'inner_glow', 'color_overlay'],
|
|
93
|
+
description: 'Which layer style to apply. drop_shadow/inner_shadow share angle/distance/spread/size; outer_glow/inner_glow share glow_size/glow_spread; color_overlay uses only color + opacity. inner_shadow = recessed shadow inside the layer edges (Multiply); inner_glow = glow inset from the edges (Screen); color_overlay = flat solid-color fill (Normal).',
|
|
82
94
|
},
|
|
83
95
|
color: {
|
|
84
96
|
type: 'object',
|
|
@@ -99,24 +111,24 @@ const addLayerStyleSchema = {
|
|
|
99
111
|
},
|
|
100
112
|
angle: {
|
|
101
113
|
type: 'number',
|
|
102
|
-
description: 'drop_shadow
|
|
114
|
+
description: 'drop_shadow/inner_shadow: light angle in degrees. 90 = top-down (standard product shadow).',
|
|
103
115
|
default: 90,
|
|
104
116
|
},
|
|
105
117
|
distance: {
|
|
106
118
|
type: 'number',
|
|
107
|
-
description: 'drop_shadow
|
|
119
|
+
description: 'drop_shadow/inner_shadow: shadow offset in pixels.',
|
|
108
120
|
default: 8,
|
|
109
121
|
},
|
|
110
122
|
spread: {
|
|
111
123
|
type: 'number',
|
|
112
124
|
minimum: 0,
|
|
113
125
|
maximum: 100,
|
|
114
|
-
description: 'drop_shadow
|
|
126
|
+
description: 'drop_shadow/inner_shadow: shadow choke/spread (0-100%).',
|
|
115
127
|
default: 0,
|
|
116
128
|
},
|
|
117
129
|
size: {
|
|
118
130
|
type: 'number',
|
|
119
|
-
description: 'drop_shadow
|
|
131
|
+
description: 'drop_shadow/inner_shadow: shadow blur size in pixels.',
|
|
120
132
|
default: 12,
|
|
121
133
|
},
|
|
122
134
|
stroke_size: {
|
|
@@ -132,14 +144,14 @@ const addLayerStyleSchema = {
|
|
|
132
144
|
},
|
|
133
145
|
glow_size: {
|
|
134
146
|
type: 'number',
|
|
135
|
-
description: 'outer_glow
|
|
147
|
+
description: 'outer_glow/inner_glow: glow blur size in pixels.',
|
|
136
148
|
default: 12,
|
|
137
149
|
},
|
|
138
150
|
glow_spread: {
|
|
139
151
|
type: 'number',
|
|
140
152
|
minimum: 0,
|
|
141
153
|
maximum: 100,
|
|
142
|
-
description: 'outer_glow
|
|
154
|
+
description: 'outer_glow/inner_glow: glow choke/spread (0-100%).',
|
|
143
155
|
default: 0,
|
|
144
156
|
},
|
|
145
157
|
},
|
|
@@ -182,9 +194,9 @@ export function createLayerPropertiesTools(connection, snippetClient) {
|
|
|
182
194
|
return [
|
|
183
195
|
{
|
|
184
196
|
tool: {
|
|
185
|
-
name: '
|
|
186
|
-
description: 'Convert the active layer to a Smart Object.
|
|
187
|
-
inputSchema:
|
|
197
|
+
name: 'ps_convert_to_smart_object',
|
|
198
|
+
description: 'Convert the active layer to a Smart Object, or (mode=new_via_copy) copy an existing Smart Object to an independent new one. `convert` (default) wraps the layer content so every subsequent filter becomes a Smart Filter — fully editable and non-destructive. PREREQUISITE for Camera Raw Filter, non-destructive frequency separation, and any workflow where filter parameters need to stay adjustable after the fact. Works on pixel, text, shape, and adjustment layers. Auto-promotes the background layer if needed. Wrapping an already-Smart-Object layer creates a nested Smart Object (valid in Photoshop — the inner SO is preserved as-is). `new_via_copy` requires the active layer to already be a Smart Object and yields a copy with its OWN source (unlinked). Use ps_rasterize_layer to go the other direction.',
|
|
199
|
+
inputSchema: convertToSmartObjectSchema,
|
|
188
200
|
outputSchema: {
|
|
189
201
|
type: 'object',
|
|
190
202
|
properties: {
|
|
@@ -193,6 +205,7 @@ export function createLayerPropertiesTools(connection, snippetClient) {
|
|
|
193
205
|
original_kind: { type: 'string' },
|
|
194
206
|
was_already_smart_object: { type: 'boolean' },
|
|
195
207
|
background_promoted: { type: 'boolean' },
|
|
208
|
+
source_unlinked: { type: 'boolean' },
|
|
196
209
|
context: { type: 'object' },
|
|
197
210
|
},
|
|
198
211
|
},
|
|
@@ -201,11 +214,11 @@ export function createLayerPropertiesTools(connection, snippetClient) {
|
|
|
201
214
|
idempotentHint: false,
|
|
202
215
|
},
|
|
203
216
|
},
|
|
204
|
-
handler: async () => convertToSmartObject(connection, snippetClient),
|
|
217
|
+
handler: async (args) => convertToSmartObject(connection, snippetClient, args),
|
|
205
218
|
},
|
|
206
219
|
{
|
|
207
220
|
tool: {
|
|
208
|
-
name: '
|
|
221
|
+
name: 'ps_rasterize_layer',
|
|
209
222
|
description: 'Rasterize the active layer (convert text or smart object to plain pixels). Destructive — the text/smart-object source is lost. No-op if already a normal raster layer. Returns context so the caller sees the layer kind transition.',
|
|
210
223
|
inputSchema: emptySchema,
|
|
211
224
|
outputSchema: {
|
|
@@ -228,7 +241,7 @@ export function createLayerPropertiesTools(connection, snippetClient) {
|
|
|
228
241
|
},
|
|
229
242
|
{
|
|
230
243
|
tool: {
|
|
231
|
-
name: '
|
|
244
|
+
name: 'ps_set_layer',
|
|
232
245
|
description: 'Set a property of the active layer — chosen via `property`. Idempotent absolute setters; do not change which layer is active. `opacity`: pass opacity (whole layer incl. styles) and/or fill_percent (pixels only — Photoshop\'s separate "Fill" slider, for Hard Mix / stroke-only effects). `blend_mode`: how the layer composites below (NORMAL to reset). `visibility`: show/hide via `visible`. `locked`: allLocked via `locked`. `name`: rename via `name`. Returns context so the LLM keeps state awareness.',
|
|
233
246
|
inputSchema: SET_LAYER_INPUT_SCHEMA,
|
|
234
247
|
outputSchema: {
|
|
@@ -257,7 +270,7 @@ export function createLayerPropertiesTools(connection, snippetClient) {
|
|
|
257
270
|
},
|
|
258
271
|
{
|
|
259
272
|
tool: {
|
|
260
|
-
name: '
|
|
273
|
+
name: 'ps_duplicate_layer',
|
|
261
274
|
description: 'Duplicate the active layer. The new layer becomes active. Returns context so the caller sees the new active layer. Not idempotent (each call creates another copy).',
|
|
262
275
|
inputSchema: duplicateLayerSchema,
|
|
263
276
|
outputSchema: {
|
|
@@ -277,7 +290,7 @@ export function createLayerPropertiesTools(connection, snippetClient) {
|
|
|
277
290
|
},
|
|
278
291
|
{
|
|
279
292
|
tool: {
|
|
280
|
-
name: '
|
|
293
|
+
name: 'ps_copy_to_new_layer',
|
|
281
294
|
description: 'Copy the current selection into a NEW layer above the active one — Photoshop\'s "Layer via Copy" (Ctrl+J). The source layer is left untouched and the new copied layer becomes active. With an active selection only the selected pixels are lifted; with no selection it copies the whole active layer (a plain duplicate). Reach for this to isolate a region for independent transforms or filters (stretch, light rays, a local grade) without altering the source. Returns context so the caller sees the new active layer.',
|
|
282
295
|
inputSchema: emptySchema,
|
|
283
296
|
outputSchema: {
|
|
@@ -300,7 +313,7 @@ export function createLayerPropertiesTools(connection, snippetClient) {
|
|
|
300
313
|
},
|
|
301
314
|
{
|
|
302
315
|
tool: {
|
|
303
|
-
name: '
|
|
316
|
+
name: 'ps_merge',
|
|
304
317
|
description: 'Merge layers — choose with `mode`. `visible`: collapse all visible layers into one (DESTRUCTIVE; identities lost). `stamp`: merge visible into a NEW layer on top, leaving originals intact (Ctrl+Alt+Shift+E — the non-destructive "final tweak" composite for output sharpening/grain/contrast; prefer this when building on top rather than collapsing). `flatten`: flatten ALL layers into the background (DESTRUCTIVE; transparency filled with bg color — use sparingly, usually prefer `visible` or exporting a flattened copy). Returns context.',
|
|
305
318
|
inputSchema: MERGE_INPUT_SCHEMA,
|
|
306
319
|
outputSchema: {
|
|
@@ -325,7 +338,7 @@ export function createLayerPropertiesTools(connection, snippetClient) {
|
|
|
325
338
|
},
|
|
326
339
|
{
|
|
327
340
|
tool: {
|
|
328
|
-
name: '
|
|
341
|
+
name: 'ps_bake_layer',
|
|
329
342
|
description: 'Flatten the active layer\'s CURRENT APPEARANCE — the layer plus any adjustment layers clipped to it plus its layer styles — into a NEW pixel layer named "<name> (baked)". Non-destructive: the originals are left intact. This is how you get real baked pixels out of a non-destructive stack: clip a Hue/Saturation (saturation -100) or an Invert adjustment to a layer, then bake to get a desaturated / inverted PIXEL layer for techniques that need actual pixels (line art, blurred-layer blends). Also works on a lone layer with nothing clipped to it — it rasterizes that layer\'s own appearance (layer styles, smart-object or text content) into a flat pixel copy. The active layer must be top-level (not inside a group).',
|
|
330
343
|
inputSchema: emptySchema,
|
|
331
344
|
outputSchema: {
|
|
@@ -347,8 +360,8 @@ export function createLayerPropertiesTools(connection, snippetClient) {
|
|
|
347
360
|
},
|
|
348
361
|
{
|
|
349
362
|
tool: {
|
|
350
|
-
name: '
|
|
351
|
-
description: 'Apply a layer style (drop shadow, stroke, or
|
|
363
|
+
name: 'ps_add_layer_style',
|
|
364
|
+
description: 'Apply a layer style (drop shadow, stroke, outer glow, inner shadow, inner glow, or color overlay) to the active layer. Non-destructive and editable. For PHOTO workflows: `stroke` = print-style border around the image (apply to the flattened photo on a transparent canvas, or to a duplicated background); `outer_glow` = atmospheric lift on highlights (halo around the sun, glow around a window or lamp); `drop_shadow` = depth on text overlays and layered composites. Drop shadow uses Multiply blend; outer glow uses Screen; stroke uses Normal. Cannot be applied to a background layer (duplicate or convert it first). Returns context — applying a style changes what exists on the layer (the Lefx descriptor).',
|
|
352
365
|
inputSchema: addLayerStyleSchema,
|
|
353
366
|
outputSchema: {
|
|
354
367
|
type: 'object',
|
|
@@ -414,15 +427,21 @@ async function mergeLayers(connection, snippetClient, rawArgs) {
|
|
|
414
427
|
};
|
|
415
428
|
}
|
|
416
429
|
}
|
|
417
|
-
async function convertToSmartObject(connection, snippetClient) {
|
|
430
|
+
async function convertToSmartObject(connection, snippetClient, rawArgs) {
|
|
418
431
|
try {
|
|
419
|
-
const
|
|
432
|
+
const args = validateArgs(convertToSmartObjectSchema, rawArgs);
|
|
433
|
+
const mode = args.mode ?? 'convert';
|
|
434
|
+
const snippet = mode === 'new_via_copy' ? 'newSmartObjectViaCopy' : 'convertToSmartObject';
|
|
435
|
+
const script = await snippetClient.build(snippet);
|
|
420
436
|
const result = await runScript(connection, script);
|
|
437
|
+
const verb = mode === 'new_via_copy'
|
|
438
|
+
? 'New independent Smart Object created via copy'
|
|
439
|
+
: 'Layer converted to Smart Object';
|
|
421
440
|
return {
|
|
422
441
|
content: [
|
|
423
442
|
{
|
|
424
443
|
type: 'text',
|
|
425
|
-
text:
|
|
444
|
+
text: `${verb}.\nResult: ${JSON.stringify(result)}`,
|
|
426
445
|
},
|
|
427
446
|
],
|
|
428
447
|
structuredContent: result,
|
|
@@ -103,8 +103,8 @@ export function createLayerTools(connection, snippetClient) {
|
|
|
103
103
|
return [
|
|
104
104
|
{
|
|
105
105
|
tool: {
|
|
106
|
-
name: '
|
|
107
|
-
description: 'Create a new empty raster layer above the currently active layer. Non-destructive. Use
|
|
106
|
+
name: 'ps_create_layer',
|
|
107
|
+
description: 'Create a new empty raster layer above the currently active layer. Non-destructive. Use ps_create_text_layer for text, ps_add_adjustment_layer for adjustments.',
|
|
108
108
|
inputSchema: createLayerSchema,
|
|
109
109
|
outputSchema: {
|
|
110
110
|
type: 'object',
|
|
@@ -123,8 +123,8 @@ export function createLayerTools(connection, snippetClient) {
|
|
|
123
123
|
},
|
|
124
124
|
{
|
|
125
125
|
tool: {
|
|
126
|
-
name: '
|
|
127
|
-
description: 'DESTRUCTIVE: Delete a layer. With no arg, deletes the currently active layer (backward-compatible). With `name`, recurses into groups and deletes the first layer matching that name — useful for cleanup workflows where the dead layer is not currently active. Recoverable only via Edit > Undo. To delete a group and all its contents, use
|
|
126
|
+
name: 'ps_delete_layer',
|
|
127
|
+
description: 'DESTRUCTIVE: Delete a layer. With no arg, deletes the currently active layer (backward-compatible). With `name`, recurses into groups and deletes the first layer matching that name — useful for cleanup workflows where the dead layer is not currently active. Recoverable only via Edit > Undo. To delete a group and all its contents, use ps_delete_group.',
|
|
128
128
|
inputSchema: deleteLayerSchema,
|
|
129
129
|
outputSchema: {
|
|
130
130
|
type: 'object',
|
|
@@ -144,7 +144,7 @@ export function createLayerTools(connection, snippetClient) {
|
|
|
144
144
|
},
|
|
145
145
|
{
|
|
146
146
|
tool: {
|
|
147
|
-
name: '
|
|
147
|
+
name: 'ps_create_text_layer',
|
|
148
148
|
description: 'Create a new text layer with the given content, position, and font size. The text is editable (vector). Use photoshop_set_text_font / photoshop_set_text_color afterwards to style.',
|
|
149
149
|
inputSchema: createTextLayerSchema,
|
|
150
150
|
outputSchema: {
|
|
@@ -167,7 +167,7 @@ export function createLayerTools(connection, snippetClient) {
|
|
|
167
167
|
},
|
|
168
168
|
{
|
|
169
169
|
tool: {
|
|
170
|
-
name: '
|
|
170
|
+
name: 'ps_fill_layer',
|
|
171
171
|
description: 'Fill the active layer (or the current selection within it) with a solid RGB color. Idempotent for the same color. Throws on fully-locked or text layers — rasterize text first if needed.',
|
|
172
172
|
inputSchema: fillLayerSchema,
|
|
173
173
|
outputSchema: {
|
|
@@ -187,8 +187,8 @@ export function createLayerTools(connection, snippetClient) {
|
|
|
187
187
|
},
|
|
188
188
|
{
|
|
189
189
|
tool: {
|
|
190
|
-
name: '
|
|
191
|
-
description: 'Add a non-destructive SOLID COLOR fill layer (an editable, re-colorable content layer — distinct from
|
|
190
|
+
name: 'ps_add_fill_layer',
|
|
191
|
+
description: 'Add a non-destructive SOLID COLOR fill layer (an editable, re-colorable content layer — distinct from ps_fill_layer, which bakes color into the active pixel layer). The new fill layer becomes active. Use for color washes, base backgrounds, and clipped color overlays. (Gradient and pattern fill types are planned.)',
|
|
192
192
|
inputSchema: addFillLayerSchema,
|
|
193
193
|
outputSchema: {
|
|
194
194
|
type: 'object',
|
|
@@ -209,8 +209,8 @@ export function createLayerTools(connection, snippetClient) {
|
|
|
209
209
|
},
|
|
210
210
|
{
|
|
211
211
|
tool: {
|
|
212
|
-
name: '
|
|
213
|
-
description: 'Make a layer active by name. Recurses into groups; if a name appears more than once, picks the first match in stack order (top-to-bottom). Throws if no layer by that name exists. Foundational for multi-layer workflows — use after
|
|
212
|
+
name: 'ps_select_layer',
|
|
213
|
+
description: 'Make a layer active by name. Recurses into groups; if a name appears more than once, picks the first match in stack order (top-to-bottom). Throws if no layer by that name exists. Foundational for multi-layer workflows — use after ps_duplicate_layer or ps_add_adjustment_layer when you need to focus a specific layer before applying further operations.',
|
|
214
214
|
inputSchema: selectLayerSchema,
|
|
215
215
|
outputSchema: {
|
|
216
216
|
type: 'object',
|