editmamei 0.22.0 → 0.22.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/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 +74 -3
- 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/skills/editmamei-skill.zip +0 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
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
|
@@ -16,10 +16,14 @@ import { join, dirname } from 'node:path';
|
|
|
16
16
|
import { pathToFileURL } from 'node:url';
|
|
17
17
|
import { GoSnippetClient, resolveProBinaryPath, coreBinaryName } from '../api/snippet-client.js';
|
|
18
18
|
import { isProEntitled } from '../license/entitlement.js';
|
|
19
|
-
import { loadVerifiedModule, PRO_SKU } from '../delivery/store.js';
|
|
19
|
+
import { loadVerifiedModule, readInstalledModule, installedPath, PRO_SKU, } from '../delivery/store.js';
|
|
20
|
+
import { provisionModules } from '../delivery/provision.js';
|
|
21
|
+
import { readLicense } from '../license/store.js';
|
|
22
|
+
import { existsSync } from 'node:fs';
|
|
20
23
|
import { runScript } from '../utils/run-script.js';
|
|
21
24
|
import { listTemplates } from '../utils/template-storage.js';
|
|
22
25
|
import { Kernel } from '../kernel/kernel.js';
|
|
26
|
+
import { HOST_MIN_ABI } from '../kernel/host-api.js';
|
|
23
27
|
import { ceModule } from '../modules/ce/index.js';
|
|
24
28
|
let logScriptOnErrorWarned = false;
|
|
25
29
|
function warnLogScriptOnErrorOnce(logger) {
|
|
@@ -49,6 +53,7 @@ export class EditmameiServer {
|
|
|
49
53
|
telemetry;
|
|
50
54
|
kernel;
|
|
51
55
|
proModule = null;
|
|
56
|
+
moduleSkipReason = null;
|
|
52
57
|
psVersion = null;
|
|
53
58
|
updateInfo = null;
|
|
54
59
|
snippetClient = new GoSnippetClient();
|
|
@@ -202,16 +207,22 @@ export class EditmameiServer {
|
|
|
202
207
|
if (isProEntitled()) {
|
|
203
208
|
const verified = loadVerifiedModule(PRO_SKU);
|
|
204
209
|
if (verified) {
|
|
210
|
+
const abi = readInstalledModule(PRO_SKU)?.abi ?? null;
|
|
205
211
|
return {
|
|
206
212
|
importer: () => import(pathToFileURL(verified.handlersPath).href),
|
|
207
213
|
binDir: verified.binDir,
|
|
214
|
+
abi,
|
|
208
215
|
};
|
|
209
216
|
}
|
|
217
|
+
if (EDITION !== 'dev' && existsSync(installedPath(PRO_SKU))) {
|
|
218
|
+
this.moduleSkipReason = 'corrupt';
|
|
219
|
+
}
|
|
210
220
|
}
|
|
211
221
|
if (EDITION === 'dev') {
|
|
212
222
|
return {
|
|
213
223
|
importer: () => import('../modules/pro/index.js'),
|
|
214
224
|
binDir: dirname(resolveProBinaryPath()),
|
|
225
|
+
abi: null,
|
|
215
226
|
};
|
|
216
227
|
}
|
|
217
228
|
return null;
|
|
@@ -219,14 +230,73 @@ export class EditmameiServer {
|
|
|
219
230
|
async loadModules() {
|
|
220
231
|
if (!this.proModule)
|
|
221
232
|
return;
|
|
233
|
+
if (this.proModule.abi !== null && this.proModule.abi < HOST_MIN_ABI) {
|
|
234
|
+
this.logger.warn(`Pro module (abi ${this.proModule.abi}) is older than this host requires ` +
|
|
235
|
+
`(min abi ${HOST_MIN_ABI}) — booting Community; will re-provision in the background.`);
|
|
236
|
+
this.moduleSkipReason = 'incompatible';
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const snapshot = this.toolRegistry.snapshot();
|
|
222
240
|
try {
|
|
223
241
|
await this.kernel.loadDownloaded(this.proModule.importer);
|
|
242
|
+
this.assertToolsClassified();
|
|
224
243
|
}
|
|
225
244
|
catch (err) {
|
|
226
|
-
this.
|
|
245
|
+
const changed = this.toolRegistry.count() - snapshot.size;
|
|
246
|
+
this.toolRegistry.restore(snapshot);
|
|
247
|
+
this.logger.warn(`Pro module could not be loaded on this host — booting Community and rolling back ` +
|
|
248
|
+
`${changed} module tool change(s); will re-provision in the background: ` +
|
|
249
|
+
`${err instanceof Error ? err.message : String(err)}`);
|
|
250
|
+
this.moduleSkipReason = 'incompatible';
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
async reprovisionIfModuleSkipped(delivery = {}) {
|
|
254
|
+
const reason = this.moduleSkipReason;
|
|
255
|
+
if (reason === null)
|
|
256
|
+
return;
|
|
257
|
+
const license = readLicense();
|
|
258
|
+
if (!license) {
|
|
259
|
+
this.logger.warn('A Pro module was skipped but no cached license was found — staying Community. ' +
|
|
260
|
+
'Run `editmamei activate <key>` to restore Pro.');
|
|
227
261
|
return;
|
|
228
262
|
}
|
|
229
|
-
|
|
263
|
+
try {
|
|
264
|
+
const prov = await provisionModules(license.key, {
|
|
265
|
+
force: reason === 'corrupt',
|
|
266
|
+
config: delivery.config,
|
|
267
|
+
fetchImpl: delivery.fetchImpl,
|
|
268
|
+
signingKeys: delivery.signingKeys,
|
|
269
|
+
sleep: delivery.sleep,
|
|
270
|
+
});
|
|
271
|
+
if (prov.installed.length > 0) {
|
|
272
|
+
for (const m of prov.installed) {
|
|
273
|
+
this.logger.info(`Pro module updated to v${m.version}, restart to load.`);
|
|
274
|
+
}
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (prov.notConfigured) {
|
|
278
|
+
this.logger.warn('Module delivery is not configured — staying Community.');
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
for (const e of prov.errors) {
|
|
282
|
+
this.logger.warn(`Could not re-provision the ${e.sku} module (staying Community): ${e.message}`);
|
|
283
|
+
}
|
|
284
|
+
if (prov.errors.length === 0) {
|
|
285
|
+
if (reason === 'incompatible') {
|
|
286
|
+
this.logger.warn('The published Pro module does not yet support this host version — staying ' +
|
|
287
|
+
'Community. Update Editmamei when a compatible release ships; ' +
|
|
288
|
+
'`editmamei report` files a diagnostic.');
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
this.logger.warn('Re-provision installed nothing for the corrupt Pro module — staying ' +
|
|
292
|
+
'Community. Try `editmamei repair`; `editmamei report` if it persists.');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
catch (err) {
|
|
297
|
+
this.logger.warn(`Background Pro-module re-provision failed (staying Community): ` +
|
|
298
|
+
`${err instanceof Error ? err.message : String(err)}`);
|
|
299
|
+
}
|
|
230
300
|
}
|
|
231
301
|
assertToolsClassified() {
|
|
232
302
|
for (const tool of this.toolRegistry.list()) {
|
|
@@ -381,6 +451,7 @@ export class EditmameiServer {
|
|
|
381
451
|
const transport = new StdioServerTransport();
|
|
382
452
|
await this.server.connect(transport);
|
|
383
453
|
void this.session.initialize().catch(() => undefined);
|
|
454
|
+
void this.reprovisionIfModuleSkipped();
|
|
384
455
|
this.telemetry.start();
|
|
385
456
|
void this.telemetry.flushOutboxOnStartup();
|
|
386
457
|
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
|
Binary file
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.22.
|
|
1
|
+
export const VERSION = '0.22.1';
|