editmamei 1.5.0 → 1.6.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 +17 -1
- package/dist/backends/detect-editors.js +79 -0
- package/dist/backends/gimp/backend.js +96 -0
- package/dist/backends/gimp/bridge/lib.py +1229 -0
- package/dist/backends/gimp/bridge/ops.py +1501 -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 +629 -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/server.js +142 -56
- package/dist/core/settings.js +25 -0
- package/dist/core/tool-activity.js +114 -0
- package/dist/core/tool-groups.js +16 -0
- package/dist/core/tool-tiers.js +16 -0
- package/dist/diagnostics/collect.js +4 -0
- package/dist/index.js +5 -1
- package/dist/kernel/kernel.js +3 -0
- package/dist/kernel/module-lifecycle.js +69 -9
- package/dist/license/advisory.js +109 -0
- package/dist/license/entitlement.js +34 -6
- package/dist/license/polar-client.js +50 -7
- package/dist/license/store.js +59 -0
- package/dist/modules/gimp/index.js +35 -0
- package/dist/skills/editmamei-skill.zip +0 -0
- package/dist/telemetry/activity.js +1 -93
- package/dist/telemetry/client.js +55 -17
- package/dist/telemetry/events.js +5 -0
- package/dist/telemetry/outbox.js +43 -18
- package/dist/tools/adjustment-tools.js +2 -1
- package/dist/tools/gimp-adjustment-tools.js +382 -0
- package/dist/tools/gimp-core-tools.js +288 -0
- package/dist/tools/gimp-document-tools.js +321 -0
- package/dist/tools/gimp-filter-tools.js +93 -0
- package/dist/tools/gimp-geometry-tools.js +191 -0
- package/dist/tools/gimp-inspect-tools.js +70 -0
- package/dist/tools/gimp-mask-tools.js +102 -0
- package/dist/tools/gimp-shared.js +51 -0
- package/dist/tools/gimp-verify-tools.js +374 -0
- package/dist/utils/gimp-path.js +27 -0
- package/dist/utils/operation-timeouts.js +16 -0
- package/dist/utils/session-log.js +19 -1
- package/dist/utils/tool-helpers.js +14 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/core/server.js
CHANGED
|
@@ -5,6 +5,7 @@ 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';
|
|
8
9
|
import { EDITION } from '../edition.js';
|
|
9
10
|
import { VERSION } from '../version.js';
|
|
10
11
|
import { Session } from './session.js';
|
|
@@ -19,6 +20,7 @@ import { createHash } from 'node:crypto';
|
|
|
19
20
|
import { GoSnippetClient, coreBinaryName } from '../api/snippet-client.js';
|
|
20
21
|
import { isProEntitled } from '../license/entitlement.js';
|
|
21
22
|
import { createPingLicenseRefresher } from '../license/ping-refresh.js';
|
|
23
|
+
import { licenseAdvisory } from '../license/advisory.js';
|
|
22
24
|
import { runScript } from '../utils/run-script.js';
|
|
23
25
|
import { listTemplates } from '../utils/template-storage.js';
|
|
24
26
|
import { toolErrorResult } from '../utils/tool-helpers.js';
|
|
@@ -28,6 +30,9 @@ import { ceModule } from '../modules/ce/index.js';
|
|
|
28
30
|
import { hostDetectionRuntime } from '../detection/runtime.js';
|
|
29
31
|
import { ModuleLifecycle, classifyModuleOutcome } from '../kernel/module-lifecycle.js';
|
|
30
32
|
import { unsupportedHostReason } from '../platform/host-platform.js';
|
|
33
|
+
import { gimpModule } from '../modules/gimp/index.js';
|
|
34
|
+
import { GimpBackend } from '../backends/gimp/backend.js';
|
|
35
|
+
import { createDiagnosticsTools } from '../tools/diagnostics-tools.js';
|
|
31
36
|
export { classifyModuleOutcome };
|
|
32
37
|
let logScriptOnErrorWarned = false;
|
|
33
38
|
function warnLogScriptOnErrorOnce(logger) {
|
|
@@ -67,6 +72,9 @@ export class EditmameiServer {
|
|
|
67
72
|
session;
|
|
68
73
|
sessionLog;
|
|
69
74
|
telemetry;
|
|
75
|
+
editorOpts;
|
|
76
|
+
gimpBackend;
|
|
77
|
+
lastLoggedGimpVersion;
|
|
70
78
|
kernel;
|
|
71
79
|
moduleLifecycle;
|
|
72
80
|
get moduleSkipReason() {
|
|
@@ -84,7 +92,24 @@ export class EditmameiServer {
|
|
|
84
92
|
updateNoticeShown = false;
|
|
85
93
|
snippetClient = new GoSnippetClient();
|
|
86
94
|
refreshLicenseOnPing = createPingLicenseRefresher();
|
|
95
|
+
moduleUpdateFailed = false;
|
|
96
|
+
licenseAdvisorySource;
|
|
97
|
+
licenseStoreOptions;
|
|
98
|
+
licenseAdvisoryShown = false;
|
|
87
99
|
constructor(opts = {}) {
|
|
100
|
+
this.licenseStoreOptions = opts.licenseStore ?? {};
|
|
101
|
+
this.editorOpts = opts.editors ?? {
|
|
102
|
+
registerPhotoshop: true,
|
|
103
|
+
registerGimp: false,
|
|
104
|
+
gimpInstall: null,
|
|
105
|
+
gimpDetectionTimedOut: false,
|
|
106
|
+
};
|
|
107
|
+
if (this.editorOpts.registerGimp) {
|
|
108
|
+
this.gimpBackend = new GimpBackend(this.editorOpts.gimpInstall, {
|
|
109
|
+
gimpDetectionTimedOut: this.editorOpts.gimpDetectionTimedOut,
|
|
110
|
+
gimpPath: this.editorOpts.gimpPathOverride,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
88
113
|
this.logger = new Logger('EditmameiServer');
|
|
89
114
|
this.session = new Session();
|
|
90
115
|
this.sessionLog = new SessionLog(this.session.getSessionId());
|
|
@@ -121,6 +146,13 @@ export class EditmameiServer {
|
|
|
121
146
|
this.resolveLiveVersionInBackground();
|
|
122
147
|
}
|
|
123
148
|
}
|
|
149
|
+
if (entry.success && entry.tool.startsWith('gimp_') && this.gimpBackend?.gimpVersion) {
|
|
150
|
+
const version = this.gimpBackend.gimpVersion;
|
|
151
|
+
if (version !== this.lastLoggedGimpVersion) {
|
|
152
|
+
this.lastLoggedGimpVersion = version;
|
|
153
|
+
void this.sessionLog.setGimpVersion(version);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
124
156
|
const resultBytes = computeResultBytes(entry.result);
|
|
125
157
|
void this.sessionLog
|
|
126
158
|
.append({
|
|
@@ -189,56 +221,77 @@ export class EditmameiServer {
|
|
|
189
221
|
capRoots: caps?.roots != null,
|
|
190
222
|
});
|
|
191
223
|
};
|
|
224
|
+
this.licenseAdvisorySource =
|
|
225
|
+
opts.licenseAdvisory ??
|
|
226
|
+
((advisoryOpts) => {
|
|
227
|
+
if (process.env.VITEST !== undefined || process.env.NODE_ENV === 'test')
|
|
228
|
+
return null;
|
|
229
|
+
return licenseAdvisory(advisoryOpts);
|
|
230
|
+
});
|
|
192
231
|
this.registerTools();
|
|
193
232
|
this.setupHandlers();
|
|
194
233
|
}
|
|
234
|
+
buildLicenseAdvisoryNote() {
|
|
235
|
+
if (this.licenseAdvisoryShown)
|
|
236
|
+
return { note: '', stamp: () => { } };
|
|
237
|
+
const text = this.licenseAdvisorySource({ moduleUpdateFailed: this.moduleUpdateFailed });
|
|
238
|
+
if (!text)
|
|
239
|
+
return { note: '', stamp: () => { } };
|
|
240
|
+
return {
|
|
241
|
+
note: ' ' + text,
|
|
242
|
+
stamp: () => {
|
|
243
|
+
this.licenseAdvisoryShown = true;
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
}
|
|
195
247
|
registerTools() {
|
|
196
|
-
this.
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
248
|
+
if (this.editorOpts.registerPhotoshop)
|
|
249
|
+
this.toolRegistry.register('ps_ping', {
|
|
250
|
+
tool: {
|
|
251
|
+
name: 'ps_ping',
|
|
252
|
+
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).',
|
|
253
|
+
inputSchema: {
|
|
254
|
+
type: 'object',
|
|
255
|
+
properties: {},
|
|
256
|
+
},
|
|
257
|
+
outputSchema: {
|
|
258
|
+
type: 'object',
|
|
259
|
+
properties: {
|
|
260
|
+
connected: { type: 'boolean' },
|
|
261
|
+
version: { type: 'string' },
|
|
262
|
+
custom_action_sets: { type: 'number' },
|
|
263
|
+
user_templates: { type: 'number' },
|
|
264
|
+
open_documents: { type: 'array', items: { type: 'string' } },
|
|
265
|
+
degraded: {
|
|
266
|
+
type: 'array',
|
|
267
|
+
items: { type: 'string' },
|
|
268
|
+
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.',
|
|
269
|
+
},
|
|
270
|
+
update_available: {
|
|
271
|
+
type: ['object', 'null'],
|
|
272
|
+
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`.',
|
|
273
|
+
properties: {
|
|
274
|
+
current: { type: 'string' },
|
|
275
|
+
latest: { type: 'string' },
|
|
276
|
+
channel: { type: 'string' },
|
|
277
|
+
how_to_update: { type: 'string' },
|
|
278
|
+
fixed_tools: { type: 'array', items: { type: 'string' } },
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
notify_user: {
|
|
282
|
+
type: 'boolean',
|
|
283
|
+
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.',
|
|
226
284
|
},
|
|
227
|
-
},
|
|
228
|
-
notify_user: {
|
|
229
|
-
type: 'boolean',
|
|
230
|
-
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.',
|
|
231
285
|
},
|
|
232
286
|
},
|
|
287
|
+
annotations: {
|
|
288
|
+
title: 'Ping Photoshop',
|
|
289
|
+
readOnlyHint: true,
|
|
290
|
+
idempotentHint: true,
|
|
291
|
+
},
|
|
233
292
|
},
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
readOnlyHint: true,
|
|
237
|
-
idempotentHint: true,
|
|
238
|
-
},
|
|
239
|
-
},
|
|
240
|
-
handler: async () => await this.pingPhotoshop(),
|
|
241
|
-
});
|
|
293
|
+
handler: async () => await this.pingPhotoshop(),
|
|
294
|
+
});
|
|
242
295
|
this.toolRegistry.register('ps_list_capabilities', {
|
|
243
296
|
tool: {
|
|
244
297
|
name: 'ps_list_capabilities',
|
|
@@ -274,9 +327,14 @@ export class EditmameiServer {
|
|
|
274
327
|
this.moduleLifecycle = new ModuleLifecycle({
|
|
275
328
|
toolRegistry: this.toolRegistry,
|
|
276
329
|
logger: this.logger,
|
|
330
|
+
licenseStore: this.licenseStoreOptions,
|
|
277
331
|
assertToolsClassified: () => this.assertToolsClassified(),
|
|
278
332
|
classifyTool: (name) => this.classifyTool(name),
|
|
279
|
-
onModuleUpdate: (outcome) =>
|
|
333
|
+
onModuleUpdate: (outcome) => {
|
|
334
|
+
if (outcome === 'failed')
|
|
335
|
+
this.moduleUpdateFailed = true;
|
|
336
|
+
this.telemetry.setModuleUpdate(outcome);
|
|
337
|
+
},
|
|
280
338
|
});
|
|
281
339
|
const proModule = this.moduleLifecycle.resolveProModule();
|
|
282
340
|
this.kernel = new Kernel({
|
|
@@ -284,6 +342,7 @@ export class EditmameiServer {
|
|
|
284
342
|
connection: this.session.getConnection(),
|
|
285
343
|
snippet: this.snippetClient,
|
|
286
344
|
detection: hostDetectionRuntime(),
|
|
345
|
+
gimp: this.gimpBackend,
|
|
287
346
|
resolveModuleSnippet: (manifest) => manifest.goCoreSnippets && manifest.goCoreSnippets.length > 0 && proModule
|
|
288
347
|
? new GoSnippetClient({ binaryPath: join(proModule.binDir, coreBinaryName()) })
|
|
289
348
|
: null,
|
|
@@ -291,11 +350,22 @@ export class EditmameiServer {
|
|
|
291
350
|
logger: this.logger,
|
|
292
351
|
});
|
|
293
352
|
this.moduleLifecycle.setKernel(this.kernel);
|
|
294
|
-
|
|
353
|
+
const builtins = [];
|
|
354
|
+
if (this.editorOpts.registerPhotoshop)
|
|
355
|
+
builtins.push(ceModule);
|
|
356
|
+
if (this.editorOpts.registerGimp)
|
|
357
|
+
builtins.push(gimpModule);
|
|
358
|
+
this.kernel.loadBuiltins(builtins);
|
|
295
359
|
this.assertToolsClassified();
|
|
360
|
+
if (!this.editorOpts.registerPhotoshop) {
|
|
361
|
+
const diagnosticsDefs = createDiagnosticsTools(this.session.getConnection(), this.snippetClient).filter((def) => def.tool.name === 'ps_report_problem');
|
|
362
|
+
this.toolRegistry.registerAll(diagnosticsDefs);
|
|
363
|
+
}
|
|
296
364
|
this.logger.info(`Registered ${this.toolRegistry.count()} tools (edition: ${EDITION})`);
|
|
297
365
|
}
|
|
298
366
|
async loadModules() {
|
|
367
|
+
if (!this.editorOpts.registerPhotoshop)
|
|
368
|
+
return;
|
|
299
369
|
return this.moduleLifecycle.loadModules();
|
|
300
370
|
}
|
|
301
371
|
computeModuleStatus() {
|
|
@@ -378,7 +448,7 @@ export class EditmameiServer {
|
|
|
378
448
|
return;
|
|
379
449
|
if (name === 'ps_open_document') {
|
|
380
450
|
const sc = result.structuredContent;
|
|
381
|
-
if (sc?.is_raw_source === true && this.toolRegistry.get(
|
|
451
|
+
if (sc?.is_raw_source === true && this.toolRegistry.get(CAMERA_RAW_TOOL)) {
|
|
382
452
|
const opened = sc;
|
|
383
453
|
markRawOpened(String(opened.document_name ?? ''), String(opened.file_path ?? ''));
|
|
384
454
|
}
|
|
@@ -386,12 +456,12 @@ export class EditmameiServer {
|
|
|
386
456
|
clearPendingRawDevelop();
|
|
387
457
|
}
|
|
388
458
|
}
|
|
389
|
-
else if (name ===
|
|
459
|
+
else if (name === RAW_DEVELOP_TOOL) {
|
|
390
460
|
const sc = result.structuredContent;
|
|
391
461
|
if (sc?.opened === true)
|
|
392
462
|
clearPendingRawDevelop();
|
|
393
463
|
}
|
|
394
|
-
else if (name ===
|
|
464
|
+
else if (name === CAMERA_RAW_TOOL || name === 'ps_close_document') {
|
|
395
465
|
clearPendingRawDevelop();
|
|
396
466
|
}
|
|
397
467
|
}
|
|
@@ -425,6 +495,11 @@ export class EditmameiServer {
|
|
|
425
495
|
if (this.updateCheck)
|
|
426
496
|
await this.updateCheck;
|
|
427
497
|
const update = await this.buildUpdateNotice();
|
|
498
|
+
const advisory = this.buildLicenseAdvisoryNote();
|
|
499
|
+
const withAdvisory = (text) => {
|
|
500
|
+
advisory.stamp();
|
|
501
|
+
return text + advisory.note;
|
|
502
|
+
};
|
|
428
503
|
const connection = this.session.getConnection();
|
|
429
504
|
let version = 'Unknown';
|
|
430
505
|
let actionSetsCount = 0;
|
|
@@ -451,8 +526,8 @@ export class EditmameiServer {
|
|
|
451
526
|
content: [
|
|
452
527
|
{
|
|
453
528
|
type: 'text',
|
|
454
|
-
text: 'Photoshop does not appear to be running. Start Photoshop, then call ps_ping again.' +
|
|
455
|
-
update.note,
|
|
529
|
+
text: withAdvisory('Photoshop does not appear to be running. Start Photoshop, then call ps_ping again.' +
|
|
530
|
+
update.note),
|
|
456
531
|
},
|
|
457
532
|
],
|
|
458
533
|
structuredContent: {
|
|
@@ -478,7 +553,7 @@ export class EditmameiServer {
|
|
|
478
553
|
content: [
|
|
479
554
|
{
|
|
480
555
|
type: 'text',
|
|
481
|
-
text: (reason ?? 'Photoshop did not respond') + update.note,
|
|
556
|
+
text: withAdvisory((reason ?? 'Photoshop did not respond') + update.note),
|
|
482
557
|
},
|
|
483
558
|
],
|
|
484
559
|
structuredContent: {
|
|
@@ -501,7 +576,12 @@ export class EditmameiServer {
|
|
|
501
576
|
this.lastDocDepth = null;
|
|
502
577
|
this.lastDocMode = null;
|
|
503
578
|
return {
|
|
504
|
-
content: [
|
|
579
|
+
content: [
|
|
580
|
+
{
|
|
581
|
+
type: 'text',
|
|
582
|
+
text: withAdvisory('Photoshop did not respond' + update.note),
|
|
583
|
+
},
|
|
584
|
+
],
|
|
505
585
|
structuredContent: {
|
|
506
586
|
connected: false,
|
|
507
587
|
update_available: this.updateInfo,
|
|
@@ -567,11 +647,11 @@ export class EditmameiServer {
|
|
|
567
647
|
content: [
|
|
568
648
|
{
|
|
569
649
|
type: 'text',
|
|
570
|
-
text: `Connected to Photoshop (v${version}). ` +
|
|
650
|
+
text: withAdvisory(`Connected to Photoshop (v${version}). ` +
|
|
571
651
|
`${actionSetsCount} custom action set(s), ${userTemplates} saved template(s), ` +
|
|
572
652
|
`${openDocuments.length} open document(s)${openDocuments.length ? ': ' + openDocuments.join(', ') : ''}` +
|
|
573
653
|
`${degradedNote}.` +
|
|
574
|
-
update.note,
|
|
654
|
+
update.note),
|
|
575
655
|
},
|
|
576
656
|
],
|
|
577
657
|
structuredContent: {
|
|
@@ -626,18 +706,24 @@ export class EditmameiServer {
|
|
|
626
706
|
await this.loadModules();
|
|
627
707
|
const transport = new StdioServerTransport();
|
|
628
708
|
await this.server.connect(transport);
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
709
|
+
if (this.editorOpts.registerPhotoshop) {
|
|
710
|
+
void this.session.initialize().catch(() => undefined);
|
|
711
|
+
void this.reprovisionIfModuleSkipped();
|
|
712
|
+
void this.ensureEntitledModuleFresh();
|
|
713
|
+
}
|
|
632
714
|
this.telemetry.start();
|
|
633
715
|
void this.telemetry.flushOutboxOnStartup();
|
|
634
716
|
this.server.onclose = () => {
|
|
635
717
|
void this.telemetry.shutdown();
|
|
718
|
+
if (this.gimpBackend)
|
|
719
|
+
void this.gimpBackend.shutdown();
|
|
636
720
|
};
|
|
637
721
|
this.logger.info('Editmamei is listening on stdio');
|
|
638
722
|
}
|
|
639
723
|
async stop() {
|
|
640
724
|
await this.telemetry.shutdown();
|
|
725
|
+
if (this.gimpBackend)
|
|
726
|
+
await this.gimpBackend.shutdown();
|
|
641
727
|
await this.session.disconnect();
|
|
642
728
|
await this.sessionLog.close();
|
|
643
729
|
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,114 @@
|
|
|
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_filter',
|
|
108
|
+
'gimp_crop_document',
|
|
109
|
+
'gimp_resize_image',
|
|
110
|
+
'gimp_transform_canvas',
|
|
111
|
+
'gimp_create_mask',
|
|
112
|
+
]);
|
|
113
|
+
export const RAW_DEVELOP_TOOL = 'ps_develop_raw';
|
|
114
|
+
export const CAMERA_RAW_TOOL = 'ps_apply_camera_raw';
|
package/dist/core/tool-groups.js
CHANGED
|
@@ -171,6 +171,22 @@ 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_create_mask: 'masks',
|
|
174
190
|
};
|
|
175
191
|
export function groupOf(toolName) {
|
|
176
192
|
const group = TOOL_GROUPS[toolName];
|
package/dist/core/tool-tiers.js
CHANGED
|
@@ -89,6 +89,22 @@ 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_crop_document: 'community',
|
|
102
|
+
gimp_resize_image: 'community',
|
|
103
|
+
gimp_transform_canvas: 'community',
|
|
104
|
+
gimp_create_mask: 'community',
|
|
105
|
+
gimp_get_preview: 'community',
|
|
106
|
+
gimp_get_histogram: 'community',
|
|
107
|
+
gimp_compare: 'community',
|
|
92
108
|
};
|
|
93
109
|
export function tierOf(toolName) {
|
|
94
110
|
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
|
};
|