editmamei 1.0.2 → 1.1.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/README.md +7 -7
- package/dist/api/extendscript/_helpers.js +64 -2
- package/dist/api/photoshop-api.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/activate.js +1 -1
- package/dist/cli/config.js +1 -1
- package/dist/cli/deactivate.js +1 -1
- package/dist/core/raw-develop-state.js +13 -0
- package/dist/core/server.js +64 -3
- package/dist/core/tool-groups.js +14 -12
- package/dist/core/tool-tiers.js +4 -2
- package/dist/detection/runtime.js +2 -2
- package/dist/perception/region-scorer.js +1 -1
- package/dist/platform/connection.js +15 -0
- package/dist/skills/editmamei-skill.zip +0 -0
- package/dist/telemetry/client.js +15 -2
- package/dist/telemetry/sanitize.js +1 -0
- package/dist/tools/adjustment-tools.js +21 -3
- package/dist/tools/detection-tools.js +1 -1
- package/dist/tools/document-tools.js +5 -2
- package/dist/tools/filter-tools.js +130 -13
- package/dist/tools/group-tools.js +172 -54
- package/dist/tools/history-tools.js +1 -1
- package/dist/tools/inspect-tools.js +22 -3
- package/dist/tools/layer-tools.js +4 -4
- package/dist/tools/overview-tools.js +37 -8
- package/dist/tools/preview-tools.js +3 -2
- package/dist/tools/scene-tools.js +1 -1
- package/dist/tools/selection-tools.js +19 -17
- package/dist/tools/smart-object-tools.js +163 -0
- package/dist/tools/text-tools.js +92 -2
- package/dist/utils/session-log.js +42 -6
- package/dist/utils/temp.js +1 -1
- package/dist/utils/tool-helpers.js +23 -0
- package/dist/version.js +1 -1
- package/package.json +3 -2
|
@@ -1,11 +1,7 @@
|
|
|
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
|
+
import { toolErrorResult, runSnippetTool, unknownDiscriminator } from '../utils/tool-helpers.js';
|
|
9
5
|
const deleteGroupSchema = {
|
|
10
6
|
type: 'object',
|
|
11
7
|
properties: {
|
|
@@ -83,12 +79,118 @@ const ungroupSchema = {
|
|
|
83
79
|
},
|
|
84
80
|
required: ['name', 'confirm'],
|
|
85
81
|
};
|
|
82
|
+
const CLIPPING_MASK_OPS = ['create', 'release'];
|
|
83
|
+
const clippingMaskSchema = {
|
|
84
|
+
type: 'object',
|
|
85
|
+
properties: {
|
|
86
|
+
op: {
|
|
87
|
+
type: 'string',
|
|
88
|
+
enum: [...CLIPPING_MASK_OPS],
|
|
89
|
+
description: 'create: clip the active layer to the layer directly below it (that layer becomes the alpha source); no-ops (already_clipped:true) when the layer is already clipped. ' +
|
|
90
|
+
'release: release the active layer from its clipping mask; no-ops (released:false) when the layer is not clipped.',
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
required: ['op'],
|
|
94
|
+
};
|
|
95
|
+
const clippingMaskOpArgsSchema = {
|
|
96
|
+
type: 'object',
|
|
97
|
+
properties: {},
|
|
98
|
+
};
|
|
99
|
+
const GROUP_OPS = ['create', 'delete', 'ungroup', 'add_layer', 'set_blend_mode'];
|
|
100
|
+
export const GROUP_OP_SCHEMAS = {
|
|
101
|
+
create: createGroupSchema,
|
|
102
|
+
delete: deleteGroupSchema,
|
|
103
|
+
ungroup: ungroupSchema,
|
|
104
|
+
add_layer: moveLayerToGroupSchema,
|
|
105
|
+
set_blend_mode: setGroupBlendModeSchema,
|
|
106
|
+
};
|
|
107
|
+
const GROUP_INPUT_SCHEMA = {
|
|
108
|
+
type: 'object',
|
|
109
|
+
properties: {
|
|
110
|
+
op: {
|
|
111
|
+
type: 'string',
|
|
112
|
+
enum: [...GROUP_OPS],
|
|
113
|
+
description: "create: make a new group above the active layer named `name` (hoisted out of an active group by default; into_active_group:true keeps Photoshop's native nesting), optionally moving `layers` into it. " +
|
|
114
|
+
'delete: DESTRUCTIVE — delete group `name` and everything inside it (nested groups and their layers); requires confirm:true. To dissolve a group while keeping its contents, use ungroup instead. ' +
|
|
115
|
+
'ungroup: DESTRUCTIVE structural change — dissolve group `name`, promoting its contents to the parent level in their existing stack order; requires confirm:true. ' +
|
|
116
|
+
'add_layer: move `layer_name` into `group_name` (top of its stack). ' +
|
|
117
|
+
"set_blend_mode: set group `name`'s blend mode to `blend_mode` — PASSTHROUGH (default for new groups) lets adjustments inside affect layers below the group; NORMAL treats the group as a single composite.",
|
|
118
|
+
},
|
|
119
|
+
...createGroupSchema.properties,
|
|
120
|
+
name: {
|
|
121
|
+
type: 'string',
|
|
122
|
+
description: 'Group name. create: name for the NEW group. delete/ungroup/set_blend_mode: the EXISTING group to act on (recursive search).',
|
|
123
|
+
},
|
|
124
|
+
confirm: {
|
|
125
|
+
type: 'boolean',
|
|
126
|
+
description: 'REQUIRED for op=delete and op=ungroup, ignored by the other ops. Must be true — guards against accidental loss of a group and (for delete) everything it contains.',
|
|
127
|
+
},
|
|
128
|
+
...moveLayerToGroupSchema.properties,
|
|
129
|
+
blend_mode: setGroupBlendModeSchema.properties.blend_mode,
|
|
130
|
+
},
|
|
131
|
+
required: ['op'],
|
|
132
|
+
};
|
|
86
133
|
export function createGroupTools(connection, snippetClient) {
|
|
87
134
|
return [
|
|
135
|
+
{
|
|
136
|
+
tool: {
|
|
137
|
+
name: 'ps_group',
|
|
138
|
+
description: 'Layer group (LayerSet) lifecycle and membership — choose the operation with `op`. create/delete/ungroup/add_layer/set_blend_mode. See the `op` enum for per-operation params. delete and ungroup are DESTRUCTIVE and require confirm:true — delete removes the group AND everything inside it; ungroup dissolves the group but promotes its contents to the parent level (use ungroup, not delete, to keep the layers).',
|
|
139
|
+
inputSchema: GROUP_INPUT_SCHEMA,
|
|
140
|
+
outputSchema: {
|
|
141
|
+
type: 'object',
|
|
142
|
+
properties: {
|
|
143
|
+
created: { type: 'boolean', description: 'op=create: true on success.' },
|
|
144
|
+
deleted: { type: 'boolean', description: 'op=delete: true on success.' },
|
|
145
|
+
ungrouped: { type: 'boolean', description: 'op=ungroup: true on success.' },
|
|
146
|
+
moved: { type: 'boolean', description: 'op=add_layer: true on success.' },
|
|
147
|
+
set: { type: 'boolean', description: 'op=set_blend_mode: true on success.' },
|
|
148
|
+
groupName: { type: 'string' },
|
|
149
|
+
layerName: { type: 'string', description: 'op=add_layer: the moved layer.' },
|
|
150
|
+
blendMode: { type: 'string', description: 'op=set_blend_mode: the mode applied.' },
|
|
151
|
+
moved_count: { type: 'number', description: 'op=create: layers moved into it.' },
|
|
152
|
+
not_found: {
|
|
153
|
+
type: 'array',
|
|
154
|
+
items: { type: 'string' },
|
|
155
|
+
description: 'op=create: requested layer names that were not found.',
|
|
156
|
+
},
|
|
157
|
+
hoisted: {
|
|
158
|
+
type: 'boolean',
|
|
159
|
+
description: 'op=create: true when the new group had to be moved back out of the previously-active group to honor into_active_group:false.',
|
|
160
|
+
},
|
|
161
|
+
parent_path: {
|
|
162
|
+
type: ['array', 'null'],
|
|
163
|
+
items: { type: 'string' },
|
|
164
|
+
description: 'op=create: the containing-group name chain (outermost first), empty array at the document root.',
|
|
165
|
+
},
|
|
166
|
+
descendants_deleted: {
|
|
167
|
+
type: 'number',
|
|
168
|
+
description: 'op=delete: total layers removed with the group.',
|
|
169
|
+
},
|
|
170
|
+
children_promoted: {
|
|
171
|
+
type: 'number',
|
|
172
|
+
description: 'op=ungroup: children promoted to the parent level.',
|
|
173
|
+
},
|
|
174
|
+
child_names: {
|
|
175
|
+
type: 'array',
|
|
176
|
+
items: { type: 'string' },
|
|
177
|
+
description: 'op=ungroup: names of the promoted children.',
|
|
178
|
+
},
|
|
179
|
+
context: { type: 'object' },
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
annotations: {
|
|
183
|
+
title: 'Group',
|
|
184
|
+
destructiveHint: true,
|
|
185
|
+
idempotentHint: false,
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
handler: async (args) => groupDispatch(connection, snippetClient, args),
|
|
189
|
+
},
|
|
88
190
|
{
|
|
89
191
|
tool: {
|
|
90
192
|
name: 'ps_create_group',
|
|
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.`,
|
|
193
|
+
description: `DEPRECATED — use ps_group(op=create) instead (kept for one release for backward compatibility, identical behaviour). 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.`,
|
|
92
194
|
inputSchema: createGroupSchema,
|
|
93
195
|
outputSchema: {
|
|
94
196
|
type: 'object',
|
|
@@ -119,7 +221,7 @@ export function createGroupTools(connection, snippetClient) {
|
|
|
119
221
|
{
|
|
120
222
|
tool: {
|
|
121
223
|
name: 'ps_move_layer_to_group',
|
|
122
|
-
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.',
|
|
224
|
+
description: 'DEPRECATED — use ps_group(op=add_layer) instead (kept for one release for backward compatibility, identical behaviour). 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.',
|
|
123
225
|
inputSchema: moveLayerToGroupSchema,
|
|
124
226
|
outputSchema: {
|
|
125
227
|
type: 'object',
|
|
@@ -140,7 +242,7 @@ export function createGroupTools(connection, snippetClient) {
|
|
|
140
242
|
{
|
|
141
243
|
tool: {
|
|
142
244
|
name: 'ps_set_group_blend_mode',
|
|
143
|
-
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.",
|
|
245
|
+
description: "DEPRECATED — use ps_group(op=set_blend_mode) instead (kept for one release for backward compatibility, identical behaviour). 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.",
|
|
144
246
|
inputSchema: setGroupBlendModeSchema,
|
|
145
247
|
outputSchema: {
|
|
146
248
|
type: 'object',
|
|
@@ -161,7 +263,7 @@ export function createGroupTools(connection, snippetClient) {
|
|
|
161
263
|
{
|
|
162
264
|
tool: {
|
|
163
265
|
name: 'ps_ungroup',
|
|
164
|
-
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.',
|
|
266
|
+
description: 'DEPRECATED — use ps_group(op=ungroup) instead (kept for one release for backward compatibility, identical behaviour). 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.',
|
|
165
267
|
inputSchema: ungroupSchema,
|
|
166
268
|
outputSchema: {
|
|
167
269
|
type: 'object',
|
|
@@ -183,48 +285,30 @@ export function createGroupTools(connection, snippetClient) {
|
|
|
183
285
|
},
|
|
184
286
|
{
|
|
185
287
|
tool: {
|
|
186
|
-
name: '
|
|
187
|
-
description: 'Clip the active layer
|
|
188
|
-
inputSchema:
|
|
288
|
+
name: 'ps_clipping_mask',
|
|
289
|
+
description: 'Clip or un-clip the active layer against the layer directly below it — choose with `op`. `create`: use the layer below as the alpha source; PS paints the active layer only where the layer below has pixels. Non-destructive — the upper layer is unchanged. Common for constraining a texture/photo to a shape, or masking an effect to a single underlying layer (the add_adjustment_layer tool already accepts clip_to_below for the adjustment-layer-specific case). Equivalent to Layer > Create Clipping Mask (Ctrl+Alt+G). `release`: the inverse — the layer returns to compositing against the whole canvas. Both ops are idempotent: create no-ops (already_clipped:true) on an already-clipped layer; release no-ops (released:false) on a non-clipped layer.',
|
|
290
|
+
inputSchema: clippingMaskSchema,
|
|
189
291
|
outputSchema: {
|
|
190
292
|
type: 'object',
|
|
191
293
|
properties: {
|
|
192
294
|
clipped: { type: 'boolean' },
|
|
193
|
-
|
|
194
|
-
context: { type: 'object' },
|
|
195
|
-
},
|
|
196
|
-
},
|
|
197
|
-
annotations: {
|
|
198
|
-
title: 'Create Clipping Mask',
|
|
199
|
-
idempotentHint: false,
|
|
200
|
-
},
|
|
201
|
-
},
|
|
202
|
-
handler: async (args) => createClippingMask(connection, snippetClient, args),
|
|
203
|
-
},
|
|
204
|
-
{
|
|
205
|
-
tool: {
|
|
206
|
-
name: 'ps_release_clipping_mask',
|
|
207
|
-
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).",
|
|
208
|
-
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
209
|
-
outputSchema: {
|
|
210
|
-
type: 'object',
|
|
211
|
-
properties: {
|
|
295
|
+
already_clipped: { type: 'boolean' },
|
|
212
296
|
released: { type: 'boolean' },
|
|
213
297
|
layerName: { type: 'string' },
|
|
214
298
|
context: { type: 'object' },
|
|
215
299
|
},
|
|
216
300
|
},
|
|
217
301
|
annotations: {
|
|
218
|
-
title: '
|
|
302
|
+
title: 'Clipping Mask',
|
|
219
303
|
idempotentHint: true,
|
|
220
304
|
},
|
|
221
305
|
},
|
|
222
|
-
handler: async (args) =>
|
|
306
|
+
handler: async (args) => clippingMask(connection, snippetClient, args),
|
|
223
307
|
},
|
|
224
308
|
{
|
|
225
309
|
tool: {
|
|
226
310
|
name: 'ps_delete_group',
|
|
227
|
-
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.',
|
|
311
|
+
description: 'DEPRECATED — use ps_group(op=delete) instead (kept for one release for backward compatibility, identical behaviour). 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.',
|
|
228
312
|
inputSchema: deleteGroupSchema,
|
|
229
313
|
outputSchema: {
|
|
230
314
|
type: 'object',
|
|
@@ -245,27 +329,61 @@ export function createGroupTools(connection, snippetClient) {
|
|
|
245
329
|
},
|
|
246
330
|
];
|
|
247
331
|
}
|
|
248
|
-
async function
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
332
|
+
async function clippingMask(connection, snippetClient, rawArgs) {
|
|
333
|
+
const op = rawArgs.op;
|
|
334
|
+
const { op: _omit, ...rest } = rawArgs;
|
|
335
|
+
switch (op) {
|
|
336
|
+
case 'create':
|
|
337
|
+
return runSnippetTool({
|
|
338
|
+
connection,
|
|
339
|
+
snippetClient,
|
|
340
|
+
rawArgs: rest,
|
|
341
|
+
schema: clippingMaskOpArgsSchema,
|
|
342
|
+
snippet: 'createClippingMask',
|
|
343
|
+
errorPrefix: 'Error creating clipping mask',
|
|
344
|
+
successText: (result) => {
|
|
345
|
+
const r = result;
|
|
346
|
+
return r.already_clipped === true
|
|
347
|
+
? `Layer "${r.layerName ?? ''}" is already clipped — nothing to do.`
|
|
348
|
+
: `Clipped layer "${r.layerName ?? ''}" to the layer below.`;
|
|
349
|
+
},
|
|
350
|
+
});
|
|
351
|
+
case 'release':
|
|
352
|
+
return runSnippetTool({
|
|
353
|
+
connection,
|
|
354
|
+
snippetClient,
|
|
355
|
+
rawArgs: rest,
|
|
356
|
+
schema: clippingMaskOpArgsSchema,
|
|
357
|
+
snippet: 'releaseClippingMask',
|
|
358
|
+
errorPrefix: 'Error releasing clipping mask',
|
|
359
|
+
successText: (result) => {
|
|
360
|
+
const r = result;
|
|
361
|
+
return r.released === false
|
|
362
|
+
? `Layer "${r.layerName ?? ''}" is not clipped — nothing to release.`
|
|
363
|
+
: `Released clipping mask on layer "${r.layerName ?? ''}".`;
|
|
364
|
+
},
|
|
365
|
+
});
|
|
366
|
+
default:
|
|
367
|
+
return unknownDiscriminator('clipping_mask op', op, CLIPPING_MASK_OPS);
|
|
368
|
+
}
|
|
258
369
|
}
|
|
259
|
-
async function
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
370
|
+
async function groupDispatch(connection, snippetClient, rawArgs) {
|
|
371
|
+
const op = rawArgs.op;
|
|
372
|
+
const { op: _omit, ...rest } = rawArgs;
|
|
373
|
+
switch (op) {
|
|
374
|
+
case 'create':
|
|
375
|
+
return createGroup(connection, snippetClient, rest);
|
|
376
|
+
case 'delete':
|
|
377
|
+
return deleteGroup(connection, snippetClient, rest);
|
|
378
|
+
case 'ungroup':
|
|
379
|
+
return ungroup(connection, snippetClient, rest);
|
|
380
|
+
case 'add_layer':
|
|
381
|
+
return moveLayerToGroup(connection, snippetClient, rest);
|
|
382
|
+
case 'set_blend_mode':
|
|
383
|
+
return setGroupBlendMode(connection, snippetClient, rest);
|
|
384
|
+
default:
|
|
385
|
+
return unknownDiscriminator('group op', op, GROUP_OPS);
|
|
386
|
+
}
|
|
269
387
|
}
|
|
270
388
|
async function createGroup(connection, snippetClient, rawArgs) {
|
|
271
389
|
return runSnippetTool({
|
|
@@ -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 (ps_merge mode=visible/flatten,
|
|
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_filter op=apply 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: {
|
|
@@ -2,7 +2,14 @@ import { getMetadata, getMetadataSchema } from './metadata-tools.js';
|
|
|
2
2
|
import { getHistory } from './history-tools.js';
|
|
3
3
|
import { getLayerTree } from './layer-tools.js';
|
|
4
4
|
import { getSelectionInfoHandler } from './selection-tools.js';
|
|
5
|
-
|
|
5
|
+
import { getSmartObjectInfoHandler } from './smart-object-tools.js';
|
|
6
|
+
const INSPECT_WHATS = [
|
|
7
|
+
'metadata',
|
|
8
|
+
'layer_tree',
|
|
9
|
+
'history',
|
|
10
|
+
'selection_info',
|
|
11
|
+
'smart_object',
|
|
12
|
+
];
|
|
6
13
|
const INSPECT_INPUT_SCHEMA = {
|
|
7
14
|
type: 'object',
|
|
8
15
|
properties: {
|
|
@@ -13,7 +20,8 @@ const INSPECT_INPUT_SCHEMA = {
|
|
|
13
20
|
'metadata: document/IPTC/camera-EXIF/GPS/ACR develop settings + active context (optionally subset with `sections`; sections=["context"] is the cheap orientation probe). ' +
|
|
14
21
|
'layer_tree: the full recursive layer tree (name/kind/visibility/opacity/blend/clipping/bounds) — use whenever you need what is inside a group. ' +
|
|
15
22
|
'history: all history states + the current cursor, for deciding how far to undo. ' +
|
|
16
|
-
'selection_info: current selection bounds/coverage/edge-complexity without modifying anything.'
|
|
23
|
+
'selection_info: current selection bounds/coverage/edge-complexity without modifying anything. ' +
|
|
24
|
+
'smart_object: whether the ACTIVE layer is a Smart Object and, if so, whether its source is embedded or linked to a file on disk, plus how many Smart Filters it carries.',
|
|
17
25
|
},
|
|
18
26
|
...getMetadataSchema.properties,
|
|
19
27
|
},
|
|
@@ -24,7 +32,7 @@ export function createInspectTools(connection, snippetClient) {
|
|
|
24
32
|
{
|
|
25
33
|
tool: {
|
|
26
34
|
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.',
|
|
35
|
+
description: 'Read-only document inspection — choose with `what` (metadata / layer_tree / history / selection_info / smart_object). 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
36
|
inputSchema: INSPECT_INPUT_SCHEMA,
|
|
29
37
|
outputSchema: {
|
|
30
38
|
type: 'object',
|
|
@@ -47,6 +55,15 @@ export function createInspectTools(connection, snippetClient) {
|
|
|
47
55
|
canRedo: { type: 'boolean' },
|
|
48
56
|
states: { type: 'array' },
|
|
49
57
|
selection_info: { type: 'object' },
|
|
58
|
+
is_smart_object: { type: 'boolean' },
|
|
59
|
+
linked: { type: 'boolean' },
|
|
60
|
+
file_reference: { type: ['string', 'null'] },
|
|
61
|
+
document_id: { type: ['string', 'null'] },
|
|
62
|
+
placed: { type: ['string', 'null'] },
|
|
63
|
+
smart_filter_count: { type: 'number' },
|
|
64
|
+
layer_name: { type: 'string' },
|
|
65
|
+
layer_kind: { type: 'string' },
|
|
66
|
+
bounds: { type: 'array' },
|
|
50
67
|
},
|
|
51
68
|
},
|
|
52
69
|
annotations: {
|
|
@@ -72,6 +89,8 @@ async function inspect(connection, snippetClient, rawArgs) {
|
|
|
72
89
|
return getHistory(connection, snippetClient);
|
|
73
90
|
case 'selection_info':
|
|
74
91
|
return getSelectionInfoHandler(connection, snippetClient);
|
|
92
|
+
case 'smart_object':
|
|
93
|
+
return getSmartObjectInfoHandler(connection, snippetClient);
|
|
75
94
|
default:
|
|
76
95
|
return {
|
|
77
96
|
content: [
|
|
@@ -19,7 +19,7 @@ const deleteLayerSchema = {
|
|
|
19
19
|
},
|
|
20
20
|
};
|
|
21
21
|
const PS_TEXT_COORD_MAX = 300_000;
|
|
22
|
-
const createTextLayerSchema = {
|
|
22
|
+
export const createTextLayerSchema = {
|
|
23
23
|
type: 'object',
|
|
24
24
|
properties: {
|
|
25
25
|
text: {
|
|
@@ -195,7 +195,7 @@ export function createLayerTools(connection, snippetClient) {
|
|
|
195
195
|
{
|
|
196
196
|
tool: {
|
|
197
197
|
name: 'ps_create_layer',
|
|
198
|
-
description: 'Create a new empty raster layer above the currently active layer. Non-destructive. Use
|
|
198
|
+
description: 'Create a new empty raster layer above the currently active layer. Non-destructive. Use ps_text (op=create) for text, ps_add_adjustment_layer for adjustments.',
|
|
199
199
|
inputSchema: createLayerSchema,
|
|
200
200
|
outputSchema: {
|
|
201
201
|
type: 'object',
|
|
@@ -241,7 +241,7 @@ export function createLayerTools(connection, snippetClient) {
|
|
|
241
241
|
{
|
|
242
242
|
tool: {
|
|
243
243
|
name: 'ps_create_text_layer',
|
|
244
|
-
description: 'Create a new text layer with the given content, position, and font size. The text is editable (vector). Use
|
|
244
|
+
description: 'DEPRECATED — use ps_text(op=create) instead (kept for one release for backward compatibility, identical behaviour). Create a new text layer with the given content, position, and font size. The text is editable (vector). Use ps_text (op=set_font / set_color / set_alignment) afterwards to style.',
|
|
245
245
|
inputSchema: createTextLayerSchema,
|
|
246
246
|
outputSchema: {
|
|
247
247
|
type: 'object',
|
|
@@ -382,7 +382,7 @@ async function deleteLayer(connection, snippetClient, rawArgs) {
|
|
|
382
382
|
},
|
|
383
383
|
});
|
|
384
384
|
}
|
|
385
|
-
async function createTextLayer(connection, snippetClient, rawArgs) {
|
|
385
|
+
export async function createTextLayer(connection, snippetClient, rawArgs) {
|
|
386
386
|
return runSnippetTool({
|
|
387
387
|
connection,
|
|
388
388
|
snippetClient,
|
|
@@ -19,6 +19,8 @@ Every non-trivial edit follows the same six phases:
|
|
|
19
19
|
|
|
20
20
|
1. **Assess** — \`ps_inspect\` (what=metadata / layer_tree / history /
|
|
21
21
|
selection_info) + \`ps_get_preview\` to understand what you're working with.
|
|
22
|
+
If \`ps_open_document\` reported \`is_raw_source: true\`, see
|
|
23
|
+
"Raw-sourced documents" below.
|
|
22
24
|
2. **Plan** — name the intent, the layers/regions involved, and the
|
|
23
25
|
exit criteria. Tell the user before you execute.
|
|
24
26
|
3. **Enact** — call the tools.
|
|
@@ -31,6 +33,33 @@ Every non-trivial edit follows the same six phases:
|
|
|
31
33
|
\`ps_save_psd\`. Confirm the user wants to flatten before
|
|
32
34
|
destructive saves.
|
|
33
35
|
|
|
36
|
+
## Raw-sourced documents
|
|
37
|
+
|
|
38
|
+
\`ps_open_document\` reports \`is_raw_source: true\` for raw captures
|
|
39
|
+
(DNG, NEF, CR3, ARW, …) — the open applied last-used/default Camera
|
|
40
|
+
Raw settings, so no deliberate develop has happened yet. When
|
|
41
|
+
\`tools/list\` includes a camera-raw develop tool, the FIRST enacting
|
|
42
|
+
step on a raw document is that develop pass on the base smart object —
|
|
43
|
+
NOT stacked Levels/Curves adjustment layers. The develop pass owns
|
|
44
|
+
global tone and color; adjustment layers come after, for local/masked
|
|
45
|
+
corrections and finishing moves.
|
|
46
|
+
|
|
47
|
+
Make the first develop pass a complete grade in ONE call, not a timid
|
|
48
|
+
nudge: tone endpoints (exposure, contrast, highlights/shadows AND
|
|
49
|
+
whites/blacks), presence (texture, clarity, dehaze), a parametric
|
|
50
|
+
curve, white balance + vibrance + per-channel HSL where colors need
|
|
51
|
+
steering, color grading, capture sharpening + noise reduction (raws
|
|
52
|
+
get none by default), optics/vignette where they serve the image.
|
|
53
|
+
Three sliders at small values reads as "untouched." Large moves are
|
|
54
|
+
safe — the develop is a re-editable smart filter, nothing bakes:
|
|
55
|
+
start bold, check preview + histogram, ease off.
|
|
56
|
+
|
|
57
|
+
To refine, call the develop tool again in its adjust-existing mode —
|
|
58
|
+
it preserves every slider you don't pass. Never add a second
|
|
59
|
+
camera-raw filter. If no camera-raw develop tool is in \`tools/list\`,
|
|
60
|
+
say so in one sentence and build global tone with adjustment layers.
|
|
61
|
+
Explicit user instructions always win.
|
|
62
|
+
|
|
34
63
|
## Capabilities map
|
|
35
64
|
|
|
36
65
|
This map covers the workflow categories. For specific tool names and
|
|
@@ -40,8 +69,9 @@ schemas in this session, consult \`tools/list\`.
|
|
|
40
69
|
covers brightness/contrast, levels, curves, hue/sat, color balance,
|
|
41
70
|
photo filter, vibrance, channel mixer, selective color, black & white,
|
|
42
71
|
gradient map, exposure, color lookup, invert. Always prefer this over
|
|
43
|
-
destructive filters for tone/color work
|
|
44
|
-
-
|
|
72
|
+
destructive filters for tone/color work — except raw-sourced
|
|
73
|
+
documents: the camera-raw develop pass goes first (see above).
|
|
74
|
+
- **Filters (destructive)** — \`ps_filter\` with a
|
|
45
75
|
\`type\` (gaussian_blur, motion_blur, lens_blur, radial_blur, sharpen,
|
|
46
76
|
smart_sharpen, noise, reduce_noise, high_pass, pixelate, distort,
|
|
47
77
|
displace, oil_paint). Auto-duplicates the active layer by default so
|
|
@@ -81,16 +111,15 @@ schemas in this session, consult \`tools/list\`.
|
|
|
81
111
|
locally. Reach for these to get REAL coordinates rather than
|
|
82
112
|
estimating from the preview — they're the antidote to spatial
|
|
83
113
|
guessing.
|
|
84
|
-
- **Text** — \`
|
|
85
|
-
|
|
86
|
-
|
|
114
|
+
- **Text** — \`ps_text\` (op create, then set_font / set_color /
|
|
115
|
+
set_alignment / set_content; font resolves family names to
|
|
116
|
+
PostScript).
|
|
87
117
|
- **Image** — \`ps_place_image\` (links external files as smart
|
|
88
118
|
objects), \`ps_resize_image\`, \`ps_crop_document\`,
|
|
89
119
|
\`ps_convert_image_mode\`, open / close / save_psd, and
|
|
90
120
|
\`ps_export\` (format jpeg/png).
|
|
91
|
-
- **Groups** — \`
|
|
92
|
-
|
|
93
|
-
\`ps_ungroup\`, \`ps_delete_group\`.
|
|
121
|
+
- **Groups** — \`ps_group\` (op create / delete / ungroup /
|
|
122
|
+
add_layer / set_blend_mode).
|
|
94
123
|
- **Verification** — see below; this is the most underused part of
|
|
95
124
|
the surface.
|
|
96
125
|
- **Templates** — when template tools are present in \`tools/list\`,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
|
-
import { getContextInfo, normNameHelper } from '../api/extendscript/_helpers.js';
|
|
2
|
+
import { getContextInfo, normNameHelper, notFoundMessageHelper, } from '../api/extendscript/_helpers.js';
|
|
3
3
|
import { jsLit, jsNum } from '../utils/jsx.js';
|
|
4
4
|
import { runScript } from '../utils/run-script.js';
|
|
5
5
|
import { TempDir, userOwnedTempRoot } from '../utils/temp.js';
|
|
@@ -899,6 +899,7 @@ async function getLayerBoundsDiff(connection, rawArgs) {
|
|
|
899
899
|
var doc = app.activeDocument;
|
|
900
900
|
|
|
901
901
|
${normNameHelper}
|
|
902
|
+
${notFoundMessageHelper}
|
|
902
903
|
// Em-dash / en-dash tolerant comparison (Bug I) via normName — the
|
|
903
904
|
// LLM routinely swaps these silently; raw equality would miss.
|
|
904
905
|
// Depth cap is defense-in-depth (see selectLayer's identical comment).
|
|
@@ -919,7 +920,7 @@ async function getLayerBoundsDiff(connection, rawArgs) {
|
|
|
919
920
|
return null;
|
|
920
921
|
}
|
|
921
922
|
var lyr = findLayerByName(doc.layers);
|
|
922
|
-
if (!lyr) throw new Error('Layer
|
|
923
|
+
if (!lyr) throw new Error(__notFoundMessage('Layer', ${jsLit(layer)}, false));
|
|
923
924
|
|
|
924
925
|
var b = (lyr.boundsNoEffects !== undefined) ? lyr.boundsNoEffects : lyr.bounds;
|
|
925
926
|
var aLeft = b[0].as('px');
|
|
@@ -367,7 +367,7 @@ export function createSceneTools(connection, snippetClient, opts = {}) {
|
|
|
367
367
|
{
|
|
368
368
|
tool: {
|
|
369
369
|
name: 'ps_read_scene',
|
|
370
|
-
description: '
|
|
370
|
+
description: 'The full scene model — run this before a spatially-targeted edit, not the cheaper ps_detect: detected subjects (with the main one flagged) and faces in document pixels, a coarse sky/ground region map, the horizon line (y + placement + confidence), tonal zones (shadow/midtone/highlight bands + coverage), composition geometry (which thirds cell the subject sits in, balance, headroom), plus an annotated preview and the menu of selectable named regions. Built using LOCAL on-device vision + classical CV; the image never leaves the machine. Select regions by name with ps_select_by_reference instead of guessing a rectangle. Read-only: renders a throwaway duplicate. Perception is cached per document state, so repeated reads are cheap.',
|
|
371
371
|
inputSchema: sceneSchema,
|
|
372
372
|
outputSchema: {
|
|
373
373
|
type: 'object',
|