editmamei 1.5.1 → 1.7.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 +22 -1
- package/dist/backends/detect-editors.js +79 -0
- package/dist/backends/gimp/backend.js +99 -0
- package/dist/backends/gimp/bridge/lib.py +1988 -0
- package/dist/backends/gimp/bridge/ops.py +3120 -0
- package/dist/backends/gimp/detect.js +86 -0
- package/dist/backends/gimp/errors.js +21 -0
- package/dist/backends/gimp/launcher.js +6 -0
- package/dist/backends/gimp/session.js +632 -0
- 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 +28 -0
- package/dist/core/preview-privacy.js +54 -0
- package/dist/core/server.js +98 -49
- package/dist/core/settings.js +25 -0
- package/dist/core/tool-activity.js +122 -0
- package/dist/core/tool-groups.js +24 -0
- package/dist/core/tool-tiers.js +24 -0
- package/dist/diagnostics/collect.js +4 -0
- package/dist/index.js +5 -1
- package/dist/kernel/kernel.js +3 -0
- package/dist/modules/gimp/index.js +43 -0
- package/dist/skills/editmamei-skill.zip +0 -0
- package/dist/telemetry/activity.js +1 -93
- package/dist/tools/adjustment-tools.js +2 -1
- package/dist/tools/gimp-adjustment-tools.js +385 -0
- package/dist/tools/gimp-checkpoint-tools.js +541 -0
- package/dist/tools/gimp-compose-tools.js +399 -0
- package/dist/tools/gimp-core-tools.js +302 -0
- package/dist/tools/gimp-document-tools.js +321 -0
- package/dist/tools/gimp-effect-tools.js +267 -0
- package/dist/tools/gimp-filter-tools.js +94 -0
- package/dist/tools/gimp-geometry-tools.js +223 -0
- package/dist/tools/gimp-inspect-tools.js +157 -0
- package/dist/tools/gimp-layer-tools.js +385 -0
- package/dist/tools/gimp-mask-tools.js +102 -0
- package/dist/tools/gimp-shared.js +58 -0
- package/dist/tools/gimp-verify-tools.js +409 -0
- package/dist/utils/gimp-path.js +27 -0
- package/dist/utils/operation-timeouts.js +24 -0
- package/dist/utils/session-log.js +19 -1
- package/dist/utils/tool-helpers.js +14 -0
- package/dist/utils/validate.js +11 -0
- package/dist/version.js +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
function isImageBlock(value) {
|
|
2
|
+
if (typeof value !== 'object' || value === null)
|
|
3
|
+
return false;
|
|
4
|
+
const block = value;
|
|
5
|
+
if (block.type === 'image') {
|
|
6
|
+
return typeof block.data === 'string' && typeof block.mimeType === 'string';
|
|
7
|
+
}
|
|
8
|
+
return (block.type === 'resource' &&
|
|
9
|
+
typeof block.resource?.blob === 'string' &&
|
|
10
|
+
typeof block.resource.mimeType === 'string' &&
|
|
11
|
+
block.resource.mimeType.startsWith('image/'));
|
|
12
|
+
}
|
|
13
|
+
function strip(value, counter) {
|
|
14
|
+
if (Array.isArray(value)) {
|
|
15
|
+
const out = [];
|
|
16
|
+
for (const item of value) {
|
|
17
|
+
if (isImageBlock(item))
|
|
18
|
+
counter.n++;
|
|
19
|
+
else
|
|
20
|
+
out.push(strip(item, counter));
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
if (typeof value === 'object' && value !== null) {
|
|
25
|
+
const out = {};
|
|
26
|
+
for (const [k, v] of Object.entries(value))
|
|
27
|
+
out[k] = strip(v, counter);
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
export function hasImageContent(result) {
|
|
33
|
+
const counter = { n: 0 };
|
|
34
|
+
strip(result, counter);
|
|
35
|
+
return counter.n > 0;
|
|
36
|
+
}
|
|
37
|
+
export function withholdImages(result) {
|
|
38
|
+
const counter = { n: 0 };
|
|
39
|
+
const stripped = strip(result, counter);
|
|
40
|
+
if (counter.n === 0)
|
|
41
|
+
return result;
|
|
42
|
+
const what = counter.n === 1 ? 'An image was' : `${counter.n} images were`;
|
|
43
|
+
return {
|
|
44
|
+
...stripped,
|
|
45
|
+
content: [
|
|
46
|
+
...(stripped.content ?? []),
|
|
47
|
+
{
|
|
48
|
+
type: 'text',
|
|
49
|
+
text: `${what} withheld because the user's privacy.send_previews_to_llm setting is off. ` +
|
|
50
|
+
'Work from the text and numbers; ask the user if you need to see the image.',
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
}
|
package/dist/core/server.js
CHANGED
|
@@ -5,6 +5,8 @@ import { Logger } from '../utils/logger.js';
|
|
|
5
5
|
import { ToolRegistry } from './tool-registry.js';
|
|
6
6
|
import { tierOf } from './tool-tiers.js';
|
|
7
7
|
import { groupOf, GROUPS } from './tool-groups.js';
|
|
8
|
+
import { CAMERA_RAW_TOOL, RAW_DEVELOP_TOOL } from './tool-activity.js';
|
|
9
|
+
import { hasImageContent, withholdImages } from './preview-privacy.js';
|
|
8
10
|
import { EDITION } from '../edition.js';
|
|
9
11
|
import { VERSION } from '../version.js';
|
|
10
12
|
import { Session } from './session.js';
|
|
@@ -29,6 +31,9 @@ import { ceModule } from '../modules/ce/index.js';
|
|
|
29
31
|
import { hostDetectionRuntime } from '../detection/runtime.js';
|
|
30
32
|
import { ModuleLifecycle, classifyModuleOutcome } from '../kernel/module-lifecycle.js';
|
|
31
33
|
import { unsupportedHostReason } from '../platform/host-platform.js';
|
|
34
|
+
import { gimpModule } from '../modules/gimp/index.js';
|
|
35
|
+
import { GimpBackend } from '../backends/gimp/backend.js';
|
|
36
|
+
import { createDiagnosticsTools } from '../tools/diagnostics-tools.js';
|
|
32
37
|
export { classifyModuleOutcome };
|
|
33
38
|
let logScriptOnErrorWarned = false;
|
|
34
39
|
function warnLogScriptOnErrorOnce(logger) {
|
|
@@ -68,6 +73,9 @@ export class EditmameiServer {
|
|
|
68
73
|
session;
|
|
69
74
|
sessionLog;
|
|
70
75
|
telemetry;
|
|
76
|
+
editorOpts;
|
|
77
|
+
gimpBackend;
|
|
78
|
+
lastLoggedGimpVersion;
|
|
71
79
|
kernel;
|
|
72
80
|
moduleLifecycle;
|
|
73
81
|
get moduleSkipReason() {
|
|
@@ -83,6 +91,7 @@ export class EditmameiServer {
|
|
|
83
91
|
updateInfo = null;
|
|
84
92
|
updateCheck = null;
|
|
85
93
|
updateNoticeShown = false;
|
|
94
|
+
previewsAllowed = () => loadSettings().settings.privacy.send_previews_to_llm;
|
|
86
95
|
snippetClient = new GoSnippetClient();
|
|
87
96
|
refreshLicenseOnPing = createPingLicenseRefresher();
|
|
88
97
|
moduleUpdateFailed = false;
|
|
@@ -91,6 +100,18 @@ export class EditmameiServer {
|
|
|
91
100
|
licenseAdvisoryShown = false;
|
|
92
101
|
constructor(opts = {}) {
|
|
93
102
|
this.licenseStoreOptions = opts.licenseStore ?? {};
|
|
103
|
+
this.editorOpts = opts.editors ?? {
|
|
104
|
+
registerPhotoshop: true,
|
|
105
|
+
registerGimp: false,
|
|
106
|
+
gimpInstall: null,
|
|
107
|
+
gimpDetectionTimedOut: false,
|
|
108
|
+
};
|
|
109
|
+
if (this.editorOpts.registerGimp) {
|
|
110
|
+
this.gimpBackend = new GimpBackend(this.editorOpts.gimpInstall, {
|
|
111
|
+
gimpDetectionTimedOut: this.editorOpts.gimpDetectionTimedOut,
|
|
112
|
+
gimpPath: this.editorOpts.gimpPathOverride,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
94
115
|
this.logger = new Logger('EditmameiServer');
|
|
95
116
|
this.session = new Session();
|
|
96
117
|
this.sessionLog = new SessionLog(this.session.getSessionId());
|
|
@@ -127,6 +148,13 @@ export class EditmameiServer {
|
|
|
127
148
|
this.resolveLiveVersionInBackground();
|
|
128
149
|
}
|
|
129
150
|
}
|
|
151
|
+
if (entry.success && entry.tool.startsWith('gimp_') && this.gimpBackend?.gimpVersion) {
|
|
152
|
+
const version = this.gimpBackend.gimpVersion;
|
|
153
|
+
if (version !== this.lastLoggedGimpVersion) {
|
|
154
|
+
this.lastLoggedGimpVersion = version;
|
|
155
|
+
void this.sessionLog.setGimpVersion(version);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
130
158
|
const resultBytes = computeResultBytes(entry.result);
|
|
131
159
|
void this.sessionLog
|
|
132
160
|
.append({
|
|
@@ -219,52 +247,53 @@ export class EditmameiServer {
|
|
|
219
247
|
};
|
|
220
248
|
}
|
|
221
249
|
registerTools() {
|
|
222
|
-
this.
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
250
|
+
if (this.editorOpts.registerPhotoshop)
|
|
251
|
+
this.toolRegistry.register('ps_ping', {
|
|
252
|
+
tool: {
|
|
253
|
+
name: 'ps_ping',
|
|
254
|
+
description: 'Test connection to Photoshop and report session-start discovery signals. Read-only and idempotent. Call this once at the start of a session to confirm liveness before invoking any other tool. **If the user has given you an open-ended editing task, ALSO call `ps_overview` after this** — it returns the workflow contract + capabilities map + verification primitives so you can plan well. Returns `version` (folds the removed get-version tool), `custom_action_sets` (count of Action Sets loaded in the Photoshop Actions palette — non-zero means recorded workflows exist; if action tools are available in this build they will appear in `tools/list`), `user_templates` (count of saved Editmamei templates), and `open_documents` (names of documents already open in Photoshop, so the LLM does not have to ask which doc to edit).',
|
|
255
|
+
inputSchema: {
|
|
256
|
+
type: 'object',
|
|
257
|
+
properties: {},
|
|
258
|
+
},
|
|
259
|
+
outputSchema: {
|
|
260
|
+
type: 'object',
|
|
261
|
+
properties: {
|
|
262
|
+
connected: { type: 'boolean' },
|
|
263
|
+
version: { type: 'string' },
|
|
264
|
+
custom_action_sets: { type: 'number' },
|
|
265
|
+
user_templates: { type: 'number' },
|
|
266
|
+
open_documents: { type: 'array', items: { type: 'string' } },
|
|
267
|
+
degraded: {
|
|
268
|
+
type: 'array',
|
|
269
|
+
items: { type: 'string' },
|
|
270
|
+
description: 'List of optional discovery signals that could not be gathered (e.g. "pingState" if the go-core snippet builder itself failed to produce the in-PS state script — a broken/missing local install, unrelated to whether Photoshop is reachable; liveness falls back to a cheap ping probe in that case — "templates" if ~/.editmamei/templates was unreadable). Empty when all signals were collected cleanly. Returned defaults for any degraded field cannot be trusted as ground truth.',
|
|
271
|
+
},
|
|
272
|
+
update_available: {
|
|
273
|
+
type: ['object', 'null'],
|
|
274
|
+
description: 'Set when a newer Editmamei version is published, else null: { current, latest, channel (npm/mcpb/dev), how_to_update, fixed_tools (tools whose recorded failures the newer version fixes) }. The relay instruction rides the ping TEXT on the first ping — see notify_user. Anonymous npm-registry check at boot; opt out with `editmamei config set update_check false`.',
|
|
275
|
+
properties: {
|
|
276
|
+
current: { type: 'string' },
|
|
277
|
+
latest: { type: 'string' },
|
|
278
|
+
channel: { type: 'string' },
|
|
279
|
+
how_to_update: { type: 'string' },
|
|
280
|
+
fixed_tools: { type: 'array', items: { type: 'string' } },
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
notify_user: {
|
|
284
|
+
type: 'boolean',
|
|
285
|
+
description: 'True on the one ping whose text carries the update notice — relay that notice to the user before continuing. False on later pings and when no update is available.',
|
|
252
286
|
},
|
|
253
|
-
},
|
|
254
|
-
notify_user: {
|
|
255
|
-
type: 'boolean',
|
|
256
|
-
description: 'True on the one ping whose text carries the update notice — relay that notice to the user before continuing. False on later pings and when no update is available.',
|
|
257
287
|
},
|
|
258
288
|
},
|
|
289
|
+
annotations: {
|
|
290
|
+
title: 'Ping Photoshop',
|
|
291
|
+
readOnlyHint: true,
|
|
292
|
+
idempotentHint: true,
|
|
293
|
+
},
|
|
259
294
|
},
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
readOnlyHint: true,
|
|
263
|
-
idempotentHint: true,
|
|
264
|
-
},
|
|
265
|
-
},
|
|
266
|
-
handler: async () => await this.pingPhotoshop(),
|
|
267
|
-
});
|
|
295
|
+
handler: async () => await this.pingPhotoshop(),
|
|
296
|
+
});
|
|
268
297
|
this.toolRegistry.register('ps_list_capabilities', {
|
|
269
298
|
tool: {
|
|
270
299
|
name: 'ps_list_capabilities',
|
|
@@ -315,6 +344,7 @@ export class EditmameiServer {
|
|
|
315
344
|
connection: this.session.getConnection(),
|
|
316
345
|
snippet: this.snippetClient,
|
|
317
346
|
detection: hostDetectionRuntime(),
|
|
347
|
+
gimp: this.gimpBackend,
|
|
318
348
|
resolveModuleSnippet: (manifest) => manifest.goCoreSnippets && manifest.goCoreSnippets.length > 0 && proModule
|
|
319
349
|
? new GoSnippetClient({ binaryPath: join(proModule.binDir, coreBinaryName()) })
|
|
320
350
|
: null,
|
|
@@ -322,11 +352,22 @@ export class EditmameiServer {
|
|
|
322
352
|
logger: this.logger,
|
|
323
353
|
});
|
|
324
354
|
this.moduleLifecycle.setKernel(this.kernel);
|
|
325
|
-
|
|
355
|
+
const builtins = [];
|
|
356
|
+
if (this.editorOpts.registerPhotoshop)
|
|
357
|
+
builtins.push(ceModule);
|
|
358
|
+
if (this.editorOpts.registerGimp)
|
|
359
|
+
builtins.push(gimpModule);
|
|
360
|
+
this.kernel.loadBuiltins(builtins);
|
|
326
361
|
this.assertToolsClassified();
|
|
362
|
+
if (!this.editorOpts.registerPhotoshop) {
|
|
363
|
+
const diagnosticsDefs = createDiagnosticsTools(this.session.getConnection(), this.snippetClient).filter((def) => def.tool.name === 'ps_report_problem');
|
|
364
|
+
this.toolRegistry.registerAll(diagnosticsDefs);
|
|
365
|
+
}
|
|
327
366
|
this.logger.info(`Registered ${this.toolRegistry.count()} tools (edition: ${EDITION})`);
|
|
328
367
|
}
|
|
329
368
|
async loadModules() {
|
|
369
|
+
if (!this.editorOpts.registerPhotoshop)
|
|
370
|
+
return;
|
|
330
371
|
return this.moduleLifecycle.loadModules();
|
|
331
372
|
}
|
|
332
373
|
computeModuleStatus() {
|
|
@@ -397,6 +438,8 @@ export class EditmameiServer {
|
|
|
397
438
|
const result = await this.toolRegistry.execute(name, args);
|
|
398
439
|
this.session.updateActivity();
|
|
399
440
|
this.trackRawDevelopState(name, result);
|
|
441
|
+
if (hasImageContent(result) && !this.previewsAllowed())
|
|
442
|
+
return withholdImages(result);
|
|
400
443
|
return result;
|
|
401
444
|
}
|
|
402
445
|
catch (error) {
|
|
@@ -409,7 +452,7 @@ export class EditmameiServer {
|
|
|
409
452
|
return;
|
|
410
453
|
if (name === 'ps_open_document') {
|
|
411
454
|
const sc = result.structuredContent;
|
|
412
|
-
if (sc?.is_raw_source === true && this.toolRegistry.get(
|
|
455
|
+
if (sc?.is_raw_source === true && this.toolRegistry.get(CAMERA_RAW_TOOL)) {
|
|
413
456
|
const opened = sc;
|
|
414
457
|
markRawOpened(String(opened.document_name ?? ''), String(opened.file_path ?? ''));
|
|
415
458
|
}
|
|
@@ -417,12 +460,12 @@ export class EditmameiServer {
|
|
|
417
460
|
clearPendingRawDevelop();
|
|
418
461
|
}
|
|
419
462
|
}
|
|
420
|
-
else if (name ===
|
|
463
|
+
else if (name === RAW_DEVELOP_TOOL) {
|
|
421
464
|
const sc = result.structuredContent;
|
|
422
465
|
if (sc?.opened === true)
|
|
423
466
|
clearPendingRawDevelop();
|
|
424
467
|
}
|
|
425
|
-
else if (name ===
|
|
468
|
+
else if (name === CAMERA_RAW_TOOL || name === 'ps_close_document') {
|
|
426
469
|
clearPendingRawDevelop();
|
|
427
470
|
}
|
|
428
471
|
}
|
|
@@ -667,18 +710,24 @@ export class EditmameiServer {
|
|
|
667
710
|
await this.loadModules();
|
|
668
711
|
const transport = new StdioServerTransport();
|
|
669
712
|
await this.server.connect(transport);
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
713
|
+
if (this.editorOpts.registerPhotoshop) {
|
|
714
|
+
void this.session.initialize().catch(() => undefined);
|
|
715
|
+
void this.reprovisionIfModuleSkipped();
|
|
716
|
+
void this.ensureEntitledModuleFresh();
|
|
717
|
+
}
|
|
673
718
|
this.telemetry.start();
|
|
674
719
|
void this.telemetry.flushOutboxOnStartup();
|
|
675
720
|
this.server.onclose = () => {
|
|
676
721
|
void this.telemetry.shutdown();
|
|
722
|
+
if (this.gimpBackend)
|
|
723
|
+
void this.gimpBackend.shutdown();
|
|
677
724
|
};
|
|
678
725
|
this.logger.info('Editmamei is listening on stdio');
|
|
679
726
|
}
|
|
680
727
|
async stop() {
|
|
681
728
|
await this.telemetry.shutdown();
|
|
729
|
+
if (this.gimpBackend)
|
|
730
|
+
await this.gimpBackend.shutdown();
|
|
682
731
|
await this.session.disconnect();
|
|
683
732
|
await this.sessionLog.close();
|
|
684
733
|
this.logger.info('Editmamei has shut down');
|
package/dist/core/settings.js
CHANGED
|
@@ -3,6 +3,7 @@ import { join, dirname } from 'node:path';
|
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from 'node:fs';
|
|
4
4
|
import { randomBytes } from 'node:crypto';
|
|
5
5
|
import { Logger } from '../utils/logger.js';
|
|
6
|
+
import { requireAbsoluteGimpPath } from '../utils/gimp-path.js';
|
|
6
7
|
const logger = new Logger('Settings');
|
|
7
8
|
const SETTINGS_DIRNAME = '.editmamei';
|
|
8
9
|
const SETTINGS_FILENAME = 'settings.json';
|
|
@@ -15,12 +16,15 @@ export function settingsPath(opts = {}) {
|
|
|
15
16
|
export function mintInstallId() {
|
|
16
17
|
return randomBytes(16).toString('hex');
|
|
17
18
|
}
|
|
19
|
+
const EDITOR_PINS = ['auto', 'photoshop', 'gimp'];
|
|
18
20
|
function defaults(installId) {
|
|
19
21
|
return {
|
|
20
22
|
telemetry: { usage: true, diagnostics: false, install_id: installId },
|
|
21
23
|
privacy: { send_previews_to_llm: true },
|
|
22
24
|
ps_path: null,
|
|
23
25
|
update_check: true,
|
|
26
|
+
editor: 'auto',
|
|
27
|
+
gimp_path: null,
|
|
24
28
|
};
|
|
25
29
|
}
|
|
26
30
|
function coerce(raw, installId) {
|
|
@@ -45,8 +49,23 @@ function coerce(raw, installId) {
|
|
|
45
49
|
},
|
|
46
50
|
ps_path: typeof r.ps_path === 'string' ? r.ps_path : null,
|
|
47
51
|
update_check: typeof r.update_check === 'boolean' ? r.update_check : base.update_check,
|
|
52
|
+
editor: typeof r.editor === 'string' && EDITOR_PINS.includes(r.editor)
|
|
53
|
+
? r.editor
|
|
54
|
+
: base.editor,
|
|
55
|
+
gimp_path: coerceGimpPath(r.gimp_path),
|
|
48
56
|
};
|
|
49
57
|
}
|
|
58
|
+
function coerceGimpPath(raw) {
|
|
59
|
+
if (typeof raw !== 'string' || raw.trim() === '')
|
|
60
|
+
return null;
|
|
61
|
+
try {
|
|
62
|
+
return requireAbsoluteGimpPath('gimp_path', raw);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
logger.warn(`ignoring settings.json gimp_path (falling back to auto-detect): ${errMsg(err)}`);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
50
69
|
export function loadSettings(opts = {}) {
|
|
51
70
|
const path = settingsPath(opts);
|
|
52
71
|
if (!existsSync(path)) {
|
|
@@ -110,6 +129,12 @@ export function applyUpdateCheckEnvOverride(settings, env = process.env) {
|
|
|
110
129
|
return settings;
|
|
111
130
|
return { ...settings, update_check: v };
|
|
112
131
|
}
|
|
132
|
+
export function applyEditorEnvOverride(settings, env = process.env) {
|
|
133
|
+
const raw = env.EDITMAMEI_EDITOR?.trim().toLowerCase();
|
|
134
|
+
if (!raw || !EDITOR_PINS.includes(raw))
|
|
135
|
+
return settings;
|
|
136
|
+
return { ...settings, editor: raw };
|
|
137
|
+
}
|
|
113
138
|
export function saveSettings(settings, opts = {}) {
|
|
114
139
|
const path = settingsPath(opts);
|
|
115
140
|
const dir = dirname(path);
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
export const READ_ONLY_TOOLS = new Set([
|
|
2
|
+
'ps_sequence',
|
|
3
|
+
'ps_batch',
|
|
4
|
+
'ps_ping',
|
|
5
|
+
'ps_overview',
|
|
6
|
+
'ps_list_capabilities',
|
|
7
|
+
'ps_get_preview',
|
|
8
|
+
'ps_get_selection_preview',
|
|
9
|
+
'ps_get_histogram',
|
|
10
|
+
'ps_inspect',
|
|
11
|
+
'ps_read_scene',
|
|
12
|
+
'ps_compare_regions',
|
|
13
|
+
'ps_get_layer_bounds_diff',
|
|
14
|
+
'ps_template_list',
|
|
15
|
+
'ps_template_recall',
|
|
16
|
+
'ps_list_actions',
|
|
17
|
+
'ps_report_problem',
|
|
18
|
+
'ps_detect',
|
|
19
|
+
'ps_detect_landmarks',
|
|
20
|
+
'ps_document',
|
|
21
|
+
'ps_template_verify',
|
|
22
|
+
'ps_resolve_placement',
|
|
23
|
+
'ps_template_save',
|
|
24
|
+
'ps_template_delete',
|
|
25
|
+
'ps_template_create_evidence',
|
|
26
|
+
'gimp_ping',
|
|
27
|
+
'gimp_overview',
|
|
28
|
+
'gimp_inspect',
|
|
29
|
+
'gimp_get_preview',
|
|
30
|
+
'gimp_get_histogram',
|
|
31
|
+
'gimp_compare',
|
|
32
|
+
]);
|
|
33
|
+
export const KEPT_WORK_TOOLS = new Set([
|
|
34
|
+
'ps_export',
|
|
35
|
+
'ps_save_psd',
|
|
36
|
+
'gimp_export',
|
|
37
|
+
'gimp_save_xcf',
|
|
38
|
+
]);
|
|
39
|
+
export const MUTATING_TOOLS = new Set([
|
|
40
|
+
'ps_play_action',
|
|
41
|
+
'ps_execute_script',
|
|
42
|
+
'ps_add_adjustment_layer',
|
|
43
|
+
'ps_apply_adjustment',
|
|
44
|
+
'ps_create_document',
|
|
45
|
+
'ps_close_document',
|
|
46
|
+
'ps_open_document',
|
|
47
|
+
'ps_develop_raw',
|
|
48
|
+
'ps_filter',
|
|
49
|
+
'ps_group',
|
|
50
|
+
'ps_clipping_mask',
|
|
51
|
+
'ps_undo',
|
|
52
|
+
'ps_redo',
|
|
53
|
+
'ps_place_image',
|
|
54
|
+
'ps_resize_image',
|
|
55
|
+
'ps_crop_document',
|
|
56
|
+
'ps_convert_image_mode',
|
|
57
|
+
'ps_move_layer_to_position',
|
|
58
|
+
'ps_convert_to_smart_object',
|
|
59
|
+
'ps_rasterize_layer',
|
|
60
|
+
'ps_set_layer',
|
|
61
|
+
'ps_duplicate_layer',
|
|
62
|
+
'ps_copy_to_new_layer',
|
|
63
|
+
'ps_merge',
|
|
64
|
+
'ps_bake_layer',
|
|
65
|
+
'ps_add_layer_style',
|
|
66
|
+
'ps_create_layer',
|
|
67
|
+
'ps_delete_layer',
|
|
68
|
+
'ps_fill_layer',
|
|
69
|
+
'ps_add_fill_layer',
|
|
70
|
+
'ps_select_layer',
|
|
71
|
+
'ps_transform_layer',
|
|
72
|
+
'ps_warp_layer',
|
|
73
|
+
'ps_warp_layer_mesh',
|
|
74
|
+
'ps_warp_layer_along',
|
|
75
|
+
'ps_warp_layer_region',
|
|
76
|
+
'ps_warp_layer_to',
|
|
77
|
+
'ps_apply_camera_raw',
|
|
78
|
+
'ps_transform_canvas',
|
|
79
|
+
'ps_guides',
|
|
80
|
+
'ps_retouch',
|
|
81
|
+
'ps_apply_brush_stroke',
|
|
82
|
+
'ps_edit_object',
|
|
83
|
+
'ps_portrait_touchup',
|
|
84
|
+
'ps_add_text_to_object',
|
|
85
|
+
'ps_select_face_feature',
|
|
86
|
+
'ps_stroke_face_contour',
|
|
87
|
+
'ps_select_by_reference',
|
|
88
|
+
'ps_select',
|
|
89
|
+
'ps_select_subject',
|
|
90
|
+
'ps_select_sky',
|
|
91
|
+
'ps_select_subject_instance',
|
|
92
|
+
'ps_select_object',
|
|
93
|
+
'ps_replace_sky',
|
|
94
|
+
'ps_modify_selection',
|
|
95
|
+
'ps_selection_channel',
|
|
96
|
+
'ps_layer_mask',
|
|
97
|
+
'ps_path',
|
|
98
|
+
'ps_vector_mask',
|
|
99
|
+
'ps_apply_image',
|
|
100
|
+
'ps_calculations',
|
|
101
|
+
'ps_shape',
|
|
102
|
+
'ps_template_apply',
|
|
103
|
+
'ps_text',
|
|
104
|
+
'gimp_open_document',
|
|
105
|
+
'gimp_close_document',
|
|
106
|
+
'gimp_add_adjustment',
|
|
107
|
+
'gimp_add_effect',
|
|
108
|
+
'gimp_filter',
|
|
109
|
+
'gimp_crop_document',
|
|
110
|
+
'gimp_resize_image',
|
|
111
|
+
'gimp_transform_canvas',
|
|
112
|
+
'gimp_create_mask',
|
|
113
|
+
'gimp_checkpoint',
|
|
114
|
+
'gimp_layer',
|
|
115
|
+
'gimp_bake',
|
|
116
|
+
'gimp_create_document',
|
|
117
|
+
'gimp_place_image',
|
|
118
|
+
'gimp_canvas',
|
|
119
|
+
'gimp_convert_image_mode',
|
|
120
|
+
]);
|
|
121
|
+
export const RAW_DEVELOP_TOOL = 'ps_develop_raw';
|
|
122
|
+
export const CAMERA_RAW_TOOL = 'ps_apply_camera_raw';
|
package/dist/core/tool-groups.js
CHANGED
|
@@ -171,6 +171,30 @@ export const TOOL_GROUPS = {
|
|
|
171
171
|
ps_play_action: 'automation',
|
|
172
172
|
ps_execute_script: 'automation',
|
|
173
173
|
ps_batch: 'automation',
|
|
174
|
+
gimp_ping: 'core',
|
|
175
|
+
gimp_overview: 'core',
|
|
176
|
+
gimp_inspect: 'inspect',
|
|
177
|
+
gimp_get_preview: 'verify',
|
|
178
|
+
gimp_get_histogram: 'verify',
|
|
179
|
+
gimp_compare: 'verify',
|
|
180
|
+
gimp_open_document: 'document',
|
|
181
|
+
gimp_close_document: 'document',
|
|
182
|
+
gimp_save_xcf: 'document',
|
|
183
|
+
gimp_export: 'document',
|
|
184
|
+
gimp_crop_document: 'document',
|
|
185
|
+
gimp_resize_image: 'document',
|
|
186
|
+
gimp_transform_canvas: 'document',
|
|
187
|
+
gimp_add_adjustment: 'adjust',
|
|
188
|
+
gimp_filter: 'filter',
|
|
189
|
+
gimp_add_effect: 'filter',
|
|
190
|
+
gimp_create_mask: 'masks',
|
|
191
|
+
gimp_checkpoint: 'document',
|
|
192
|
+
gimp_layer: 'layers',
|
|
193
|
+
gimp_bake: 'layers',
|
|
194
|
+
gimp_create_document: 'document',
|
|
195
|
+
gimp_place_image: 'layers',
|
|
196
|
+
gimp_canvas: 'document',
|
|
197
|
+
gimp_convert_image_mode: 'document',
|
|
174
198
|
};
|
|
175
199
|
export function groupOf(toolName) {
|
|
176
200
|
const group = TOOL_GROUPS[toolName];
|
package/dist/core/tool-tiers.js
CHANGED
|
@@ -89,6 +89,30 @@ export const TOOL_TIERS = {
|
|
|
89
89
|
ps_template_recall: 'pro',
|
|
90
90
|
ps_template_delete: 'pro',
|
|
91
91
|
ps_text: 'community',
|
|
92
|
+
gimp_ping: 'community',
|
|
93
|
+
gimp_overview: 'community',
|
|
94
|
+
gimp_open_document: 'community',
|
|
95
|
+
gimp_close_document: 'community',
|
|
96
|
+
gimp_save_xcf: 'community',
|
|
97
|
+
gimp_export: 'community',
|
|
98
|
+
gimp_inspect: 'community',
|
|
99
|
+
gimp_add_adjustment: 'community',
|
|
100
|
+
gimp_filter: 'community',
|
|
101
|
+
gimp_add_effect: 'community',
|
|
102
|
+
gimp_crop_document: 'community',
|
|
103
|
+
gimp_resize_image: 'community',
|
|
104
|
+
gimp_transform_canvas: 'community',
|
|
105
|
+
gimp_create_mask: 'community',
|
|
106
|
+
gimp_get_preview: 'community',
|
|
107
|
+
gimp_get_histogram: 'community',
|
|
108
|
+
gimp_compare: 'community',
|
|
109
|
+
gimp_checkpoint: 'community',
|
|
110
|
+
gimp_layer: 'community',
|
|
111
|
+
gimp_bake: 'community',
|
|
112
|
+
gimp_create_document: 'community',
|
|
113
|
+
gimp_place_image: 'community',
|
|
114
|
+
gimp_canvas: 'community',
|
|
115
|
+
gimp_convert_image_mode: 'community',
|
|
92
116
|
};
|
|
93
117
|
export function tierOf(toolName) {
|
|
94
118
|
const tier = TOOL_TIERS[toolName];
|
|
@@ -35,6 +35,7 @@ export async function collectDiagnostics(opts = {}) {
|
|
|
35
35
|
const recentIds = await listRecentSessionIds(MAX_RECENT_SESSIONS, { dir: sessionsDir });
|
|
36
36
|
const recent_sessions = [];
|
|
37
37
|
let psVersion = opts.psVersion ?? null;
|
|
38
|
+
let gimpVersion = null;
|
|
38
39
|
let mcpClient = null;
|
|
39
40
|
for (const id of recentIds) {
|
|
40
41
|
const entries = await readSessionLog(id, { dir: sessionsDir });
|
|
@@ -51,6 +52,8 @@ export async function collectDiagnostics(opts = {}) {
|
|
|
51
52
|
if (meta) {
|
|
52
53
|
if (!psVersion && meta.ps_version)
|
|
53
54
|
psVersion = meta.ps_version;
|
|
55
|
+
if (!gimpVersion && meta.gimp_version)
|
|
56
|
+
gimpVersion = meta.gimp_version;
|
|
54
57
|
if (!mcpClient)
|
|
55
58
|
mcpClient = mcpClientLabel(meta);
|
|
56
59
|
}
|
|
@@ -78,6 +81,7 @@ export async function collectDiagnostics(opts = {}) {
|
|
|
78
81
|
node_version: process.version,
|
|
79
82
|
install_id: settings.telemetry.install_id,
|
|
80
83
|
ps_version: psVersion,
|
|
84
|
+
gimp_version: gimpVersion,
|
|
81
85
|
mcp_client: mcpClient,
|
|
82
86
|
settings: {
|
|
83
87
|
telemetry_usage: settings.telemetry.usage,
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,8 @@ import { routeCli } from './cli/router.js';
|
|
|
6
6
|
import { maybeActivateFromEnv } from './license/env-activation.js';
|
|
7
7
|
import { refreshIfStale } from './license/entitlement.js';
|
|
8
8
|
import { Logger } from './utils/logger.js';
|
|
9
|
+
import { resolveBootEditors } from './backends/detect-editors.js';
|
|
10
|
+
import { loadSettings } from './core/settings.js';
|
|
9
11
|
const logger = new Logger('Main');
|
|
10
12
|
function installProcessHandlers() {
|
|
11
13
|
process.on('unhandledRejection', (reason, promise) => {
|
|
@@ -39,7 +41,9 @@ async function main() {
|
|
|
39
41
|
logger.info('Starting Editmamei...');
|
|
40
42
|
await maybeActivateFromEnv();
|
|
41
43
|
await refreshIfStale();
|
|
42
|
-
const
|
|
44
|
+
const { settings } = loadSettings();
|
|
45
|
+
const editors = await resolveBootEditors({ settings });
|
|
46
|
+
const server = new EditmameiServer({ editors });
|
|
43
47
|
await server.start();
|
|
44
48
|
let shuttingDown = false;
|
|
45
49
|
const shutdown = async (signal) => {
|
package/dist/kernel/kernel.js
CHANGED
|
@@ -9,6 +9,7 @@ export class Kernel {
|
|
|
9
9
|
snippet;
|
|
10
10
|
resolveModuleSnippet;
|
|
11
11
|
detection;
|
|
12
|
+
gimp;
|
|
12
13
|
sessionId;
|
|
13
14
|
logger;
|
|
14
15
|
invokeDepth = new AsyncLocalStorage();
|
|
@@ -19,6 +20,7 @@ export class Kernel {
|
|
|
19
20
|
this.snippet = deps.snippet;
|
|
20
21
|
this.resolveModuleSnippet = deps.resolveModuleSnippet;
|
|
21
22
|
this.detection = deps.detection;
|
|
23
|
+
this.gimp = deps.gimp;
|
|
22
24
|
this.sessionId = deps.sessionId;
|
|
23
25
|
this.logger = deps.logger;
|
|
24
26
|
}
|
|
@@ -40,6 +42,7 @@ export class Kernel {
|
|
|
40
42
|
executeScript: (innerBody, timeoutMs) => runScript(this.connection, innerBody, timeoutMs),
|
|
41
43
|
snippet: this.snippetFor(manifest),
|
|
42
44
|
detection: this.detection,
|
|
45
|
+
gimp: this.gimp,
|
|
43
46
|
session: { id: this.sessionId },
|
|
44
47
|
logger: new Logger(`module:${manifest.id}`),
|
|
45
48
|
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { KERNEL_ABI } from '../../kernel/host-api.js';
|
|
2
|
+
import { EDITION } from '../../edition.js';
|
|
3
|
+
import { isToolAllowedInEdition } from '../../core/tool-tiers.js';
|
|
4
|
+
import { createGimpCoreTools } from '../../tools/gimp-core-tools.js';
|
|
5
|
+
import { createGimpDocumentTools } from '../../tools/gimp-document-tools.js';
|
|
6
|
+
import { createGimpInspectTools } from '../../tools/gimp-inspect-tools.js';
|
|
7
|
+
import { createGimpAdjustmentTools } from '../../tools/gimp-adjustment-tools.js';
|
|
8
|
+
import { createGimpEffectTools } from '../../tools/gimp-effect-tools.js';
|
|
9
|
+
import { createGimpFilterTools } from '../../tools/gimp-filter-tools.js';
|
|
10
|
+
import { createGimpGeometryTools } from '../../tools/gimp-geometry-tools.js';
|
|
11
|
+
import { createGimpMaskTools } from '../../tools/gimp-mask-tools.js';
|
|
12
|
+
import { createGimpVerifyTools } from '../../tools/gimp-verify-tools.js';
|
|
13
|
+
import { createGimpCheckpointTools } from '../../tools/gimp-checkpoint-tools.js';
|
|
14
|
+
import { createGimpLayerTools } from '../../tools/gimp-layer-tools.js';
|
|
15
|
+
import { createGimpComposeTools } from '../../tools/gimp-compose-tools.js';
|
|
16
|
+
export const gimpFactories = [
|
|
17
|
+
createGimpCoreTools,
|
|
18
|
+
createGimpDocumentTools,
|
|
19
|
+
createGimpInspectTools,
|
|
20
|
+
createGimpAdjustmentTools,
|
|
21
|
+
createGimpEffectTools,
|
|
22
|
+
createGimpFilterTools,
|
|
23
|
+
createGimpGeometryTools,
|
|
24
|
+
createGimpMaskTools,
|
|
25
|
+
createGimpVerifyTools,
|
|
26
|
+
createGimpCheckpointTools,
|
|
27
|
+
createGimpLayerTools,
|
|
28
|
+
createGimpComposeTools,
|
|
29
|
+
];
|
|
30
|
+
export const gimpModule = {
|
|
31
|
+
manifest: { id: 'gimp', name: 'Editmamei GIMP Tools', abi: KERNEL_ABI },
|
|
32
|
+
register(host) {
|
|
33
|
+
if (!host.gimp) {
|
|
34
|
+
host.logger.warn('gimpModule.register called with no host.gimp backend — skipping.');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const gimp = host.gimp;
|
|
38
|
+
const defs = gimpFactories
|
|
39
|
+
.flatMap((f) => f(gimp))
|
|
40
|
+
.filter((def) => isToolAllowedInEdition(def.tool.name, EDITION));
|
|
41
|
+
host.registerTools(defs);
|
|
42
|
+
},
|
|
43
|
+
};
|