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.
Files changed (46) hide show
  1. package/README.md +22 -1
  2. package/dist/backends/detect-editors.js +79 -0
  3. package/dist/backends/gimp/backend.js +99 -0
  4. package/dist/backends/gimp/bridge/lib.py +1988 -0
  5. package/dist/backends/gimp/bridge/ops.py +3120 -0
  6. package/dist/backends/gimp/detect.js +86 -0
  7. package/dist/backends/gimp/errors.js +21 -0
  8. package/dist/backends/gimp/launcher.js +6 -0
  9. package/dist/backends/gimp/session.js +632 -0
  10. package/dist/bin/editmamei-core-darwin-arm64 +0 -0
  11. package/dist/bin/editmamei-core-darwin-x64 +0 -0
  12. package/dist/bin/editmamei-core-win-x64.exe +0 -0
  13. package/dist/cli/config.js +28 -0
  14. package/dist/core/preview-privacy.js +54 -0
  15. package/dist/core/server.js +98 -49
  16. package/dist/core/settings.js +25 -0
  17. package/dist/core/tool-activity.js +122 -0
  18. package/dist/core/tool-groups.js +24 -0
  19. package/dist/core/tool-tiers.js +24 -0
  20. package/dist/diagnostics/collect.js +4 -0
  21. package/dist/index.js +5 -1
  22. package/dist/kernel/kernel.js +3 -0
  23. package/dist/modules/gimp/index.js +43 -0
  24. package/dist/skills/editmamei-skill.zip +0 -0
  25. package/dist/telemetry/activity.js +1 -93
  26. package/dist/tools/adjustment-tools.js +2 -1
  27. package/dist/tools/gimp-adjustment-tools.js +385 -0
  28. package/dist/tools/gimp-checkpoint-tools.js +541 -0
  29. package/dist/tools/gimp-compose-tools.js +399 -0
  30. package/dist/tools/gimp-core-tools.js +302 -0
  31. package/dist/tools/gimp-document-tools.js +321 -0
  32. package/dist/tools/gimp-effect-tools.js +267 -0
  33. package/dist/tools/gimp-filter-tools.js +94 -0
  34. package/dist/tools/gimp-geometry-tools.js +223 -0
  35. package/dist/tools/gimp-inspect-tools.js +157 -0
  36. package/dist/tools/gimp-layer-tools.js +385 -0
  37. package/dist/tools/gimp-mask-tools.js +102 -0
  38. package/dist/tools/gimp-shared.js +58 -0
  39. package/dist/tools/gimp-verify-tools.js +409 -0
  40. package/dist/utils/gimp-path.js +27 -0
  41. package/dist/utils/operation-timeouts.js +24 -0
  42. package/dist/utils/session-log.js +19 -1
  43. package/dist/utils/tool-helpers.js +14 -0
  44. package/dist/utils/validate.js +11 -0
  45. package/dist/version.js +1 -1
  46. 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
- **83 tools across 16 capability groups** (62 Community, 21 Pro). Every tool is namespaced `ps_*` and discoverable at runtime via `tools/list`. Community tools ship in both editions; Pro tools unlock with a license.
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
+ }