editmamei 1.0.3 → 1.2.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 (44) hide show
  1. package/README.md +9 -9
  2. package/dist/api/extendscript/_helpers.js +70 -2
  3. package/dist/api/photoshop-api.js +17 -2
  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/activate.js +1 -1
  8. package/dist/cli/config.js +1 -1
  9. package/dist/cli/deactivate.js +1 -1
  10. package/dist/core/raw-develop-state.js +13 -0
  11. package/dist/core/server.js +147 -18
  12. package/dist/core/tool-groups.js +18 -21
  13. package/dist/core/tool-tiers.js +7 -10
  14. package/dist/detection/runtime.js +2 -2
  15. package/dist/modules/ce/index.js +2 -0
  16. package/dist/perception/region-scorer.js +1 -1
  17. package/dist/platform/connection.js +15 -0
  18. package/dist/skills/editmamei-skill.zip +0 -0
  19. package/dist/telemetry/client.js +15 -2
  20. package/dist/telemetry/sanitize.js +1 -0
  21. package/dist/tools/adjustment-tools.js +21 -3
  22. package/dist/tools/detection-tools.js +1 -1
  23. package/dist/tools/document-tools.js +5 -2
  24. package/dist/tools/filter-tools.js +118 -15
  25. package/dist/tools/group-tools.js +146 -146
  26. package/dist/tools/history-tools.js +1 -1
  27. package/dist/tools/inspect-tools.js +22 -3
  28. package/dist/tools/layer-tools.js +4 -27
  29. package/dist/tools/overview-tools.js +37 -8
  30. package/dist/tools/preview-tools.js +3 -2
  31. package/dist/tools/scene-tools.js +1 -1
  32. package/dist/tools/selection-tools.js +91 -18
  33. package/dist/tools/sky-tools.js +126 -0
  34. package/dist/tools/smart-object-tools.js +163 -0
  35. package/dist/tools/text-tools.js +62 -41
  36. package/dist/update/check.js +65 -6
  37. package/dist/update/session-fixes.js +33 -0
  38. package/dist/utils/jsx.js +2 -1
  39. package/dist/utils/operation-timeouts.js +2 -0
  40. package/dist/utils/session-log.js +46 -6
  41. package/dist/utils/temp.js +1 -1
  42. package/dist/utils/tool-helpers.js +23 -0
  43. package/dist/version.js +1 -1
  44. package/package.json +13 -2
@@ -5,6 +5,7 @@ export const TOOL_TIERS = {
5
5
  ps_list_actions: 'pro',
6
6
  ps_play_action: 'pro',
7
7
  ps_execute_script: 'pro',
8
+ ps_batch: 'pro',
8
9
  ps_add_adjustment_layer: 'community',
9
10
  ps_apply_adjustment: 'community',
10
11
  ps_create_document: 'community',
@@ -12,14 +13,9 @@ export const TOOL_TIERS = {
12
13
  ps_open_document: 'community',
13
14
  ps_save_psd: 'community',
14
15
  ps_export: 'community',
15
- ps_apply_filter: 'community',
16
- ps_create_group: 'community',
17
- ps_move_layer_to_group: 'community',
18
- ps_set_group_blend_mode: 'community',
19
- ps_ungroup: 'community',
20
- ps_delete_group: 'community',
21
- ps_create_clipping_mask: 'community',
22
- ps_release_clipping_mask: 'dev',
16
+ ps_filter: 'community',
17
+ ps_group: 'community',
18
+ ps_clipping_mask: 'community',
23
19
  ps_undo: 'community',
24
20
  ps_redo: 'community',
25
21
  ps_place_image: 'community',
@@ -37,7 +33,6 @@ export const TOOL_TIERS = {
37
33
  ps_add_layer_style: 'community',
38
34
  ps_create_layer: 'community',
39
35
  ps_delete_layer: 'community',
40
- ps_create_text_layer: 'community',
41
36
  ps_fill_layer: 'community',
42
37
  ps_add_fill_layer: 'community',
43
38
  ps_select_layer: 'community',
@@ -72,6 +67,8 @@ export const TOOL_TIERS = {
72
67
  ps_select_sky: 'community',
73
68
  ps_select_subject_instance: 'pro',
74
69
  ps_select_object: 'pro',
70
+ ps_select_focus_area: 'dev',
71
+ ps_replace_sky: 'dev',
75
72
  ps_modify_selection: 'community',
76
73
  ps_get_selection_preview: 'community',
77
74
  ps_selection_channel: 'community',
@@ -89,7 +86,7 @@ export const TOOL_TIERS = {
89
86
  ps_template_verify: 'pro',
90
87
  ps_template_recall: 'pro',
91
88
  ps_template_delete: 'pro',
92
- ps_set_text: 'community',
89
+ ps_text: 'community',
93
90
  };
94
91
  export function tierOf(toolName) {
95
92
  const tier = TOOL_TIERS[toolName];
@@ -67,7 +67,7 @@ export function decodeJpegBuffer(bytes) {
67
67
  }
68
68
  catch (err) {
69
69
  const msg = err instanceof Error ? err.message : String(err);
70
- throw new Error(`failed to decode JPEG buffer: ${msg}`);
70
+ throw new Error(`failed to decode JPEG buffer: ${msg}`, { cause: err });
71
71
  }
72
72
  }
73
73
  export function decodeJpeg(path) {
@@ -76,6 +76,6 @@ export function decodeJpeg(path) {
76
76
  }
77
77
  catch (err) {
78
78
  const msg = err instanceof Error ? err.message : String(err);
79
- throw new Error(`failed to decode JPEG at ${path}: ${msg}`);
79
+ throw new Error(`failed to decode JPEG at ${path}: ${msg}`, { cause: err });
80
80
  }
81
81
  }
@@ -29,6 +29,7 @@ import { createPathTools } from '../../tools/path-tools.js';
29
29
  import { createVectorMaskTools } from '../../tools/vector-mask-tools.js';
30
30
  import { createChannelComposeTools } from '../../tools/channel-compose-tools.js';
31
31
  import { createShapeTools } from '../../tools/shape-tools.js';
32
+ import { createSkyTools } from '../../tools/sky-tools.js';
32
33
  const ceFactories = [
33
34
  createDocumentTools,
34
35
  createLayerTools,
@@ -57,6 +58,7 @@ const ceFactories = [
57
58
  createVectorMaskTools,
58
59
  createChannelComposeTools,
59
60
  createShapeTools,
61
+ createSkyTools,
60
62
  ];
61
63
  export const ceModule = {
62
64
  manifest: { id: 'ce', name: 'Editmamei Community Tools', abi: KERNEL_ABI },
@@ -81,7 +81,7 @@ export function scoreRegion(kind, signals, ctx) {
81
81
  signals,
82
82
  };
83
83
  }
84
- let confidence = 0;
84
+ let confidence;
85
85
  switch (kind) {
86
86
  case 'sky': {
87
87
  const upper = rampDown(signals.centroidY, 0.35, 0.65);
@@ -13,6 +13,7 @@ export class PhotoshopConnection {
13
13
  photoshopInfo = null;
14
14
  detectInFlight = null;
15
15
  runningLatchUntil = 0;
16
+ everReachedPhotoshop = false;
16
17
  constructor(opts = {}) {
17
18
  this.logger = new Logger('PhotoshopConnection');
18
19
  this.now = opts.now ?? Date.now;
@@ -71,6 +72,7 @@ export class PhotoshopConnection {
71
72
  }
72
73
  const result = await this.host.adapter.run(script, timeout);
73
74
  this.runningLatchUntil = this.now() + RUNNING_LATCH_TTL_MS;
75
+ this.everReachedPhotoshop = true;
74
76
  return result;
75
77
  }
76
78
  catch (error) {
@@ -99,4 +101,17 @@ export class PhotoshopConnection {
99
101
  getPhotoshopInfo() {
100
102
  return this.photoshopInfo;
101
103
  }
104
+ hasReachedPhotoshop() {
105
+ return this.everReachedPhotoshop;
106
+ }
107
+ async isCurrentlyRunning() {
108
+ if (!this.photoshopInfo)
109
+ return false;
110
+ try {
111
+ return await this.host.adapter.isRunning();
112
+ }
113
+ catch {
114
+ return false;
115
+ }
116
+ }
102
117
  }
Binary file
@@ -118,6 +118,19 @@ export class TelemetryClient {
118
118
  ...(diag.stderr_tail ? { stderr_tail: sanitizeStderrTail(diag.stderr_tail) } : {}),
119
119
  }, this.now()));
120
120
  }
121
+ restampPsVersion(events) {
122
+ const resolved = this.dims.getPsVersion();
123
+ if (!resolved)
124
+ return events;
125
+ for (const event of events) {
126
+ if (event.type === 'session_summary')
127
+ continue;
128
+ if ('ps_version' in event && event.ps_version === PS_VERSION_UNKNOWN) {
129
+ event.ps_version = resolved;
130
+ }
131
+ }
132
+ return events;
133
+ }
121
134
  enqueue(event) {
122
135
  this.queue.push(event);
123
136
  if (this.queue.length > MAX_QUEUE_SIZE) {
@@ -129,7 +142,7 @@ export class TelemetryClient {
129
142
  async flush() {
130
143
  if (!this.active || this.queue.length === 0)
131
144
  return;
132
- const batch = this.queue.splice(0, this.maxBatchSize).filter(isContentSafe);
145
+ const batch = this.restampPsVersion(this.queue.splice(0, this.maxBatchSize)).filter(isContentSafe);
133
146
  if (batch.length === 0)
134
147
  return;
135
148
  try {
@@ -160,7 +173,7 @@ export class TelemetryClient {
160
173
  }, this.now()));
161
174
  }
162
175
  if (this.queue.length > 0) {
163
- appendOutboxSync(this.queue.splice(0).filter(isContentSafe), this.outboxOpts);
176
+ appendOutboxSync(this.restampPsVersion(this.queue.splice(0)).filter(isContentSafe), this.outboxOpts);
164
177
  }
165
178
  clearSessionState(this.outboxOpts);
166
179
  }
@@ -45,6 +45,7 @@ export function sanitizeMessage(input, maxLen = MAX_MESSAGE_LEN) {
45
45
  }
46
46
  out = redactGenericUserDirs(out);
47
47
  out = basenamePaths(out);
48
+ out = out.replace(/\bnot found: [^\n]*/gi, 'not found: [redacted]');
48
49
  out = out.replace(/\\/g, '/');
49
50
  out = out.replace(/^[/]+/, '');
50
51
  if (out.length > maxLen)
@@ -1,6 +1,7 @@
1
1
  import { runScript } from '../utils/run-script.js';
2
2
  import { validateArgs } from '../utils/validate.js';
3
3
  import { toolErrorResult, runSnippetTool, applyToActiveLayerProp } from '../utils/tool-helpers.js';
4
+ import { getPendingRawDevelop } from '../core/raw-develop-state.js';
4
5
  const addAdjustmentLayerSchema = {
5
6
  type: 'object',
6
7
  properties: {
@@ -669,7 +670,7 @@ export function createAdjustmentTools(connection, snippetClient) {
669
670
  {
670
671
  tool: {
671
672
  name: 'ps_add_adjustment_layer',
672
- description: "Create a non-destructive adjustment layer above the active layer — hoisted out of the active layer's group by default even though Photoshop's own Mk-AdjL placement rule would otherwise nest it INSIDE that group (pass into_active_group:true to keep that native nesting). Supports the full real-Photoshop tonal/color surface: Curves (with S-curve presets), Levels, Hue/Saturation, Brightness/Contrast, Black & White (with optional tint), Color Balance, Photo Filter (preset or custom color), Vibrance, Channel Mixer, Selective Color, Gradient Map (preset), Exposure (stops + offset + gamma), Color Lookup (3DLUT presets or custom file path), and Invert. Values are editable, maskable, and removable. This is the canonical entry point for ALL tonal/color adjustments; the old destructive bake tools (auto_levels / auto_contrast / desaturate / invert) were removed on 2026-05-31 — if you genuinely need a pixel bake, follow this call with `ps_merge` (mode=visible). Optionally clips the adjustment to only affect the layer directly below it. If a selection is active at call time, the new layer is automatically masked by it (toggle with mask_from_selection / mask_inverted). For destructive ops that don't have an adjustment-layer equivalent in Photoshop (Shadows/Highlights — single-pass shadow/highlight recovery), use `ps_apply_adjustment` (type=shadows_highlights) which auto-duplicates the active layer to keep the original intact. Returns context (the new adjustment layer becomes active) plus parent_path — the actual containing-group chain, so placement is never silent.",
673
+ description: "Create a non-destructive adjustment layer above the active layer — hoisted out of the active layer's group by default even though Photoshop's own Mk-AdjL placement rule would otherwise nest it INSIDE that group (pass into_active_group:true to keep that native nesting). Supports the full real-Photoshop tonal/color surface: Curves (with S-curve presets), Levels, Hue/Saturation, Brightness/Contrast, Black & White (with optional tint), Color Balance, Photo Filter (preset or custom color), Vibrance, Channel Mixer, Selective Color, Gradient Map (preset), Exposure (stops + offset + gamma), Color Lookup (3DLUT presets or custom file path), and Invert. Values are editable, maskable, and removable. This is the canonical entry point for tonal/color adjustments — EXCEPT global tone/color on raw-sourced documents, which starts with a Camera Raw develop pass when a camera-raw develop tool is registered (see ps_open_document's is_raw_source); the old destructive bake tools (auto_levels / auto_contrast / desaturate / invert) were removed on 2026-05-31 — if you genuinely need a pixel bake, follow this call with `ps_merge` (mode=visible). Optionally clips the adjustment to only affect the layer directly below it. If a selection is active at call time, the new layer is automatically masked by it (toggle with mask_from_selection / mask_inverted). For destructive ops that don't have an adjustment-layer equivalent in Photoshop (Shadows/Highlights — single-pass shadow/highlight recovery), use `ps_apply_adjustment` (type=shadows_highlights) which auto-duplicates the active layer to keep the original intact. Returns context (the new adjustment layer becomes active) plus parent_path — the actual containing-group chain, so placement is never silent.",
673
674
  inputSchema: addAdjustmentLayerSchema,
674
675
  outputSchema: {
675
676
  type: 'object',
@@ -695,6 +696,10 @@ export function createAdjustmentTools(connection, snippetClient) {
695
696
  items: { type: 'string' },
696
697
  description: 'The containing-group name chain (outermost first), empty array at the document root.',
697
698
  },
699
+ raw_develop_pending: {
700
+ type: 'string',
701
+ description: 'Advisory, present only when the active document was opened from a raw source this session and no Camera Raw develop pass has run yet. Nothing failed — the layer was created. Global tone/color on raw sources normally starts with the camera-raw develop pass; ignore if the user prescribed this exact layer or the file was already developed elsewhere.',
702
+ },
698
703
  context: { type: 'object' },
699
704
  },
700
705
  },
@@ -833,14 +838,27 @@ async function addAdjustmentLayer(connection, snippetClient, rawArgs) {
833
838
  buildParams.name = name;
834
839
  const script = await snippetClient.build('addAdjustmentLayer', buildParams);
835
840
  const result = await runScript(connection, script);
841
+ const structured = result;
842
+ let advisory = '';
843
+ const pendingRaw = getPendingRawDevelop();
844
+ if (pendingRaw) {
845
+ const source = pendingRaw.documentName || pendingRaw.filePath || 'a raw source';
846
+ const note = `This document was opened from raw source ${source} with no Camera Raw develop pass yet — ` +
847
+ 'consider ps_apply_camera_raw first for global tone/color. Nothing failed; ignore if the ' +
848
+ 'user prescribed this exact layer or the file was already developed elsewhere.';
849
+ if (typeof result === 'object' && result !== null) {
850
+ structured.raw_develop_pending = note;
851
+ }
852
+ advisory = `\nNote: ${note}`;
853
+ }
836
854
  return {
837
855
  content: [
838
856
  {
839
857
  type: 'text',
840
- text: `Adjustment layer added:\n${JSON.stringify(result, null, 2)}`,
858
+ text: `Adjustment layer added:\n${JSON.stringify(result, null, 2)}${advisory}`,
841
859
  },
842
860
  ],
843
- structuredContent: result,
861
+ structuredContent: structured,
844
862
  };
845
863
  }
846
864
  catch (error) {
@@ -174,7 +174,7 @@ export function createDetectionTools(connection, _snippetClient, client = new On
174
174
  {
175
175
  tool: {
176
176
  name: 'ps_detect',
177
- description: 'Locate faces and/or COCO-80 objects (person, dog, car, chair, sofa, …) in the active document using LOCAL on-device computer vision — the image is never sent anywhere. Returns labeled bounding boxes in DOCUMENT-pixel space plus an annotated preview for visual confirmation. Use this to gain semantic scene awareness ("there are 2 people and a dog") and real coordinates before any spatially-targeted edit — far more reliable than estimating positions from a preview. `target` selects faces / objects / both. Read-only: renders a throwaway duplicate, never modifies the working document. Boxes are [x1, y1, x2, y2]. Validate surprising results against the annotated image before acting on them.',
177
+ description: 'The cheap, narrow read: labeled bounding boxes only — faces and/or COCO-80 objects (person, dog, car, chair, sofa, …) in DOCUMENT-pixel space, plus an annotated preview for visual confirmation. LOCAL on-device computer vision; the image is never sent anywhere. Use this for real coordinates before a spatially-targeted edit when boxes are all you need — far more reliable than estimating positions from a preview. For the full scene model (regions, horizon, tonal zones, composition, and a menu of selectable named regions), use ps_read_scene instead. `target` selects faces / objects / both. Read-only: renders a throwaway duplicate, never modifies the working document. Boxes are [x1, y1, x2, y2]. Validate surprising results against the annotated image before acting on them.',
178
178
  inputSchema: detectSchema,
179
179
  outputSchema: {
180
180
  type: 'object',
@@ -205,7 +205,7 @@ export function createDocumentTools(connection, snippetClient) {
205
205
  {
206
206
  tool: {
207
207
  name: 'ps_open_document',
208
- description: 'Open a file from disk into Photoshop with all dialogs suppressed (uses last-used Camera Raw settings for raw/HEIC). Returns document name, dimensions, color mode, and whether the source was a raw format. Use this in the pipeline to load Inbox files for editing. If the file is ALREADY open, its existing document is activated rather than opened a second time — `already_open: true` says so, and any edits made to it are still there (Photoshop would otherwise open a duplicate with a fresh Background, which silently strands prior work).',
208
+ description: 'Open a file from disk into Photoshop with all dialogs suppressed (uses last-used Camera Raw settings for raw/HEIC). Returns document name, dimensions, color mode, and whether the source was a raw format. `is_raw_source` is workflow-critical, not a passive status field: when true, the first edit should be a Camera Raw develop pass on the base smart object (via a camera-raw develop tool, if one is registered in tools/list) — NOT stacked tonal adjustment layers. Use this in the pipeline to load Inbox files for editing. If the file is ALREADY open, its existing document is activated rather than opened a second time — `already_open: true` says so, and any edits made to it are still there (Photoshop would otherwise open a duplicate with a fresh Background, which silently strands prior work).',
209
209
  inputSchema: openDocumentSchema,
210
210
  outputSchema: {
211
211
  type: 'object',
@@ -217,7 +217,10 @@ export function createDocumentTools(connection, snippetClient) {
217
217
  resolution: { type: 'number' },
218
218
  color_mode: { type: 'string' },
219
219
  bits_per_channel: { type: 'number' },
220
- is_raw_source: { type: 'boolean' },
220
+ is_raw_source: {
221
+ type: 'boolean',
222
+ description: 'True when the source file was a raw capture (DNG/NEF/CR3/ARW/…). Workflow-critical: the open used last-used/default Camera Raw settings, so no deliberate develop has happened yet. When true, run the Camera Raw develop pass FIRST (via a camera-raw develop tool, if registered) for global tone/color — before any tonal adjustment layers — unless the user explicitly directs otherwise.',
223
+ },
221
224
  already_open: { type: 'boolean' },
222
225
  file_path: { type: 'string' },
223
226
  context: { type: 'object' },