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.
- package/LICENSE.md +110 -0
- package/NOTICES.md +6 -30
- package/README.md +49 -13
- package/dist/api/extendscript/_helpers.js +1 -1
- package/dist/api/photoshop-api.js +20 -14
- 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/help.js +1 -1
- package/dist/core/server.js +6 -6
- package/dist/core/session.js +9 -10
- package/dist/core/tool-registry.js +4 -4
- package/dist/delivery/store.js +10 -2
- package/dist/detection/detect-active-doc.js +1 -1
- package/dist/diagnostics/collect.js +1 -1
- package/dist/kernel/module-lifecycle.js +7 -1
- package/dist/perception/region-precompute.js +13 -5
- package/dist/perception/select-recipes.js +2 -2
- package/dist/perception/sky-mask-transfer.js +1 -1
- package/dist/platform/connection.js +32 -46
- package/dist/platform/host-platform.js +16 -0
- package/dist/platform/macos-detector.js +41 -71
- package/dist/platform/macos-runner.js +94 -0
- package/dist/platform/script-result.js +39 -0
- package/dist/platform/windows-detector.js +44 -68
- package/dist/platform/windows-runner.js +84 -0
- package/dist/skills/editmamei-skill.zip +0 -0
- package/dist/tools/adjustment-tools.js +18 -2
- package/dist/tools/brush-tools.js +1 -1
- package/dist/tools/document-tools.js +9 -2
- package/dist/tools/filter-tools.js +1 -1
- package/dist/tools/layer-tools.js +132 -7
- package/dist/tools/path-tools.js +1 -1
- package/dist/tools/preview-tools.js +8 -8
- package/dist/tools/retouch-tools.js +1 -1
- package/dist/tools/scene-tools.js +29 -2
- package/dist/tools/selection-tools.js +81 -3
- package/dist/update/check.js +1 -1
- package/dist/utils/logger.js +53 -46
- package/dist/utils/session-log.js +1 -1
- package/dist/utils/tool-helpers.js +9 -3
- package/dist/version.js +1 -1
- package/package.json +12 -5
- package/LICENSE +0 -81
- package/dist/platform/detector.js +0 -32
- package/dist/platform/macos-executor.js +0 -96
- package/dist/platform/windows-executor.js +0 -105
- /package/dist/platform/{script-executor.js → ports.js} +0 -0
- /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
|
-
###
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
[](https://www.npmjs.com/package/editmamei)
|
|
10
|
-
[](LICENSE.md)
|
|
11
9
|
[]()
|
|
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
|
|
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
|
|
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](
|
|
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](
|
|
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
|
|
131
|
-
- **
|
|
132
|
-
- **
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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(`
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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
|
|
Binary file
|
package/dist/cli/help.js
CHANGED
package/dist/core/server.js
CHANGED
|
@@ -268,13 +268,13 @@ export class EditmameiServer {
|
|
|
268
268
|
}
|
|
269
269
|
setupHandlers() {
|
|
270
270
|
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
271
|
-
this.logger.debug('
|
|
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(`
|
|
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: '
|
|
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: '
|
|
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('
|
|
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('
|
|
406
|
+
this.logger.info('Editmamei has shut down');
|
|
407
407
|
}
|
|
408
408
|
}
|
package/dist/core/session.js
CHANGED
|
@@ -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('
|
|
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.
|
|
34
|
-
|
|
35
|
-
if (connected) {
|
|
33
|
+
const reached = await this.ensureConnection().ping();
|
|
34
|
+
if (reached) {
|
|
36
35
|
this.updateActivity();
|
|
37
|
-
this.logger.info('
|
|
36
|
+
this.logger.info('Photoshop is reachable');
|
|
38
37
|
return true;
|
|
39
38
|
}
|
|
40
|
-
this.logger.warn('
|
|
39
|
+
this.logger.warn('Photoshop did not answer');
|
|
41
40
|
return false;
|
|
42
41
|
}
|
|
43
42
|
catch (error) {
|
|
44
|
-
this.logger.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('
|
|
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(`
|
|
13
|
+
this.logger.warn(`Replacing an already-registered tool: ${name}`);
|
|
14
14
|
}
|
|
15
15
|
this.tools.set(name, definition);
|
|
16
|
-
this.logger.debug(`Registered
|
|
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(`
|
|
38
|
+
throw new Error(`No tool is registered under the name '${name}'`);
|
|
39
39
|
}
|
|
40
|
-
this.logger.debug(`
|
|
40
|
+
this.logger.debug(`Dispatching ${name}`);
|
|
41
41
|
const started = Date.now();
|
|
42
42
|
let success = true;
|
|
43
43
|
let error;
|
package/dist/delivery/store.js
CHANGED
|
@@ -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
|
-
|
|
288
|
+
removeQuietly(oldDir);
|
|
281
289
|
}
|
|
282
290
|
catch (err) {
|
|
283
291
|
rmSync(regenDir, { recursive: true, force: true });
|
|
284
292
|
if (existsSync(dir)) {
|
|
285
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|