editmamei 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/api/extendscript/_helpers.js +9 -3
- package/dist/api/photoshop-api.js +16 -1
- package/dist/bin/editmamei-core-darwin-arm64 +0 -0
- package/dist/bin/editmamei-core-darwin-x64 +0 -0
- package/dist/bin/editmamei-core-win-x64.exe +0 -0
- package/dist/cli/help.js +1 -0
- package/dist/core/server.js +91 -17
- package/dist/core/tool-groups.js +5 -9
- package/dist/core/tool-registry.js +3 -0
- package/dist/core/tool-tiers.js +4 -8
- package/dist/delivery/provision.js +2 -2
- package/dist/kernel/module-lifecycle.js +30 -4
- package/dist/modules/ce/index.js +2 -0
- package/dist/perception/region-precompute.js +39 -0
- package/dist/skills/editmamei-skill.zip +0 -0
- package/dist/tools/document-tools.js +143 -2
- package/dist/tools/filter-tools.js +0 -14
- package/dist/tools/group-tools.js +1 -119
- package/dist/tools/layer-tools.js +1 -24
- package/dist/tools/scene-tools.js +45 -21
- package/dist/tools/selection-tools.js +72 -1
- package/dist/tools/sky-tools.js +126 -0
- package/dist/tools/text-tools.js +0 -69
- package/dist/update/check.js +65 -6
- package/dist/update/session-fixes.js +33 -0
- package/dist/utils/jsx.js +2 -1
- package/dist/utils/operation-timeouts.js +2 -0
- package/dist/utils/session-log.js +17 -0
- package/dist/version.js +1 -1
- package/package.json +15 -1
package/README.md
CHANGED
|
@@ -75,7 +75,7 @@ These are the choices that shape the surface, and the reason an AI assistant can
|
|
|
75
75
|
|
|
76
76
|
## Tool surface
|
|
77
77
|
|
|
78
|
-
**
|
|
78
|
+
**79 tools across 16 capability groups** (59 Community, 20 Pro). Every tool is namespaced `ps_*` and discoverable at runtime via `tools/list`. Community tools ship in both editions; Pro tools unlock with a license.
|
|
79
79
|
|
|
80
80
|
| Group | Edition | Tools |
|
|
81
81
|
| --- | --- | --- |
|
|
@@ -94,11 +94,11 @@ These are the choices that shape the surface, and the reason an AI assistant can
|
|
|
94
94
|
| **AI selection** | Community | `select_subject` · `select_sky` |
|
|
95
95
|
| **AI selection** | Pro | `select_subject_instance` · `select_object` |
|
|
96
96
|
| **Filters** | Pro | `apply_camera_raw` |
|
|
97
|
-
| **Layers (warp)** | Pro | `warp_layer`
|
|
97
|
+
| **Layers (warp)** | Pro | `warp_layer` |
|
|
98
98
|
| **Perception** | Pro | `edit_object` · `add_text_to_object` · `resolve_placement` |
|
|
99
99
|
| **Face mesh** | Pro | `detect_landmarks` · `select_face_feature` |
|
|
100
100
|
| **Templates** | Pro | `template_create_evidence` · `template_save` · `template_list` · `template_apply` · `template_verify` · `template_recall` · `template_delete` |
|
|
101
|
-
| **Automation** | Pro | `list_actions` · `play_action` · `execute_script` |
|
|
101
|
+
| **Automation** | Pro | `list_actions` · `play_action` · `execute_script` · `batch` |
|
|
102
102
|
|
|
103
103
|
## Editions
|
|
104
104
|
|
|
@@ -27,12 +27,13 @@ function __notFoundMessage(label, requested, groupsOnly) {
|
|
|
27
27
|
var out = '';
|
|
28
28
|
for (var c = 0; c < s.length; c++) {
|
|
29
29
|
var code = s.charCodeAt(c);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
} else {
|
|
30
|
+
// Control characters and backslash only — see the doc comment above.
|
|
31
|
+
if (code < 32 || code === 127 || code === 92) {
|
|
33
32
|
var hex = code.toString(16);
|
|
34
33
|
while (hex.length < 4) hex = '0' + hex;
|
|
35
34
|
out += '\\\\u' + hex;
|
|
35
|
+
} else {
|
|
36
|
+
out += s.charAt(c);
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
return out;
|
|
@@ -47,6 +48,11 @@ function __notFoundMessage(label, requested, groupsOnly) {
|
|
|
47
48
|
nm = nm.substring(0, 40);
|
|
48
49
|
var cut = nm.lastIndexOf('\\\\u');
|
|
49
50
|
if (cut > 34) nm = nm.substring(0, cut);
|
|
51
|
+
// Non-ASCII is no longer escaped, so the cut counts raw UTF-16 units and
|
|
52
|
+
// can land between the halves of a surrogate pair. Drop a trailing high
|
|
53
|
+
// surrogate rather than emit a lone one.
|
|
54
|
+
var lastCode = nm.length > 0 ? nm.charCodeAt(nm.length - 1) : 0;
|
|
55
|
+
if (lastCode >= 0xD800 && lastCode <= 0xDBFF) nm = nm.substring(0, nm.length - 1);
|
|
50
56
|
nm = nm + '...';
|
|
51
57
|
}
|
|
52
58
|
kept.push(nm);
|
|
@@ -55,6 +55,21 @@ class ExtendScriptPhotoshopAPI {
|
|
|
55
55
|
// numbers, booleans, null/undefined, arrays, and plain objects. Cycles
|
|
56
56
|
// and very deep nesting are capped via __depth to avoid stack overflow
|
|
57
57
|
// on pathological inputs.
|
|
58
|
+
//
|
|
59
|
+
// Everything outside printable ASCII is escaped as a JSON u-escape, not
|
|
60
|
+
// just the control range. The Windows cscript stdout transport is
|
|
61
|
+
// codepage-bound and flattens raw non-ASCII to '?' (measured live,
|
|
62
|
+
// PS 27.2.0), so a layer Photoshop named itself in a non-English UI —
|
|
63
|
+
// 'Farbfuellung 1' spelled with the u-umlaut — used to reach the caller as
|
|
64
|
+
// 'Farbf?llung 1'. Naming that layer back at us then missed, because '?'
|
|
65
|
+
// is not what the layer is called. The escape is valid JSON, survives the
|
|
66
|
+
// transport, and JSON.parse restores the exact character, so the round trip
|
|
67
|
+
// is lossless rather than merely legible. Every outcome the script itself
|
|
68
|
+
// produces routes through here, errors included, so this covers messages as
|
|
69
|
+
// well as values. It does NOT cover the Windows shim's own COM failures
|
|
70
|
+
// (windows-runner.ts echoes Err.Description straight to stdout), nor raw
|
|
71
|
+
// non-ASCII sitting in a snippet's own source text — that arrives by a
|
|
72
|
+
// different path and is not addressed here.
|
|
58
73
|
function __mcpJsonEncode(v, __depth) {
|
|
59
74
|
if (__depth === undefined) __depth = 0;
|
|
60
75
|
if (__depth > 32) return '"<<truncated:max-depth>>"';
|
|
@@ -72,7 +87,7 @@ class ExtendScriptPhotoshopAPI {
|
|
|
72
87
|
else if (c === '\\t') s += '\\\\t';
|
|
73
88
|
else if (c === '\\b') s += '\\\\b';
|
|
74
89
|
else if (c === '\\f') s += '\\\\f';
|
|
75
|
-
else if (cc < 32) {
|
|
90
|
+
else if (cc < 32 || cc > 126) {
|
|
76
91
|
var hex = cc.toString(16);
|
|
77
92
|
while (hex.length < 4) hex = '0' + hex;
|
|
78
93
|
s += '\\\\u' + hex;
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/cli/help.js
CHANGED
package/dist/core/server.js
CHANGED
|
@@ -13,6 +13,7 @@ import { loadSettings, applyTelemetryEnvOverrides, applyUpdateCheckEnvOverride,
|
|
|
13
13
|
import { TelemetryClient } from '../telemetry/client.js';
|
|
14
14
|
import { resolveInstallChannel } from '../install-channel.js';
|
|
15
15
|
import { checkForUpdate, shouldCheckForUpdate } from '../update/check.js';
|
|
16
|
+
import { previousSessionFailureCounts, relevantFixes } from '../update/session-fixes.js';
|
|
16
17
|
import { join } from 'node:path';
|
|
17
18
|
import { GoSnippetClient, coreBinaryName } from '../api/snippet-client.js';
|
|
18
19
|
import { isProEntitled } from '../license/entitlement.js';
|
|
@@ -63,6 +64,8 @@ export class EditmameiServer {
|
|
|
63
64
|
lastPingReachedPs = null;
|
|
64
65
|
liveVersionResolutionAttempted = false;
|
|
65
66
|
updateInfo = null;
|
|
67
|
+
updateCheck = null;
|
|
68
|
+
updateNoticeShown = false;
|
|
66
69
|
snippetClient = new GoSnippetClient();
|
|
67
70
|
refreshLicenseOnPing = createPingLicenseRefresher();
|
|
68
71
|
constructor() {
|
|
@@ -82,7 +85,7 @@ export class EditmameiServer {
|
|
|
82
85
|
if (created)
|
|
83
86
|
this.logger.info(FIRST_RUN_DISCLOSURE);
|
|
84
87
|
if (shouldCheckForUpdate(effectiveSettings.update_check)) {
|
|
85
|
-
|
|
88
|
+
this.updateCheck = checkForUpdate().then((info) => {
|
|
86
89
|
this.updateInfo = info;
|
|
87
90
|
});
|
|
88
91
|
}
|
|
@@ -102,18 +105,23 @@ export class EditmameiServer {
|
|
|
102
105
|
...(entry.error ? { error: entry.error } : {}),
|
|
103
106
|
}, entry.result);
|
|
104
107
|
const telemetrySuccess = entry.tool === 'ps_ping' && this.lastPingReachedPs === false ? false : entry.success;
|
|
105
|
-
const
|
|
108
|
+
const isPingDowngrade = entry.tool === 'ps_ping' && !telemetrySuccess && entry.error === undefined;
|
|
109
|
+
const errorClass = classifyError(entry.error) ??
|
|
110
|
+
(telemetrySuccess ? null : isPingDowngrade ? 'ps_not_running' : 'other');
|
|
106
111
|
this.telemetry.recordCall({
|
|
107
112
|
tool: entry.tool,
|
|
108
113
|
success: telemetrySuccess,
|
|
109
114
|
duration_ms: entry.duration_ms,
|
|
110
115
|
error_class: errorClass,
|
|
111
116
|
});
|
|
112
|
-
if (!
|
|
117
|
+
if (!telemetrySuccess) {
|
|
113
118
|
this.telemetry.recordDiagnostic({
|
|
114
119
|
tool: entry.tool,
|
|
115
120
|
error_class: errorClass ?? 'other',
|
|
116
|
-
error_message: entry.error
|
|
121
|
+
error_message: entry.error ||
|
|
122
|
+
(isPingDowngrade
|
|
123
|
+
? 'ps_ping did not reach Photoshop'
|
|
124
|
+
: 'tool reported failure with no message'),
|
|
117
125
|
});
|
|
118
126
|
}
|
|
119
127
|
},
|
|
@@ -155,14 +163,19 @@ export class EditmameiServer {
|
|
|
155
163
|
},
|
|
156
164
|
update_available: {
|
|
157
165
|
type: ['object', 'null'],
|
|
158
|
-
description: 'Set when a newer Editmamei version is published, else null
|
|
166
|
+
description: 'Set when a newer Editmamei version is published, else null: { current, latest, channel (npm/mcpb/dev), how_to_update, fixed_tools (tools whose recorded failures the newer version fixes) }. The relay instruction rides the ping TEXT on the first ping — see notify_user. Anonymous npm-registry check at boot; opt out with `editmamei config set update_check false`.',
|
|
159
167
|
properties: {
|
|
160
168
|
current: { type: 'string' },
|
|
161
169
|
latest: { type: 'string' },
|
|
162
170
|
channel: { type: 'string' },
|
|
163
171
|
how_to_update: { type: 'string' },
|
|
172
|
+
fixed_tools: { type: 'array', items: { type: 'string' } },
|
|
164
173
|
},
|
|
165
174
|
},
|
|
175
|
+
notify_user: {
|
|
176
|
+
type: 'boolean',
|
|
177
|
+
description: 'True on the one ping whose text carries the update notice — relay that notice to the user before continuing. False on later pings and when no update is available.',
|
|
178
|
+
},
|
|
166
179
|
},
|
|
167
180
|
},
|
|
168
181
|
annotations: {
|
|
@@ -209,6 +222,7 @@ export class EditmameiServer {
|
|
|
209
222
|
toolRegistry: this.toolRegistry,
|
|
210
223
|
logger: this.logger,
|
|
211
224
|
assertToolsClassified: () => this.assertToolsClassified(),
|
|
225
|
+
classifyTool: (name) => this.classifyTool(name),
|
|
212
226
|
});
|
|
213
227
|
const proModule = this.moduleLifecycle.resolveProModule();
|
|
214
228
|
this.kernel = new Kernel({
|
|
@@ -239,10 +253,13 @@ export class EditmameiServer {
|
|
|
239
253
|
async ensureEntitledModuleFresh(delivery = {}) {
|
|
240
254
|
return this.moduleLifecycle.ensureEntitledModuleFresh(delivery);
|
|
241
255
|
}
|
|
256
|
+
classifyTool(name) {
|
|
257
|
+
tierOf(name);
|
|
258
|
+
groupOf(name);
|
|
259
|
+
}
|
|
242
260
|
assertToolsClassified() {
|
|
243
261
|
for (const tool of this.toolRegistry.list()) {
|
|
244
|
-
|
|
245
|
-
groupOf(tool.name);
|
|
262
|
+
this.classifyTool(tool.name);
|
|
246
263
|
}
|
|
247
264
|
}
|
|
248
265
|
listCapabilities() {
|
|
@@ -346,6 +363,9 @@ export class EditmameiServer {
|
|
|
346
363
|
async pingPhotoshop() {
|
|
347
364
|
this.lastPingReachedPs = null;
|
|
348
365
|
this.refreshLicenseOnPing();
|
|
366
|
+
if (this.updateCheck)
|
|
367
|
+
await this.updateCheck;
|
|
368
|
+
const update = await this.buildUpdateNotice();
|
|
349
369
|
const connection = this.session.getConnection();
|
|
350
370
|
let version = 'Unknown';
|
|
351
371
|
let actionSetsCount = 0;
|
|
@@ -359,6 +379,23 @@ export class EditmameiServer {
|
|
|
359
379
|
}
|
|
360
380
|
catch {
|
|
361
381
|
}
|
|
382
|
+
if (connection.getPhotoshopInfo() !== null && !(await connection.isCurrentlyRunning())) {
|
|
383
|
+
this.lastPingReachedPs = false;
|
|
384
|
+
return {
|
|
385
|
+
content: [
|
|
386
|
+
{
|
|
387
|
+
type: 'text',
|
|
388
|
+
text: 'Photoshop does not appear to be running. Start Photoshop, then call ps_ping again.' +
|
|
389
|
+
update.note,
|
|
390
|
+
},
|
|
391
|
+
],
|
|
392
|
+
structuredContent: {
|
|
393
|
+
connected: false,
|
|
394
|
+
update_available: this.updateInfo,
|
|
395
|
+
notify_user: update.notify,
|
|
396
|
+
},
|
|
397
|
+
};
|
|
398
|
+
}
|
|
362
399
|
let pingStateSnippet = null;
|
|
363
400
|
try {
|
|
364
401
|
pingStateSnippet = await this.snippetClient.build('pingState');
|
|
@@ -373,10 +410,14 @@ export class EditmameiServer {
|
|
|
373
410
|
content: [
|
|
374
411
|
{
|
|
375
412
|
type: 'text',
|
|
376
|
-
text: (reason ?? 'Photoshop did not respond') +
|
|
413
|
+
text: (reason ?? 'Photoshop did not respond') + update.note,
|
|
377
414
|
},
|
|
378
415
|
],
|
|
379
|
-
structuredContent: {
|
|
416
|
+
structuredContent: {
|
|
417
|
+
connected: false,
|
|
418
|
+
update_available: this.updateInfo,
|
|
419
|
+
notify_user: update.notify,
|
|
420
|
+
},
|
|
380
421
|
};
|
|
381
422
|
}
|
|
382
423
|
degraded.push('pingState');
|
|
@@ -390,10 +431,12 @@ export class EditmameiServer {
|
|
|
390
431
|
this.logger.warn(`pingState snippet failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
391
432
|
this.lastPingReachedPs = false;
|
|
392
433
|
return {
|
|
393
|
-
content: [
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
434
|
+
content: [{ type: 'text', text: 'Photoshop did not respond' + update.note }],
|
|
435
|
+
structuredContent: {
|
|
436
|
+
connected: false,
|
|
437
|
+
update_available: this.updateInfo,
|
|
438
|
+
notify_user: update.notify,
|
|
439
|
+
},
|
|
397
440
|
};
|
|
398
441
|
}
|
|
399
442
|
if (state.version) {
|
|
@@ -431,7 +474,7 @@ export class EditmameiServer {
|
|
|
431
474
|
`${actionSetsCount} custom action set(s), ${userTemplates} saved template(s), ` +
|
|
432
475
|
`${openDocuments.length} open document(s)${openDocuments.length ? ': ' + openDocuments.join(', ') : ''}` +
|
|
433
476
|
`${degradedNote}.` +
|
|
434
|
-
|
|
477
|
+
update.note,
|
|
435
478
|
},
|
|
436
479
|
],
|
|
437
480
|
structuredContent: {
|
|
@@ -442,14 +485,45 @@ export class EditmameiServer {
|
|
|
442
485
|
open_documents: openDocuments,
|
|
443
486
|
degraded,
|
|
444
487
|
update_available: this.updateInfo,
|
|
488
|
+
notify_user: update.notify,
|
|
445
489
|
},
|
|
446
490
|
};
|
|
447
491
|
}
|
|
448
|
-
|
|
492
|
+
async buildUpdateNotice() {
|
|
449
493
|
const u = this.updateInfo;
|
|
450
494
|
if (!u)
|
|
451
|
-
return '';
|
|
452
|
-
|
|
495
|
+
return { note: '', notify: false };
|
|
496
|
+
if (this.updateNoticeShown) {
|
|
497
|
+
return {
|
|
498
|
+
note: ` Update available: v${u.current} → v${u.latest}. ${u.how_to_update}`,
|
|
499
|
+
notify: false,
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
this.updateNoticeShown = true;
|
|
503
|
+
let fixNote = '';
|
|
504
|
+
if (u.fixed_tools.length > 0) {
|
|
505
|
+
try {
|
|
506
|
+
const counts = await previousSessionFailureCounts(this.session.getSessionId(), {
|
|
507
|
+
dir: this.sessionLog.directory,
|
|
508
|
+
});
|
|
509
|
+
const hits = relevantFixes(counts, u.fixed_tools);
|
|
510
|
+
if (hits.length > 0) {
|
|
511
|
+
const named = hits
|
|
512
|
+
.map((h) => `${h.tool} (failed ${h.failures} time${h.failures === 1 ? '' : 's'} last session)`)
|
|
513
|
+
.join(', ');
|
|
514
|
+
fixNote = ` v${u.latest} fixes known failures in ${named}.`;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
catch {
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return {
|
|
521
|
+
note: ` IMPORTANT — tell the user before continuing: Editmamei v${u.latest} is available` +
|
|
522
|
+
` (this install runs v${u.current}).${fixNote} To update: ${u.how_to_update}` +
|
|
523
|
+
` Not urgent: they can also read and subscribe to release notes at` +
|
|
524
|
+
` https://editmamei.com/blog?src=update_notice`,
|
|
525
|
+
notify: true,
|
|
526
|
+
};
|
|
453
527
|
}
|
|
454
528
|
async start() {
|
|
455
529
|
await this.loadModules();
|
package/dist/core/tool-groups.js
CHANGED
|
@@ -77,7 +77,7 @@ export const GROUPS = {
|
|
|
77
77
|
automation: {
|
|
78
78
|
id: 'automation',
|
|
79
79
|
label: 'Automation (Pro)',
|
|
80
|
-
purpose: '
|
|
80
|
+
purpose: 'Batch files, play recorded Actions, and the ExtendScript escape hatch (Pro).',
|
|
81
81
|
},
|
|
82
82
|
};
|
|
83
83
|
export const TOOL_GROUPS = {
|
|
@@ -93,6 +93,7 @@ export const TOOL_GROUPS = {
|
|
|
93
93
|
ps_compare_regions: 'verify',
|
|
94
94
|
ps_get_layer_bounds_diff: 'verify',
|
|
95
95
|
ps_get_selection_preview: 'verify',
|
|
96
|
+
ps_document: 'document',
|
|
96
97
|
ps_create_document: 'document',
|
|
97
98
|
ps_open_document: 'document',
|
|
98
99
|
ps_close_document: 'document',
|
|
@@ -111,10 +112,11 @@ export const TOOL_GROUPS = {
|
|
|
111
112
|
ps_select_sky: 'select_ai',
|
|
112
113
|
ps_select_subject_instance: 'select_ai',
|
|
113
114
|
ps_select_object: 'select_ai',
|
|
115
|
+
ps_select_focus_area: 'select_ai',
|
|
116
|
+
ps_replace_sky: 'select_ai',
|
|
114
117
|
ps_add_adjustment_layer: 'adjust',
|
|
115
118
|
ps_apply_adjustment: 'adjust',
|
|
116
119
|
ps_filter: 'filter',
|
|
117
|
-
ps_apply_filter: 'filter',
|
|
118
120
|
ps_apply_camera_raw: 'filter',
|
|
119
121
|
ps_retouch: 'retouch',
|
|
120
122
|
ps_apply_brush_stroke: 'retouch',
|
|
@@ -139,11 +141,6 @@ export const TOOL_GROUPS = {
|
|
|
139
141
|
ps_warp_layer_region: 'layers',
|
|
140
142
|
ps_warp_layer_to: 'layers',
|
|
141
143
|
ps_group: 'layers',
|
|
142
|
-
ps_create_group: 'layers',
|
|
143
|
-
ps_move_layer_to_group: 'layers',
|
|
144
|
-
ps_set_group_blend_mode: 'layers',
|
|
145
|
-
ps_ungroup: 'layers',
|
|
146
|
-
ps_delete_group: 'layers',
|
|
147
144
|
ps_shape: 'layers',
|
|
148
145
|
ps_layer_mask: 'masks',
|
|
149
146
|
ps_clipping_mask: 'masks',
|
|
@@ -152,8 +149,6 @@ export const TOOL_GROUPS = {
|
|
|
152
149
|
ps_apply_image: 'masks',
|
|
153
150
|
ps_calculations: 'masks',
|
|
154
151
|
ps_text: 'type',
|
|
155
|
-
ps_create_text_layer: 'type',
|
|
156
|
-
ps_set_text: 'type',
|
|
157
152
|
ps_detect: 'perception',
|
|
158
153
|
ps_read_scene: 'perception',
|
|
159
154
|
ps_select_by_reference: 'perception',
|
|
@@ -174,6 +169,7 @@ export const TOOL_GROUPS = {
|
|
|
174
169
|
ps_list_actions: 'automation',
|
|
175
170
|
ps_play_action: 'automation',
|
|
176
171
|
ps_execute_script: 'automation',
|
|
172
|
+
ps_batch: 'automation',
|
|
177
173
|
};
|
|
178
174
|
export function groupOf(toolName) {
|
|
179
175
|
const group = TOOL_GROUPS[toolName];
|
package/dist/core/tool-tiers.js
CHANGED
|
@@ -5,21 +5,17 @@ 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',
|
|
11
|
+
ps_document: 'dev',
|
|
10
12
|
ps_create_document: 'community',
|
|
11
13
|
ps_close_document: 'community',
|
|
12
14
|
ps_open_document: 'community',
|
|
13
15
|
ps_save_psd: 'community',
|
|
14
16
|
ps_export: 'community',
|
|
15
17
|
ps_filter: 'community',
|
|
16
|
-
ps_apply_filter: 'community',
|
|
17
18
|
ps_group: 'community',
|
|
18
|
-
ps_create_group: 'community',
|
|
19
|
-
ps_move_layer_to_group: 'community',
|
|
20
|
-
ps_set_group_blend_mode: 'community',
|
|
21
|
-
ps_ungroup: 'community',
|
|
22
|
-
ps_delete_group: 'community',
|
|
23
19
|
ps_clipping_mask: 'community',
|
|
24
20
|
ps_undo: 'community',
|
|
25
21
|
ps_redo: 'community',
|
|
@@ -38,7 +34,6 @@ export const TOOL_TIERS = {
|
|
|
38
34
|
ps_add_layer_style: 'community',
|
|
39
35
|
ps_create_layer: 'community',
|
|
40
36
|
ps_delete_layer: 'community',
|
|
41
|
-
ps_create_text_layer: 'community',
|
|
42
37
|
ps_fill_layer: 'community',
|
|
43
38
|
ps_add_fill_layer: 'community',
|
|
44
39
|
ps_select_layer: 'community',
|
|
@@ -73,6 +68,8 @@ export const TOOL_TIERS = {
|
|
|
73
68
|
ps_select_sky: 'community',
|
|
74
69
|
ps_select_subject_instance: 'pro',
|
|
75
70
|
ps_select_object: 'pro',
|
|
71
|
+
ps_select_focus_area: 'dev',
|
|
72
|
+
ps_replace_sky: 'dev',
|
|
76
73
|
ps_modify_selection: 'community',
|
|
77
74
|
ps_get_selection_preview: 'community',
|
|
78
75
|
ps_selection_channel: 'community',
|
|
@@ -91,7 +88,6 @@ export const TOOL_TIERS = {
|
|
|
91
88
|
ps_template_recall: 'pro',
|
|
92
89
|
ps_template_delete: 'pro',
|
|
93
90
|
ps_text: 'community',
|
|
94
|
-
ps_set_text: 'community',
|
|
95
91
|
};
|
|
96
92
|
export function tierOf(toolName) {
|
|
97
93
|
const tier = TOOL_TIERS[toolName];
|
|
@@ -5,7 +5,7 @@ import { installModule, readInstalledModule } from './store.js';
|
|
|
5
5
|
import { Logger } from '../utils/logger.js';
|
|
6
6
|
const logger = new Logger('Modules');
|
|
7
7
|
const SKU_RE = /^[a-z0-9-]{2,32}$/;
|
|
8
|
-
const VERSION_RE = /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/;
|
|
8
|
+
export const VERSION_RE = /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/;
|
|
9
9
|
const MAX_ARTIFACT_BYTES = 64 * 1024 * 1024;
|
|
10
10
|
export async function provisionModules(key, opts = {}) {
|
|
11
11
|
const result = {
|
|
@@ -137,7 +137,7 @@ export async function provisionModules(key, opts = {}) {
|
|
|
137
137
|
function errMsg(e) {
|
|
138
138
|
return e instanceof Error ? e.message : String(e);
|
|
139
139
|
}
|
|
140
|
-
function compareVersions(a, b) {
|
|
140
|
+
export function compareVersions(a, b) {
|
|
141
141
|
const [aCore, aPre] = a.split('-', 2);
|
|
142
142
|
const [bCore, bPre] = b.split('-', 2);
|
|
143
143
|
const ap = aCore.split('.').map(Number);
|
|
@@ -5,9 +5,10 @@ import { EDITION } from '../edition.js';
|
|
|
5
5
|
import { resolveProBinaryPath } from '../api/snippet-client.js';
|
|
6
6
|
import { isProEntitled } from '../license/entitlement.js';
|
|
7
7
|
import { loadVerifiedModule, readInstalledModule, installedPath, PRO_SKU, } from '../delivery/store.js';
|
|
8
|
-
import { provisionModules } from '../delivery/provision.js';
|
|
8
|
+
import { provisionModules, compareVersions, VERSION_RE, } from '../delivery/provision.js';
|
|
9
9
|
import { readLicense } from '../license/store.js';
|
|
10
10
|
import { HOST_MIN_ABI } from './host-api.js';
|
|
11
|
+
import { VERSION } from '../version.js';
|
|
11
12
|
export function classifyModuleOutcome(inputs) {
|
|
12
13
|
if (inputs.proModuleLoaded && inputs.skipReason === null)
|
|
13
14
|
return 'loaded';
|
|
@@ -38,11 +39,12 @@ export class ModuleLifecycle {
|
|
|
38
39
|
if (isProEntitled()) {
|
|
39
40
|
const verified = loadVerifiedModule(PRO_SKU);
|
|
40
41
|
if (verified) {
|
|
41
|
-
const
|
|
42
|
+
const installed = readInstalledModule(PRO_SKU);
|
|
42
43
|
this._proModule = {
|
|
43
44
|
importer: () => import(pathToFileURL(verified.handlersPath).href),
|
|
44
45
|
binDir: verified.binDir,
|
|
45
|
-
abi,
|
|
46
|
+
abi: installed?.abi ?? null,
|
|
47
|
+
version: installed?.version ?? null,
|
|
46
48
|
};
|
|
47
49
|
return this._proModule;
|
|
48
50
|
}
|
|
@@ -61,6 +63,7 @@ export class ModuleLifecycle {
|
|
|
61
63
|
importer: () => import(inTreeProSpecifier),
|
|
62
64
|
binDir: dirname(resolveProBinaryPath()),
|
|
63
65
|
abi: null,
|
|
66
|
+
version: null,
|
|
64
67
|
};
|
|
65
68
|
return this._proModule;
|
|
66
69
|
}
|
|
@@ -79,10 +82,33 @@ export class ModuleLifecycle {
|
|
|
79
82
|
const snapshot = this.deps.toolRegistry.snapshot();
|
|
80
83
|
try {
|
|
81
84
|
await this.kernel.loadDownloaded(this._proModule.importer);
|
|
85
|
+
if (this._proModule.version !== null &&
|
|
86
|
+
VERSION_RE.test(this._proModule.version) &&
|
|
87
|
+
compareVersions(this._proModule.version, VERSION) > 0) {
|
|
88
|
+
const added = this.deps.toolRegistry
|
|
89
|
+
.list()
|
|
90
|
+
.map((tool) => tool.name)
|
|
91
|
+
.filter((name) => !snapshot.has(name));
|
|
92
|
+
for (const name of added) {
|
|
93
|
+
try {
|
|
94
|
+
this.deps.classifyTool(name);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
this.deps.toolRegistry.unregister(name);
|
|
98
|
+
this.deps.logger.warn(`Module tool '${name}' is not recognized by this host — skipping it; the ` +
|
|
99
|
+
`rest of the module is loaded. Update Editmamei to use it.`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
82
103
|
this.deps.assertToolsClassified();
|
|
83
104
|
}
|
|
84
105
|
catch (err) {
|
|
85
|
-
|
|
106
|
+
let changed = 0;
|
|
107
|
+
for (const tool of this.deps.toolRegistry.list()) {
|
|
108
|
+
const prior = snapshot.get(tool.name);
|
|
109
|
+
if (!prior || this.deps.toolRegistry.get(tool.name) !== prior)
|
|
110
|
+
changed++;
|
|
111
|
+
}
|
|
86
112
|
this.deps.toolRegistry.restore(snapshot);
|
|
87
113
|
this.deps.logger.warn(`Pro module could not be loaded on this host — booting Community and rolling back ` +
|
|
88
114
|
`${changed} module tool change(s); will re-provision in the background: ` +
|
package/dist/modules/ce/index.js
CHANGED
|
@@ -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 },
|
|
@@ -10,11 +10,24 @@ let cachedMenu = [];
|
|
|
10
10
|
export function __resetPrecompute() {
|
|
11
11
|
lastPrecomputedKey = null;
|
|
12
12
|
cachedMenu = [];
|
|
13
|
+
channelsDocState = null;
|
|
13
14
|
}
|
|
14
15
|
export const CHANNEL_PREFIX = 'scene:';
|
|
15
16
|
export async function saveSelectionAsSceneChannel(connection, target) {
|
|
16
17
|
await runScript(connection, saveSelectionToNamedChannelScript(`${CHANNEL_PREFIX}${target}`), SCENE_CHANNEL_TIMEOUT_MS);
|
|
17
18
|
}
|
|
19
|
+
let channelsDocState = null;
|
|
20
|
+
export async function invalidateSceneChannelsIfStale(connection, cacheKey) {
|
|
21
|
+
if (channelsDocState === cacheKey)
|
|
22
|
+
return;
|
|
23
|
+
try {
|
|
24
|
+
await runScript(connection, deleteSceneChannelsScript(), SCENE_CHANNEL_TIMEOUT_MS);
|
|
25
|
+
channelsDocState = cacheKey;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
channelsDocState = null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
18
31
|
const PRECOMPUTE_TARGETS = [
|
|
19
32
|
'sky',
|
|
20
33
|
'ground',
|
|
@@ -24,6 +37,31 @@ const PRECOMPUTE_TARGETS = [
|
|
|
24
37
|
'subject',
|
|
25
38
|
'face',
|
|
26
39
|
];
|
|
40
|
+
export function candidateMenu(model) {
|
|
41
|
+
const advertise = (target) => ({
|
|
42
|
+
key: `${CHANNEL_PREFIX}${target}`,
|
|
43
|
+
target,
|
|
44
|
+
method: 'on_demand',
|
|
45
|
+
bounds: null,
|
|
46
|
+
on_demand: true,
|
|
47
|
+
});
|
|
48
|
+
const hasFace = model.faces.length > 0;
|
|
49
|
+
const hasSubject = model.subjects.length > 0;
|
|
50
|
+
const hasPerson = model.subjects.some((s) => s.label === 'person');
|
|
51
|
+
const menu = [
|
|
52
|
+
advertise('sky'),
|
|
53
|
+
advertise('ground'),
|
|
54
|
+
advertise('shadows'),
|
|
55
|
+
advertise('highlights'),
|
|
56
|
+
];
|
|
57
|
+
if (hasPerson || hasFace)
|
|
58
|
+
menu.push(advertise('skin'));
|
|
59
|
+
if (hasSubject)
|
|
60
|
+
menu.push(advertise('subject'));
|
|
61
|
+
if (hasFace)
|
|
62
|
+
menu.push(advertise('face'));
|
|
63
|
+
return menu;
|
|
64
|
+
}
|
|
27
65
|
function deleteSceneChannelsScript() {
|
|
28
66
|
return `
|
|
29
67
|
if (app.documents.length === 0) { throw new Error('No document is open in Photoshop'); }
|
|
@@ -145,6 +183,7 @@ export async function precomputeRegions(connection, snippet, model, composition,
|
|
|
145
183
|
const tally = { scripts: 0 };
|
|
146
184
|
const countedConnection = countingConnection(connection, tally);
|
|
147
185
|
await runScript(countedConnection, deleteSceneChannelsScript(), SCENE_CHANNEL_TIMEOUT_MS);
|
|
186
|
+
channelsDocState = model.provenance.cache_key;
|
|
148
187
|
const menu = [];
|
|
149
188
|
for (const target of PRECOMPUTE_TARGETS) {
|
|
150
189
|
try {
|
|
Binary file
|