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.
- 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 +2 -0
- package/dist/cli/repair.js +36 -0
- package/dist/cli/router.js +4 -0
- package/dist/core/server.js +107 -5
- package/dist/core/tool-registry.js +6 -0
- package/dist/delivery/provision.js +2 -2
- package/dist/delivery/store.js +63 -4
- package/dist/kernel/host-api.js +1 -0
- package/dist/license/entitlement.js +4 -2
- package/dist/license/ping-refresh.js +14 -0
- package/dist/platform/macos-executor.js +0 -1
- package/dist/skills/editmamei-skill.zip +0 -0
- package/dist/telemetry/client.js +9 -2
- package/dist/telemetry/events.js +16 -0
- package/dist/version.js +1 -1
- package/package.json +2 -2
|
Binary file
|
|
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
|
+
}
|
package/dist/cli/router.js
CHANGED
|
@@ -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 };
|
package/dist/core/server.js
CHANGED
|
@@ -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 {
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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 = () => {
|
|
@@ -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: `
|
|
62
|
+
reason: `downgrade-blocked (installed v${installed.version} is newer than manifest latest v${latest})`,
|
|
63
63
|
});
|
|
64
64
|
continue;
|
|
65
65
|
}
|
package/dist/delivery/store.js
CHANGED
|
@@ -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
|
|
76
|
-
|
|
77
|
-
|
|
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)
|
package/dist/kernel/host-api.js
CHANGED
|
@@ -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
|
-
|
|
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
|
package/dist/telemetry/client.js
CHANGED
|
@@ -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 {
|
|
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
|
}
|
package/dist/telemetry/events.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
44
|
+
"adm-zip": "^0.6.0",
|
|
45
45
|
"jpeg-js": "^0.4.4",
|
|
46
46
|
"onnxruntime-web": "1.27.0"
|
|
47
47
|
}
|