editmamei 0.22.0 → 0.22.2

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.
Binary file
Binary file
package/dist/cli/help.js CHANGED
@@ -9,6 +9,8 @@ Usage:
9
9
  editmamei config Get/set settings (telemetry, privacy) in ~/.editmamei/settings.json
10
10
  editmamei activate <key> Activate a Pro license on this device
11
11
  editmamei deactivate Free this device's seat (before moving Pro to another machine)
12
+ editmamei repair Re-download the Pro module if it wedged after a host update
13
+ (fixes it without deleting ~/.editmamei — keeps templates + license)
12
14
  editmamei license Show the current license + whether Pro is unlocked
13
15
  editmamei report Write an anonymized diagnostic bundle to Downloads for a bug report
14
16
  editmamei help, --help Print this help
@@ -0,0 +1,36 @@
1
+ import { readLicense } from '../license/store.js';
2
+ import { provisionModules } from '../delivery/provision.js';
3
+ export async function runRepair(opts = {}) {
4
+ const out = opts.stdout ?? ((s) => process.stdout.write(s));
5
+ const err = opts.stderr ?? ((s) => process.stderr.write(s));
6
+ const license = readLicense({ dir: opts.dir });
7
+ if (!license) {
8
+ err('No Pro license found on this device. Run `editmamei activate <license-key>` first.\n');
9
+ throw new Error('no license');
10
+ }
11
+ out('Repairing the Pro module (re-provisioning from the delivery service)…\n');
12
+ const prov = await provisionModules(license.key, {
13
+ force: true,
14
+ dir: opts.dir,
15
+ now: opts.now,
16
+ config: opts.delivery?.config,
17
+ fetchImpl: opts.delivery?.fetchImpl,
18
+ signingKeys: opts.delivery?.signingKeys,
19
+ sleep: opts.delivery?.sleep,
20
+ });
21
+ if (prov.notConfigured) {
22
+ out(' Module delivery is not configured in this build — nothing to repair.\n');
23
+ return;
24
+ }
25
+ for (const m of prov.installed)
26
+ out(` Installed ${m.sku} module v${m.version}.\n`);
27
+ for (const s of prov.skipped)
28
+ out(` Skipped ${s.sku} v${s.version}: ${s.reason}.\n`);
29
+ for (const e of prov.errors) {
30
+ err(` Error: could not provision the ${e.sku} module: ${e.message}\n`);
31
+ }
32
+ if (prov.errors.length > 0) {
33
+ throw new Error('module re-provisioning failed');
34
+ }
35
+ out('\nRestart your MCP client (Claude Desktop / Claude Code) to load Pro tools.\n');
36
+ }
@@ -4,6 +4,7 @@ import { runStatus } from './status.js';
4
4
  import { runConfig } from './config.js';
5
5
  import { runActivate } from './activate.js';
6
6
  import { runDeactivate } from './deactivate.js';
7
+ import { runRepair } from './repair.js';
7
8
  import { runLicenseStatus } from './license.js';
8
9
  import { runReport } from './report.js';
9
10
  import { printHelp } from './help.js';
@@ -39,6 +40,9 @@ export async function routeCli(argv, opts = {}) {
39
40
  case 'deactivate':
40
41
  await runDeactivate({ stderr: err });
41
42
  return { handled: true, exitCode: 0 };
43
+ case 'repair':
44
+ await runRepair({ stderr: err });
45
+ return { handled: true, exitCode: 0 };
42
46
  case 'license':
43
47
  await runLicenseStatus();
44
48
  return { handled: true, exitCode: 0 };
@@ -11,16 +11,31 @@ import { Session } from './session.js';
11
11
  import { SessionLog, classifyError } from '../utils/session-log.js';
12
12
  import { loadSettings, applyTelemetryEnvOverrides, applyUpdateCheckEnvOverride, } from './settings.js';
13
13
  import { TelemetryClient } from '../telemetry/client.js';
14
+ import { resolveInstallChannel } from '../install-channel.js';
14
15
  import { checkForUpdate, shouldCheckForUpdate } from '../update/check.js';
15
16
  import { join, dirname } from 'node:path';
16
17
  import { pathToFileURL } from 'node:url';
17
18
  import { GoSnippetClient, resolveProBinaryPath, coreBinaryName } from '../api/snippet-client.js';
18
19
  import { isProEntitled } from '../license/entitlement.js';
19
- import { loadVerifiedModule, PRO_SKU } from '../delivery/store.js';
20
+ import { createPingLicenseRefresher } from '../license/ping-refresh.js';
21
+ import { loadVerifiedModule, readInstalledModule, installedPath, PRO_SKU, } from '../delivery/store.js';
22
+ import { provisionModules } from '../delivery/provision.js';
23
+ import { readLicense } from '../license/store.js';
24
+ import { existsSync } from 'node:fs';
20
25
  import { runScript } from '../utils/run-script.js';
21
26
  import { listTemplates } from '../utils/template-storage.js';
22
27
  import { Kernel } from '../kernel/kernel.js';
28
+ import { HOST_MIN_ABI } from '../kernel/host-api.js';
23
29
  import { ceModule } from '../modules/ce/index.js';
30
+ export function classifyModuleOutcome(inputs) {
31
+ if (inputs.proModuleLoaded && inputs.skipReason === null)
32
+ return 'loaded';
33
+ if (inputs.skipReason === 'corrupt')
34
+ return 'skipped_corrupt';
35
+ if (inputs.skipReason === 'incompatible')
36
+ return 'skipped_incompatible';
37
+ return inputs.entitled ? 'absent' : 'lapsed';
38
+ }
24
39
  let logScriptOnErrorWarned = false;
25
40
  function warnLogScriptOnErrorOnce(logger) {
26
41
  if (logScriptOnErrorWarned)
@@ -36,8 +51,8 @@ export function __resetLogScriptOnErrorWarnForTests() {
36
51
  logScriptOnErrorWarned = false;
37
52
  }
38
53
  const FIRST_RUN_DISCLOSURE = 'First run: Editmamei collects anonymous, content-free usage telemetry (tool name, ' +
39
- 'success, duration, version/edition/OS/PS-version) to find what breaks. It never sends ' +
40
- 'image content, file paths, or personal data. Opt out anytime: ' +
54
+ 'success, duration, version/edition/OS/PS-version, install channel) to find what breaks. ' +
55
+ 'It never sends image content, file paths, or personal data. Opt out anytime: ' +
41
56
  '`editmamei config set telemetry.usage false` (or edit ~/.editmamei/settings.json). ' +
42
57
  'Opt in to sanitized diagnostics: `editmamei config set telemetry.diagnostics true`.';
43
58
  export class EditmameiServer {
@@ -49,9 +64,11 @@ export class EditmameiServer {
49
64
  telemetry;
50
65
  kernel;
51
66
  proModule = null;
67
+ moduleSkipReason = null;
52
68
  psVersion = null;
53
69
  updateInfo = null;
54
70
  snippetClient = new GoSnippetClient();
71
+ refreshLicenseOnPing = createPingLicenseRefresher();
55
72
  constructor() {
56
73
  this.logger = new Logger('EditmameiServer');
57
74
  this.session = new Session();
@@ -62,6 +79,9 @@ export class EditmameiServer {
62
79
  this.telemetry = new TelemetryClient({
63
80
  settings: effectiveSettings,
64
81
  getPsVersion: () => this.psVersion,
82
+ edition: isProEntitled() ? 'pro' : 'community',
83
+ channel: resolveInstallChannel(),
84
+ getModuleStatus: () => this.computeModuleStatus(),
65
85
  });
66
86
  if (created)
67
87
  this.logger.info(FIRST_RUN_DISCLOSURE);
@@ -202,16 +222,22 @@ export class EditmameiServer {
202
222
  if (isProEntitled()) {
203
223
  const verified = loadVerifiedModule(PRO_SKU);
204
224
  if (verified) {
225
+ const abi = readInstalledModule(PRO_SKU)?.abi ?? null;
205
226
  return {
206
227
  importer: () => import(pathToFileURL(verified.handlersPath).href),
207
228
  binDir: verified.binDir,
229
+ abi,
208
230
  };
209
231
  }
232
+ if (EDITION !== 'dev' && existsSync(installedPath(PRO_SKU))) {
233
+ this.moduleSkipReason = 'corrupt';
234
+ }
210
235
  }
211
236
  if (EDITION === 'dev') {
212
237
  return {
213
238
  importer: () => import('../modules/pro/index.js'),
214
239
  binDir: dirname(resolveProBinaryPath()),
240
+ abi: null,
215
241
  };
216
242
  }
217
243
  return null;
@@ -219,14 +245,88 @@ export class EditmameiServer {
219
245
  async loadModules() {
220
246
  if (!this.proModule)
221
247
  return;
248
+ if (this.proModule.abi !== null && this.proModule.abi < HOST_MIN_ABI) {
249
+ this.logger.warn(`Pro module (abi ${this.proModule.abi}) is older than this host requires ` +
250
+ `(min abi ${HOST_MIN_ABI}) — booting Community; will re-provision in the background.`);
251
+ this.moduleSkipReason = 'incompatible';
252
+ return;
253
+ }
254
+ const snapshot = this.toolRegistry.snapshot();
222
255
  try {
223
256
  await this.kernel.loadDownloaded(this.proModule.importer);
257
+ this.assertToolsClassified();
224
258
  }
225
259
  catch (err) {
226
- this.logger.warn(`Pro module not loaded: ${err instanceof Error ? err.message : String(err)}`);
260
+ const changed = this.toolRegistry.count() - snapshot.size;
261
+ this.toolRegistry.restore(snapshot);
262
+ this.logger.warn(`Pro module could not be loaded on this host — booting Community and rolling back ` +
263
+ `${changed} module tool change(s); will re-provision in the background: ` +
264
+ `${err instanceof Error ? err.message : String(err)}`);
265
+ this.moduleSkipReason = 'incompatible';
266
+ }
267
+ }
268
+ computeModuleStatus() {
269
+ if (!readLicense())
270
+ return null;
271
+ const installed = readInstalledModule(PRO_SKU);
272
+ return {
273
+ module: PRO_SKU,
274
+ outcome: classifyModuleOutcome({
275
+ proModuleLoaded: this.proModule !== null,
276
+ skipReason: this.moduleSkipReason,
277
+ entitled: isProEntitled(),
278
+ }),
279
+ module_version: installed?.version ?? null,
280
+ abi: installed?.abi ?? null,
281
+ };
282
+ }
283
+ async reprovisionIfModuleSkipped(delivery = {}) {
284
+ const reason = this.moduleSkipReason;
285
+ if (reason === null)
286
+ return;
287
+ const license = readLicense();
288
+ if (!license) {
289
+ this.logger.warn('A Pro module was skipped but no cached license was found — staying Community. ' +
290
+ 'Run `editmamei activate <key>` to restore Pro.');
227
291
  return;
228
292
  }
229
- this.assertToolsClassified();
293
+ try {
294
+ const prov = await provisionModules(license.key, {
295
+ force: reason === 'corrupt',
296
+ config: delivery.config,
297
+ fetchImpl: delivery.fetchImpl,
298
+ signingKeys: delivery.signingKeys,
299
+ sleep: delivery.sleep,
300
+ });
301
+ if (prov.installed.length > 0) {
302
+ for (const m of prov.installed) {
303
+ this.logger.info(`Pro module updated to v${m.version}, restart to load.`);
304
+ }
305
+ return;
306
+ }
307
+ if (prov.notConfigured) {
308
+ this.logger.warn('Module delivery is not configured — staying Community.');
309
+ return;
310
+ }
311
+ for (const e of prov.errors) {
312
+ this.logger.warn(`Could not re-provision the ${e.sku} module (staying Community): ${e.message}`);
313
+ }
314
+ if (prov.errors.length === 0) {
315
+ if (reason === 'incompatible') {
316
+ this.logger.warn('The published Pro module does not yet support this host version — staying ' +
317
+ 'Community. Update Editmamei when a compatible release ships; ' +
318
+ '`editmamei report` files a diagnostic.');
319
+ }
320
+ else {
321
+ this.logger.warn('Re-provision installed nothing for the corrupt Pro module — staying ' +
322
+ 'Community. Try `editmamei repair`; `editmamei report` if it persists.');
323
+ }
324
+ }
325
+ }
326
+ catch (err) {
327
+ this.logger.warn(`Background Pro-module re-provision failed (staying Community): ` +
328
+ `${err instanceof Error ? err.message : String(err)}`);
329
+ }
230
330
  }
231
331
  assertToolsClassified() {
232
332
  for (const tool of this.toolRegistry.list()) {
@@ -299,6 +399,7 @@ export class EditmameiServer {
299
399
  }
300
400
  }
301
401
  async pingPhotoshop() {
402
+ this.refreshLicenseOnPing();
302
403
  const connection = this.session.getConnection();
303
404
  const isConnected = await connection.ping();
304
405
  if (!isConnected) {
@@ -381,6 +482,7 @@ export class EditmameiServer {
381
482
  const transport = new StdioServerTransport();
382
483
  await this.server.connect(transport);
383
484
  void this.session.initialize().catch(() => undefined);
485
+ void this.reprovisionIfModuleSkipped();
384
486
  this.telemetry.start();
385
487
  void this.telemetry.flushOutboxOnStartup();
386
488
  this.server.onclose = () => {
@@ -20,6 +20,12 @@ export class ToolRegistry {
20
20
  this.register(def.tool.name, def);
21
21
  }
22
22
  }
23
+ snapshot() {
24
+ return new Map(this.tools);
25
+ }
26
+ restore(snap) {
27
+ this.tools = new Map(snap);
28
+ }
23
29
  get(name) {
24
30
  return this.tools.get(name);
25
31
  }
@@ -51,7 +51,7 @@ export async function provisionModules(key, opts = {}) {
51
51
  continue;
52
52
  }
53
53
  const installed = readInstalledModule(sku, opts);
54
- if (installed && installed.version === latest) {
54
+ if (installed && installed.version === latest && !opts.force) {
55
55
  result.skipped.push({ sku, version: latest, reason: 'up-to-date' });
56
56
  continue;
57
57
  }
@@ -59,7 +59,7 @@ export async function provisionModules(key, opts = {}) {
59
59
  result.skipped.push({
60
60
  sku,
61
61
  version: latest,
62
- reason: `rollback-blocked (installed v${installed.version} is newer than manifest latest v${latest})`,
62
+ reason: `downgrade-blocked (installed v${installed.version} is newer than manifest latest v${latest})`,
63
63
  });
64
64
  continue;
65
65
  }
@@ -1,5 +1,5 @@
1
1
  import { join, dirname } from 'node:path';
2
- import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from 'node:fs';
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, readdirSync, rmSync, lstatSync, } from 'node:fs';
3
3
  import { settingsDir } from '../core/settings.js';
4
4
  import { installBundle } from './bundle.js';
5
5
  import { sha256Hex } from './crypto.js';
@@ -7,6 +7,8 @@ import { verifyModuleSignature, MODULE_SIGNING_PUBLIC_KEYS } from './signing.js'
7
7
  import { Logger } from '../utils/logger.js';
8
8
  const logger = new Logger('Modules');
9
9
  const INSTALLED_FILENAME = 'installed.json';
10
+ const TMP_PREFIX = '.tmp-';
11
+ const TMP_STALE_MS = 5 * 60 * 1000;
10
12
  export const PRO_SKU = 'pro';
11
13
  export const PRO_HANDLERS_ENTRY = 'pro-handlers.mjs';
12
14
  const MODULE_MANIFEST = 'manifest.json';
@@ -72,14 +74,71 @@ function writeFileAtomic(path, data) {
72
74
  renameSync(tmp, path);
73
75
  }
74
76
  export function installModule(rec, blob, opts = {}) {
75
- const dir = installedModuleDir(rec.sku, rec.version, opts);
76
- installBundle(blob, rec.content_key, dir);
77
- writeFileSync(moduleArtifactPath(rec.sku, rec.version, opts), Buffer.from(blob), { mode: 0o600 });
77
+ const finalDir = installedModuleDir(rec.sku, rec.version, opts);
78
+ const skuDir = dirname(finalDir);
79
+ const tmpDir = join(skuDir, `${TMP_PREFIX}${rec.version}-${process.pid}`);
80
+ rmSync(tmpDir, { recursive: true, force: true });
81
+ try {
82
+ installBundle(blob, rec.content_key, tmpDir);
83
+ writeFileSync(join(tmpDir, MODULE_ARTIFACT), Buffer.from(blob), { mode: 0o600 });
84
+ }
85
+ catch (err) {
86
+ rmSync(tmpDir, { recursive: true, force: true });
87
+ throw err;
88
+ }
89
+ if (opts.force && existsSync(finalDir)) {
90
+ rmSync(finalDir, { recursive: true, force: true });
91
+ }
92
+ try {
93
+ renameSync(tmpDir, finalDir);
94
+ }
95
+ catch (err) {
96
+ rmSync(tmpDir, { recursive: true, force: true });
97
+ if (!existsSync(finalDir))
98
+ throw err;
99
+ }
78
100
  const now = opts.now ?? Date.now;
79
101
  const installed = { ...rec, installed_at: new Date(now()).toISOString() };
80
102
  writeFileAtomic(installedPath(rec.sku, opts), JSON.stringify(installed, null, 2) + '\n');
103
+ pruneOldModuleVersions(rec.sku, rec.version, opts);
81
104
  return installed;
82
105
  }
106
+ export function pruneOldModuleVersions(sku, keepVersion, opts = {}) {
107
+ const skuDir = join(modulesRoot(opts), sku);
108
+ if (!existsSync(skuDir))
109
+ return;
110
+ let entries;
111
+ try {
112
+ entries = readdirSync(skuDir);
113
+ }
114
+ catch (err) {
115
+ logger.warn(`could not list module dir for '${sku}' to prune: ${err instanceof Error ? err.message : String(err)}`);
116
+ return;
117
+ }
118
+ const now = (opts.now ?? Date.now)();
119
+ for (const entry of entries) {
120
+ if (entry === keepVersion || entry === INSTALLED_FILENAME)
121
+ continue;
122
+ const full = join(skuDir, entry);
123
+ try {
124
+ const st = lstatSync(full);
125
+ if (entry.startsWith(TMP_PREFIX)) {
126
+ if (now - st.mtimeMs > TMP_STALE_MS) {
127
+ rmSync(full, { recursive: true, force: true });
128
+ logger.info(`Pruned stale staging dir '${sku}/${entry}'.`);
129
+ }
130
+ continue;
131
+ }
132
+ if (!st.isDirectory())
133
+ continue;
134
+ rmSync(full, { recursive: true, force: true });
135
+ logger.info(`Pruned stale module version '${sku}' v${entry}.`);
136
+ }
137
+ catch (err) {
138
+ logger.warn(`could not prune stale module entry '${sku}/${entry}': ${err instanceof Error ? err.message : String(err)}`);
139
+ }
140
+ }
141
+ }
83
142
  export function loadVerifiedModule(sku, opts = {}, pubKeys = MODULE_SIGNING_PUBLIC_KEYS) {
84
143
  const rec = readInstalledModule(sku, opts);
85
144
  if (!rec)
@@ -1 +1,2 @@
1
1
  export const KERNEL_ABI = 1;
2
+ export const HOST_MIN_ABI = 1;
@@ -7,6 +7,7 @@ const logger = new Logger('License');
7
7
  export const GRACE_MS = 30 * 24 * 60 * 60 * 1000;
8
8
  export const REFRESH_AFTER_MS = 7 * 24 * 60 * 60 * 1000;
9
9
  export const EXPIRED_REFRESH_TIMEOUT_MS = 5_000;
10
+ export const CLOCK_SKEW_TOLERANCE_MS = 24 * 60 * 60 * 1000;
10
11
  export function evaluateEntitlement(rec, now) {
11
12
  if (!rec)
12
13
  return { entitled: false, reason: 'no-license' };
@@ -77,9 +78,10 @@ export async function refreshIfStale(ops = {}) {
77
78
  return;
78
79
  const last = Date.parse(rec.last_validated_at);
79
80
  const age = Number.isFinite(last) ? now - last : Infinity;
80
- if (age <= REFRESH_AFTER_MS)
81
+ const clockSkewed = age < -CLOCK_SKEW_TOLERANCE_MS;
82
+ if (!clockSkewed && age <= REFRESH_AFTER_MS)
81
83
  return;
82
- if (age <= GRACE_MS) {
84
+ if (clockSkewed || age <= GRACE_MS) {
83
85
  refresh(ops).catch((err) => {
84
86
  logger.warn(`Background license refresh failed (grace covers offline use): ` +
85
87
  `${err instanceof Error ? err.message : String(err)}`);
@@ -0,0 +1,14 @@
1
+ import { refreshIfStale } from './entitlement.js';
2
+ export function createPingLicenseRefresher(refresh = refreshIfStale) {
3
+ let fired = false;
4
+ return () => {
5
+ if (fired)
6
+ return;
7
+ fired = true;
8
+ try {
9
+ void refresh().catch(() => { });
10
+ }
11
+ catch {
12
+ }
13
+ };
14
+ }
@@ -103,7 +103,6 @@ export class MacOSExecutor {
103
103
  throw new Error('jsxPath contains a character that would break AppleScript interpolation');
104
104
  }
105
105
  return `tell application "${this.appName}"
106
- \tactivate
107
106
  \tdo javascript "$.evalFile(decodeURI('${encodeURI(posixPath)}'))"
108
107
  end tell`;
109
108
  }
Binary file
@@ -1,7 +1,8 @@
1
1
  import { Logger } from '../utils/logger.js';
2
2
  import { EDITION } from '../edition.js';
3
3
  import { VERSION } from '../version.js';
4
- import { buildDiagnosticEvent, buildSessionStart, buildSessionSummary, buildUsageEvent, dayBucket, isContentSafe, PS_VERSION_UNKNOWN, } from './events.js';
4
+ import { resolveInstallChannel } from '../install-channel.js';
5
+ import { buildDiagnosticEvent, buildModuleStatus, buildSessionStart, buildSessionSummary, buildUsageEvent, dayBucket, isContentSafe, PS_VERSION_UNKNOWN, } from './events.js';
5
6
  import { sanitizeMessage, sanitizeSnippet, sanitizeStderrTail } from './sanitize.js';
6
7
  import { httpTransport, resolveEndpoint } from './transport.js';
7
8
  import { appendOutboxSync, clearOutbox, clearSessionState, readOutbox, readSessionState, writeSessionStateSync, } from './outbox.js';
@@ -23,6 +24,7 @@ export class TelemetryClient {
23
24
  maxBatchSize;
24
25
  active;
25
26
  outboxOpts;
27
+ getModuleStatus;
26
28
  queue = [];
27
29
  timer = null;
28
30
  shutdownPromise = null;
@@ -39,11 +41,13 @@ export class TelemetryClient {
39
41
  this.maxBatchSize = opts.maxBatchSize ?? MAX_BATCH_SIZE;
40
42
  this.active = opts.active ?? (EDITION !== 'dev' && !isTestEnv());
41
43
  this.outboxOpts = opts.outboxDir ? { dir: opts.outboxDir } : {};
44
+ this.getModuleStatus = opts.getModuleStatus ?? (() => null);
42
45
  this.dims = {
43
46
  install_id: this.settings.telemetry.install_id,
44
47
  editmamei_version: VERSION,
45
- edition: EDITION,
48
+ edition: opts.edition ?? EDITION,
46
49
  platform: process.platform,
50
+ channel: opts.channel ?? resolveInstallChannel(),
47
51
  getPsVersion: opts.getPsVersion,
48
52
  };
49
53
  }
@@ -57,6 +61,9 @@ export class TelemetryClient {
57
61
  this.timer.unref?.();
58
62
  if (this.settings.telemetry.usage) {
59
63
  this.enqueue(buildSessionStart(this.dims, this.now()));
64
+ const moduleStatus = this.getModuleStatus();
65
+ if (moduleStatus)
66
+ this.enqueue(buildModuleStatus(this.dims, moduleStatus, this.now()));
60
67
  void this.flush();
61
68
  }
62
69
  }
@@ -55,6 +55,22 @@ export function buildSessionStart(dims, now) {
55
55
  edition: dims.edition,
56
56
  platform: dims.platform,
57
57
  ps_version: psVersionOf(dims),
58
+ channel: dims.channel,
59
+ };
60
+ }
61
+ export function buildModuleStatus(dims, status, now) {
62
+ return {
63
+ v: TELEMETRY_SCHEMA_VERSION,
64
+ type: 'module_status',
65
+ install_id: dims.install_id,
66
+ ts_bucket: dayBucket(now),
67
+ editmamei_version: dims.editmamei_version,
68
+ edition: dims.edition,
69
+ platform: dims.platform,
70
+ module: status.module,
71
+ outcome: status.outcome,
72
+ module_version: status.module_version,
73
+ abi: status.abi,
58
74
  };
59
75
  }
60
76
  export function buildDiagnosticEvent(dims, diag, now) {
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.22.0';
1
+ export const VERSION = '0.22.2';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "editmamei",
3
- "version": "0.22.0",
3
+ "version": "0.22.2",
4
4
  "description": "Editmamei — Unlock Photoshop with natural-language photo editing (Community Edition)",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -41,7 +41,7 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@modelcontextprotocol/sdk": "^1.0.4",
44
- "adm-zip": "^0.5.17",
44
+ "adm-zip": "^0.6.0",
45
45
  "jpeg-js": "^0.4.4",
46
46
  "onnxruntime-web": "1.27.0"
47
47
  }