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
package/README.md
CHANGED
|
@@ -15,6 +15,12 @@ It serves photographers and retouchers who want to edit by conversation just as
|
|
|
15
15
|
|
|
16
16
|
**[editmamei.com](https://editmamei.com)** · [Docs](docs/getting-started.md) · [Report a bug](https://github.com/editmamei/editmamei/issues)
|
|
17
17
|
|
|
18
|
+
<p align="center">
|
|
19
|
+
<a href="https://editmamei.com"><img src="https://raw.githubusercontent.com/editmamei/editmamei/dev/docs/media/batch-flip.webp" width="540" alt="Seventy-three travel photos graded in Photoshop from one sentence: the prompt, the grading calls three photos at a time, and the finished set."></a>
|
|
20
|
+
<br>
|
|
21
|
+
<sub>A real run: 73 travel photos, one sentence, graded in Photoshop.</sub>
|
|
22
|
+
</p>
|
|
23
|
+
|
|
18
24
|
## How it works
|
|
19
25
|
|
|
20
26
|
Editmamei is a pure MCP **stdio** server written in Node. Your AI client launches it as a subprocess and calls its tools over stdio. Each tool builds a small Photoshop script and runs it in your local Photoshop over the platform's native automation channel:
|
|
@@ -75,7 +81,7 @@ These are the choices that shape the surface, and the reason an AI assistant can
|
|
|
75
81
|
|
|
76
82
|
## Tool surface
|
|
77
83
|
|
|
78
|
-
**
|
|
84
|
+
**111 tools total.** The Photoshop surface is **87 tools across 16 capability groups** (62 Community, 25 Pro), every one namespaced `ps_*` and discoverable at runtime via `tools/list`. Community tools ship in both editions; Pro tools unlock with a license. Alongside it, a second, independent editor surface drives headless GIMP: **24 `gimp_*` tools, Community, currently in beta**, see [GIMP (beta)](#gimp-beta) below. Pro's additions are Photoshop-only; GIMP has no Pro tier.
|
|
79
85
|
|
|
80
86
|
| Group | Edition | Tools |
|
|
81
87
|
| --- | --- | --- |
|
|
@@ -101,15 +107,29 @@ These are the choices that shape the surface, and the reason an AI assistant can
|
|
|
101
107
|
| **Templates** | Pro | `template_create_evidence` · `template_save` · `template_list` · `template_apply` · `template_verify` · `template_recall` · `template_delete` |
|
|
102
108
|
| **Automation** | Pro | `list_actions` · `play_action` · `execute_script` · `batch` |
|
|
103
109
|
|
|
110
|
+
**GIMP (beta), a separate namespace, all Community:**
|
|
111
|
+
|
|
112
|
+
| Group | Edition | Tools |
|
|
113
|
+
| --- | --- | --- |
|
|
114
|
+
| **GIMP** | Community (beta) | `gimp_ping` · `gimp_overview` · `gimp_open_document` · `gimp_close_document` · `gimp_save_xcf` · `gimp_export` · `gimp_inspect` · `gimp_add_adjustment` · `gimp_add_effect` · `gimp_filter` · `gimp_bake` · `gimp_layer` · `gimp_create_document` · `gimp_place_image` · `gimp_canvas` · `gimp_convert_image_mode` · `gimp_crop_document` · `gimp_resize_image` · `gimp_transform_canvas` · `gimp_create_mask` · `gimp_checkpoint` · `gimp_get_preview` · `gimp_get_histogram` · `gimp_compare` |
|
|
115
|
+
|
|
104
116
|
## Editions
|
|
105
117
|
|
|
106
118
|
- **Community** is free and covers the everyday editing surface: documents, layers, selections (including AI Select Subject and Select Sky), non-destructive adjustments, filters, masks, type, retouching, on-device perception, and the inspect/verify primitives.
|
|
107
119
|
- **Pro** adds Camera Raw develop, the grounded precision tools (warp, named-object masks, precision placement), subject-instance targeting, face-mesh perception, the reproducible-template system, and Photoshop Actions plus the scripting escape hatch.
|
|
108
120
|
|
|
121
|
+
Pro is a Photoshop-only upgrade: it adds nothing to the GIMP surface below.
|
|
122
|
+
|
|
109
123
|
The split is detailed in [pro-features.md](docs/pro-features.md). Pricing is at [editmamei.com](https://editmamei.com).
|
|
110
124
|
|
|
111
125
|
This repository holds the Community source. Pro is a separately licensed module and its source is not published here; Community never imports it, so this tree builds and runs on its own.
|
|
112
126
|
|
|
127
|
+
## GIMP (beta)
|
|
128
|
+
|
|
129
|
+
Editmamei can also drive GIMP, headless, as a second editor alongside (or instead of) Photoshop. When GIMP 3.2+ is detected on your machine, a `gimp_*` tool set registers next to the `ps_*` one; use it when you ask for GIMP, or when Photoshop isn't available. There's no visible window: edits happen in a background `gimp-console` process, and you follow along through rendered previews in the conversation instead of watching a window change.
|
|
130
|
+
|
|
131
|
+
The beta covers documents (open, create, save `.xcf`, export), layers and groups, placing one photo into another, 13 non-destructive adjustment types and six effects (vignette, black and white, motion and lens blur, noise, drop shadow), geometric masks, crop/resize/rotate/flip, canvas extension, colour/grayscale conversion, checkpoints you can restore, and the same preview/histogram/compare verification primitives the Photoshop side has. It doesn't yet cover heal/clone, AI subject or sky selection, or text. Full details, requirements, and troubleshooting: [docs/gimp.md](docs/gimp.md).
|
|
132
|
+
|
|
113
133
|
## Build from source
|
|
114
134
|
|
|
115
135
|
```bash
|
|
@@ -151,6 +171,7 @@ The source in this repository is the same code published to npm, so none of the
|
|
|
151
171
|
- **Getting started:** [docs/getting-started.md](docs/getting-started.md)
|
|
152
172
|
- **FAQ:** [docs/faq.md](docs/faq.md)
|
|
153
173
|
- **Troubleshooting:** [docs/troubleshooting.md](docs/troubleshooting.md)
|
|
174
|
+
- **GIMP (beta):** [docs/gimp.md](docs/gimp.md)
|
|
154
175
|
- **Pro features:** [docs/pro-features.md](docs/pro-features.md)
|
|
155
176
|
- **Roadmap:** [docs/roadmap.md](docs/roadmap.md)
|
|
156
177
|
- **Bugs and feature requests:** [the issue tracker](https://github.com/editmamei/editmamei/issues). If something's broken, ask your assistant to "report a problem" (or run `editmamei report`) to drop an anonymized diagnostic bundle in your Downloads folder, then attach it to the issue.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { detectGimp } from './gimp/detect.js';
|
|
2
|
+
import { applyEditorEnvOverride } from '../core/settings.js';
|
|
3
|
+
import { TOOL_TIERS, isToolAllowedInEdition } from '../core/tool-tiers.js';
|
|
4
|
+
import { EDITION } from '../edition.js';
|
|
5
|
+
import { Logger } from '../utils/logger.js';
|
|
6
|
+
const logger = new Logger('DetectEditors');
|
|
7
|
+
function anyGimpToolAllowedInEdition(edition) {
|
|
8
|
+
return Object.keys(TOOL_TIERS).some((name) => name.startsWith('gimp_') && isToolAllowedInEdition(name, edition));
|
|
9
|
+
}
|
|
10
|
+
export async function detectEditors(opts = {}) {
|
|
11
|
+
const budgetMs = opts.budgetMs ?? 750;
|
|
12
|
+
const gimpDetector = opts.detectGimp ?? detectGimp;
|
|
13
|
+
let gimp = null;
|
|
14
|
+
const gimpProbe = gimpDetector().then((install) => {
|
|
15
|
+
gimp = install;
|
|
16
|
+
}, () => {
|
|
17
|
+
gimp = null;
|
|
18
|
+
});
|
|
19
|
+
let timer;
|
|
20
|
+
const timeout = new Promise((resolve) => {
|
|
21
|
+
timer = setTimeout(() => resolve('timeout'), budgetMs);
|
|
22
|
+
});
|
|
23
|
+
const winner = await Promise.race([gimpProbe.then(() => 'settled'), timeout]);
|
|
24
|
+
clearTimeout(timer);
|
|
25
|
+
return { gimp, timedOut: winner === 'timeout' };
|
|
26
|
+
}
|
|
27
|
+
export function resolveEditorRegistration(detected, pin = 'auto', gimpToolsAllowed = true) {
|
|
28
|
+
if (pin === 'gimp') {
|
|
29
|
+
if (!gimpToolsAllowed) {
|
|
30
|
+
logger.warn("editor pinned to 'gimp', but no gimp_* tool ships in this build — falling back to " +
|
|
31
|
+
'registering ps_* instead of leaving the tool surface almost empty.');
|
|
32
|
+
return {
|
|
33
|
+
registerPhotoshop: true,
|
|
34
|
+
registerGimp: false,
|
|
35
|
+
gimpInstall: null,
|
|
36
|
+
gimpDetectionTimedOut: false,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
registerPhotoshop: false,
|
|
41
|
+
registerGimp: true,
|
|
42
|
+
gimpInstall: detected.gimp,
|
|
43
|
+
gimpDetectionTimedOut: detected.timedOut,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
if (pin === 'photoshop') {
|
|
47
|
+
return {
|
|
48
|
+
registerPhotoshop: true,
|
|
49
|
+
registerGimp: false,
|
|
50
|
+
gimpInstall: null,
|
|
51
|
+
gimpDetectionTimedOut: false,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const gimpFound = detected.gimp !== null;
|
|
55
|
+
return {
|
|
56
|
+
registerPhotoshop: true,
|
|
57
|
+
registerGimp: gimpFound,
|
|
58
|
+
gimpInstall: gimpFound ? detected.gimp : null,
|
|
59
|
+
gimpDetectionTimedOut: false,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export async function resolveBootEditors(opts) {
|
|
63
|
+
const env = opts.env ?? process.env;
|
|
64
|
+
const effectiveSettings = applyEditorEnvOverride(opts.settings, env);
|
|
65
|
+
const envGimpPath = env.EDITMAMEI_GIMP_PATH;
|
|
66
|
+
const gimpPathOverride = envGimpPath && envGimpPath.length > 0
|
|
67
|
+
? envGimpPath
|
|
68
|
+
: (effectiveSettings.gimp_path ?? undefined);
|
|
69
|
+
const detectGimpOverride = gimpPathOverride
|
|
70
|
+
? (o) => detectGimp({ ...o, env: { ...(o?.env ?? env), EDITMAMEI_GIMP_PATH: gimpPathOverride } })
|
|
71
|
+
: undefined;
|
|
72
|
+
const detectEditorsFn = opts.detectEditorsFn ?? detectEditors;
|
|
73
|
+
const gimpToolsAllowed = anyGimpToolAllowedInEdition(EDITION);
|
|
74
|
+
const detected = gimpToolsAllowed
|
|
75
|
+
? await detectEditorsFn({ detectGimp: detectGimpOverride })
|
|
76
|
+
: { gimp: null, timedOut: false };
|
|
77
|
+
const decision = resolveEditorRegistration(detected, effectiveSettings.editor, gimpToolsAllowed);
|
|
78
|
+
return gimpPathOverride ? { ...decision, gimpPathOverride } : decision;
|
|
79
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { GimpSession } from './session.js';
|
|
2
|
+
import { detectGimp } from './detect.js';
|
|
3
|
+
import { GimpError } from './errors.js';
|
|
4
|
+
import { currentToolBudget } from '../../utils/tool-budget-context.js';
|
|
5
|
+
export class GimpBackend {
|
|
6
|
+
installInfo;
|
|
7
|
+
sessionFactory;
|
|
8
|
+
sessionOptions;
|
|
9
|
+
detectGimpFn;
|
|
10
|
+
redetectPending;
|
|
11
|
+
session;
|
|
12
|
+
closed = false;
|
|
13
|
+
constructor(install, opts = {}) {
|
|
14
|
+
this.installInfo = install;
|
|
15
|
+
this.sessionFactory = opts.sessionFactory ?? ((o) => new GimpSession(o));
|
|
16
|
+
this.sessionOptions = opts.sessionOptions ?? {};
|
|
17
|
+
const gimpPath = opts.gimpPath;
|
|
18
|
+
this.detectGimpFn =
|
|
19
|
+
opts.detectGimpFn ??
|
|
20
|
+
(gimpPath
|
|
21
|
+
? () => detectGimp({ env: { ...process.env, EDITMAMEI_GIMP_PATH: gimpPath } })
|
|
22
|
+
: () => detectGimp());
|
|
23
|
+
this.redetectPending = opts.gimpDetectionTimedOut === true && install === null;
|
|
24
|
+
}
|
|
25
|
+
get installed() {
|
|
26
|
+
return this.installInfo !== null;
|
|
27
|
+
}
|
|
28
|
+
get install() {
|
|
29
|
+
return this.installInfo;
|
|
30
|
+
}
|
|
31
|
+
get gimpVersion() {
|
|
32
|
+
return this.session?.gimpVersion;
|
|
33
|
+
}
|
|
34
|
+
get state() {
|
|
35
|
+
return this.session?.state ?? 'idle';
|
|
36
|
+
}
|
|
37
|
+
get startOrigin() {
|
|
38
|
+
return this.session?.lastStartOrigin;
|
|
39
|
+
}
|
|
40
|
+
get generation() {
|
|
41
|
+
return this.session?.generation;
|
|
42
|
+
}
|
|
43
|
+
ensureSession() {
|
|
44
|
+
if (this.closed) {
|
|
45
|
+
throw new GimpError('gimp_session_restarted', 'session closed');
|
|
46
|
+
}
|
|
47
|
+
if (!this.installInfo) {
|
|
48
|
+
throw new GimpError('gimp_not_installed', 'no GIMP install was found on this machine. Install GIMP 3.2 or newer, or point at a ' +
|
|
49
|
+
'gimp-console binary with the EDITMAMEI_GIMP_PATH environment variable, or by running ' +
|
|
50
|
+
'`editmamei config set gimp_path <path>` (the gimp_path key in settings.json).');
|
|
51
|
+
}
|
|
52
|
+
if (!this.session) {
|
|
53
|
+
this.session = this.sessionFactory({ install: this.installInfo, ...this.sessionOptions });
|
|
54
|
+
}
|
|
55
|
+
return this.session;
|
|
56
|
+
}
|
|
57
|
+
async maybeRedetect() {
|
|
58
|
+
if (!this.redetectPending)
|
|
59
|
+
return;
|
|
60
|
+
this.redetectPending = false;
|
|
61
|
+
if (this.installInfo !== null)
|
|
62
|
+
return;
|
|
63
|
+
const found = await this.detectGimpFn();
|
|
64
|
+
if (found)
|
|
65
|
+
this.installInfo = found;
|
|
66
|
+
}
|
|
67
|
+
async call(op, args = {}, timeoutMs) {
|
|
68
|
+
await this.maybeRedetect();
|
|
69
|
+
let effectiveTimeoutMs = timeoutMs;
|
|
70
|
+
if (effectiveTimeoutMs === undefined) {
|
|
71
|
+
const budget = currentToolBudget();
|
|
72
|
+
if (budget) {
|
|
73
|
+
const remaining = budget.deadline - Date.now();
|
|
74
|
+
if (remaining <= 0) {
|
|
75
|
+
throw new GimpError('gimp_timeout', `Tool '${budget.toolName}' exceeded its ${budget.budgetMs}ms budget before ${op} could run.`);
|
|
76
|
+
}
|
|
77
|
+
effectiveTimeoutMs = remaining;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return this.ensureSession().call(op, args, { timeoutMs: effectiveTimeoutMs });
|
|
81
|
+
}
|
|
82
|
+
async prepare() {
|
|
83
|
+
await this.maybeRedetect();
|
|
84
|
+
}
|
|
85
|
+
latestPreviewPath() {
|
|
86
|
+
return this.ensureSession().latestPreviewPath();
|
|
87
|
+
}
|
|
88
|
+
copyToLatestPreview(src) {
|
|
89
|
+
this.ensureSession().copyToLatestPreview(src);
|
|
90
|
+
}
|
|
91
|
+
tempPath(name) {
|
|
92
|
+
return this.ensureSession().tempPath(name);
|
|
93
|
+
}
|
|
94
|
+
async shutdown() {
|
|
95
|
+
this.closed = true;
|
|
96
|
+
if (this.session)
|
|
97
|
+
await this.session.shutdown();
|
|
98
|
+
}
|
|
99
|
+
}
|