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.
Files changed (72) hide show
  1. package/NOTICES.md +43 -0
  2. package/README.md +91 -8
  3. package/dist/api/extendscript/_helpers.js +8 -0
  4. package/dist/bin/editmamei-core-darwin-arm64 +0 -0
  5. package/dist/bin/editmamei-core-darwin-x64 +0 -0
  6. package/dist/bin/editmamei-core-win-x64.exe +0 -0
  7. package/dist/cli/clients/json-config.js +1 -1
  8. package/dist/cli/config.js +7 -0
  9. package/dist/core/server.js +124 -29
  10. package/dist/core/settings.js +8 -0
  11. package/dist/core/tool-groups.js +178 -0
  12. package/dist/core/tool-tiers.js +34 -48
  13. package/dist/delivery/bundle.js +8 -0
  14. package/dist/delivery/config.js +23 -0
  15. package/dist/delivery/provision.js +30 -2
  16. package/dist/delivery/store.js +40 -1
  17. package/dist/detection/detect-active-doc.js +84 -0
  18. package/dist/detection/detection-client.js +62 -0
  19. package/dist/detection/face-contour-geometry.js +137 -0
  20. package/dist/detection/face-detector.js +34 -0
  21. package/dist/detection/face-feature-geometry.js +180 -0
  22. package/dist/detection/geometry.js +35 -0
  23. package/dist/detection/landmark-detection-client.js +38 -0
  24. package/dist/detection/landmark-detector.js +63 -0
  25. package/dist/detection/landmark-spec.js +29 -0
  26. package/dist/detection/mesh-face.js +36 -0
  27. package/dist/detection/object-detector.js +135 -0
  28. package/dist/detection/runtime.js +46 -0
  29. package/dist/install-channel.js +8 -0
  30. package/dist/models/dfine-s-coco-quant.onnx +0 -0
  31. package/dist/models/ultraface-rfb-320.onnx +0 -0
  32. package/dist/modules/ce/index.js +23 -5
  33. package/dist/perception/facets.js +148 -0
  34. package/dist/perception/region-precompute.js +122 -0
  35. package/dist/perception/region-scorer.js +151 -0
  36. package/dist/perception/scene-model.js +156 -0
  37. package/dist/perception/select-recipes.js +493 -0
  38. package/dist/perception/sky-ground-flood.js +234 -0
  39. package/dist/perception/sky-mask-transfer.js +77 -0
  40. package/dist/platform/macos-executor.js +0 -1
  41. package/dist/platform/windows-executor.js +6 -1
  42. package/dist/preview/coordinate-grid.js +250 -0
  43. package/dist/skills/editmamei-skill.zip +0 -0
  44. package/dist/telemetry/events.js +2 -1
  45. package/dist/telemetry/sanitize.js +15 -1
  46. package/dist/tools/adjustment-tools.js +46 -51
  47. package/dist/tools/detection-tools.js +209 -0
  48. package/dist/tools/document-tools.js +41 -30
  49. package/dist/tools/filter-tools.js +616 -101
  50. package/dist/tools/guide-tools.js +186 -0
  51. package/dist/tools/history-tools.js +1 -39
  52. package/dist/tools/image-tools.js +88 -0
  53. package/dist/tools/inspect-tools.js +86 -0
  54. package/dist/tools/layer-properties-tools.js +195 -106
  55. package/dist/tools/layer-tools.js +55 -14
  56. package/dist/tools/layer-transform-tools.js +236 -73
  57. package/dist/tools/metadata-tools.js +2 -32
  58. package/dist/tools/object-tools.js +193 -0
  59. package/dist/tools/overview-tools.js +69 -43
  60. package/dist/tools/path-tools.js +274 -0
  61. package/dist/tools/portrait-tools.js +146 -0
  62. package/dist/tools/preview-tools.js +78 -3
  63. package/dist/tools/retouch-tools.js +45 -33
  64. package/dist/tools/scene-tools.js +401 -0
  65. package/dist/tools/selection-tools.js +766 -228
  66. package/dist/tools/text-on-object-tools.js +225 -0
  67. package/dist/tools/text-tools.js +47 -56
  68. package/dist/tools/transform-canvas-tools.js +138 -0
  69. package/dist/tools/vector-mask-tools.js +116 -0
  70. package/dist/update/check.js +77 -0
  71. package/dist/version.js +1 -1
  72. 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
  [![License](https://img.shields.io/badge/License-Proprietary-blue.svg)](https://editmamei.com/license)
11
11
  [![Platform](https://img.shields.io/badge/Platform-Windows%20%7C%20macOS-lightgrey.svg)]()
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; your AI assistant plans the steps; your own copy of Photoshop carries them out with its standard adjustment layers, masks, selections, and filters. The AI directs; Photoshop edits. No generative model touches your pixels.
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
- **[editmamei.com](https://editmamei.com)** · [Docs](https://github.com/editmamei/editmamei-wiki) · [Report a bug](https://github.com/editmamei/editmamei-wiki/issues)
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
- **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.
63
+ Your assistant composes those into tool calls, checks the result with previews and measurements, and iterates.
30
64
 
31
- ## What it does
65
+ ## Design principles
32
66
 
33
- Editmamei gives your AI assistant a working photographer's toolkit inside Photoshop: open and save documents, build layer stacks, make selections, add non-destructive adjustments, run filters, mask, retouch, and check the result with inline previews and histograms. Your AI calls these as building blocks, so you can say _"make the sky more dramatic but keep the foreground natural"_ instead of clicking through menus.
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
- The full capability surface is at [editmamei.com](https://editmamei.com). The Community and Pro split is in [pro-features.md](https://github.com/editmamei/editmamei-wiki/blob/main/docs/pro-features.md): Community is free and covers the everyday editing surface; Pro adds the reproducible-template system, Sensei-backed Select Subject and Select Sky, and Photoshop Actions plus scripting.
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; 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).
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
@@ -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
  }
@@ -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)
@@ -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 { readInstalledModule, moduleHandlersPath, moduleBinDir, PRO_SKU, } from '../delivery/store.js';
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 installed = readInstalledModule(PRO_SKU);
154
- if (installed) {
155
- const entry = moduleHandlersPath(PRO_SKU, installed.version);
203
+ const verified = loadVerifiedModule(PRO_SKU);
204
+ if (verified) {
156
205
  return {
157
- importer: () => import(pathToFileURL(entry).href),
158
- binDir: moduleBinDir(PRO_SKU, installed.version),
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
- try {
197
- const args = request.params.arguments || {};
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: [{ type: 'text', text: 'Failed to connect to Photoshop' }],
222
- structuredContent: { connected: false },
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();
@@ -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);