editmamei 0.17.4 → 0.18.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 (69) 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/core/server.js +92 -24
  9. package/dist/core/tool-groups.js +178 -0
  10. package/dist/core/tool-tiers.js +34 -48
  11. package/dist/delivery/bundle.js +8 -0
  12. package/dist/delivery/config.js +23 -0
  13. package/dist/delivery/provision.js +30 -2
  14. package/dist/delivery/store.js +40 -1
  15. package/dist/detection/detect-active-doc.js +84 -0
  16. package/dist/detection/detection-client.js +62 -0
  17. package/dist/detection/face-contour-geometry.js +137 -0
  18. package/dist/detection/face-detector.js +34 -0
  19. package/dist/detection/face-feature-geometry.js +180 -0
  20. package/dist/detection/geometry.js +35 -0
  21. package/dist/detection/landmark-detection-client.js +38 -0
  22. package/dist/detection/landmark-detector.js +63 -0
  23. package/dist/detection/landmark-spec.js +29 -0
  24. package/dist/detection/mesh-face.js +36 -0
  25. package/dist/detection/object-detector.js +135 -0
  26. package/dist/detection/runtime.js +46 -0
  27. package/dist/license/env-activation.js +46 -7
  28. package/dist/models/dfine-s-coco-quant.onnx +0 -0
  29. package/dist/models/ultraface-rfb-320.onnx +0 -0
  30. package/dist/modules/ce/index.js +23 -5
  31. package/dist/perception/facets.js +148 -0
  32. package/dist/perception/region-precompute.js +122 -0
  33. package/dist/perception/region-scorer.js +151 -0
  34. package/dist/perception/scene-model.js +156 -0
  35. package/dist/perception/select-recipes.js +493 -0
  36. package/dist/perception/sky-ground-flood.js +234 -0
  37. package/dist/perception/sky-mask-transfer.js +77 -0
  38. package/dist/platform/macos-executor.js +0 -1
  39. package/dist/platform/windows-executor.js +6 -1
  40. package/dist/preview/coordinate-grid.js +250 -0
  41. package/dist/skills/editmamei-skill.zip +0 -0
  42. package/dist/telemetry/events.js +2 -1
  43. package/dist/telemetry/sanitize.js +15 -1
  44. package/dist/tools/adjustment-tools.js +46 -51
  45. package/dist/tools/detection-tools.js +209 -0
  46. package/dist/tools/document-tools.js +41 -30
  47. package/dist/tools/filter-tools.js +616 -101
  48. package/dist/tools/guide-tools.js +186 -0
  49. package/dist/tools/history-tools.js +1 -39
  50. package/dist/tools/image-tools.js +88 -0
  51. package/dist/tools/inspect-tools.js +86 -0
  52. package/dist/tools/layer-properties-tools.js +195 -106
  53. package/dist/tools/layer-tools.js +55 -14
  54. package/dist/tools/layer-transform-tools.js +236 -73
  55. package/dist/tools/metadata-tools.js +2 -32
  56. package/dist/tools/object-tools.js +193 -0
  57. package/dist/tools/overview-tools.js +69 -43
  58. package/dist/tools/path-tools.js +274 -0
  59. package/dist/tools/portrait-tools.js +146 -0
  60. package/dist/tools/preview-tools.js +78 -3
  61. package/dist/tools/retouch-tools.js +45 -33
  62. package/dist/tools/scene-tools.js +401 -0
  63. package/dist/tools/selection-tools.js +766 -228
  64. package/dist/tools/text-on-object-tools.js +225 -0
  65. package/dist/tools/text-tools.js +47 -56
  66. package/dist/tools/transform-canvas-tools.js +138 -0
  67. package/dist/tools/vector-mask-tools.js +116 -0
  68. package/dist/version.js +1 -1
  69. 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
  }
@@ -4,6 +4,7 @@ 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';
@@ -14,7 +15,7 @@ import { join, dirname } from 'node:path';
14
15
  import { pathToFileURL } from 'node:url';
15
16
  import { GoSnippetClient, resolveProBinaryPath, coreBinaryName } from '../api/snippet-client.js';
16
17
  import { isProEntitled } from '../license/entitlement.js';
17
- import { readInstalledModule, moduleHandlersPath, moduleBinDir, PRO_SKU, } from '../delivery/store.js';
18
+ import { loadVerifiedModule, PRO_SKU } from '../delivery/store.js';
18
19
  import { runScript } from '../utils/run-script.js';
19
20
  import { listTemplates } from '../utils/template-storage.js';
20
21
  import { Kernel } from '../kernel/kernel.js';
@@ -133,6 +134,38 @@ export class EditmameiServer {
133
134
  },
134
135
  handler: async () => await this.pingPhotoshop(),
135
136
  });
137
+ this.toolRegistry.register('photoshop_list_capabilities', {
138
+ tool: {
139
+ name: 'photoshop_list_capabilities',
140
+ 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.',
141
+ inputSchema: { type: 'object', properties: {} },
142
+ outputSchema: {
143
+ type: 'object',
144
+ properties: {
145
+ tool_count: { type: 'number' },
146
+ group_count: { type: 'number' },
147
+ groups: {
148
+ type: 'array',
149
+ items: {
150
+ type: 'object',
151
+ properties: {
152
+ id: { type: 'string' },
153
+ label: { type: 'string' },
154
+ purpose: { type: 'string' },
155
+ tools: { type: 'array', items: { type: 'string' } },
156
+ },
157
+ },
158
+ },
159
+ },
160
+ },
161
+ annotations: {
162
+ title: 'List Capabilities',
163
+ readOnlyHint: true,
164
+ idempotentHint: true,
165
+ },
166
+ },
167
+ handler: async () => this.listCapabilities(),
168
+ });
136
169
  this.proModule = this.resolveProModule();
137
170
  this.kernel = new Kernel({
138
171
  registry: this.toolRegistry,
@@ -150,12 +183,11 @@ export class EditmameiServer {
150
183
  }
151
184
  resolveProModule() {
152
185
  if (isProEntitled()) {
153
- const installed = readInstalledModule(PRO_SKU);
154
- if (installed) {
155
- const entry = moduleHandlersPath(PRO_SKU, installed.version);
186
+ const verified = loadVerifiedModule(PRO_SKU);
187
+ if (verified) {
156
188
  return {
157
- importer: () => import(pathToFileURL(entry).href),
158
- binDir: moduleBinDir(PRO_SKU, installed.version),
189
+ importer: () => import(pathToFileURL(verified.handlersPath).href),
190
+ binDir: verified.binDir,
159
191
  };
160
192
  }
161
193
  }
@@ -182,7 +214,40 @@ export class EditmameiServer {
182
214
  assertToolsClassified() {
183
215
  for (const tool of this.toolRegistry.list()) {
184
216
  tierOf(tool.name);
217
+ groupOf(tool.name);
218
+ }
219
+ }
220
+ listCapabilities() {
221
+ const registered = this.toolRegistry.list();
222
+ const toolsByGroup = new Map();
223
+ for (const tool of registered) {
224
+ const g = groupOf(tool.name);
225
+ const arr = toolsByGroup.get(g) ?? [];
226
+ arr.push(tool.name);
227
+ toolsByGroup.set(g, arr);
185
228
  }
229
+ const groups = Object.keys(GROUPS)
230
+ .map((id) => ({
231
+ id,
232
+ label: GROUPS[id].label,
233
+ purpose: GROUPS[id].purpose,
234
+ tools: (toolsByGroup.get(id) ?? []).sort(),
235
+ }))
236
+ .filter((g) => g.tools.length > 0);
237
+ const text = `Editmamei exposes ${registered.length} tools across ${groups.length} capability groups. ` +
238
+ `This is the live map of WHAT exists; photoshop_overview is HOW to combine them, and ` +
239
+ `tools/list has the full schema for any one tool.\n\n` +
240
+ groups
241
+ .map((g) => `${g.label} (${g.tools.length}) — ${g.purpose}\n ${g.tools.join(', ')}`)
242
+ .join('\n');
243
+ return {
244
+ content: [{ type: 'text', text }],
245
+ structuredContent: {
246
+ tool_count: registered.length,
247
+ group_count: groups.length,
248
+ groups,
249
+ },
250
+ };
186
251
  }
187
252
  setupHandlers() {
188
253
  this.server.setRequestHandler(ListToolsRequestSchema, async () => {
@@ -193,26 +258,29 @@ export class EditmameiServer {
193
258
  });
194
259
  this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
195
260
  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
- }
261
+ const args = request.params.arguments || {};
262
+ return this.handleToolCall(request.params.name, args);
214
263
  });
215
264
  }
265
+ async handleToolCall(name, args) {
266
+ try {
267
+ const result = await this.toolRegistry.execute(name, args);
268
+ this.session.updateActivity();
269
+ return result;
270
+ }
271
+ catch (error) {
272
+ this.logger.error(`Tool execution failed: ${name}`, error);
273
+ return {
274
+ content: [
275
+ {
276
+ type: 'text',
277
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
278
+ },
279
+ ],
280
+ isError: true,
281
+ };
282
+ }
283
+ }
216
284
  async pingPhotoshop() {
217
285
  const connection = this.session.getConnection();
218
286
  const isConnected = await connection.ping();
@@ -0,0 +1,178 @@
1
+ export const GROUPS = {
2
+ core: {
3
+ id: 'core',
4
+ label: 'Core',
5
+ purpose: 'Session liveness, orientation, and undo/redo — always available.',
6
+ },
7
+ inspect: {
8
+ id: 'inspect',
9
+ label: 'Inspect',
10
+ purpose: 'Read document/layer/selection/history state and render previews.',
11
+ },
12
+ verify: {
13
+ id: 'verify',
14
+ label: 'Verify',
15
+ purpose: 'Measurement primitives — histogram, region compare, bounds diff — to check work instead of eyeballing.',
16
+ },
17
+ document: {
18
+ id: 'document',
19
+ label: 'Document & canvas',
20
+ purpose: 'Open/create/save/export documents; resize/crop/convert/rotate the canvas; place images; guides.',
21
+ },
22
+ select: {
23
+ id: 'select',
24
+ label: 'Selection',
25
+ purpose: 'Manual selections, selection edits, and alpha-channel save/load.',
26
+ },
27
+ select_ai: {
28
+ id: 'select_ai',
29
+ label: 'AI selection',
30
+ purpose: 'Sensei/CV-backed subject & sky selection (Pro).',
31
+ },
32
+ adjust: {
33
+ id: 'adjust',
34
+ label: 'Adjustments',
35
+ purpose: 'Non-destructive adjustment layers + the destructive tonal bakes with no layer equivalent.',
36
+ },
37
+ filter: {
38
+ id: 'filter',
39
+ label: 'Filters',
40
+ purpose: 'Blur/sharpen/noise/stylize filters (auto-duplicate-first by default).',
41
+ },
42
+ retouch: {
43
+ id: 'retouch',
44
+ label: 'Retouch',
45
+ purpose: 'Content-aware fill/patch/move and brush strokes.',
46
+ },
47
+ layers: {
48
+ id: 'layers',
49
+ label: 'Layers',
50
+ purpose: 'Layer lifecycle, properties, transforms, ordering, groups, and styles.',
51
+ },
52
+ masks: {
53
+ id: 'masks',
54
+ label: 'Masks & paths',
55
+ purpose: 'Layer masks, clipping masks, vector masks, and work paths.',
56
+ },
57
+ type: {
58
+ id: 'type',
59
+ label: 'Type',
60
+ purpose: 'Create and style text layers.',
61
+ },
62
+ perception: {
63
+ id: 'perception',
64
+ label: 'Perception',
65
+ purpose: 'On-device CV that gives the model real coordinates/selections — detection, scene read, and detection-driven edits.',
66
+ },
67
+ face: {
68
+ id: 'face',
69
+ label: 'Face mesh (Pro)',
70
+ purpose: 'Face-landmark perception and mesh-aimed feature selection / contour strokes (Pro).',
71
+ },
72
+ templates: {
73
+ id: 'templates',
74
+ label: 'Templates (Pro)',
75
+ purpose: 'Save, recall, apply, and verify reproducible aesthetic recipes (Pro).',
76
+ },
77
+ automation: {
78
+ id: 'automation',
79
+ label: 'Automation (Pro)',
80
+ purpose: 'Play recorded Actions and the ExtendScript escape hatch (Pro).',
81
+ },
82
+ };
83
+ export const TOOL_GROUPS = {
84
+ photoshop_ping: 'core',
85
+ photoshop_overview: 'core',
86
+ photoshop_undo: 'core',
87
+ photoshop_redo: 'core',
88
+ photoshop_list_capabilities: 'core',
89
+ photoshop_inspect: 'inspect',
90
+ photoshop_get_preview: 'inspect',
91
+ photoshop_get_histogram: 'verify',
92
+ photoshop_compare_regions: 'verify',
93
+ photoshop_get_layer_bounds_diff: 'verify',
94
+ photoshop_get_selection_preview: 'verify',
95
+ photoshop_create_document: 'document',
96
+ photoshop_open_document: 'document',
97
+ photoshop_close_document: 'document',
98
+ photoshop_save_psd: 'document',
99
+ photoshop_export: 'document',
100
+ photoshop_place_image: 'document',
101
+ photoshop_resize_image: 'document',
102
+ photoshop_crop_document: 'document',
103
+ photoshop_convert_image_mode: 'document',
104
+ photoshop_transform_canvas: 'document',
105
+ photoshop_guides: 'document',
106
+ photoshop_select: 'select',
107
+ photoshop_modify_selection: 'select',
108
+ photoshop_selection_channel: 'select',
109
+ photoshop_select_subject: 'select_ai',
110
+ photoshop_select_sky: 'select_ai',
111
+ photoshop_select_subject_instance: 'select_ai',
112
+ photoshop_add_adjustment_layer: 'adjust',
113
+ photoshop_apply_adjustment: 'adjust',
114
+ photoshop_apply_filter: 'filter',
115
+ photoshop_retouch: 'retouch',
116
+ photoshop_apply_brush_stroke: 'retouch',
117
+ photoshop_create_layer: 'layers',
118
+ photoshop_delete_layer: 'layers',
119
+ photoshop_fill_layer: 'layers',
120
+ photoshop_add_fill_layer: 'layers',
121
+ photoshop_select_layer: 'layers',
122
+ photoshop_move_layer_to_position: 'layers',
123
+ photoshop_duplicate_layer: 'layers',
124
+ photoshop_layer_via_copy: 'layers',
125
+ photoshop_convert_to_smart_object: 'layers',
126
+ photoshop_rasterize_layer: 'layers',
127
+ photoshop_set_layer: 'layers',
128
+ photoshop_merge: 'layers',
129
+ photoshop_bake_layer: 'layers',
130
+ photoshop_add_layer_style: 'layers',
131
+ photoshop_transform_layer: 'layers',
132
+ photoshop_warp_layer: 'layers',
133
+ photoshop_warp_layer_mesh: 'layers',
134
+ photoshop_create_group: 'layers',
135
+ photoshop_move_layer_to_group: 'layers',
136
+ photoshop_set_group_blend_mode: 'layers',
137
+ photoshop_ungroup: 'layers',
138
+ photoshop_delete_group: 'layers',
139
+ photoshop_layer_mask: 'masks',
140
+ photoshop_create_clipping_mask: 'masks',
141
+ photoshop_release_clipping_mask: 'masks',
142
+ photoshop_path: 'masks',
143
+ photoshop_vector_mask: 'masks',
144
+ photoshop_create_text_layer: 'type',
145
+ photoshop_set_text: 'type',
146
+ photoshop_detect: 'perception',
147
+ photoshop_scene: 'perception',
148
+ photoshop_select_by_reference: 'perception',
149
+ photoshop_object: 'perception',
150
+ photoshop_portrait_touchup: 'perception',
151
+ photoshop_text_on_object: 'perception',
152
+ photoshop_detect_landmarks: 'face',
153
+ photoshop_select_face_feature: 'face',
154
+ photoshop_stroke_face_contour: 'face',
155
+ photoshop_template_create_evidence: 'templates',
156
+ photoshop_template_save: 'templates',
157
+ photoshop_template_list: 'templates',
158
+ photoshop_template_apply: 'templates',
159
+ photoshop_template_verify: 'templates',
160
+ photoshop_template_recall: 'templates',
161
+ photoshop_template_delete: 'templates',
162
+ photoshop_list_actions: 'automation',
163
+ photoshop_play_action: 'automation',
164
+ photoshop_execute_script: 'automation',
165
+ };
166
+ export function groupOf(toolName) {
167
+ const group = TOOL_GROUPS[toolName];
168
+ if (!group) {
169
+ throw new Error(`Tool '${toolName}' has no entry in src/core/tool-groups.ts. ` +
170
+ `Every registered tool must be assigned a capability group.`);
171
+ }
172
+ return group;
173
+ }
174
+ export function toolsInGroup(group) {
175
+ return Object.entries(TOOL_GROUPS)
176
+ .filter(([, g]) => g === group)
177
+ .map(([name]) => name);
178
+ }