editmamei 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/api/extendscript/_helpers.js +9 -3
- package/dist/api/photoshop-api.js +16 -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/help.js +1 -0
- package/dist/core/server.js +91 -17
- package/dist/core/tool-groups.js +5 -9
- package/dist/core/tool-registry.js +3 -0
- package/dist/core/tool-tiers.js +4 -8
- package/dist/delivery/provision.js +2 -2
- package/dist/kernel/module-lifecycle.js +30 -4
- package/dist/modules/ce/index.js +2 -0
- package/dist/perception/region-precompute.js +39 -0
- package/dist/skills/editmamei-skill.zip +0 -0
- package/dist/tools/document-tools.js +143 -2
- package/dist/tools/filter-tools.js +0 -14
- package/dist/tools/group-tools.js +1 -119
- package/dist/tools/layer-tools.js +1 -24
- package/dist/tools/scene-tools.js +45 -21
- package/dist/tools/selection-tools.js +72 -1
- package/dist/tools/sky-tools.js +126 -0
- package/dist/tools/text-tools.js +0 -69
- package/dist/update/check.js +65 -6
- package/dist/update/session-fixes.js +33 -0
- package/dist/utils/jsx.js +2 -1
- package/dist/utils/operation-timeouts.js +2 -0
- package/dist/utils/session-log.js +17 -0
- package/dist/version.js +1 -1
- package/package.json +15 -1
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { runSnippetTool } from '../utils/tool-helpers.js';
|
|
2
|
+
import { SKY_REPLACEMENT_TIMEOUT_MS } from '../utils/operation-timeouts.js';
|
|
3
|
+
const PLACEHOLDER_SKY_ID = '00000000-0000-0000-0000-000000000000';
|
|
4
|
+
const replaceSkyInputSchema = {
|
|
5
|
+
type: 'object',
|
|
6
|
+
properties: {
|
|
7
|
+
sky_file: {
|
|
8
|
+
type: 'string',
|
|
9
|
+
description: 'ABSOLUTE path to the image to use as the sky. Any image Photoshop can open works — it does not need to be a registered Photoshop sky preset. The built-in skies live as .jpg files under the Sky_Presets folder inside your Photoshop settings directory, and their paths can be passed here directly.',
|
|
10
|
+
},
|
|
11
|
+
sky_name: {
|
|
12
|
+
type: 'string',
|
|
13
|
+
description: 'Label recorded on the operation. Cosmetic — it does not select the sky, sky_file does.',
|
|
14
|
+
default: 'Custom Sky',
|
|
15
|
+
},
|
|
16
|
+
shift_edge: {
|
|
17
|
+
type: 'number',
|
|
18
|
+
description: 'Moves the sky/foreground boundary inward (negative) or outward (positive). Use it when the horizon leaves a halo or eats into the foreground.',
|
|
19
|
+
default: 0,
|
|
20
|
+
minimum: -100,
|
|
21
|
+
maximum: 100,
|
|
22
|
+
},
|
|
23
|
+
border_smoothness: {
|
|
24
|
+
type: 'number',
|
|
25
|
+
description: 'Softens the boundary between sky and foreground. 0 is a hard cut.',
|
|
26
|
+
default: 50,
|
|
27
|
+
minimum: 0,
|
|
28
|
+
maximum: 100,
|
|
29
|
+
},
|
|
30
|
+
brightness: {
|
|
31
|
+
type: 'number',
|
|
32
|
+
description: 'Brightness of the replaced sky itself.',
|
|
33
|
+
default: 0,
|
|
34
|
+
minimum: -100,
|
|
35
|
+
maximum: 100,
|
|
36
|
+
},
|
|
37
|
+
temperature: {
|
|
38
|
+
type: 'number',
|
|
39
|
+
description: 'Warms (positive) or cools (negative) the replaced sky.',
|
|
40
|
+
default: 0,
|
|
41
|
+
minimum: -100,
|
|
42
|
+
maximum: 100,
|
|
43
|
+
},
|
|
44
|
+
harmonization_opacity: {
|
|
45
|
+
type: 'number',
|
|
46
|
+
description: 'How strongly the foreground is colour-graded to match the new sky. This is what sells the composite — 0 leaves the foreground untouched and usually reads as pasted-on.',
|
|
47
|
+
default: 35,
|
|
48
|
+
minimum: 0,
|
|
49
|
+
maximum: 100,
|
|
50
|
+
},
|
|
51
|
+
foreground_lighting_opacity: {
|
|
52
|
+
type: 'number',
|
|
53
|
+
description: 'Strength of the relighting applied to the foreground so it appears lit by the new sky.',
|
|
54
|
+
default: 78,
|
|
55
|
+
minimum: 0,
|
|
56
|
+
maximum: 100,
|
|
57
|
+
},
|
|
58
|
+
edge_lighting_opacity: {
|
|
59
|
+
type: 'number',
|
|
60
|
+
description: 'Strength of the light wrap along the foreground edge where it meets the sky.',
|
|
61
|
+
default: 70,
|
|
62
|
+
minimum: 0,
|
|
63
|
+
maximum: 100,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
required: ['sky_file'],
|
|
67
|
+
};
|
|
68
|
+
async function replaceSky(connection, snippetClient, rawArgs) {
|
|
69
|
+
return runSnippetTool({
|
|
70
|
+
connection,
|
|
71
|
+
snippetClient,
|
|
72
|
+
rawArgs,
|
|
73
|
+
schema: replaceSkyInputSchema,
|
|
74
|
+
snippet: 'replaceSky',
|
|
75
|
+
errorPrefix: 'Error running Sky Replacement',
|
|
76
|
+
timeoutMs: SKY_REPLACEMENT_TIMEOUT_MS,
|
|
77
|
+
params: (args) => ({
|
|
78
|
+
skyPath: args.sky_file,
|
|
79
|
+
skyName: args.sky_name ?? 'Custom Sky',
|
|
80
|
+
skyId: PLACEHOLDER_SKY_ID,
|
|
81
|
+
shiftEdge: args.shift_edge ?? 0,
|
|
82
|
+
borderSmoothness: args.border_smoothness ?? 50,
|
|
83
|
+
brightness: args.brightness ?? 0,
|
|
84
|
+
temperature: args.temperature ?? 0,
|
|
85
|
+
harmonizationOpacity: args.harmonization_opacity ?? 35,
|
|
86
|
+
foregroundLightingOpacity: args.foreground_lighting_opacity ?? 78,
|
|
87
|
+
edgeLightingOpacity: args.edge_lighting_opacity ?? 70,
|
|
88
|
+
}),
|
|
89
|
+
successText: (_result, args) => `Sky replaced using ${String(args.sky_file)}`,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
export function createSkyTools(connection, snippetClient) {
|
|
93
|
+
return [
|
|
94
|
+
{
|
|
95
|
+
tool: {
|
|
96
|
+
name: 'ps_replace_sky',
|
|
97
|
+
description: 'Run Photoshop\'s Sky Replacement (Adobe Sensei): detect the sky, composite a replacement, and relight/colour-grade the foreground to match. Non-destructive — everything lands in a "Sky Replacement Group" of editable layers above the original, so the result can be tuned or deleted afterwards. sky_file takes an ABSOLUTE path to ANY image on disk, not only Photoshop\'s built-in presets. Reach for harmonization_opacity and foreground_lighting_opacity when the composite reads as pasted-on; those two carry most of the believability. Fails cleanly when the image has no detectable sky (indoor or closed compositions).',
|
|
98
|
+
inputSchema: replaceSkyInputSchema,
|
|
99
|
+
outputSchema: {
|
|
100
|
+
type: 'object',
|
|
101
|
+
properties: {
|
|
102
|
+
replaced: { type: 'boolean' },
|
|
103
|
+
strategy_used: { type: 'string' },
|
|
104
|
+
group_name: {
|
|
105
|
+
type: 'string',
|
|
106
|
+
description: 'Name of the layer group Photoshop created.',
|
|
107
|
+
},
|
|
108
|
+
group_layers: {
|
|
109
|
+
type: 'array',
|
|
110
|
+
items: { type: 'string' },
|
|
111
|
+
description: 'Layers inside the group, top to bottom — typically the sky layer, an edge-lighting group, a foreground-lighting layer and a foreground-colour curves layer.',
|
|
112
|
+
},
|
|
113
|
+
sky_file: { type: 'string' },
|
|
114
|
+
sky_name: { type: 'string' },
|
|
115
|
+
context: { type: 'object' },
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
annotations: {
|
|
119
|
+
title: 'Replace Sky (Sensei)',
|
|
120
|
+
idempotentHint: false,
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
handler: async (args) => replaceSky(connection, snippetClient, args),
|
|
124
|
+
},
|
|
125
|
+
];
|
|
126
|
+
}
|
package/dist/tools/text-tools.js
CHANGED
|
@@ -70,24 +70,6 @@ const updateTextContentSchema = {
|
|
|
70
70
|
},
|
|
71
71
|
required: ['text'],
|
|
72
72
|
};
|
|
73
|
-
const SET_TEXT_PROPERTIES = ['font', 'color', 'alignment', 'content'];
|
|
74
|
-
const SET_TEXT_INPUT_SCHEMA = {
|
|
75
|
-
type: 'object',
|
|
76
|
-
properties: {
|
|
77
|
-
property: {
|
|
78
|
-
type: 'string',
|
|
79
|
-
enum: [...SET_TEXT_PROPERTIES],
|
|
80
|
-
description: 'Which text attribute to set on the active text layer: ' +
|
|
81
|
-
'font(font_name, optional font_size); color(red, green, blue); ' +
|
|
82
|
-
'alignment(alignment); content(text).',
|
|
83
|
-
},
|
|
84
|
-
...setTextFontSchema.properties,
|
|
85
|
-
...setTextColorSchema.properties,
|
|
86
|
-
...setTextAlignmentSchema.properties,
|
|
87
|
-
...updateTextContentSchema.properties,
|
|
88
|
-
},
|
|
89
|
-
required: ['property'],
|
|
90
|
-
};
|
|
91
73
|
const TEXT_OPS = ['create', 'set_content', 'set_font', 'set_color', 'set_alignment'];
|
|
92
74
|
export const TEXT_OP_SCHEMAS = {
|
|
93
75
|
create: createTextLayerSchema,
|
|
@@ -161,33 +143,6 @@ export function createTextTools(connection, snippetClient) {
|
|
|
161
143
|
},
|
|
162
144
|
handler: async (args) => textDispatch(connection, snippetClient, args),
|
|
163
145
|
},
|
|
164
|
-
{
|
|
165
|
-
tool: {
|
|
166
|
-
name: 'ps_set_text',
|
|
167
|
-
description: 'DEPRECATED — use ps_text(op=set_font / set_color / set_alignment / set_content) instead (kept for one release for backward compatibility, identical behaviour). Set an attribute of the currently active text layer — font (family or PostScript name, optionally size), color (RGB), alignment, or text content — selected via `property`. Idempotent. Throws if the active layer is not a text layer. Font names accept either the PostScript name ("ArialMT") or family name ("Arial", resolved to its Regular/first variant); throws clearly if no installed font matches.',
|
|
168
|
-
inputSchema: SET_TEXT_INPUT_SCHEMA,
|
|
169
|
-
outputSchema: {
|
|
170
|
-
type: 'object',
|
|
171
|
-
properties: {
|
|
172
|
-
requested: { type: 'string' },
|
|
173
|
-
font: { type: 'string' },
|
|
174
|
-
size: { type: 'number' },
|
|
175
|
-
matched_by: {
|
|
176
|
-
type: 'string',
|
|
177
|
-
enum: ['postScriptName', 'family+regular', 'family', 'name'],
|
|
178
|
-
},
|
|
179
|
-
color: { type: 'string' },
|
|
180
|
-
alignment: { type: 'string' },
|
|
181
|
-
text: { type: 'string' },
|
|
182
|
-
},
|
|
183
|
-
},
|
|
184
|
-
annotations: {
|
|
185
|
-
title: 'Set Text',
|
|
186
|
-
idempotentHint: true,
|
|
187
|
-
},
|
|
188
|
-
},
|
|
189
|
-
handler: async (args) => setText(connection, snippetClient, args),
|
|
190
|
-
},
|
|
191
146
|
];
|
|
192
147
|
}
|
|
193
148
|
async function textDispatch(connection, snippetClient, rawArgs) {
|
|
@@ -208,30 +163,6 @@ async function textDispatch(connection, snippetClient, rawArgs) {
|
|
|
208
163
|
return unknownDiscriminator('text op', op, TEXT_OPS);
|
|
209
164
|
}
|
|
210
165
|
}
|
|
211
|
-
async function setText(connection, snippetClient, rawArgs) {
|
|
212
|
-
const property = rawArgs.property;
|
|
213
|
-
const { property: _omit, ...rest } = rawArgs;
|
|
214
|
-
switch (property) {
|
|
215
|
-
case 'font':
|
|
216
|
-
return setTextFont(connection, snippetClient, rest);
|
|
217
|
-
case 'color':
|
|
218
|
-
return setTextColor(connection, snippetClient, rest);
|
|
219
|
-
case 'alignment':
|
|
220
|
-
return setTextAlignment(connection, snippetClient, rest);
|
|
221
|
-
case 'content':
|
|
222
|
-
return updateTextContent(connection, snippetClient, rest);
|
|
223
|
-
default:
|
|
224
|
-
return {
|
|
225
|
-
content: [
|
|
226
|
-
{
|
|
227
|
-
type: 'text',
|
|
228
|
-
text: `Error: unknown text property "${String(property)}". Allowed: ${SET_TEXT_PROPERTIES.join(', ')}.`,
|
|
229
|
-
},
|
|
230
|
-
],
|
|
231
|
-
isError: true,
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
166
|
async function setTextFont(connection, snippetClient, rawArgs) {
|
|
236
167
|
return runSnippetTool({
|
|
237
168
|
connection,
|
package/dist/update/check.js
CHANGED
|
@@ -1,10 +1,33 @@
|
|
|
1
1
|
import { VERSION } from '../version.js';
|
|
2
2
|
import { resolveInstallChannel } from '../install-channel.js';
|
|
3
|
-
const
|
|
3
|
+
const DEFAULT_LATEST_MANIFEST_URL = 'https://registry.npmjs.org/editmamei/latest';
|
|
4
4
|
const RELEASES_URL = 'https://editmamei.com/download';
|
|
5
5
|
export function resolveUpdateCheckUrl(env = process.env) {
|
|
6
6
|
const override = env.EDITMAMEI_UPDATE_CHECK_URL;
|
|
7
|
-
return override && override.length > 0 ? override :
|
|
7
|
+
return override && override.length > 0 ? override : DEFAULT_LATEST_MANIFEST_URL;
|
|
8
|
+
}
|
|
9
|
+
const MAX_FIX_VERSIONS = 16;
|
|
10
|
+
const MAX_TOOLS_PER_VERSION = 16;
|
|
11
|
+
const MAX_TOOL_NAME_LENGTH = 64;
|
|
12
|
+
export function parseFixesByVersion(raw) {
|
|
13
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
|
|
14
|
+
return {};
|
|
15
|
+
const out = {};
|
|
16
|
+
let versions = 0;
|
|
17
|
+
for (const [version, tools] of Object.entries(raw)) {
|
|
18
|
+
if (versions >= MAX_FIX_VERSIONS)
|
|
19
|
+
break;
|
|
20
|
+
if (!parseSemver(version) || !Array.isArray(tools))
|
|
21
|
+
continue;
|
|
22
|
+
const names = tools
|
|
23
|
+
.filter((t) => typeof t === 'string' && t.length > 0 && t.length <= MAX_TOOL_NAME_LENGTH)
|
|
24
|
+
.slice(0, MAX_TOOLS_PER_VERSION);
|
|
25
|
+
if (names.length > 0) {
|
|
26
|
+
out[version] = names;
|
|
27
|
+
versions++;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
8
31
|
}
|
|
9
32
|
export function httpFetchLatest() {
|
|
10
33
|
return async (url, timeoutMs) => {
|
|
@@ -18,7 +41,12 @@ export function httpFetchLatest() {
|
|
|
18
41
|
if (!res.ok)
|
|
19
42
|
return null;
|
|
20
43
|
const data = (await res.json());
|
|
21
|
-
|
|
44
|
+
if (typeof data.version !== 'string')
|
|
45
|
+
return null;
|
|
46
|
+
return {
|
|
47
|
+
version: data.version,
|
|
48
|
+
fixesByVersion: parseFixesByVersion(data.editmamei?.fixesByVersion),
|
|
49
|
+
};
|
|
22
50
|
}
|
|
23
51
|
catch {
|
|
24
52
|
return null;
|
|
@@ -43,6 +71,30 @@ export function isNewer(latest, current) {
|
|
|
43
71
|
}
|
|
44
72
|
return false;
|
|
45
73
|
}
|
|
74
|
+
export function fixedToolsSince(fixesByVersion, current, latest) {
|
|
75
|
+
const versions = Object.keys(fixesByVersion)
|
|
76
|
+
.filter((v) => isNewer(v, current) && !isNewer(v, latest))
|
|
77
|
+
.sort((a, b) => {
|
|
78
|
+
const pa = parseSemver(a);
|
|
79
|
+
const pb = parseSemver(b);
|
|
80
|
+
for (let i = 0; i < 3; i++) {
|
|
81
|
+
if (pa[i] !== pb[i])
|
|
82
|
+
return pa[i] - pb[i];
|
|
83
|
+
}
|
|
84
|
+
return 0;
|
|
85
|
+
});
|
|
86
|
+
const seen = new Set();
|
|
87
|
+
const out = [];
|
|
88
|
+
for (const v of versions) {
|
|
89
|
+
for (const tool of fixesByVersion[v]) {
|
|
90
|
+
if (!seen.has(tool)) {
|
|
91
|
+
seen.add(tool);
|
|
92
|
+
out.push(tool);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
46
98
|
export function updateMessage(channel, latest) {
|
|
47
99
|
switch (channel) {
|
|
48
100
|
case 'mcpb':
|
|
@@ -64,12 +116,19 @@ export async function checkForUpdate(opts = {}) {
|
|
|
64
116
|
const current = opts.current ?? VERSION;
|
|
65
117
|
const channel = resolveInstallChannel(env);
|
|
66
118
|
const fetchLatest = opts.fetchLatest ?? httpFetchLatest();
|
|
67
|
-
const
|
|
68
|
-
if (!
|
|
119
|
+
const manifest = await fetchLatest(resolveUpdateCheckUrl(env), opts.timeoutMs ?? 4000);
|
|
120
|
+
if (!manifest || !parseSemver(manifest.version))
|
|
69
121
|
return null;
|
|
122
|
+
const latest = manifest.version;
|
|
70
123
|
if (!isNewer(latest, current))
|
|
71
124
|
return null;
|
|
72
|
-
return {
|
|
125
|
+
return {
|
|
126
|
+
current,
|
|
127
|
+
latest,
|
|
128
|
+
channel,
|
|
129
|
+
how_to_update: updateMessage(channel, latest),
|
|
130
|
+
fixed_tools: fixedToolsSince(manifest.fixesByVersion, current, latest),
|
|
131
|
+
};
|
|
73
132
|
}
|
|
74
133
|
catch {
|
|
75
134
|
return null;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { listRecentSessionIds, readSessionLog, } from '../utils/session-log-reader.js';
|
|
2
|
+
const RECENT_SESSION_SCAN_LIMIT = 5;
|
|
3
|
+
export async function previousSessionFailureCounts(currentSessionId, opts = {}) {
|
|
4
|
+
const ids = await listRecentSessionIds(RECENT_SESSION_SCAN_LIMIT, opts);
|
|
5
|
+
for (const id of ids) {
|
|
6
|
+
if (id === currentSessionId)
|
|
7
|
+
continue;
|
|
8
|
+
const entries = await readSessionLog(id, opts);
|
|
9
|
+
const counts = new Map();
|
|
10
|
+
let sawCall = false;
|
|
11
|
+
for (const entry of entries) {
|
|
12
|
+
if ('type' in entry && entry.type === 'meta')
|
|
13
|
+
continue;
|
|
14
|
+
const call = entry;
|
|
15
|
+
if (typeof call.tool !== 'string')
|
|
16
|
+
continue;
|
|
17
|
+
sawCall = true;
|
|
18
|
+
if (call.success === false) {
|
|
19
|
+
counts.set(call.tool, (counts.get(call.tool) ?? 0) + 1);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (sawCall)
|
|
23
|
+
return counts;
|
|
24
|
+
}
|
|
25
|
+
return new Map();
|
|
26
|
+
}
|
|
27
|
+
export function relevantFixes(failureCounts, fixedTools, cap = 3) {
|
|
28
|
+
return fixedTools
|
|
29
|
+
.filter((tool) => (failureCounts.get(tool) ?? 0) > 0)
|
|
30
|
+
.map((tool) => ({ tool, failures: failureCounts.get(tool) }))
|
|
31
|
+
.sort((a, b) => b.failures - a.failures)
|
|
32
|
+
.slice(0, cap);
|
|
33
|
+
}
|
package/dist/utils/jsx.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
const NON_ASCII = /[^ -~]/g;
|
|
1
2
|
export function jsLit(value) {
|
|
2
|
-
return JSON.stringify(String(value));
|
|
3
|
+
return JSON.stringify(String(value)).replace(NON_ASCII, (ch) => `\\u${ch.charCodeAt(0).toString(16).padStart(4, '0')}`);
|
|
3
4
|
}
|
|
4
5
|
export function jsNum(value, fallback) {
|
|
5
6
|
const n = typeof value === 'number' ? value : Number(value);
|
|
@@ -3,5 +3,7 @@ export const OPEN_DOCUMENT_REPROBE_TIMEOUT_MS = 10_000;
|
|
|
3
3
|
export const CAMERA_RAW_FILTER_TIMEOUT_MS = 120_000;
|
|
4
4
|
export const SELECT_SUBJECT_TIMEOUT_MS = 120_000;
|
|
5
5
|
export const SELECT_SKY_TIMEOUT_MS = 120_000;
|
|
6
|
+
export const SELECT_FOCUS_AREA_TIMEOUT_MS = 120_000;
|
|
7
|
+
export const SKY_REPLACEMENT_TIMEOUT_MS = 120_000;
|
|
6
8
|
export const ANNOTATED_PREVIEW_TIMEOUT_MS = 90_000;
|
|
7
9
|
export const SCENE_CHANNEL_TIMEOUT_MS = 120_000;
|
|
@@ -24,6 +24,7 @@ export function generateSessionId(now = new Date()) {
|
|
|
24
24
|
}
|
|
25
25
|
export const ERROR_CLASS_TABLE = [
|
|
26
26
|
{ errorClass: 'ps_empty_error', pattern: /returned an empty error|failed with no message/i },
|
|
27
|
+
{ errorClass: 'wrong_layer_kind', pattern: /is a group, not an art layer/i },
|
|
27
28
|
{
|
|
28
29
|
errorClass: 'layer_not_found',
|
|
29
30
|
pattern: /\blayer not found|no layer named|(?:layer_to_move|target_layer_name) not found|layer "[^"]*" not found/i,
|
|
@@ -37,6 +38,19 @@ export const ERROR_CLASS_TABLE = [
|
|
|
37
38
|
pattern: /file not found|map not found|lut not found|could not open lut/i,
|
|
38
39
|
},
|
|
39
40
|
{ errorClass: 'face_not_found', pattern: /no face mesh|no face detected/i },
|
|
41
|
+
{
|
|
42
|
+
errorClass: 'perception_export_failed',
|
|
43
|
+
pattern: /saveAs reported success but no file/i,
|
|
44
|
+
},
|
|
45
|
+
{ errorClass: 'image_decode_failed', pattern: /failed to decode jpeg/i },
|
|
46
|
+
{
|
|
47
|
+
errorClass: 'detection_unavailable',
|
|
48
|
+
pattern: /onnxruntime|onnx runtime/i,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
errorClass: 'file_io',
|
|
52
|
+
pattern: /\b(ENOENT|EBUSY|EACCES|EPERM|EMFILE|ENOSPC)\b/,
|
|
53
|
+
},
|
|
40
54
|
{
|
|
41
55
|
errorClass: 'schema_validation',
|
|
42
56
|
pattern: /\bvalidat|required.*field|missing required argument|must be.*type|invalid (input|argument)/i,
|
|
@@ -210,6 +224,9 @@ export class SessionLog {
|
|
|
210
224
|
get path() {
|
|
211
225
|
return join(this.dir, `${this.sessionId}.ndjson`);
|
|
212
226
|
}
|
|
227
|
+
get directory() {
|
|
228
|
+
return this.dir;
|
|
229
|
+
}
|
|
213
230
|
setMcpClientGetter(fn) {
|
|
214
231
|
this.getMcpClientFn = fn;
|
|
215
232
|
}
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '1.1
|
|
1
|
+
export const VERSION = '1.2.1';
|
package/package.json
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "editmamei",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Photoshop MCP server: natural-language AI photo editing with your own Photoshop (Community Edition)",
|
|
5
5
|
"mcpName": "io.github.editmamei/editmamei",
|
|
6
|
+
"editmamei": {
|
|
7
|
+
"fixesByVersion": {
|
|
8
|
+
"1.1.0": [
|
|
9
|
+
"ps_create_clipping_mask"
|
|
10
|
+
],
|
|
11
|
+
"1.2.0": [
|
|
12
|
+
"ps_delete_layer",
|
|
13
|
+
"ps_select_layer"
|
|
14
|
+
],
|
|
15
|
+
"1.2.1": [
|
|
16
|
+
"ps_read_scene"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
},
|
|
6
20
|
"main": "dist/index.js",
|
|
7
21
|
"type": "module",
|
|
8
22
|
"bin": {
|