editmamei 0.24.1 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/LICENSE.md +110 -0
  2. package/NOTICES.md +6 -30
  3. package/README.md +49 -13
  4. package/dist/api/extendscript/_helpers.js +1 -1
  5. package/dist/api/photoshop-api.js +20 -14
  6. package/dist/bin/editmamei-core-darwin-arm64 +0 -0
  7. package/dist/bin/editmamei-core-darwin-x64 +0 -0
  8. package/dist/bin/editmamei-core-win-x64.exe +0 -0
  9. package/dist/cli/help.js +1 -1
  10. package/dist/core/server.js +6 -6
  11. package/dist/core/session.js +9 -10
  12. package/dist/core/tool-registry.js +4 -4
  13. package/dist/delivery/store.js +10 -2
  14. package/dist/detection/detect-active-doc.js +1 -1
  15. package/dist/diagnostics/collect.js +1 -1
  16. package/dist/kernel/module-lifecycle.js +7 -1
  17. package/dist/perception/region-precompute.js +13 -5
  18. package/dist/perception/select-recipes.js +2 -2
  19. package/dist/perception/sky-mask-transfer.js +1 -1
  20. package/dist/platform/connection.js +32 -46
  21. package/dist/platform/host-platform.js +16 -0
  22. package/dist/platform/macos-detector.js +41 -71
  23. package/dist/platform/macos-runner.js +94 -0
  24. package/dist/platform/script-result.js +39 -0
  25. package/dist/platform/windows-detector.js +44 -68
  26. package/dist/platform/windows-runner.js +84 -0
  27. package/dist/skills/editmamei-skill.zip +0 -0
  28. package/dist/tools/adjustment-tools.js +18 -2
  29. package/dist/tools/brush-tools.js +1 -1
  30. package/dist/tools/document-tools.js +9 -2
  31. package/dist/tools/filter-tools.js +1 -1
  32. package/dist/tools/layer-tools.js +132 -7
  33. package/dist/tools/path-tools.js +1 -1
  34. package/dist/tools/preview-tools.js +8 -8
  35. package/dist/tools/retouch-tools.js +1 -1
  36. package/dist/tools/scene-tools.js +29 -2
  37. package/dist/tools/selection-tools.js +81 -3
  38. package/dist/update/check.js +1 -1
  39. package/dist/utils/logger.js +53 -46
  40. package/dist/utils/session-log.js +1 -1
  41. package/dist/utils/tool-helpers.js +9 -3
  42. package/dist/version.js +1 -1
  43. package/package.json +12 -5
  44. package/LICENSE +0 -81
  45. package/dist/platform/detector.js +0 -32
  46. package/dist/platform/macos-executor.js +0 -96
  47. package/dist/platform/windows-executor.js +0 -105
  48. /package/dist/platform/{script-executor.js → ports.js} +0 -0
  49. /package/dist/platform/{executor-queue.js → script-queue.js} +0 -0
package/LICENSE.md ADDED
@@ -0,0 +1,110 @@
1
+ # Functional Source License, Version 1.1, MIT Future License
2
+
3
+ ## Abbreviation
4
+
5
+ FSL-1.1-MIT
6
+
7
+ ## Notice
8
+
9
+ Copyright 2026 EMBD Artifacts LLC
10
+
11
+ ## Terms and Conditions
12
+
13
+ ### Licensor ("We")
14
+
15
+ The party offering the Software under these Terms and Conditions.
16
+
17
+ ### The Software
18
+
19
+ The "Software" is each version of the software that we make available under
20
+ these Terms and Conditions, as indicated by our inclusion of these Terms and
21
+ Conditions with the Software.
22
+
23
+ ### License Grant
24
+
25
+ Subject to your compliance with this License Grant and the Patents,
26
+ Redistribution and Trademark clauses below, we hereby grant you the right to
27
+ use, copy, modify, create derivative works, publicly perform, publicly display
28
+ and redistribute the Software for any Permitted Purpose identified below.
29
+
30
+ ### Permitted Purpose
31
+
32
+ A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
33
+ means making the Software available to others in a commercial product or
34
+ service that:
35
+
36
+ 1. substitutes for the Software;
37
+
38
+ 2. substitutes for any other product or service we offer using the Software
39
+ that exists as of the date we make the Software available; or
40
+
41
+ 3. offers the same or substantially similar functionality as the Software.
42
+
43
+ Permitted Purposes specifically include using the Software:
44
+
45
+ 1. for your internal use and access;
46
+
47
+ 2. for non-commercial education;
48
+
49
+ 3. for non-commercial research; and
50
+
51
+ 4. in connection with professional services that you provide to a licensee
52
+ using the Software in accordance with these Terms and Conditions.
53
+
54
+ ### Patents
55
+
56
+ To the extent your use for a Permitted Purpose would necessarily infringe our
57
+ patents, the license grant above includes a license under our patents. If you
58
+ make a claim against any party that the Software infringes or contributes to
59
+ the infringement of any patent, then your patent license to the Software ends
60
+ immediately.
61
+
62
+ ### Redistribution
63
+
64
+ The Terms and Conditions apply to all copies, modifications and derivatives of
65
+ the Software.
66
+
67
+ If you redistribute any copies, modifications or derivatives of the Software,
68
+ you must include a copy of or a link to these Terms and Conditions and not
69
+ remove any copyright notices provided in or with the Software.
70
+
71
+ ### Disclaimer
72
+
73
+ THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
74
+ IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
75
+ PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
76
+
77
+ IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
78
+ SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
79
+ EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
80
+
81
+ ### Trademarks
82
+
83
+ Except for displaying the License Details and identifying us as the origin of
84
+ the Software, you have no right under these Terms and Conditions to use our
85
+ trademarks, trade names, service marks or product names.
86
+
87
+ ## Grant of Future License
88
+
89
+ We hereby irrevocably grant you an additional license to use the Software under
90
+ the MIT license that is effective on the second anniversary of the date we make
91
+ the Software available. On or after that date, you may use the Software under
92
+ the MIT license, in which case the following will apply:
93
+
94
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
95
+ this software and associated documentation files (the "Software"), to deal in
96
+ the Software without restriction, including without limitation the rights to
97
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
98
+ of the Software, and to permit persons to whom the Software is furnished to do
99
+ so, subject to the following conditions:
100
+
101
+ The above copyright notice and this permission notice shall be included in all
102
+ copies or substantial portions of the Software.
103
+
104
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
105
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
106
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
107
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
108
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
109
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
110
+ SOFTWARE.
package/NOTICES.md CHANGED
@@ -72,36 +72,12 @@ above.
72
72
  terms (Apache-2.0 §4(d)) are preserved by reference to that source.
73
73
  - Used to detect the 80 COCO object classes.
74
74
 
75
- ### MediaPipe FaceMesh (`models/pro/face_mesh_468.onnx`) — facial landmarks (Pro)
76
-
77
- - **Upstream:** Google MediaPipe Face Landmark / FaceMesh model (the
78
- `face_landmarks_detector` weight from `face_landmarker.task`), trained by Google
79
- on its own data. Converted to ONNX (468-point variant) and redistributed via the
80
- Hugging Face `Heliosoph/mediapipe-face-onnx` repository.
81
- - **License:** Apache-2.0 (the model is permissively licensed by Google;
82
- commercial-safe — see `docs/product/pro-vision-models.md` for the weights-license
83
- diligence). Any upstream `NOTICE`/attribution terms (Apache-2.0 §4(d)) are
84
- preserved by reference to those sources.
85
- - Runs on-device via the same `onnxruntime-web` backend; the image never leaves the
86
- machine. Used by the Pro `ps_detect_landmarks` tool to locate the 468-point
87
- face mesh (eyes, brows, lips, nose, jaw). Ships in the **Pro module**
88
- (`models/pro/`), not the CE tarball.
89
-
90
- ### MobileSAM (`models/pro/mobile_sam_{encoder,decoder}.onnx`) — promptable segmentation (Pro)
91
-
92
- - **Upstream:** MobileSAM ("Faster Segment Anything: Towards Lightweight SAM for
93
- Mobile Applications", Zhang et al.; `ChaoningZhang/MobileSAM`) — a distilled
94
- TinyViT image encoder paired with the Segment Anything prompt-mask decoder,
95
- exported to ONNX (encoder + decoder). Redistributed here as an **FP16** build
96
- converted from the FP32 ONNX export (halves the download; INT8 dynamic
97
- quantization was evaluated and rejected — it collapsed the encoder to
98
- whole-frame masks).
99
- - **License:** Apache-2.0. Any upstream `NOTICE`/attribution terms (Apache-2.0
100
- §4(d)) are preserved by reference to that source.
101
- - Runs on-device via the same `onnxruntime-web` backend; the image never leaves the
102
- machine. Used by the Pro `ps_select_object` tool to turn a box/point prompt into a
103
- pixel-accurate organic selection. Ships in the **Pro module** (`models/pro/`), not
104
- the CE tarball.
75
+ ### Separately licensed modules
76
+
77
+ The Pro module is delivered as its own artifact and bundles additional third-party
78
+ models. Redistribution obligations follow the artifact that carries the files, so
79
+ those notices and their full license texts ship **inside the Pro module**, beside
80
+ its manifest, rather than being enumerated here.
105
81
 
106
82
  ---
107
83
 
package/README.md CHANGED
@@ -2,19 +2,17 @@
2
2
 
3
3
  **Unlock Photoshop with natural-language photo editing.** AI orchestration, not generation.
4
4
 
5
- (Pronounced like _edamame_. Yes, the snack.)
6
-
7
5
  > Independent project, not affiliated with or endorsed by Adobe Inc.
8
6
 
9
7
  [![npm version](https://img.shields.io/npm/v/editmamei.svg)](https://www.npmjs.com/package/editmamei)
10
- [![License](https://img.shields.io/badge/License-Proprietary-blue.svg)](https://editmamei.com/license)
8
+ [![License](https://img.shields.io/badge/License-FSL--1.1--MIT-blue.svg)](LICENSE.md)
11
9
  [![Platform](https://img.shields.io/badge/Platform-Windows%20%7C%20macOS-lightgrey.svg)]()
12
10
 
13
11
  Editmamei is a Model Context Protocol (MCP) server that drives the Adobe Photoshop you already have. You describe the edit in plain words, your AI assistant plans the steps, and your own copy of Photoshop carries them out with its standard adjustment layers, masks, selections, and filters. The AI directs and Photoshop edits. No generative model touches your pixels, and no image is uploaded to perform an edit.
14
12
 
15
13
  It serves photographers and retouchers who want to edit by conversation just as much as developers wiring Photoshop into an AI pipeline. To learn more or get started, visit **[editmamei.com](https://editmamei.com)**.
16
14
 
17
- **[editmamei.com](https://editmamei.com)** · [Docs & wiki](https://github.com/editmamei/editmamei-wiki) · [Report a bug](https://github.com/editmamei/editmamei-wiki/issues)
15
+ **[editmamei.com](https://editmamei.com)** · [Docs](docs/getting-started.md) · [Report a bug](https://github.com/editmamei/editmamei/issues)
18
16
 
19
17
  ## How it works
20
18
 
@@ -46,7 +44,7 @@ editmamei status # confirms install state + Photoshop detection
46
44
 
47
45
  Then restart your AI client and ask it: _"Is Photoshop connected?"_
48
46
 
49
- Prefer not to use a terminal? Claude Desktop users can install the one-click `.mcpb` extension from [the latest release](https://github.com/editmamei/editmamei-wiki/releases/latest) (no system Node required, Claude Desktop ships its own runtime). Full walkthrough in the [install guide](https://github.com/editmamei/editmamei-wiki/blob/main/docs/installation.md).
47
+ Prefer not to use a terminal? Claude Desktop users can install the one-click [`.mcpb` extension](https://github.com/editmamei/editmamei/releases/latest/download/editmamei.mcpb) directly (no system Node required, Claude Desktop ships its own runtime). Full walkthrough in the [install guide](docs/installation.md).
50
48
 
51
49
  **Requirements:** Adobe Photoshop 2026 (v27.x), Windows 10/11 or macOS 12+, Node.js 20+ (for the npm path), and an MCP-compatible AI client (Claude Desktop, Cursor, Claude Code).
52
50
 
@@ -106,7 +104,25 @@ These are the choices that shape the surface, and the reason an AI assistant can
106
104
  - **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
105
  - **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
106
 
109
- The split is detailed in [pro-features.md](https://github.com/editmamei/editmamei-wiki/blob/main/docs/pro-features.md). Pricing is at [editmamei.com](https://editmamei.com).
107
+ The split is detailed in [pro-features.md](docs/pro-features.md). Pricing is at [editmamei.com](https://editmamei.com).
108
+
109
+ 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.
110
+
111
+ ## Build from source
112
+
113
+ ```bash
114
+ git clone https://github.com/editmamei/editmamei.git
115
+ cd editmamei
116
+ npm install
117
+ npm run build
118
+ npm test
119
+ ```
120
+
121
+ You need Node.js 20+ and a Go toolchain (the build compiles the `editmamei-core` binary that generates Photoshop scripts). The build warns instead of failing when Go is missing, so you can still run the test suite; set `EDITMAMEI_CORE_BIN` to a prebuilt binary if you would rather not install Go.
122
+
123
+ The test suite runs without Photoshop. It verifies the ExtendScript Editmamei generates, never that Photoshop accepted it, so live verification against a real Photoshop is a separate step.
124
+
125
+ Contributions are welcome. Start with [CONTRIBUTING.md](CONTRIBUTING.md), which covers the CLA, the branch and pull-request flow, and what to include in a bug report.
110
126
 
111
127
  ## Configuration
112
128
 
@@ -122,19 +138,39 @@ editmamei config set ps_path "/path/to/Photoshop"
122
138
 
123
139
  ## Privacy
124
140
 
125
- Editmamei runs on your computer and edits in your own Photoshop. No image content, document data, or file paths are sent to Editmamei's servers. It reports anonymous, content-free usage telemetry (on by default, turn it off with `editmamei config set telemetry.usage false`). When your AI assistant needs to see a result, Editmamei sends it a downscaled preview, the same as dropping a photo into a chat with that assistant. Every field is documented in [privacy.md](https://github.com/editmamei/editmamei-wiki/blob/main/docs/privacy.md).
141
+ Editmamei runs on your computer and edits in your own Photoshop. No image content, document data, or file paths are sent to Editmamei's servers. It reports anonymous, content-free usage telemetry (on by default, turn it off with `editmamei config set telemetry.usage false`). When your AI assistant needs to see a result, Editmamei sends it a downscaled preview, the same as dropping a photo into a chat with that assistant. Every field is documented in [privacy.md](docs/privacy.md).
142
+
143
+ The source in this repository is the same code published to npm, so none of the above has to be taken on trust.
126
144
 
127
145
  ## Docs and support
128
146
 
129
147
  - **Features and pricing:** [editmamei.com](https://editmamei.com)
130
- - **Install, getting started, FAQ, Pro features:** [the wiki](https://github.com/editmamei/editmamei-wiki)
131
- - **Bugs and feature requests:** [the issue tracker](https://github.com/editmamei/editmamei-wiki/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.
132
- - **Security:** see [editmamei.com/security](https://editmamei.com/security) (don't file security issues publicly)
148
+ - **Install:** [docs/installation.md](docs/installation.md)
149
+ - **Getting started:** [docs/getting-started.md](docs/getting-started.md)
150
+ - **FAQ:** [docs/faq.md](docs/faq.md)
151
+ - **Pro features:** [docs/pro-features.md](docs/pro-features.md)
152
+ - **Roadmap:** [docs/roadmap.md](docs/roadmap.md)
153
+ - **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.
154
+ - **Security:** see [SECURITY.md](SECURITY.md) (don't file security issues publicly)
133
155
 
134
156
  ## License
135
157
 
136
- Proprietary, free to install and use. See [the license](https://editmamei.com/license). Third-party dependencies keep their own licenses, listed in the bundled `NOTICES.md`.
158
+ Editmamei CE is [Fair Source](https://fair.io) software under the
159
+ [Functional Source License, v1.1, MIT Future License](LICENSE.md) (FSL-1.1-MIT).
160
+
161
+ You can read, run, modify, and redistribute the code for almost anything: commercial photo
162
+ editing, internal tools, education, research, security review. The one thing the license
163
+ holds back, for two years per release, is offering the code to others in a commercial
164
+ product or service that competes with Editmamei CE or Pro. If you redistribute the code,
165
+ keep the license and copyright notices with it. Two years after each version ships, that
166
+ version automatically becomes available under plain [MIT](LICENSE.md).
167
+
168
+ This section is a plain-English summary. The [LICENSE](LICENSE.md) file is the license;
169
+ where they differ, the LICENSE file controls.
170
+
171
+ Editmamei is not open source under the OSI definition, and we don't call it that. It is
172
+ source-available, with the full CE stack developed in the open.
137
173
 
138
- ---
174
+ Editmamei Pro is a separate, commercially licensed module; its source is not published.
139
175
 
140
- _Pairs well with: a layered PSD, a willing AI, and a small bowl of edamame._
176
+ Third-party dependencies keep their own licenses, listed in [NOTICES.md](NOTICES.md).
@@ -182,7 +182,7 @@ export function duplicateForOp(opName, applyToActiveLayer) {
182
182
  `;
183
183
  }
184
184
  return `
185
- // Bundle O auto-duplicate-first: the destructive op below will act on
185
+ // Auto-duplicate-first: the destructive op below will act on
186
186
  // a fresh copy of the active layer so the original is preserved.
187
187
  // Undoing the op is as simple as deleting the copy.
188
188
  var __opOriginal = doc.activeLayer;
@@ -12,7 +12,7 @@ export class PhotoshopAPIFactory {
12
12
  throw new Error('Photoshop info not available — the local install could not be detected. ' +
13
13
  'Check that Photoshop is installed (set PHOTOSHOP_PATH to override detection).');
14
14
  }
15
- this.logger.debug(`Creating ExtendScript API for Photoshop version ${info.version}`);
15
+ this.logger.debug(`Photoshop ${info.version} ready for scripting`);
16
16
  return new ExtendScriptPhotoshopAPI(this.connection);
17
17
  }
18
18
  }
@@ -120,17 +120,26 @@ class ExtendScriptPhotoshopAPI {
120
120
  } catch (e) {}
121
121
  }
122
122
 
123
- if (typeof result === 'object' && result !== null) {
124
- return __mcpJsonEncode(result);
125
- }
126
- if (typeof result === 'string') {
127
- // Plain string return — wrap as a JSON string so the Node-side parser
128
- // sees valid JSON. Handlers that expect a string still receive one.
129
- return __mcpJsonEncode(result);
130
- }
131
- return String(result);
123
+ // Every outcome leaves inside an envelope, so the value a script returned
124
+ // is never confused with how it ended. A script that legitimately returns
125
+ // a string beginning with the failure marker used to come back as a thrown
126
+ // error; inside an envelope it round-trips untouched.
127
+ return __mcpJsonEncode({ __em: 1, ok: true, value: result });
132
128
  } catch (error) {
133
- return 'ERROR: ' + (error.message || String(error) || ('code ' + error.number) || 'no error message');
129
+ // Photoshop's error carries more than a message. Passing the number and
130
+ // line through means a failure can be identified rather than string-matched
131
+ // on prose that changes between releases and locales.
132
+ var __message = '';
133
+ try { __message = (error && error.message) ? String(error.message) : String(error); } catch (e) { __message = 'unreadable error'; }
134
+ var __number = null;
135
+ try { if (error && typeof error.number === 'number') __number = error.number; } catch (e) {}
136
+ var __line = null;
137
+ try { if (error && typeof error.line === 'number') __line = error.line; } catch (e) {}
138
+ return __mcpJsonEncode({
139
+ __em: 1,
140
+ ok: false,
141
+ error: { message: __message, number: __number, line: __line }
142
+ });
134
143
  } finally {
135
144
  try { if (__originalRulerUnits !== null) app.preferences.rulerUnits = __originalRulerUnits; } catch (e) {}
136
145
  try { if (__originalTypeUnits !== null) app.preferences.typeUnits = __originalTypeUnits; } catch (e) {}
@@ -139,7 +148,4 @@ class ExtendScriptPhotoshopAPI {
139
148
  })();
140
149
  `.trim();
141
150
  }
142
- getAPIType() {
143
- return 'ExtendScript';
144
- }
145
151
  }
Binary file
Binary file
package/dist/cli/help.js CHANGED
@@ -47,6 +47,6 @@ Examples:
47
47
  Per-user data and session logs live in ~/.editmamei/; uninstall preserves them.
48
48
 
49
49
  Docs: https://editmamei.com/docs
50
- Issues: https://github.com/editmamei/editmamei-wiki/issues
50
+ Issues: https://github.com/editmamei/editmamei/issues
51
51
  `);
52
52
  }
@@ -268,13 +268,13 @@ export class EditmameiServer {
268
268
  }
269
269
  setupHandlers() {
270
270
  this.server.setRequestHandler(ListToolsRequestSchema, async () => {
271
- this.logger.debug('Listing available tools');
271
+ this.logger.debug('Reporting the registered tool surface');
272
272
  return {
273
273
  tools: this.toolRegistry.list(),
274
274
  };
275
275
  });
276
276
  this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
277
- this.logger.debug(`Tool called: ${request.params.name}`);
277
+ this.logger.debug(`Received a call for ${request.params.name}`);
278
278
  const args = request.params.arguments || {};
279
279
  return this.handleToolCall(request.params.name, args);
280
280
  });
@@ -314,7 +314,7 @@ export class EditmameiServer {
314
314
  if (!alive) {
315
315
  return {
316
316
  content: [
317
- { type: 'text', text: 'Failed to connect to Photoshop' + this.updateNote() },
317
+ { type: 'text', text: 'Photoshop did not respond' + this.updateNote() },
318
318
  ],
319
319
  structuredContent: { connected: false, update_available: this.updateInfo },
320
320
  };
@@ -330,7 +330,7 @@ export class EditmameiServer {
330
330
  this.logger.warn(`pingState snippet failed: ${err instanceof Error ? err.message : String(err)}`);
331
331
  return {
332
332
  content: [
333
- { type: 'text', text: 'Failed to connect to Photoshop' + this.updateNote() },
333
+ { type: 'text', text: 'Photoshop did not respond' + this.updateNote() },
334
334
  ],
335
335
  structuredContent: { connected: false, update_available: this.updateInfo },
336
336
  };
@@ -397,12 +397,12 @@ export class EditmameiServer {
397
397
  this.server.onclose = () => {
398
398
  void this.telemetry.shutdown();
399
399
  };
400
- this.logger.info('MCP Server connected via stdio');
400
+ this.logger.info('Editmamei is listening on stdio');
401
401
  }
402
402
  async stop() {
403
403
  await this.telemetry.shutdown();
404
404
  await this.session.disconnect();
405
405
  await this.sessionLog.close();
406
- this.logger.info('MCP Server stopped');
406
+ this.logger.info('Editmamei has shut down');
407
407
  }
408
408
  }
@@ -3,10 +3,10 @@ import { PhotoshopConnection } from '../platform/connection.js';
3
3
  import { generateSessionId } from '../utils/session-log.js';
4
4
  export class Session {
5
5
  logger;
6
- connection = null;
7
6
  config;
8
- lastActivity;
9
7
  sessionId;
8
+ connection = null;
9
+ lastActivity;
10
10
  constructor(config = {}) {
11
11
  this.logger = new Logger('Session');
12
12
  this.config = { autoConnect: true, ...config };
@@ -23,30 +23,29 @@ export class Session {
23
23
  return this.sessionId;
24
24
  }
25
25
  async initialize() {
26
- this.logger.info('Initializing session...');
26
+ this.logger.info('Starting session', this.sessionId);
27
27
  if (this.config.autoConnect) {
28
28
  await this.connect();
29
29
  }
30
30
  }
31
31
  async connect() {
32
32
  try {
33
- this.logger.info('Connecting to Photoshop...');
34
- const connected = await this.ensureConnection().ping();
35
- if (connected) {
33
+ const reached = await this.ensureConnection().ping();
34
+ if (reached) {
36
35
  this.updateActivity();
37
- this.logger.info('Successfully connected to Photoshop');
36
+ this.logger.info('Photoshop is reachable');
38
37
  return true;
39
38
  }
40
- this.logger.warn('Failed to connect to Photoshop');
39
+ this.logger.warn('Photoshop did not answer');
41
40
  return false;
42
41
  }
43
42
  catch (error) {
44
- this.logger.error('Connection error:', error);
43
+ this.logger.error('Could not reach Photoshop', error);
45
44
  return false;
46
45
  }
47
46
  }
48
47
  async disconnect() {
49
- this.logger.info('Disconnecting session...');
48
+ this.logger.info('Ending session', this.sessionId);
50
49
  }
51
50
  getConnection() {
52
51
  return this.ensureConnection();
@@ -10,10 +10,10 @@ export class ToolRegistry {
10
10
  }
11
11
  register(name, definition) {
12
12
  if (this.tools.has(name)) {
13
- this.logger.warn(`Tool '${name}' already registered, overwriting`);
13
+ this.logger.warn(`Replacing an already-registered tool: ${name}`);
14
14
  }
15
15
  this.tools.set(name, definition);
16
- this.logger.debug(`Registered tool: ${name}`);
16
+ this.logger.debug(`Registered ${name}`);
17
17
  }
18
18
  registerAll(definitions) {
19
19
  for (const def of definitions) {
@@ -35,9 +35,9 @@ export class ToolRegistry {
35
35
  async execute(name, args) {
36
36
  const definition = this.tools.get(name);
37
37
  if (!definition) {
38
- throw new Error(`Tool not found: ${name}`);
38
+ throw new Error(`No tool is registered under the name '${name}'`);
39
39
  }
40
- this.logger.debug(`Executing tool: ${name}`);
40
+ this.logger.debug(`Dispatching ${name}`);
41
41
  const started = Date.now();
42
42
  let success = true;
43
43
  let error;
@@ -77,6 +77,14 @@ export function readInstalledModule(sku, opts = {}) {
77
77
  return null;
78
78
  }
79
79
  }
80
+ function removeQuietly(path) {
81
+ try {
82
+ rmSync(path, { recursive: true, force: true });
83
+ }
84
+ catch (err) {
85
+ logger.warn(`could not remove superseded directory ${path}: ${err instanceof Error ? err.message : String(err)} (harmless; the current tree is unaffected)`);
86
+ }
87
+ }
80
88
  function writeFileAtomic(path, data) {
81
89
  const dir = dirname(path);
82
90
  mkdirSync(dir, { recursive: true, mode: 0o700 });
@@ -277,12 +285,12 @@ export function loadVerifiedModule(sku, opts = {}, pubKeys = MODULE_SIGNING_PUBL
277
285
  renameSync(dir, oldDir);
278
286
  try {
279
287
  renameSync(regenDir, dir);
280
- rmSync(oldDir, { recursive: true, force: true });
288
+ removeQuietly(oldDir);
281
289
  }
282
290
  catch (err) {
283
291
  rmSync(regenDir, { recursive: true, force: true });
284
292
  if (existsSync(dir)) {
285
- rmSync(oldDir, { recursive: true, force: true });
293
+ removeQuietly(oldDir);
286
294
  }
287
295
  else {
288
296
  renameSync(oldDir, dir);
@@ -18,7 +18,7 @@ export async function detectActiveDoc(connection, client, opts, deps = {}) {
18
18
  const tempPath = dir.path('detect.jpg');
19
19
  const script = `
20
20
  ${getContextInfo}
21
- if (app.documents.length === 0) { throw new Error('No active document'); }
21
+ if (app.documents.length === 0) { throw new Error('No document is open in Photoshop'); }
22
22
  for (var __pi = app.documents.length - 1; __pi >= 0; __pi--) {
23
23
  try {
24
24
  var __pd = app.documents[__pi];
@@ -10,7 +10,7 @@ import { readSessionLog, listRecentSessionIds } from '../utils/session-log-reade
10
10
  import { classifyError, generateSessionId, } from '../utils/session-log.js';
11
11
  import { detectDownloadsDir } from '../cli/downloads-dir.js';
12
12
  export const DIAGNOSTIC_BUNDLE_SCHEMA = 1;
13
- export const ISSUES_URL = 'https://github.com/editmamei/editmamei-wiki/issues';
13
+ export const ISSUES_URL = 'https://github.com/editmamei/editmamei/issues';
14
14
  const MAX_LOG_LINES = 1000;
15
15
  const MAX_DESKTOP_LOG_LINES = 400;
16
16
  const MAX_RECENT_SESSIONS = 3;
@@ -51,8 +51,14 @@ export class ModuleLifecycle {
51
51
  }
52
52
  }
53
53
  if (EDITION === 'dev') {
54
+ const inTreeProSpecifier = '../modules/pro/index.js';
55
+ const inTreePresent = ['../modules/pro/index.js', '../modules/pro/index.ts'].some((rel) => existsSync(new URL(rel, import.meta.url)));
56
+ if (!inTreePresent) {
57
+ this._proModule = null;
58
+ return null;
59
+ }
54
60
  this._proModule = {
55
- importer: () => import('../modules/pro/index.js'),
61
+ importer: () => import(inTreeProSpecifier),
56
62
  binDir: dirname(resolveProBinaryPath()),
57
63
  abi: null,
58
64
  };
@@ -26,7 +26,7 @@ const PRECOMPUTE_TARGETS = [
26
26
  ];
27
27
  function deleteSceneChannelsScript() {
28
28
  return `
29
- if (app.documents.length === 0) { throw new Error('No active document'); }
29
+ if (app.documents.length === 0) { throw new Error('No document is open in Photoshop'); }
30
30
  var doc = app.activeDocument;
31
31
  var removed = 0;
32
32
  for (var i = doc.channels.length - 1; i >= 0; i--) {
@@ -63,7 +63,7 @@ function channelsExistScript(names) {
63
63
  const namesLit = `[${names.map((n) => jsLit(n)).join(', ')}]`;
64
64
  return `
65
65
  // __mcp_scene_chk__ cheap existence check for previously-saved scene:* channels
66
- if (app.documents.length === 0) { throw new Error('No active document'); }
66
+ if (app.documents.length === 0) { throw new Error('No document is open in Photoshop'); }
67
67
  var doc = app.activeDocument;
68
68
  var have = {};
69
69
  for (var i = 0; i < doc.channels.length; i++) {
@@ -92,7 +92,17 @@ async function channelsExist(connection, names) {
92
92
  }
93
93
  function loadNamedChannelScript(name) {
94
94
  return `
95
- if (app.documents.length === 0) { throw new Error('No active document'); }
95
+ // getSelectionInfo's own source begins with the restoreCompositeChannel
96
+ // definition (see _helpers.ts), so interpolating it here brings BOTH into
97
+ // scope. Interpolating restoreCompositeChannel separately as well emitted a
98
+ // second, byte-identical copy of that function body into every load script —
99
+ // legal (a later function declaration just wins) but pure waste in a string
100
+ // that crosses the COM/AppleScript boundary on every select_by_reference.
101
+ // Hoisted to the TOP so the restoreCompositeChannel(doc) call below sits
102
+ // after its definition textually, rather than relying on hoisting across an
103
+ // interpolation boundary.
104
+ ${getSelectionInfo}
105
+ if (app.documents.length === 0) { throw new Error('No document is open in Photoshop'); }
96
106
  var doc = app.activeDocument;
97
107
  var ch = null;
98
108
  for (var i = 0; i < doc.channels.length; i++) {
@@ -100,12 +110,10 @@ function loadNamedChannelScript(name) {
100
110
  }
101
111
  if (!ch) { return { loaded: false }; }
102
112
  doc.selection.load(ch, SelectionType.REPLACE);
103
- ${restoreCompositeChannel}
104
113
  restoreCompositeChannel(doc);
105
114
  // Measure in the SAME round trip. The fast path used to return
106
115
  // selection_info:null, which forced any caller that wanted to verify the mask
107
116
  // into a second PS call — the one thing this path exists to avoid.
108
- ${getSelectionInfo}
109
117
  return {
110
118
  loaded: true,
111
119
  width: doc.width.as('px'),
@@ -134,7 +134,7 @@ function thresholdSkyScript(level, seedX, seedY) {
134
134
  ${getSelectionInfo}
135
135
  function cTID(s){ return app.charIDToTypeID(s); }
136
136
  function sTID(s){ return app.stringIDToTypeID(s); }
137
- if (app.documents.length === 0) { throw new Error('No active document'); }
137
+ if (app.documents.length === 0) { throw new Error('No document is open in Photoshop'); }
138
138
  var doc = app.activeDocument;
139
139
  var __mk = new ActionDescriptor();
140
140
  var __ref = new ActionReference();
@@ -170,7 +170,7 @@ function posterizeRegionScript(seedX, seedY) {
170
170
  ${getSelectionInfo}
171
171
  function cTID(s){ return app.charIDToTypeID(s); }
172
172
  function sTID(s){ return app.stringIDToTypeID(s); }
173
- if (app.documents.length === 0) { throw new Error('No active document'); }
173
+ if (app.documents.length === 0) { throw new Error('No document is open in Photoshop'); }
174
174
  var doc = app.activeDocument;
175
175
  var __mk = new ActionDescriptor();
176
176
  var __ref = new ActionReference();
@@ -86,7 +86,7 @@ function buildLoadScript(maskPath, docW, docH, selType) {
86
86
  return app.executeActionGet(r).hasKey(app.stringIDToTypeID('selection'));
87
87
  } catch (eSe) { return false; }
88
88
  }
89
- if (app.documents.length === 0) { throw new Error('No active document'); }
89
+ if (app.documents.length === 0) { throw new Error('No document is open in Photoshop'); }
90
90
  var orig = app.activeDocument;
91
91
  var combine = '${selType}';
92
92