mneme-ai 2.19.70 → 2.19.71-lite
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/bin/postinstall-mneme-lite.cjs +282 -99
- package/package.json +6 -6
|
@@ -1,128 +1,311 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* v2.19.
|
|
4
|
-
* global `--omit=optional` bug).
|
|
3
|
+
* v2.19.71 — MNEME postinstall: TWO independent jobs in one script.
|
|
5
4
|
*
|
|
6
|
-
*
|
|
5
|
+
* JOB 1 — MNEME_LITE prune (Gap 3 workaround #2).
|
|
6
|
+
* When the user sets MNEME_LITE=1 in the install environment,
|
|
7
|
+
* remove the optional-dep tree (@huggingface/transformers, sharp,
|
|
8
|
+
* @img, onnxruntime-*) AFTER npm has extracted it. Net result:
|
|
9
|
+
* ~462MB downloaded, ~5MB on-disk. See v2.19.69 docs/wild_workarounds/02.
|
|
7
10
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
11
|
+
* JOB 2 — Dual-install detection (N5 fix, v2.19.71).
|
|
12
|
+
* Some users run two parallel Node version managers (nvm4w +
|
|
13
|
+
* nvm-windows + Volta + fnm). Each has its own npm global prefix.
|
|
14
|
+
* `npm install -g mneme-ai` writes to the ACTIVE prefix; PATH may
|
|
15
|
+
* resolve `mneme` from a DIFFERENT prefix that has an older copy.
|
|
16
|
+
* Result: user installs v2.19.70, but `mneme --version` reports
|
|
17
|
+
* v2.19.69 because PATH wins. The user perceives "I upgraded but
|
|
18
|
+
* version didn't change" — invisible distribution failure.
|
|
10
19
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
20
|
+
* This job runs on every install, scans known npm prefixes for
|
|
21
|
+
* other mneme-ai installs, and prints a LOUD warning + actionable
|
|
22
|
+
* remediation if PATH points to a different install than the one
|
|
23
|
+
* we just landed. Writes a marker file at
|
|
24
|
+
* ~/.mneme-global/dual-install-warning.json so the doctor organ
|
|
25
|
+
* can surface the same diagnosis later.
|
|
16
26
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* -> 0 native DLL handles → EBUSY structurally extinct post-install
|
|
27
|
+
* Both jobs are NON-FATAL — they swallow all errors and always exit
|
|
28
|
+
* 0 so a bug here can never abort an npm install. Verified by the
|
|
29
|
+
* source-grep test in packages/cli/tests/postinstall_mneme_lite.test.ts
|
|
30
|
+
* ("every process.exit is exit 0").
|
|
22
31
|
*
|
|
23
|
-
*
|
|
24
|
-
* (
|
|
25
|
-
*
|
|
26
|
-
* user installs Ollama or adds an OpenAI key.
|
|
27
|
-
*
|
|
28
|
-
* Idempotent + non-fatal:
|
|
29
|
-
* - Re-running after prune is a no-op (paths gone, fs.rmSync swallows
|
|
30
|
-
* ENOENT with { force: true }).
|
|
31
|
-
* - Any prune failure is logged but never breaks the install: the
|
|
32
|
-
* fallback is "user keeps the 467MB tree", not "install aborts".
|
|
33
|
-
* - Silent on the happy path (no env var set) so the default install
|
|
34
|
-
* UX is unchanged.
|
|
35
|
-
*
|
|
36
|
-
* Logs a one-line summary on the prune path so AI agents inspecting
|
|
37
|
-
* the install output can confirm the lite mode took effect.
|
|
32
|
+
* Skip with MNEME_SKIP_DUAL_CHECK=1 (job 2 only) or just don't set
|
|
33
|
+
* MNEME_LITE (job 1). CI environments (CI=true) silence job 2 to
|
|
34
|
+
* avoid spamming build logs that already pin versions exactly.
|
|
38
35
|
*/
|
|
39
36
|
|
|
40
37
|
"use strict";
|
|
41
38
|
|
|
42
39
|
const fs = require("node:fs");
|
|
43
40
|
const path = require("node:path");
|
|
41
|
+
const os = require("node:os");
|
|
42
|
+
const { spawnSync } = require("node:child_process");
|
|
44
43
|
|
|
45
|
-
const LITE = String(process.env.MNEME_LITE || "").trim();
|
|
46
|
-
if (LITE !== "1" && LITE.toLowerCase() !== "true" && LITE.toLowerCase() !== "yes") {
|
|
47
|
-
// No-op for the default install path — keeps behaviour pristine for the
|
|
48
|
-
// 99% of users who want the bundled WASM embedder.
|
|
49
|
-
process.exit(0);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// We're inside `<install-prefix>/lib/node_modules/mneme-ai/bin/` on POSIX
|
|
53
|
-
// or `<install-prefix>/node_modules/mneme-ai/bin/` on Windows. The optional
|
|
54
|
-
// tree lives at `<install-prefix>/.../mneme-ai/node_modules/<target>`.
|
|
55
44
|
const here = __dirname;
|
|
56
45
|
const mnemeRoot = path.resolve(here, "..");
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
"
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
46
|
+
|
|
47
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
48
|
+
// JOB 1 — MNEME_LITE prune
|
|
49
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
function runLitePrune() {
|
|
52
|
+
const LITE = String(process.env.MNEME_LITE || "").trim();
|
|
53
|
+
if (LITE !== "1" && LITE.toLowerCase() !== "true" && LITE.toLowerCase() !== "yes") {
|
|
54
|
+
return; // No-op for the default install path.
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const sub = (p) => path.join(mnemeRoot, "node_modules", p);
|
|
58
|
+
|
|
59
|
+
const PRUNE_TARGETS = [
|
|
60
|
+
"@huggingface",
|
|
61
|
+
"@img",
|
|
62
|
+
"sharp",
|
|
63
|
+
"onnxruntime-common",
|
|
64
|
+
"onnxruntime-node",
|
|
65
|
+
"onnxruntime-web",
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
function dirSizeBytes(dirPath) {
|
|
69
|
+
if (!fs.existsSync(dirPath)) return 0;
|
|
70
|
+
let total = 0;
|
|
71
|
+
try {
|
|
72
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
73
|
+
for (const ent of entries) {
|
|
74
|
+
const full = path.join(dirPath, ent.name);
|
|
75
|
+
try {
|
|
76
|
+
if (ent.isDirectory()) total += dirSizeBytes(full);
|
|
77
|
+
else if (ent.isFile()) total += fs.statSync(full).size;
|
|
78
|
+
} catch { /* skip */ }
|
|
79
|
+
}
|
|
80
|
+
} catch { /* */ }
|
|
81
|
+
return total;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const fmtMB = (b) => (b / (1024 * 1024)).toFixed(1) + "MB";
|
|
85
|
+
|
|
86
|
+
let prunedBytes = 0;
|
|
87
|
+
const prunedNames = [];
|
|
88
|
+
const skippedNames = [];
|
|
89
|
+
|
|
90
|
+
for (const name of PRUNE_TARGETS) {
|
|
91
|
+
const target = sub(name);
|
|
92
|
+
if (!fs.existsSync(target)) {
|
|
93
|
+
skippedNames.push(name);
|
|
94
|
+
continue;
|
|
86
95
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
96
|
+
const sizeBefore = dirSizeBytes(target);
|
|
97
|
+
try {
|
|
98
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
99
|
+
prunedBytes += sizeBefore;
|
|
100
|
+
prunedNames.push(`${name} (${fmtMB(sizeBefore)})`);
|
|
101
|
+
} catch (err) {
|
|
102
|
+
process.stderr.write(
|
|
103
|
+
`[mneme-lite] could not prune ${name}: ${err && err.message ? err.message : err}\n`,
|
|
104
|
+
);
|
|
105
|
+
skippedNames.push(`${name} (locked)`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
90
108
|
|
|
91
|
-
|
|
92
|
-
|
|
109
|
+
const summary =
|
|
110
|
+
`[mneme-lite] MNEME_LITE=${LITE} → pruned ${prunedNames.length}/${PRUNE_TARGETS.length} ` +
|
|
111
|
+
`optional-dep directories, ${fmtMB(prunedBytes)} freed.\n` +
|
|
112
|
+
(prunedNames.length > 0 ? `[mneme-lite] removed: ${prunedNames.join(", ")}\n` : "") +
|
|
113
|
+
(skippedNames.length > 0 ? `[mneme-lite] skipped: ${skippedNames.join(", ")}\n` : "") +
|
|
114
|
+
`[mneme-lite] mneme runtime will fall back through OpenAI → Ollama → hash embedder (bundled WASM skipped).\n`;
|
|
115
|
+
process.stdout.write(summary);
|
|
93
116
|
}
|
|
94
117
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
118
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
119
|
+
// JOB 2 — Dual-install detection (N5 fix)
|
|
120
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
function runDualInstallCheck() {
|
|
123
|
+
// Skip in CI / explicit opt-out — these envs have pinned versions
|
|
124
|
+
// and don't care about PATH resolution drift.
|
|
125
|
+
if (process.env.CI === "true" || process.env.MNEME_SKIP_DUAL_CHECK === "1") return;
|
|
126
|
+
|
|
127
|
+
let myVersion;
|
|
128
|
+
try {
|
|
129
|
+
myVersion = JSON.parse(fs.readFileSync(path.join(mnemeRoot, "package.json"), "utf8")).version;
|
|
130
|
+
} catch {
|
|
131
|
+
return; // can't read own version, bail silently
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const home = os.homedir();
|
|
135
|
+
const w = process.platform === "win32";
|
|
136
|
+
const candidatePrefixes = w ? [
|
|
137
|
+
path.join(home, "AppData", "Roaming", "npm"),
|
|
138
|
+
"C:\\nvm4w\\nodejs",
|
|
139
|
+
path.join(home, "AppData", "Local", "nvm"), // nvm-windows
|
|
140
|
+
path.join(home, "AppData", "Local", "pnpm"),
|
|
141
|
+
path.join(home, "AppData", "Local", "Volta"),
|
|
142
|
+
path.join(home, "scoop", "apps", "nodejs", "current"),
|
|
143
|
+
] : [
|
|
144
|
+
"/usr/local/lib",
|
|
145
|
+
"/usr/lib",
|
|
146
|
+
"/opt/homebrew/lib", // Apple Silicon Homebrew
|
|
147
|
+
path.join(home, ".npm-global"),
|
|
148
|
+
path.join(home, ".nvm", "versions", "node"), // nvm-bash
|
|
149
|
+
path.join(home, ".volta"),
|
|
150
|
+
path.join(home, ".pnpm-global"),
|
|
151
|
+
path.join(home, ".local", "share", "pnpm"),
|
|
152
|
+
];
|
|
98
153
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
154
|
+
const otherInstalls = [];
|
|
155
|
+
function tryNodeModules(base) {
|
|
156
|
+
const pkg = path.join(base, "node_modules", "mneme-ai", "package.json");
|
|
157
|
+
if (!fs.existsSync(pkg)) return;
|
|
158
|
+
try {
|
|
159
|
+
const v = JSON.parse(fs.readFileSync(pkg, "utf8")).version;
|
|
160
|
+
const installRoot = path.join(base, "node_modules", "mneme-ai");
|
|
161
|
+
if (path.resolve(installRoot) === path.resolve(mnemeRoot)) return; // ourselves
|
|
162
|
+
otherInstalls.push({
|
|
163
|
+
version: v,
|
|
164
|
+
root: installRoot,
|
|
165
|
+
bin: path.join(installRoot, "bin", "mneme.js"),
|
|
166
|
+
});
|
|
167
|
+
} catch { /* */ }
|
|
168
|
+
}
|
|
169
|
+
function scan(dir) {
|
|
170
|
+
if (!fs.existsSync(dir)) return;
|
|
171
|
+
tryNodeModules(dir);
|
|
172
|
+
try {
|
|
173
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
174
|
+
const sub = path.join(dir, entry);
|
|
175
|
+
tryNodeModules(sub);
|
|
176
|
+
tryNodeModules(path.join(sub, "nodejs"));
|
|
177
|
+
}
|
|
178
|
+
} catch { /* */ }
|
|
104
179
|
}
|
|
105
|
-
const
|
|
180
|
+
for (const pfx of candidatePrefixes) scan(pfx);
|
|
181
|
+
|
|
182
|
+
// Resolve which install PATH points to (which `mneme` runs by default)
|
|
183
|
+
let pathInstall = null;
|
|
184
|
+
try {
|
|
185
|
+
const r = w
|
|
186
|
+
? spawnSync("where", ["mneme"], { encoding: "utf8", shell: true, timeout: 5000, windowsHide: true })
|
|
187
|
+
: spawnSync("which", ["mneme"], { encoding: "utf8", timeout: 5000 });
|
|
188
|
+
if (r.status === 0 && r.stdout) {
|
|
189
|
+
const firstLine = r.stdout.split(/\r?\n/)[0].trim();
|
|
190
|
+
if (firstLine) pathInstall = firstLine;
|
|
191
|
+
}
|
|
192
|
+
} catch { /* */ }
|
|
193
|
+
|
|
194
|
+
// Persist a marker file even if no warning prints — doctor organ
|
|
195
|
+
// and bin/mneme.js may surface it later.
|
|
106
196
|
try {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
197
|
+
const dir = path.join(home, ".mneme-global");
|
|
198
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
199
|
+
const marker = {
|
|
200
|
+
v: 1,
|
|
201
|
+
ts: new Date().toISOString(),
|
|
202
|
+
myInstall: { root: mnemeRoot, version: myVersion },
|
|
203
|
+
otherInstalls,
|
|
204
|
+
pathResolvesTo: pathInstall,
|
|
205
|
+
};
|
|
206
|
+
fs.writeFileSync(
|
|
207
|
+
path.join(dir, "dual-install-warning.json"),
|
|
208
|
+
JSON.stringify(marker, null, 2),
|
|
209
|
+
{ encoding: "utf8", mode: 0o600 },
|
|
114
210
|
);
|
|
115
|
-
|
|
211
|
+
} catch { /* */ }
|
|
212
|
+
|
|
213
|
+
if (otherInstalls.length === 0) return; // single install — all good
|
|
214
|
+
|
|
215
|
+
// Compute whether PATH points to our install or an other one.
|
|
216
|
+
const myBinCandidates = [
|
|
217
|
+
path.resolve(path.join(mnemeRoot, "bin", "mneme.js")),
|
|
218
|
+
path.resolve(path.join(mnemeRoot, "bin", "mneme.cmd")),
|
|
219
|
+
path.resolve(path.join(path.dirname(mnemeRoot), ".bin", "mneme")),
|
|
220
|
+
path.resolve(path.join(path.dirname(mnemeRoot), ".bin", "mneme.cmd")),
|
|
221
|
+
path.resolve(path.join(path.dirname(mnemeRoot), ".bin", "mneme.ps1")),
|
|
222
|
+
];
|
|
223
|
+
const normPath = pathInstall ? path.resolve(pathInstall) : null;
|
|
224
|
+
const pathPointsHere = !!normPath && myBinCandidates.includes(normPath);
|
|
225
|
+
|
|
226
|
+
// Headline — always tell the user.
|
|
227
|
+
const sep = "\n" + "─".repeat(70);
|
|
228
|
+
const Y = process.stdout.isTTY ? "\x1b[33m" : "";
|
|
229
|
+
const R = process.stdout.isTTY ? "\x1b[31m" : "";
|
|
230
|
+
const B = process.stdout.isTTY ? "\x1b[1m" : "";
|
|
231
|
+
const X = process.stdout.isTTY ? "\x1b[0m" : "";
|
|
232
|
+
|
|
233
|
+
const lines = [];
|
|
234
|
+
lines.push(sep);
|
|
235
|
+
lines.push(`${Y}${B}⚠ Mneme: dual-install detected on this machine${X}`);
|
|
236
|
+
lines.push(` This install: ${mnemeRoot} (v${myVersion})`);
|
|
237
|
+
for (const o of otherInstalls) {
|
|
238
|
+
const mark = o.version === myVersion ? " (same version)"
|
|
239
|
+
: compareSemver(o.version, myVersion) > 0 ? " (NEWER!)"
|
|
240
|
+
: " (older)";
|
|
241
|
+
lines.push(` Also found: ${o.root} (v${o.version})${mark}`);
|
|
242
|
+
}
|
|
243
|
+
if (pathInstall) {
|
|
244
|
+
lines.push(` 'mneme' on PATH: ${pathInstall}`);
|
|
245
|
+
} else {
|
|
246
|
+
lines.push(` 'mneme' on PATH: (not found via \`${w ? "where" : "which"} mneme\`)`);
|
|
116
247
|
}
|
|
248
|
+
|
|
249
|
+
if (!pathPointsHere && pathInstall) {
|
|
250
|
+
// CRITICAL CASE — user's `mneme` command will NOT run the version
|
|
251
|
+
// they just installed. Loud + actionable remediation.
|
|
252
|
+
lines.push(``);
|
|
253
|
+
lines.push(`${R}${B} → \`mneme\` will NOT run v${myVersion} you just installed.${X}`);
|
|
254
|
+
lines.push(` PATH resolves to a DIFFERENT install.`);
|
|
255
|
+
lines.push(``);
|
|
256
|
+
lines.push(` ${B}To fix, pick ONE:${X}`);
|
|
257
|
+
lines.push(` ${B}# Option 1: uninstall the stale path${X}`);
|
|
258
|
+
lines.push(` npm uninstall -g mneme-ai # from a shell where 'which mneme' shows the stale one`);
|
|
259
|
+
lines.push(` npm install -g mneme-ai@${myVersion}`);
|
|
260
|
+
lines.push(``);
|
|
261
|
+
lines.push(` ${B}# Option 2: invoke this install directly${X}`);
|
|
262
|
+
if (w) {
|
|
263
|
+
lines.push(` node "${path.join(mnemeRoot, "bin", "mneme.js")}" <args>`);
|
|
264
|
+
} else {
|
|
265
|
+
lines.push(` node "${path.join(mnemeRoot, "bin", "mneme.js")}" <args>`);
|
|
266
|
+
}
|
|
267
|
+
lines.push(``);
|
|
268
|
+
lines.push(` ${B}# Option 3: run \`mneme doctor scan\` for a full diagnosis${X}`);
|
|
269
|
+
lines.push(` node "${path.join(mnemeRoot, "bin", "mneme.js")}" doctor scan --pretty`);
|
|
270
|
+
} else if (pathPointsHere) {
|
|
271
|
+
lines.push(` ${B}PATH points to this install — you're using v${myVersion} ✓${X}`);
|
|
272
|
+
lines.push(` (The other installs above are inactive copies; safe to remove with \`npm uninstall -g mneme-ai\` from their respective shells.)`);
|
|
273
|
+
}
|
|
274
|
+
lines.push(sep);
|
|
275
|
+
|
|
276
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
117
277
|
}
|
|
118
278
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
279
|
+
/** Lexicographic semver-ish compare. Returns positive if a > b,
|
|
280
|
+
* negative if a < b, 0 if equal. Handles "X.Y.Z" + "X.Y.Z-suffix". */
|
|
281
|
+
function compareSemver(a, b) {
|
|
282
|
+
const parse = (s) => {
|
|
283
|
+
const m = /^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/.exec(String(s));
|
|
284
|
+
if (!m) return [0, 0, 0, ""];
|
|
285
|
+
return [parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10), m[4] || ""];
|
|
286
|
+
};
|
|
287
|
+
const pa = parse(a);
|
|
288
|
+
const pb = parse(b);
|
|
289
|
+
for (let i = 0; i < 3; i++) {
|
|
290
|
+
if (pa[i] !== pb[i]) return pa[i] - pb[i];
|
|
291
|
+
}
|
|
292
|
+
// Prerelease loses to release (per semver), so "lite" suffix ranks LOWER.
|
|
293
|
+
if (pa[3] === pb[3]) return 0;
|
|
294
|
+
if (pa[3] === "") return 1;
|
|
295
|
+
if (pb[3] === "") return -1;
|
|
296
|
+
return pa[3] < pb[3] ? -1 : 1;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
300
|
+
// Master invariant: NEVER abort the install. Both jobs are wrapped.
|
|
301
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
302
|
+
|
|
303
|
+
try { runLitePrune(); } catch (e) {
|
|
304
|
+
try { process.stderr.write(`[mneme-postinstall] lite-prune error (non-fatal): ${e && e.message ? e.message : e}\n`); } catch { /* */ }
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
try { runDualInstallCheck(); } catch (e) {
|
|
308
|
+
try { process.stderr.write(`[mneme-postinstall] dual-install-check error (non-fatal): ${e && e.message ? e.message : e}\n`); } catch { /* */ }
|
|
309
|
+
}
|
|
126
310
|
|
|
127
|
-
// Always exit 0 — we never want this script to abort the install.
|
|
128
311
|
process.exit(0);
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mneme-ai",
|
|
3
|
-
"version": "2.19.
|
|
3
|
+
"version": "2.19.71-lite",
|
|
4
4
|
"mcpName": "io.github.patsa2561-art/mneme-ai",
|
|
5
|
-
"description": "Mneme เนโฌโ€ the memory layer for your codebase. Knows the WHY, the WHAT, the WHERE-IT-BREAKS.",
|
|
5
|
+
"description": "Mneme เนโฌโ€ the memory layer for your codebase. Knows the WHY, the WHAT, the WHERE-IT-BREAKS. — LITE variant (no bundled WASM embedder, no native deps).",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
@@ -32,10 +32,10 @@
|
|
|
32
32
|
"preinstall": "node -e \"try{const fs=require('node:fs');const path=require('node:path');const os=require('node:os');const{spawnSync}=require('node:child_process');const crypto=require('node:crypto');const w=process.platform==='win32';const home=os.homedir();const organ=path.join(home,'.mneme-global');const trailPath=path.join(organ,'preinstall-trail.jsonl');const trailSecret=process.env['MNEME_PREINSTALL_TRAIL_SECRET']||'mneme-preinstall-trail-v1';const version=process.env['npm_package_version']||'unknown';try{if(!fs.existsSync(organ))fs.mkdirSync(organ,{recursive:true,mode:0o700})}catch(e){}const lastSig=()=>{try{if(!fs.existsSync(trailPath))return'genesis';const lines=fs.readFileSync(trailPath,'utf8').trim().split('\\\\n').filter(Boolean);if(lines.length===0)return'genesis';const last=JSON.parse(lines[lines.length-1]);return typeof last?.sig==='string'?last.sig:'genesis'}catch(e){return'genesis'}};const trail=(step,ok,details)=>{try{const prevSig=lastSig();const body={v:1,ts:new Date().toISOString(),version,step,ok,...(details?{details}:{}),pid:process.pid,prevSig};const sig=crypto.createHmac('sha256',trailSecret).update(prevSig+'::'+JSON.stringify(body)).digest('hex');fs.appendFileSync(trailPath,JSON.stringify({...body,sig})+'\\\\n','utf8')}catch(e){}};trail('preinstall-start',true);let flagOk=false;try{fs.writeFileSync(path.join(organ,'install-incoming.flag'),JSON.stringify({v:1,announcedAt:new Date().toISOString(),announcerPid:process.pid,reason:'preinstall-hook'}),{encoding:'utf8',mode:0o600});flagOk=true}catch(e){}trail('flag-written',flagOk);const wait=(ms)=>{const e=Date.now()+ms;while(Date.now()<e){}};wait(300);if(w){const r=spawnSync('taskkill',['/F','/IM','mneme.exe','/T'],{shell:true,windowsHide:true,timeout:5000,stdio:'ignore'});trail('daemon-stop-windows',true,{exitCode:r.status});let reaped=0;try{const beatDir=path.join(organ,'heartbeats');if(fs.existsSync(beatDir)){for(const f of fs.readdirSync(beatDir)){const m=f.match(/^(\\\\d+)\\\\.beat$/);if(m){const pid=parseInt(m[1]);if(pid>0&&pid!==process.pid){spawnSync('taskkill',['/F','/PID',pid.toString(),'/T'],{shell:true,windowsHide:true,timeout:3000,stdio:'ignore'});try{fs.unlinkSync(path.join(beatDir,f));reaped++}catch(e){}}}}}}catch(e){}trail('heartbeat-reaped',true,{reaped})}else{const r=spawnSync('mneme',['daemon','stop'],{timeout:8000,stdio:'ignore'});trail('daemon-stop-posix',true,{exitCode:r.status});let reaped=0;try{const beatDir=path.join(organ,'heartbeats');if(fs.existsSync(beatDir)){for(const f of fs.readdirSync(beatDir)){const m=f.match(/^(\\\\d+)\\\\.beat$/);if(m){const pid=parseInt(m[1]);if(pid>0&&pid!==process.pid){try{process.kill(pid,'SIGTERM')}catch(e){}wait(100);try{process.kill(pid,'SIGKILL')}catch(e){}try{fs.unlinkSync(path.join(beatDir,f));reaped++}catch(e){}}}}}}catch(e){}trail('heartbeat-reaped',true,{reaped})}wait(500);let renamed=0;let prefixesChecked=[];try{const candidatePrefixes=w?[path.join(home,'AppData','Roaming','npm'),path.dirname(process.execPath),'C:\\\\\\\\nvm4w\\\\\\\\nodejs',path.join(home,'AppData','Local','nvm')]:['/usr/local/lib','/usr/lib',path.join(home,'.npm-global'),path.join(home,'.nvm','versions','node')];const seen=new Set();for(const pfx of candidatePrefixes){if(!fs.existsSync(pfx))continue;let nodeModulesBases=[];if(fs.existsSync(path.join(pfx,'node_modules')))nodeModulesBases.push(path.join(pfx,'node_modules'));try{for(const entry of fs.readdirSync(pfx)){const sub=path.join(pfx,entry,'node_modules');if(fs.existsSync(sub))nodeModulesBases.push(sub);const sub2=path.join(pfx,entry,'nodejs','node_modules');if(fs.existsSync(sub2))nodeModulesBases.push(sub2)}}catch(e){}for(const nm of nodeModulesBases){if(seen.has(nm))continue;seen.add(nm);prefixesChecked.push(nm);const npmGlobal=path.join(nm,'mneme-ai');if(!fs.existsSync(npmGlobal))continue;const dllPaths=w?[path.join(npmGlobal,'node_modules','@img','sharp-libvips-win32-x64','lib','libvips-42.dll'),path.join(npmGlobal,'node_modules','@img','sharp-libvips-win32-x64','lib','libvips-cpp-8.17.3.dll'),path.join(npmGlobal,'node_modules','sharp','build','Release','sharp-win32-x64.node')]:[];for(const dll of dllPaths){if(!fs.existsSync(dll))continue;try{const target=dll+'.locked-'+Date.now()+'-'+process.pid;fs.renameSync(dll,target);renamed++}catch(e){for(let i=0;i<20;i++){try{const fd=fs.openSync(dll,'r+');fs.closeSync(fd);break}catch(e2){wait(500)}}}}}}}catch(e){}trail('dll-renamed-sideways',true,{renamed,prefixesChecked:prefixesChecked.length});let swept=0;try{const candidates=w?[path.join(home,'AppData','Roaming','npm','node_modules'),path.join(path.dirname(process.execPath),'node_modules')]:['/usr/local/lib/node_modules',path.join(home,'.npm-global','node_modules')];for(const npmParent of candidates){if(!fs.existsSync(npmParent))continue;try{for(const entry of fs.readdirSync(npmParent)){if(entry.startsWith('.mneme-ai-')){try{fs.rmSync(path.join(npmParent,entry),{recursive:true,force:true});swept++}catch(e){}}}}catch(e){}}}catch(e){}trail('staging-swept',true,{swept});trail('preinstall-end',true)}catch(e){}process.exit(0)\""
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@mneme-ai/core": "2.19.
|
|
36
|
-
"@mneme-ai/correlator": "2.19.
|
|
37
|
-
"@mneme-ai/embeddings": "2.19.
|
|
38
|
-
"@mneme-ai/mcp": "2.19.
|
|
35
|
+
"@mneme-ai/core": "2.19.71-lite",
|
|
36
|
+
"@mneme-ai/correlator": "2.19.71-lite",
|
|
37
|
+
"@mneme-ai/embeddings": "2.19.71-lite",
|
|
38
|
+
"@mneme-ai/mcp": "2.19.71-lite",
|
|
39
39
|
"commander": "^14.0.3",
|
|
40
40
|
"kleur": "^4.1.5"
|
|
41
41
|
},
|