editmamei 0.24.1 → 1.0.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 (46) hide show
  1. package/NOTICES.md +6 -30
  2. package/README.md +39 -7
  3. package/dist/api/extendscript/_helpers.js +1 -1
  4. package/dist/api/photoshop-api.js +20 -14
  5. package/dist/bin/editmamei-core-darwin-arm64 +0 -0
  6. package/dist/bin/editmamei-core-darwin-x64 +0 -0
  7. package/dist/bin/editmamei-core-win-x64.exe +0 -0
  8. package/dist/core/server.js +6 -6
  9. package/dist/core/session.js +9 -10
  10. package/dist/core/tool-registry.js +4 -4
  11. package/dist/delivery/store.js +10 -2
  12. package/dist/detection/detect-active-doc.js +1 -1
  13. package/dist/kernel/module-lifecycle.js +7 -1
  14. package/dist/perception/region-precompute.js +13 -5
  15. package/dist/perception/select-recipes.js +2 -2
  16. package/dist/perception/sky-mask-transfer.js +1 -1
  17. package/dist/platform/connection.js +32 -46
  18. package/dist/platform/host-platform.js +16 -0
  19. package/dist/platform/macos-detector.js +41 -71
  20. package/dist/platform/macos-runner.js +94 -0
  21. package/dist/platform/script-result.js +39 -0
  22. package/dist/platform/windows-detector.js +44 -68
  23. package/dist/platform/windows-runner.js +84 -0
  24. package/dist/skills/editmamei-skill.zip +0 -0
  25. package/dist/tools/adjustment-tools.js +18 -2
  26. package/dist/tools/brush-tools.js +1 -1
  27. package/dist/tools/document-tools.js +9 -2
  28. package/dist/tools/filter-tools.js +1 -1
  29. package/dist/tools/layer-tools.js +132 -7
  30. package/dist/tools/path-tools.js +1 -1
  31. package/dist/tools/preview-tools.js +8 -8
  32. package/dist/tools/retouch-tools.js +1 -1
  33. package/dist/tools/scene-tools.js +29 -2
  34. package/dist/tools/selection-tools.js +81 -3
  35. package/dist/update/check.js +1 -1
  36. package/dist/utils/logger.js +53 -46
  37. package/dist/utils/session-log.js +1 -1
  38. package/dist/utils/tool-helpers.js +9 -3
  39. package/dist/version.js +1 -1
  40. package/package.json +10 -3
  41. package/LICENSE +0 -81
  42. package/dist/platform/detector.js +0 -32
  43. package/dist/platform/macos-executor.js +0 -96
  44. package/dist/platform/windows-executor.js +0 -105
  45. /package/dist/platform/{script-executor.js → ports.js} +0 -0
  46. /package/dist/platform/{executor-queue.js → script-queue.js} +0 -0
@@ -1,20 +1,20 @@
1
1
  import { recordLogLine } from './log-buffer.js';
2
- export var LogLevel;
3
- (function (LogLevel) {
4
- LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG";
5
- LogLevel[LogLevel["INFO"] = 1] = "INFO";
6
- LogLevel[LogLevel["WARN"] = 2] = "WARN";
7
- LogLevel[LogLevel["ERROR"] = 3] = "ERROR";
8
- })(LogLevel || (LogLevel = {}));
9
- function serializeLogArg(arg) {
10
- if (arg instanceof Error) {
11
- const parts = [`${arg.name}: ${arg.message}`];
12
- if (arg.stack)
13
- parts.push(arg.stack);
2
+ export const LOG_LEVELS = {
3
+ debug: 0,
4
+ info: 1,
5
+ warn: 2,
6
+ error: 3,
7
+ };
8
+ const LEVEL_LABELS = ['DEBUG', 'INFO', 'WARN', 'ERROR'];
9
+ function describeLogValue(value) {
10
+ if (value instanceof Error) {
11
+ const parts = [`${value.name}: ${value.message}`];
12
+ if (value.stack)
13
+ parts.push(value.stack);
14
14
  const extras = {};
15
- for (const key of Object.keys(arg)) {
15
+ for (const key of Object.keys(value)) {
16
16
  if (key !== 'name' && key !== 'message' && key !== 'stack') {
17
- extras[key] = arg[key];
17
+ extras[key] = value[key];
18
18
  }
19
19
  }
20
20
  if (Object.keys(extras).length > 0) {
@@ -26,35 +26,40 @@ function serializeLogArg(arg) {
26
26
  }
27
27
  return parts.join('\n');
28
28
  }
29
- if (arg === null || arg === undefined)
30
- return String(arg);
31
- if (typeof arg === 'object') {
29
+ if (value === null || value === undefined)
30
+ return String(value);
31
+ if (typeof value === 'object') {
32
32
  try {
33
- return JSON.stringify(arg);
33
+ return JSON.stringify(value);
34
34
  }
35
35
  catch {
36
- return String(arg);
36
+ return String(value);
37
37
  }
38
38
  }
39
- return String(arg);
39
+ return String(value);
40
+ }
41
+ function formatRecord(record) {
42
+ const prefix = `[${record.at.toISOString()}] [${LEVEL_LABELS[record.level]}] [${record.scope}]`;
43
+ const details = record.details.map(describeLogValue).join(' ');
44
+ return `${prefix} ${record.message} ${details}`.trim();
40
45
  }
41
46
  export function parseLogLevel(raw, fallback) {
42
47
  if (raw === undefined || raw === '')
43
48
  return fallback;
44
49
  const asNum = Number(raw);
45
- if (Number.isFinite(asNum) && asNum >= LogLevel.DEBUG && asNum <= LogLevel.ERROR) {
50
+ if (Number.isFinite(asNum) && asNum >= LOG_LEVELS.debug && asNum <= LOG_LEVELS.error) {
46
51
  return asNum;
47
52
  }
48
53
  switch (raw.toUpperCase()) {
49
54
  case 'DEBUG':
50
- return LogLevel.DEBUG;
55
+ return LOG_LEVELS.debug;
51
56
  case 'INFO':
52
- return LogLevel.INFO;
57
+ return LOG_LEVELS.info;
53
58
  case 'WARN':
54
59
  case 'WARNING':
55
- return LogLevel.WARN;
60
+ return LOG_LEVELS.warn;
56
61
  case 'ERROR':
57
- return LogLevel.ERROR;
62
+ return LOG_LEVELS.error;
58
63
  default:
59
64
  return fallback;
60
65
  }
@@ -66,39 +71,41 @@ function isVerboseEnabled(v) {
66
71
  return s === 'true' || s === '1';
67
72
  }
68
73
  export class Logger {
69
- context;
74
+ scope;
70
75
  logLevel;
71
- constructor(context, logLevel = LogLevel.INFO) {
72
- this.context = context;
76
+ constructor(scope, logLevel = LOG_LEVELS.info) {
77
+ this.scope = scope;
73
78
  const fallback = isVerboseEnabled(process.env.EDITMAMEI_VERBOSE_LOGGING)
74
- ? LogLevel.DEBUG
79
+ ? LOG_LEVELS.debug
75
80
  : logLevel;
76
81
  this.logLevel = parseLogLevel(process.env.LOG_LEVEL, fallback);
77
82
  }
78
- log(level, message, ...args) {
83
+ log(level, message, ...details) {
79
84
  if (level < this.logLevel)
80
85
  return;
81
- const timestamp = new Date().toISOString();
82
- const levelStr = LogLevel[level];
83
- const prefix = `[${timestamp}] [${levelStr}] [${this.context}]`;
84
- const formattedArgs = args.map(serializeLogArg).join(' ');
85
- const logMessage = `${prefix} ${message} ${formattedArgs}`.trim();
86
- process.stderr.write(logMessage + '\n');
87
- recordLogLine(logMessage);
86
+ const line = formatRecord({
87
+ at: new Date(),
88
+ level,
89
+ scope: this.scope,
90
+ message,
91
+ details,
92
+ });
93
+ process.stderr.write(line + '\n');
94
+ recordLogLine(line);
88
95
  }
89
96
  isDebugEnabled() {
90
- return this.logLevel <= LogLevel.DEBUG;
97
+ return this.logLevel <= LOG_LEVELS.debug;
91
98
  }
92
- debug(message, ...args) {
93
- this.log(LogLevel.DEBUG, message, ...args);
99
+ debug(message, ...details) {
100
+ this.log(LOG_LEVELS.debug, message, ...details);
94
101
  }
95
- info(message, ...args) {
96
- this.log(LogLevel.INFO, message, ...args);
102
+ info(message, ...details) {
103
+ this.log(LOG_LEVELS.info, message, ...details);
97
104
  }
98
- warn(message, ...args) {
99
- this.log(LogLevel.WARN, message, ...args);
105
+ warn(message, ...details) {
106
+ this.log(LOG_LEVELS.warn, message, ...details);
100
107
  }
101
- error(message, ...args) {
102
- this.log(LogLevel.ERROR, message, ...args);
108
+ error(message, ...details) {
109
+ this.log(LOG_LEVELS.error, message, ...details);
103
110
  }
104
111
  }
@@ -197,8 +197,8 @@ export class SessionLog {
197
197
  };
198
198
  }
199
199
  async emitMeta() {
200
- await this.writeLine(JSON.stringify(this.buildMetaEntry()));
201
200
  this.metaEmitted = true;
201
+ await this.writeLine(JSON.stringify(this.buildMetaEntry()));
202
202
  }
203
203
  async append(entry, result) {
204
204
  if (!this.metaEmitted) {
@@ -1,13 +1,19 @@
1
1
  import { runScript } from './run-script.js';
2
2
  import { validateArgs } from './validate.js';
3
3
  const PENDING_STATE_HINT = ' This usually means an operation is pending in Photoshop (active transform, open dialog, uncommitted text edit). Commit or cancel it in Photoshop and retry.';
4
- const PENDING_STATE_RE = /pending|in progress|modal|currently in|is open/i;
4
+ const PENDING_STATE_RE = /pending|in progress|modal|currently in|(?:dialog|window|panel) is open/i;
5
5
  const HAS_OWN_ADVICE_RE = /dismiss any open Photoshop dialog/i;
6
6
  export function toolErrorResult(prefix, error) {
7
7
  const msg = error instanceof Error ? error.message : String(error);
8
- const hint = PENDING_STATE_RE.test(msg) && !HAS_OWN_ADVICE_RE.test(msg) ? PENDING_STATE_HINT : '';
8
+ const wantsHint = PENDING_STATE_RE.test(msg) && !HAS_OWN_ADVICE_RE.test(msg);
9
+ const separator = wantsHint && !/[.!?]$/.test(msg.trimEnd()) ? '.' : '';
9
10
  return {
10
- content: [{ type: 'text', text: `${prefix}: ${msg}${hint}` }],
11
+ content: [
12
+ {
13
+ type: 'text',
14
+ text: `${prefix}: ${msg}${separator}${wantsHint ? PENDING_STATE_HINT : ''}`,
15
+ },
16
+ ],
11
17
  isError: true,
12
18
  };
13
19
  }
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.24.1';
1
+ export const VERSION = '1.0.0';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "editmamei",
3
- "version": "0.24.1",
4
- "description": "Editmamei — Unlock Photoshop with natural-language photo editing (Community Edition)",
3
+ "version": "1.0.0",
4
+ "description": "Photoshop MCP server: natural-language AI photo editing with your own Photoshop (Community Edition)",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
7
7
  "bin": {
@@ -18,16 +18,23 @@
18
18
  },
19
19
  "keywords": [
20
20
  "mcp",
21
+ "mcp-server",
21
22
  "photoshop",
23
+ "photoshop-mcp",
22
24
  "adobe",
23
25
  "automation",
26
+ "photoshop-automation",
24
27
  "ai",
28
+ "ai-photo-editing",
29
+ "photoshop-assistant",
25
30
  "model-context-protocol",
26
31
  "image-editing",
32
+ "batch-editing",
33
+ "claude",
27
34
  "editmamei"
28
35
  ],
29
36
  "author": "Editmamei (https://editmamei.com)",
30
- "license": "SEE LICENSE IN LICENSE",
37
+ "license": "FSL-1.1-MIT",
31
38
  "repository": {
32
39
  "type": "git",
33
40
  "url": "git+https://github.com/editmamei/editmamei.git"
package/LICENSE DELETED
@@ -1,81 +0,0 @@
1
- Editmamei License Agreement
2
-
3
- Copyright (c) 2026 EMBD Artifacts LLC. All rights reserved.
4
-
5
- This License Agreement (the "Agreement") governs your use of the Editmamei
6
- software distributed in this package — including the command-line interface,
7
- the Model Context Protocol (MCP) server, the bundled engine binaries, and all
8
- accompanying files (collectively, the "Software"). By installing, copying, or
9
- using the Software, you agree to be bound by this Agreement. If you do not
10
- agree, do not install or use the Software.
11
-
12
- 1. License grant.
13
- Subject to your compliance with this Agreement, EMBD Artifacts LLC grants
14
- you a personal, worldwide, royalty-free, non-exclusive, non-transferable,
15
- and revocable license to install and use the Software, at no charge, on
16
- devices you own or control, for your own personal or commercial
17
- photo-editing work.
18
-
19
- 2. Paid features.
20
- Some features (the "Pro" features) are not included in the no-charge license
21
- granted above and require a separate paid license. Access to Pro features is
22
- governed by the additional terms presented at the time of purchase. Nothing
23
- in this Agreement grants any right to Pro features without a valid paid
24
- license.
25
-
26
- 3. Restrictions.
27
- Except as expressly permitted in Section 1, or to the extent a restriction
28
- below is prohibited by applicable law, you may not:
29
- (a) copy (other than a reasonable number of backup copies), publish,
30
- distribute, sublicense, rent, lease, lend, or sell the Software;
31
- (b) modify, adapt, translate, or create derivative works of the Software;
32
- (c) reverse engineer, decompile, or disassemble the Software, or otherwise
33
- attempt to derive its source code or underlying ideas;
34
- (d) remove, obscure, or alter any copyright, trademark, or other proprietary
35
- notice in the Software or this Agreement; or
36
- (e) circumvent, disable, or interfere with any licensing, entitlement, or
37
- technical protection measure, including to access Pro features without a
38
- valid paid license.
39
-
40
- 4. Ownership.
41
- The Software is licensed, not sold. EMBD Artifacts LLC and its licensors
42
- retain all right, title, and interest in and to the Software, including all
43
- intellectual property rights. All rights not expressly granted in this
44
- Agreement are reserved.
45
-
46
- 5. Third-party components.
47
- The Software incorporates components provided by third parties under their
48
- own licenses. Those licenses continue to apply to the corresponding
49
- components and, in the event of a conflict, govern those components only.
50
- Required attributions and notices are reproduced in the accompanying
51
- NOTICES.md file; the full license text for each dependency ships with that
52
- dependency through the npm package manager (see node_modules/<package>/
53
- after install).
54
-
55
- 6. Term and termination.
56
- This Agreement is effective until terminated. The license granted in
57
- Section 1 terminates automatically, without notice, if you breach any term
58
- of this Agreement. Upon termination you must cease all use of the Software
59
- and delete all copies in your possession or control. Sections 3 through 9
60
- survive termination.
61
-
62
- 7. Disclaimer of warranty.
63
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
64
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
65
- FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
66
-
67
- 8. Limitation of liability.
68
- IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES, OR
69
- OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE,
70
- ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
71
- DEALINGS IN THE SOFTWARE.
72
-
73
- 9. General.
74
- This Agreement is the entire agreement between you and EMBD Artifacts LLC
75
- regarding the Software and supersedes any prior or contemporaneous
76
- understanding. If any provision is held unenforceable, the remaining
77
- provisions remain in full force and effect. A failure to enforce any
78
- provision is not a waiver of it.
79
-
80
- For commercial licensing, redistribution rights, or other inquiries, contact:
81
- support@editmamei.com
@@ -1,32 +0,0 @@
1
- import { platform } from 'os';
2
- import { Logger } from '../utils/logger.js';
3
- import { WindowsDetector } from './windows-detector.js';
4
- import { MacOSDetector } from './macos-detector.js';
5
- export class PhotoshopDetector {
6
- logger;
7
- platformType;
8
- windowsDetector;
9
- macosDetector;
10
- constructor() {
11
- this.logger = new Logger('PhotoshopDetector');
12
- this.platformType = platform();
13
- if (this.platformType === 'win32') {
14
- this.windowsDetector = new WindowsDetector();
15
- }
16
- else if (this.platformType === 'darwin') {
17
- this.macosDetector = new MacOSDetector();
18
- }
19
- }
20
- async detect() {
21
- this.logger.info(`Detecting Photoshop on ${this.platformType}...`);
22
- if (this.platformType === 'win32' && this.windowsDetector) {
23
- return await this.windowsDetector.detect();
24
- }
25
- else if (this.platformType === 'darwin' && this.macosDetector) {
26
- return await this.macosDetector.detect();
27
- }
28
- else {
29
- throw new Error(`Unsupported platform: ${this.platformType}`);
30
- }
31
- }
32
- }
@@ -1,96 +0,0 @@
1
- import { exec, spawn } from 'child_process';
2
- import { promisify } from 'util';
3
- import { Logger } from '../utils/logger.js';
4
- import { TempDir } from '../utils/temp.js';
5
- import { runChildWithTimeout } from './run-child.js';
6
- import { ScriptQueue } from './executor-queue.js';
7
- const execAsync = promisify(exec);
8
- export class MacOSExecutor {
9
- logger;
10
- queue;
11
- appName = 'Adobe Photoshop 2025';
12
- constructor() {
13
- this.logger = new Logger('MacOSExecutor');
14
- this.queue = new ScriptQueue(this.logger);
15
- }
16
- setAppName(appName) {
17
- if (/["\\\r\n\t]/.test(appName)) {
18
- throw new Error('appName contains a character that would break AppleScript interpolation');
19
- }
20
- this.appName = appName;
21
- this.logger.debug(`App name set to: ${appName}`);
22
- }
23
- async execute(script, timeout = 30000) {
24
- return this.queue.enqueue(() => this.executeScript(script, timeout), timeout);
25
- }
26
- async executeScript(script, timeout) {
27
- const dir = await TempDir.create('editmamei-mac-');
28
- try {
29
- const jsxPath = await dir.write('script.jsx', script);
30
- const wrapperPath = await dir.write('wrapper.scpt', this.createAppleScriptWrapper(jsxPath, timeout));
31
- const { stdout, stderr, exitCode } = await runChildWithTimeout('osascript', [wrapperPath], {
32
- timeout,
33
- diagLabel: 'osascript wrapper.scpt',
34
- });
35
- if (stderr) {
36
- this.logger.warn('Script execution warning:', stderr);
37
- }
38
- if (exitCode !== 0) {
39
- throw new Error(`osascript exited with code ${exitCode}: ${stderr.trim() || '(no stderr)'}`);
40
- }
41
- return this.parseResult(stdout);
42
- }
43
- finally {
44
- await dir.cleanup();
45
- }
46
- }
47
- createAppleScriptWrapper(jsxPath, timeoutMs = 30000) {
48
- const posixPath = jsxPath.replace(/\\/g, '/');
49
- if (posixPath.includes('"') || posixPath.includes('\n')) {
50
- throw new Error('jsxPath contains a character that would break AppleScript interpolation');
51
- }
52
- const timeoutSeconds = Number.isFinite(timeoutMs) && timeoutMs > 0 ? Math.max(1, Math.ceil(timeoutMs / 1000)) : 1;
53
- const encodedPath = encodeURI(posixPath).replace(/'/g, '%27');
54
- return `with timeout of ${timeoutSeconds} seconds
55
- \ttell application "${this.appName}"
56
- \t\tdo javascript "$.evalFile(decodeURI('${encodedPath}'))"
57
- \tend tell
58
- end timeout`;
59
- }
60
- parseResult(output) {
61
- const trimmed = output.trim();
62
- if (trimmed.startsWith('ERROR:')) {
63
- throw new Error(trimmed.substring(6).trim());
64
- }
65
- try {
66
- return JSON.parse(trimmed);
67
- }
68
- catch {
69
- return trimmed;
70
- }
71
- }
72
- async isPhotoshopRunning() {
73
- try {
74
- const { stdout } = await execAsync('pgrep -f "Adobe Photoshop"');
75
- return stdout.trim().length > 0;
76
- }
77
- catch {
78
- return false;
79
- }
80
- }
81
- async launchPhotoshop(photoshopPath) {
82
- return new Promise((resolve, reject) => {
83
- this.logger.info(`Launching Photoshop: ${photoshopPath}`);
84
- const child = spawn('open', ['-a', photoshopPath], { detached: true, stdio: 'ignore' });
85
- child.unref();
86
- const ready = setTimeout(() => {
87
- ready.unref();
88
- resolve();
89
- }, 5000);
90
- child.on('error', (error) => {
91
- clearTimeout(ready);
92
- reject(new Error(`Failed to launch Photoshop: ${error.message}`));
93
- });
94
- });
95
- }
96
- }
@@ -1,105 +0,0 @@
1
- import { spawn } from 'child_process';
2
- import { promisify } from 'util';
3
- import { exec } from 'child_process';
4
- import { Logger } from '../utils/logger.js';
5
- import { TempDir } from '../utils/temp.js';
6
- import { runChildWithTimeout } from './run-child.js';
7
- import { ScriptQueue } from './executor-queue.js';
8
- const execAsync = promisify(exec);
9
- export class WindowsExecutor {
10
- logger;
11
- queue;
12
- constructor() {
13
- this.logger = new Logger('WindowsExecutor');
14
- this.queue = new ScriptQueue(this.logger);
15
- }
16
- async execute(script, timeout = 30000) {
17
- return this.queue.enqueue(() => this.executeScript(script, timeout), timeout);
18
- }
19
- async executeScript(script, timeout) {
20
- const dir = await TempDir.create('editmamei-win-');
21
- try {
22
- const jsxPath = await dir.write('script.jsx', script);
23
- const vbsPath = await dir.write('wrapper.vbs', this.createVBSWrapper(jsxPath));
24
- const { stdout, stderr } = await runChildWithTimeout('cscript', ['//nologo', vbsPath], {
25
- timeout,
26
- diagLabel: 'cscript wrapper.vbs',
27
- });
28
- if (stderr) {
29
- this.logger.warn('Script execution warning:', stderr);
30
- }
31
- return this.parseResult(stdout);
32
- }
33
- finally {
34
- await dir.cleanup();
35
- }
36
- }
37
- createVBSWrapper(jsxPath) {
38
- if (jsxPath.includes('"')) {
39
- throw new Error('jsxPath contains a double-quote — refusing to compose VBS');
40
- }
41
- return `
42
- On Error Resume Next
43
- Dim photoshopApp
44
- Set photoshopApp = CreateObject("Photoshop.Application")
45
-
46
- If Err.Number <> 0 Then
47
- WScript.Echo "ERROR: Failed to connect to Photoshop - " & Err.Description
48
- WScript.Quit 1
49
- End If
50
-
51
- ' Execute the JSX script. The path is the mkdtemp temp file (double-quote-free
52
- ' by the guard above; apostrophes are fine here), but its single backslashes
53
- ' must be DOUBLED here: this string is
54
- ' handed to DoJavaScript as ExtendScript SOURCE, and a backslash inside a quoted
55
- ' path there is a JS string-literal escape (a Users/Temp segment would be parsed
56
- ' as an escape sequence and corrupt the path). The VBS Replace doubles each
57
- ' backslash so ExtendScript resolves it back. Load-bearing — NOT a no-op.
58
- Dim result
59
- result = photoshopApp.DoJavaScript("$.evalFile(""" & Replace("${jsxPath}", "\\", "\\\\") & """);")
60
-
61
- If Err.Number <> 0 Then
62
- WScript.Echo "ERROR: " & Err.Description
63
- WScript.Quit 1
64
- Else
65
- WScript.Echo result
66
- End If
67
- `.trim();
68
- }
69
- parseResult(output) {
70
- const trimmed = output.trim();
71
- if (trimmed.startsWith('ERROR:')) {
72
- throw new Error(trimmed.substring(6).trim());
73
- }
74
- try {
75
- return JSON.parse(trimmed);
76
- }
77
- catch {
78
- return trimmed;
79
- }
80
- }
81
- async isPhotoshopRunning() {
82
- try {
83
- const { stdout } = await execAsync('tasklist /FI "IMAGENAME eq Photoshop.exe"');
84
- return stdout.toLowerCase().includes('photoshop.exe');
85
- }
86
- catch {
87
- return false;
88
- }
89
- }
90
- async launchPhotoshop(photoshopPath) {
91
- return new Promise((resolve, reject) => {
92
- this.logger.info(`Launching Photoshop: ${photoshopPath}`);
93
- const child = spawn(photoshopPath, [], { detached: true, stdio: 'ignore' });
94
- child.unref();
95
- const ready = setTimeout(() => {
96
- ready.unref();
97
- resolve();
98
- }, 5000);
99
- child.on('error', (error) => {
100
- clearTimeout(ready);
101
- reject(new Error(`Failed to launch Photoshop: ${error.message}`));
102
- });
103
- });
104
- }
105
- }
File without changes