editmamei 0.17.5 → 0.19.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/NOTICES.md +43 -0
- package/README.md +91 -8
- package/dist/api/extendscript/_helpers.js +8 -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/clients/json-config.js +1 -1
- package/dist/cli/config.js +7 -0
- package/dist/core/server.js +124 -29
- package/dist/core/settings.js +8 -0
- package/dist/core/tool-groups.js +178 -0
- package/dist/core/tool-tiers.js +34 -48
- package/dist/delivery/bundle.js +8 -0
- package/dist/delivery/config.js +23 -0
- package/dist/delivery/provision.js +30 -2
- package/dist/delivery/store.js +40 -1
- package/dist/detection/detect-active-doc.js +84 -0
- package/dist/detection/detection-client.js +62 -0
- package/dist/detection/face-contour-geometry.js +137 -0
- package/dist/detection/face-detector.js +34 -0
- package/dist/detection/face-feature-geometry.js +180 -0
- package/dist/detection/geometry.js +35 -0
- package/dist/detection/landmark-detection-client.js +38 -0
- package/dist/detection/landmark-detector.js +63 -0
- package/dist/detection/landmark-spec.js +29 -0
- package/dist/detection/mesh-face.js +36 -0
- package/dist/detection/object-detector.js +135 -0
- package/dist/detection/runtime.js +46 -0
- package/dist/install-channel.js +8 -0
- package/dist/models/dfine-s-coco-quant.onnx +0 -0
- package/dist/models/ultraface-rfb-320.onnx +0 -0
- package/dist/modules/ce/index.js +23 -5
- package/dist/perception/facets.js +148 -0
- package/dist/perception/region-precompute.js +122 -0
- package/dist/perception/region-scorer.js +151 -0
- package/dist/perception/scene-model.js +156 -0
- package/dist/perception/select-recipes.js +493 -0
- package/dist/perception/sky-ground-flood.js +234 -0
- package/dist/perception/sky-mask-transfer.js +77 -0
- package/dist/platform/macos-executor.js +0 -1
- package/dist/platform/windows-executor.js +6 -1
- package/dist/preview/coordinate-grid.js +250 -0
- package/dist/skills/editmamei-skill.zip +0 -0
- package/dist/telemetry/events.js +2 -1
- package/dist/telemetry/sanitize.js +15 -1
- package/dist/tools/adjustment-tools.js +46 -51
- package/dist/tools/detection-tools.js +209 -0
- package/dist/tools/document-tools.js +41 -30
- package/dist/tools/filter-tools.js +616 -101
- package/dist/tools/guide-tools.js +186 -0
- package/dist/tools/history-tools.js +1 -39
- package/dist/tools/image-tools.js +88 -0
- package/dist/tools/inspect-tools.js +86 -0
- package/dist/tools/layer-properties-tools.js +195 -106
- package/dist/tools/layer-tools.js +55 -14
- package/dist/tools/layer-transform-tools.js +236 -73
- package/dist/tools/metadata-tools.js +2 -32
- package/dist/tools/object-tools.js +193 -0
- package/dist/tools/overview-tools.js +69 -43
- package/dist/tools/path-tools.js +274 -0
- package/dist/tools/portrait-tools.js +146 -0
- package/dist/tools/preview-tools.js +78 -3
- package/dist/tools/retouch-tools.js +45 -33
- package/dist/tools/scene-tools.js +401 -0
- package/dist/tools/selection-tools.js +766 -228
- package/dist/tools/text-on-object-tools.js +225 -0
- package/dist/tools/text-tools.js +47 -56
- package/dist/tools/transform-canvas-tools.js +138 -0
- package/dist/tools/vector-mask-tools.js +116 -0
- package/dist/update/check.js +77 -0
- package/dist/version.js +1 -1
- package/package.json +4 -2
package/NOTICES.md
CHANGED
|
@@ -46,6 +46,49 @@ artifact rather than by inlining the text here.
|
|
|
46
46
|
|
|
47
47
|
---
|
|
48
48
|
|
|
49
|
+
## Bundled Machine-Learning Models
|
|
50
|
+
|
|
51
|
+
Unlike the npm dependencies above, the local-vision ONNX model weights are
|
|
52
|
+
**bundled** in Editmamei's tarball (under `dist/models/`) and redistributed with
|
|
53
|
+
it, so their notices are reproduced here. The models run entirely on-device; no
|
|
54
|
+
image data is transmitted. The ONNX runtime that executes them
|
|
55
|
+
(`onnxruntime-web`, MIT) is a resolved npm dependency covered by the section
|
|
56
|
+
above.
|
|
57
|
+
|
|
58
|
+
### Ultraface (`ultraface-rfb-320.onnx`) — face detection
|
|
59
|
+
|
|
60
|
+
- **Upstream:** Ultra-Light-Fast-Generic-Face-Detector-1MB (version-RFB-320), by
|
|
61
|
+
Linzaer (Fei Yu); distributed via the ONNX Model Zoo.
|
|
62
|
+
- **License:** MIT.
|
|
63
|
+
- Used to locate forward-facing faces. Returns bounding boxes only.
|
|
64
|
+
|
|
65
|
+
### D-FINE-S (`dfine-s-coco-quant.onnx`) — object detection
|
|
66
|
+
|
|
67
|
+
- **Upstream:** D-FINE ("Redefine Regression Task of DETRs as Fine-grained
|
|
68
|
+
Distribution Refinement", Peng et al.), COCO-trained S variant; ONNX export +
|
|
69
|
+
int8 quantization via the Hugging Face `onnx-community/dfine_s_coco-ONNX`
|
|
70
|
+
repository.
|
|
71
|
+
- **License:** Apache-2.0. The upstream D-FINE repository's `NOTICE`/attribution
|
|
72
|
+
terms (Apache-2.0 §4(d)) are preserved by reference to that source.
|
|
73
|
+
- Used to detect the 80 COCO object classes.
|
|
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 `photoshop_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
|
+
---
|
|
91
|
+
|
|
49
92
|
## Trademarks
|
|
50
93
|
|
|
51
94
|
"Adobe" and "Photoshop" are registered trademarks of Adobe Inc. This product
|
package/README.md
CHANGED
|
@@ -10,9 +10,31 @@
|
|
|
10
10
|
[](https://editmamei.com/license)
|
|
11
11
|
[]()
|
|
12
12
|
|
|
13
|
-
Editmamei is a Model Context Protocol (MCP) server that drives the Adobe Photoshop you already have. You describe the edit in plain words
|
|
13
|
+
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
14
|
|
|
15
|
-
|
|
15
|
+
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
|
+
|
|
17
|
+
**[editmamei.com](https://editmamei.com)** · [Docs & wiki](https://github.com/editmamei/editmamei-wiki) · [Report a bug](https://github.com/editmamei/editmamei-wiki/issues)
|
|
18
|
+
|
|
19
|
+
## How it works
|
|
20
|
+
|
|
21
|
+
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:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
AI client ──stdio──▶ Editmamei (Node MCP server)
|
|
25
|
+
│ builds a Photoshop script per tool call
|
|
26
|
+
▼
|
|
27
|
+
COM (Windows) / AppleScript (macOS)
|
|
28
|
+
│
|
|
29
|
+
▼
|
|
30
|
+
Your local Adobe Photoshop
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
- **Windows** drives Photoshop through COM (the `Photoshop.Application` object).
|
|
34
|
+
- **macOS** drives it through AppleScript / OSA.
|
|
35
|
+
- Perception (face, object, and scene detection) runs **on-device** with local ONNX computer vision, so the model gets real document coordinates without your image leaving the machine.
|
|
36
|
+
|
|
37
|
+
Photoshop does the pixel work with its own engine. Editmamei is the conductor, not a renderer.
|
|
16
38
|
|
|
17
39
|
## Install
|
|
18
40
|
|
|
@@ -24,19 +46,80 @@ editmamei status # confirms install state + Photoshop detection
|
|
|
24
46
|
|
|
25
47
|
Then restart your AI client and ask it: _"Is Photoshop connected?"_
|
|
26
48
|
|
|
27
|
-
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). Full walkthrough in the [install guide](https://github.com/editmamei/editmamei-wiki/blob/main/docs/installation.md).
|
|
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).
|
|
50
|
+
|
|
51
|
+
**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
|
+
|
|
53
|
+
## A session, end to end
|
|
54
|
+
|
|
55
|
+
The discovery chain is `photoshop_ping` (liveness), then `photoshop_overview` (the working method and capability map), then `tools/list` (the full schema-typed inventory). After that you just talk to your assistant:
|
|
56
|
+
|
|
57
|
+
> _"Open my vacation photo."_
|
|
58
|
+
> _"Make the sky more dramatic but keep the foreground natural."_
|
|
59
|
+
> _"Sharpen the eyes, soften the skin, leave everything else alone."_
|
|
60
|
+
> _"Isolate the subject onto its own layer with a clean mask."_
|
|
61
|
+
> _"Export a 2048px web JPEG."_
|
|
28
62
|
|
|
29
|
-
|
|
63
|
+
Your assistant composes those into tool calls, checks the result with previews and measurements, and iterates.
|
|
30
64
|
|
|
31
|
-
##
|
|
65
|
+
## Design principles
|
|
32
66
|
|
|
33
|
-
|
|
67
|
+
These are the choices that shape the surface, and the reason an AI assistant can drive it reliably rather than hopefully.
|
|
68
|
+
|
|
69
|
+
- **Structured results, not prose.** Every tool declares an `outputSchema` and returns a typed JSON payload. Any tool that changes what is active reports the current document and layer back, so the model keeps an accurate picture of Photoshop's state between calls instead of re-deriving it.
|
|
70
|
+
- **Guidance lives in the surface.** `photoshop_overview` returns the working method (assess, plan, enact, check, iterate) and `photoshop_list_capabilities` returns a live map of what exists. The model orients itself from the server rather than from guesswork or a stale prompt.
|
|
71
|
+
- **Measure, don't eyeball.** A dedicated set of verification tools (histogram, region compare, layer-bounds diff, selection preview) lets the model check its own work numerically instead of trusting a thumbnail.
|
|
72
|
+
- **On-device perception.** Local computer vision (ONNX) detects faces, objects, and scene regions and returns real document coordinates. The image stays on your machine for this step and is never sent to a cloud vision model.
|
|
73
|
+
- **Non-destructive by default.** Adjustments are adjustment layers, and destructive pixel operations run on an auto-created duplicate. An edit is reversible by construction.
|
|
74
|
+
- **Verified against real Photoshop.** Every tool is built on Photoshop's own native scripting functions and verified in live Photoshop sessions before it ships. A new tool stays disabled until it has passed live verification, so the shipped surface is the verified surface.
|
|
75
|
+
- **Built for modern Photoshop.** Editmamei targets Photoshop 2026 (v27.x) so it can rely on current, stable scripting descriptors rather than carrying a decade of drifted behavior.
|
|
76
|
+
|
|
77
|
+
## Tool surface
|
|
78
|
+
|
|
79
|
+
**75 tools across 16 capability groups** (58 Community, 17 Pro). Every tool is namespaced `photoshop_*` and discoverable at runtime via `tools/list`. Community tools ship in both editions; Pro tools unlock with a license.
|
|
80
|
+
|
|
81
|
+
| Group | Edition | Tools |
|
|
82
|
+
| --- | --- | --- |
|
|
83
|
+
| **Core** | Community | `ping` · `overview` · `list_capabilities` · `undo` · `redo` |
|
|
84
|
+
| **Inspect** | Community | `inspect` · `get_preview` |
|
|
85
|
+
| **Verify** | Community | `get_histogram` · `compare_regions` · `get_layer_bounds_diff` · `get_selection_preview` |
|
|
86
|
+
| **Document & canvas** | Community | `create_document` · `open_document` · `close_document` · `save_psd` · `export` · `place_image` · `resize_image` · `crop_document` · `convert_image_mode` · `transform_canvas` · `guides` |
|
|
87
|
+
| **Selection** | Community | `select` · `modify_selection` · `selection_channel` |
|
|
88
|
+
| **Adjustments** | Community | `add_adjustment_layer` · `apply_adjustment` |
|
|
89
|
+
| **Filters** | Community | `apply_filter` |
|
|
90
|
+
| **Retouch** | Community | `retouch` |
|
|
91
|
+
| **Layers** | Community | `create_layer` · `delete_layer` · `fill_layer` · `add_fill_layer` · `select_layer` · `move_layer_to_position` · `duplicate_layer` · `layer_via_copy` · `convert_to_smart_object` · `rasterize_layer` · `set_layer` · `merge` · `bake_layer` · `add_layer_style` · `transform_layer` · `create_group` · `move_layer_to_group` · `set_group_blend_mode` · `ungroup` · `delete_group` |
|
|
92
|
+
| **Masks & paths** | Community | `layer_mask` · `create_clipping_mask` · `path` · `vector_mask` |
|
|
93
|
+
| **Type** | Community | `create_text_layer` · `set_text` |
|
|
94
|
+
| **Perception** | Community | `detect` · `scene` · `select_by_reference` |
|
|
95
|
+
| **AI selection** | Pro | `select_subject` · `select_sky` · `select_subject_instance` |
|
|
96
|
+
| **Layers (warp)** | Pro | `warp_layer` · `warp_layer_mesh` |
|
|
97
|
+
| **Face mesh** | Pro | `detect_landmarks` · `select_face_feature` |
|
|
98
|
+
| **Templates** | Pro | `template_create_evidence` · `template_save` · `template_list` · `template_apply` · `template_verify` · `template_recall` · `template_delete` |
|
|
99
|
+
| **Automation** | Pro | `list_actions` · `play_action` · `execute_script` |
|
|
100
|
+
|
|
101
|
+
## Editions
|
|
102
|
+
|
|
103
|
+
- **Community** is free and covers the everyday editing surface: documents, layers, selections, non-destructive adjustments, filters, masks, type, retouching, on-device perception, and the inspect/verify primitives.
|
|
104
|
+
- **Pro** adds AI selection (Sensei-backed Select Subject and Select Sky), face-mesh perception, the reproducible-template system, custom mesh warp, and Photoshop Actions plus the scripting escape hatch.
|
|
105
|
+
|
|
106
|
+
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
|
+
|
|
108
|
+
## Configuration
|
|
109
|
+
|
|
110
|
+
Settings live in `~/.editmamei/settings.json`, managed by the `editmamei config` CLI:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
editmamei config list
|
|
114
|
+
editmamei config set telemetry.usage false # turn off anonymous usage telemetry
|
|
115
|
+
editmamei config set photoshop_path "/path/to/Photoshop"
|
|
116
|
+
```
|
|
34
117
|
|
|
35
|
-
|
|
118
|
+
`PHOTOSHOP_PATH` (env var) overrides Photoshop auto-detection for a single run.
|
|
36
119
|
|
|
37
120
|
## Privacy
|
|
38
121
|
|
|
39
|
-
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
|
|
122
|
+
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).
|
|
40
123
|
|
|
41
124
|
## Docs and support
|
|
42
125
|
|
|
@@ -49,6 +49,14 @@ function saveSelectionToTempChannel(doc) {
|
|
|
49
49
|
if (!hasActiveSelection(doc)) return null;
|
|
50
50
|
var ch = doc.channels.add();
|
|
51
51
|
doc.selection.store(ch, SelectionType.REPLACE);
|
|
52
|
+
// channels.add() makes the new alpha the ACTIVE channel. A pixel-sampling
|
|
53
|
+
// selection op that runs next (magic wand / color range / grow / similar)
|
|
54
|
+
// would then sample THIS alpha — uniform inside the stored selection — and
|
|
55
|
+
// flood the whole region, so a subsequent add/subtract/intersect collapses
|
|
56
|
+
// to empty/full. Geometric ops (rectangle/ellipse) are immune. Restore the
|
|
57
|
+
// composite so the sampler reads RGB. (See restoreCompositeChannel for the
|
|
58
|
+
// sibling cleanup-time case.)
|
|
59
|
+
doc.activeChannels = doc.componentChannels;
|
|
52
60
|
return ch;
|
|
53
61
|
}
|
|
54
62
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -27,7 +27,7 @@ export async function writeJsonMcpConfig(configPath, config) {
|
|
|
27
27
|
const dir = dirname(configPath);
|
|
28
28
|
const tmpPath = join(dir, `.editmamei.json-config.${process.pid}.tmp`);
|
|
29
29
|
const body = JSON.stringify(config, null, 2) + '\n';
|
|
30
|
-
await writeFile(tmpPath, body, 'utf8');
|
|
30
|
+
await writeFile(tmpPath, body, { encoding: 'utf8', mode: 0o600 });
|
|
31
31
|
try {
|
|
32
32
|
await rename(tmpPath, configPath);
|
|
33
33
|
}
|
package/dist/cli/config.js
CHANGED
|
@@ -43,6 +43,13 @@ const KEYS = {
|
|
|
43
43
|
},
|
|
44
44
|
coerce: coercePath,
|
|
45
45
|
},
|
|
46
|
+
update_check: {
|
|
47
|
+
get: (s) => s.update_check,
|
|
48
|
+
set: (s, v) => {
|
|
49
|
+
s.update_check = v;
|
|
50
|
+
},
|
|
51
|
+
coerce: coerceBool,
|
|
52
|
+
},
|
|
46
53
|
};
|
|
47
54
|
function knownKeysHint() {
|
|
48
55
|
return `Known keys:\n${Object.keys(KEYS)
|
package/dist/core/server.js
CHANGED
|
@@ -4,17 +4,19 @@ import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprot
|
|
|
4
4
|
import { Logger } from '../utils/logger.js';
|
|
5
5
|
import { ToolRegistry } from './tool-registry.js';
|
|
6
6
|
import { tierOf } from './tool-tiers.js';
|
|
7
|
+
import { groupOf, GROUPS } from './tool-groups.js';
|
|
7
8
|
import { EDITION } from '../edition.js';
|
|
8
9
|
import { VERSION } from '../version.js';
|
|
9
10
|
import { Session } from './session.js';
|
|
10
11
|
import { SessionLog, classifyError } from '../utils/session-log.js';
|
|
11
|
-
import { loadSettings, applyTelemetryEnvOverrides } from './settings.js';
|
|
12
|
+
import { loadSettings, applyTelemetryEnvOverrides, applyUpdateCheckEnvOverride, } from './settings.js';
|
|
12
13
|
import { TelemetryClient } from '../telemetry/client.js';
|
|
14
|
+
import { checkForUpdate, shouldCheckForUpdate } from '../update/check.js';
|
|
13
15
|
import { join, dirname } from 'node:path';
|
|
14
16
|
import { pathToFileURL } from 'node:url';
|
|
15
17
|
import { GoSnippetClient, resolveProBinaryPath, coreBinaryName } from '../api/snippet-client.js';
|
|
16
18
|
import { isProEntitled } from '../license/entitlement.js';
|
|
17
|
-
import {
|
|
19
|
+
import { loadVerifiedModule, PRO_SKU } from '../delivery/store.js';
|
|
18
20
|
import { runScript } from '../utils/run-script.js';
|
|
19
21
|
import { listTemplates } from '../utils/template-storage.js';
|
|
20
22
|
import { Kernel } from '../kernel/kernel.js';
|
|
@@ -48,6 +50,7 @@ export class EditmameiServer {
|
|
|
48
50
|
kernel;
|
|
49
51
|
proModule = null;
|
|
50
52
|
psVersion = null;
|
|
53
|
+
updateInfo = null;
|
|
51
54
|
snippetClient = new GoSnippetClient();
|
|
52
55
|
constructor() {
|
|
53
56
|
this.logger = new Logger('EditmameiServer');
|
|
@@ -55,13 +58,18 @@ export class EditmameiServer {
|
|
|
55
58
|
this.sessionLog = new SessionLog(this.session.getSessionId());
|
|
56
59
|
this.logger.info(`Session ${this.session.getSessionId()} → ${this.sessionLog.path}`);
|
|
57
60
|
const { settings, created } = loadSettings();
|
|
58
|
-
const effectiveSettings = applyTelemetryEnvOverrides(settings);
|
|
61
|
+
const effectiveSettings = applyUpdateCheckEnvOverride(applyTelemetryEnvOverrides(settings));
|
|
59
62
|
this.telemetry = new TelemetryClient({
|
|
60
63
|
settings: effectiveSettings,
|
|
61
64
|
getPsVersion: () => this.psVersion,
|
|
62
65
|
});
|
|
63
66
|
if (created)
|
|
64
67
|
this.logger.info(FIRST_RUN_DISCLOSURE);
|
|
68
|
+
if (shouldCheckForUpdate(effectiveSettings.update_check)) {
|
|
69
|
+
void checkForUpdate().then((info) => {
|
|
70
|
+
this.updateInfo = info;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
65
73
|
warnLogScriptOnErrorOnce(this.logger);
|
|
66
74
|
this.toolRegistry = new ToolRegistry({
|
|
67
75
|
onCall: (entry) => {
|
|
@@ -123,6 +131,16 @@ export class EditmameiServer {
|
|
|
123
131
|
items: { type: 'string' },
|
|
124
132
|
description: 'List of optional discovery signals that could not be gathered (e.g. "pingState" if the in-PS state snippet failed, "templates" if ~/.editmamei/templates was unreadable). Empty when all signals were collected cleanly. Returned defaults for any degraded field cannot be trusted as ground truth.',
|
|
125
133
|
},
|
|
134
|
+
update_available: {
|
|
135
|
+
type: ['object', 'null'],
|
|
136
|
+
description: 'Set when a newer Editmamei version is published, else null. When present, TELL THE USER: relay current → latest and how_to_update verbatim. { current, latest, channel (npm/mcpb/dev), how_to_update }. Anonymous npm-registry check at boot; opt out with `editmamei config set update_check false`.',
|
|
137
|
+
properties: {
|
|
138
|
+
current: { type: 'string' },
|
|
139
|
+
latest: { type: 'string' },
|
|
140
|
+
channel: { type: 'string' },
|
|
141
|
+
how_to_update: { type: 'string' },
|
|
142
|
+
},
|
|
143
|
+
},
|
|
126
144
|
},
|
|
127
145
|
},
|
|
128
146
|
annotations: {
|
|
@@ -133,6 +151,38 @@ export class EditmameiServer {
|
|
|
133
151
|
},
|
|
134
152
|
handler: async () => await this.pingPhotoshop(),
|
|
135
153
|
});
|
|
154
|
+
this.toolRegistry.register('photoshop_list_capabilities', {
|
|
155
|
+
tool: {
|
|
156
|
+
name: 'photoshop_list_capabilities',
|
|
157
|
+
description: 'Live map of every tool this MCP exposes, grouped by capability — each group is a one-line purpose plus the tool names in it. Read-only and cheap. Reach for it to re-orient mid-session (what exists, what to reach for) when the overview brief has scrolled out of context, or to learn which tool names to look up. photoshop_overview tells you HOW to combine tools; this tells you WHAT exists, currently; tools/list has the full schema for any one you pick.',
|
|
158
|
+
inputSchema: { type: 'object', properties: {} },
|
|
159
|
+
outputSchema: {
|
|
160
|
+
type: 'object',
|
|
161
|
+
properties: {
|
|
162
|
+
tool_count: { type: 'number' },
|
|
163
|
+
group_count: { type: 'number' },
|
|
164
|
+
groups: {
|
|
165
|
+
type: 'array',
|
|
166
|
+
items: {
|
|
167
|
+
type: 'object',
|
|
168
|
+
properties: {
|
|
169
|
+
id: { type: 'string' },
|
|
170
|
+
label: { type: 'string' },
|
|
171
|
+
purpose: { type: 'string' },
|
|
172
|
+
tools: { type: 'array', items: { type: 'string' } },
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
annotations: {
|
|
179
|
+
title: 'List Capabilities',
|
|
180
|
+
readOnlyHint: true,
|
|
181
|
+
idempotentHint: true,
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
handler: async () => this.listCapabilities(),
|
|
185
|
+
});
|
|
136
186
|
this.proModule = this.resolveProModule();
|
|
137
187
|
this.kernel = new Kernel({
|
|
138
188
|
registry: this.toolRegistry,
|
|
@@ -150,12 +200,11 @@ export class EditmameiServer {
|
|
|
150
200
|
}
|
|
151
201
|
resolveProModule() {
|
|
152
202
|
if (isProEntitled()) {
|
|
153
|
-
const
|
|
154
|
-
if (
|
|
155
|
-
const entry = moduleHandlersPath(PRO_SKU, installed.version);
|
|
203
|
+
const verified = loadVerifiedModule(PRO_SKU);
|
|
204
|
+
if (verified) {
|
|
156
205
|
return {
|
|
157
|
-
importer: () => import(pathToFileURL(
|
|
158
|
-
binDir:
|
|
206
|
+
importer: () => import(pathToFileURL(verified.handlersPath).href),
|
|
207
|
+
binDir: verified.binDir,
|
|
159
208
|
};
|
|
160
209
|
}
|
|
161
210
|
}
|
|
@@ -182,8 +231,41 @@ export class EditmameiServer {
|
|
|
182
231
|
assertToolsClassified() {
|
|
183
232
|
for (const tool of this.toolRegistry.list()) {
|
|
184
233
|
tierOf(tool.name);
|
|
234
|
+
groupOf(tool.name);
|
|
185
235
|
}
|
|
186
236
|
}
|
|
237
|
+
listCapabilities() {
|
|
238
|
+
const registered = this.toolRegistry.list();
|
|
239
|
+
const toolsByGroup = new Map();
|
|
240
|
+
for (const tool of registered) {
|
|
241
|
+
const g = groupOf(tool.name);
|
|
242
|
+
const arr = toolsByGroup.get(g) ?? [];
|
|
243
|
+
arr.push(tool.name);
|
|
244
|
+
toolsByGroup.set(g, arr);
|
|
245
|
+
}
|
|
246
|
+
const groups = Object.keys(GROUPS)
|
|
247
|
+
.map((id) => ({
|
|
248
|
+
id,
|
|
249
|
+
label: GROUPS[id].label,
|
|
250
|
+
purpose: GROUPS[id].purpose,
|
|
251
|
+
tools: (toolsByGroup.get(id) ?? []).sort(),
|
|
252
|
+
}))
|
|
253
|
+
.filter((g) => g.tools.length > 0);
|
|
254
|
+
const text = `Editmamei exposes ${registered.length} tools across ${groups.length} capability groups. ` +
|
|
255
|
+
`This is the live map of WHAT exists; photoshop_overview is HOW to combine them, and ` +
|
|
256
|
+
`tools/list has the full schema for any one tool.\n\n` +
|
|
257
|
+
groups
|
|
258
|
+
.map((g) => `${g.label} (${g.tools.length}) — ${g.purpose}\n ${g.tools.join(', ')}`)
|
|
259
|
+
.join('\n');
|
|
260
|
+
return {
|
|
261
|
+
content: [{ type: 'text', text }],
|
|
262
|
+
structuredContent: {
|
|
263
|
+
tool_count: registered.length,
|
|
264
|
+
group_count: groups.length,
|
|
265
|
+
groups,
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
}
|
|
187
269
|
setupHandlers() {
|
|
188
270
|
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
189
271
|
this.logger.debug('Listing available tools');
|
|
@@ -193,33 +275,38 @@ export class EditmameiServer {
|
|
|
193
275
|
});
|
|
194
276
|
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
195
277
|
this.logger.debug(`Tool called: ${request.params.name}`);
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const result = await this.toolRegistry.execute(request.params.name, args);
|
|
199
|
-
this.session.updateActivity();
|
|
200
|
-
return result;
|
|
201
|
-
}
|
|
202
|
-
catch (error) {
|
|
203
|
-
this.logger.error(`Tool execution failed: ${request.params.name}`, error);
|
|
204
|
-
return {
|
|
205
|
-
content: [
|
|
206
|
-
{
|
|
207
|
-
type: 'text',
|
|
208
|
-
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
209
|
-
},
|
|
210
|
-
],
|
|
211
|
-
isError: true,
|
|
212
|
-
};
|
|
213
|
-
}
|
|
278
|
+
const args = request.params.arguments || {};
|
|
279
|
+
return this.handleToolCall(request.params.name, args);
|
|
214
280
|
});
|
|
215
281
|
}
|
|
282
|
+
async handleToolCall(name, args) {
|
|
283
|
+
try {
|
|
284
|
+
const result = await this.toolRegistry.execute(name, args);
|
|
285
|
+
this.session.updateActivity();
|
|
286
|
+
return result;
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
this.logger.error(`Tool execution failed: ${name}`, error);
|
|
290
|
+
return {
|
|
291
|
+
content: [
|
|
292
|
+
{
|
|
293
|
+
type: 'text',
|
|
294
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
295
|
+
},
|
|
296
|
+
],
|
|
297
|
+
isError: true,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
}
|
|
216
301
|
async pingPhotoshop() {
|
|
217
302
|
const connection = this.session.getConnection();
|
|
218
303
|
const isConnected = await connection.ping();
|
|
219
304
|
if (!isConnected) {
|
|
220
305
|
return {
|
|
221
|
-
content: [
|
|
222
|
-
|
|
306
|
+
content: [
|
|
307
|
+
{ type: 'text', text: 'Failed to connect to Photoshop' + this.updateNote() },
|
|
308
|
+
],
|
|
309
|
+
structuredContent: { connected: false, update_available: this.updateInfo },
|
|
223
310
|
};
|
|
224
311
|
}
|
|
225
312
|
let version = 'Unknown';
|
|
@@ -268,7 +355,8 @@ export class EditmameiServer {
|
|
|
268
355
|
text: `Connected to Photoshop (v${version}). ` +
|
|
269
356
|
`${actionSetsCount} custom action set(s), ${userTemplates} saved template(s), ` +
|
|
270
357
|
`${openDocuments.length} open document(s)${openDocuments.length ? ': ' + openDocuments.join(', ') : ''}` +
|
|
271
|
-
`${degradedNote}
|
|
358
|
+
`${degradedNote}.` +
|
|
359
|
+
this.updateNote(),
|
|
272
360
|
},
|
|
273
361
|
],
|
|
274
362
|
structuredContent: {
|
|
@@ -278,9 +366,16 @@ export class EditmameiServer {
|
|
|
278
366
|
user_templates: userTemplates,
|
|
279
367
|
open_documents: openDocuments,
|
|
280
368
|
degraded,
|
|
369
|
+
update_available: this.updateInfo,
|
|
281
370
|
},
|
|
282
371
|
};
|
|
283
372
|
}
|
|
373
|
+
updateNote() {
|
|
374
|
+
const u = this.updateInfo;
|
|
375
|
+
if (!u)
|
|
376
|
+
return '';
|
|
377
|
+
return ` Update available: v${u.current} → v${u.latest}. ${u.how_to_update}`;
|
|
378
|
+
}
|
|
284
379
|
async start() {
|
|
285
380
|
await this.session.initialize();
|
|
286
381
|
await this.loadModules();
|
package/dist/core/settings.js
CHANGED
|
@@ -20,6 +20,7 @@ function defaults(installId) {
|
|
|
20
20
|
telemetry: { usage: true, diagnostics: false, install_id: installId },
|
|
21
21
|
privacy: { send_previews_to_llm: true },
|
|
22
22
|
photoshop_path: null,
|
|
23
|
+
update_check: true,
|
|
23
24
|
};
|
|
24
25
|
}
|
|
25
26
|
function coerce(raw, installId) {
|
|
@@ -43,6 +44,7 @@ function coerce(raw, installId) {
|
|
|
43
44
|
: base.privacy.send_previews_to_llm,
|
|
44
45
|
},
|
|
45
46
|
photoshop_path: typeof r.photoshop_path === 'string' ? r.photoshop_path : null,
|
|
47
|
+
update_check: typeof r.update_check === 'boolean' ? r.update_check : base.update_check,
|
|
46
48
|
};
|
|
47
49
|
}
|
|
48
50
|
export function loadSettings(opts = {}) {
|
|
@@ -102,6 +104,12 @@ export function applyTelemetryEnvOverrides(settings, env = process.env) {
|
|
|
102
104
|
},
|
|
103
105
|
};
|
|
104
106
|
}
|
|
107
|
+
export function applyUpdateCheckEnvOverride(settings, env = process.env) {
|
|
108
|
+
const v = parseBoolEnv(env.EDITMAMEI_UPDATE_CHECK);
|
|
109
|
+
if (v === undefined)
|
|
110
|
+
return settings;
|
|
111
|
+
return { ...settings, update_check: v };
|
|
112
|
+
}
|
|
105
113
|
export function saveSettings(settings, opts = {}) {
|
|
106
114
|
const path = settingsPath(opts);
|
|
107
115
|
const dir = dirname(path);
|