mneme-ai 2.19.67 → 2.19.69-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 +128 -0
- package/package.json +7 -6
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* v2.19.69 — MNEME_LITE postinstall pruner (Gap 3 workaround #2 — npm 10
|
|
4
|
+
* global `--omit=optional` bug).
|
|
5
|
+
*
|
|
6
|
+
* npm 10.x silently ignores `--omit=optional` for global installs:
|
|
7
|
+
*
|
|
8
|
+
* npm install -g mneme-ai --omit=optional # 467MB on disk
|
|
9
|
+
* NPM_CONFIG_OMIT=optional npm install -g mneme-ai # 467MB on disk
|
|
10
|
+
*
|
|
11
|
+
* This script gives the user a working alternative: set `MNEME_LITE=1` in
|
|
12
|
+
* the environment when installing. npm still downloads + extracts the
|
|
13
|
+
* optional `@huggingface/transformers` tree (we cannot influence that
|
|
14
|
+
* from a package.json), but THIS postinstall hook then removes it from
|
|
15
|
+
* disk before npm hands control back. Net result:
|
|
16
|
+
*
|
|
17
|
+
* MNEME_LITE=1 npm install -g mneme-ai
|
|
18
|
+
* -> 467MB downloaded + extracted
|
|
19
|
+
* -> ~462MB removed from disk by this hook (transformers + sharp + libvips)
|
|
20
|
+
* -> ~5MB final on-disk footprint
|
|
21
|
+
* -> 0 native DLL handles → EBUSY structurally extinct post-install
|
|
22
|
+
*
|
|
23
|
+
* The mneme runtime gracefully falls back through the embedder chain
|
|
24
|
+
* (OpenAI → Ollama → bundled WASM SKIPPED → hash) — every Mneme tool
|
|
25
|
+
* continues to work, just at deterministic hash quality (★★) until the
|
|
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.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
"use strict";
|
|
41
|
+
|
|
42
|
+
const fs = require("node:fs");
|
|
43
|
+
const path = require("node:path");
|
|
44
|
+
|
|
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
|
+
const here = __dirname;
|
|
56
|
+
const mnemeRoot = path.resolve(here, "..");
|
|
57
|
+
const sub = (p) => path.join(mnemeRoot, "node_modules", p);
|
|
58
|
+
|
|
59
|
+
// Targets — every directory that pulls in native binaries through the
|
|
60
|
+
// `@huggingface/transformers` optional dep tree. Kept conservative on
|
|
61
|
+
// purpose: anything outside this list might be a transitive dep some
|
|
62
|
+
// other Mneme module actually uses, so we only delete the known-safe set.
|
|
63
|
+
const PRUNE_TARGETS = [
|
|
64
|
+
"@huggingface",
|
|
65
|
+
"@img",
|
|
66
|
+
"sharp",
|
|
67
|
+
// onnxruntime-* drag a ~80MB native runtime even on Linux/macOS when
|
|
68
|
+
// transformers is present. Safe to remove in lite mode because
|
|
69
|
+
// transformers itself is gone — nothing else in Mneme uses ORT.
|
|
70
|
+
"onnxruntime-common",
|
|
71
|
+
"onnxruntime-node",
|
|
72
|
+
"onnxruntime-web",
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
function dirSizeBytes(dirPath) {
|
|
76
|
+
if (!fs.existsSync(dirPath)) return 0;
|
|
77
|
+
let total = 0;
|
|
78
|
+
try {
|
|
79
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
80
|
+
for (const ent of entries) {
|
|
81
|
+
const full = path.join(dirPath, ent.name);
|
|
82
|
+
try {
|
|
83
|
+
if (ent.isDirectory()) total += dirSizeBytes(full);
|
|
84
|
+
else if (ent.isFile()) total += fs.statSync(full).size;
|
|
85
|
+
} catch { /* skip permission errors */ }
|
|
86
|
+
}
|
|
87
|
+
} catch { /* skip unreadable dirs */ }
|
|
88
|
+
return total;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function fmtMB(bytes) {
|
|
92
|
+
return (bytes / (1024 * 1024)).toFixed(1) + "MB";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let prunedBytes = 0;
|
|
96
|
+
const prunedNames = [];
|
|
97
|
+
const skippedNames = [];
|
|
98
|
+
|
|
99
|
+
for (const name of PRUNE_TARGETS) {
|
|
100
|
+
const target = sub(name);
|
|
101
|
+
if (!fs.existsSync(target)) {
|
|
102
|
+
skippedNames.push(name);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const sizeBefore = dirSizeBytes(target);
|
|
106
|
+
try {
|
|
107
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
108
|
+
prunedBytes += sizeBefore;
|
|
109
|
+
prunedNames.push(`${name} (${fmtMB(sizeBefore)})`);
|
|
110
|
+
} catch (err) {
|
|
111
|
+
// Don't fail the install over a single stuck dir; just report.
|
|
112
|
+
process.stderr.write(
|
|
113
|
+
`[mneme-lite] could not prune ${name}: ${err && err.message ? err.message : err}\n`,
|
|
114
|
+
);
|
|
115
|
+
skippedNames.push(`${name} (locked)`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const summary =
|
|
120
|
+
`[mneme-lite] MNEME_LITE=${LITE} → pruned ${prunedNames.length}/${PRUNE_TARGETS.length} ` +
|
|
121
|
+
`optional-dep directories, ${fmtMB(prunedBytes)} freed.\n` +
|
|
122
|
+
(prunedNames.length > 0 ? `[mneme-lite] removed: ${prunedNames.join(", ")}\n` : "") +
|
|
123
|
+
(skippedNames.length > 0 ? `[mneme-lite] skipped: ${skippedNames.join(", ")}\n` : "") +
|
|
124
|
+
`[mneme-lite] mneme runtime will fall back through OpenAI → Ollama → hash embedder (bundled WASM skipped).\n`;
|
|
125
|
+
process.stdout.write(summary);
|
|
126
|
+
|
|
127
|
+
// Always exit 0 — we never want this script to abort the install.
|
|
128
|
+
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.69-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",
|
|
@@ -28,13 +28,14 @@
|
|
|
28
28
|
"scripts": {
|
|
29
29
|
"build": "tsc -b",
|
|
30
30
|
"clean": "tsc -b --clean",
|
|
31
|
+
"postinstall": "node bin/postinstall-mneme-lite.cjs",
|
|
31
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)\""
|
|
32
33
|
},
|
|
33
34
|
"dependencies": {
|
|
34
|
-
"@mneme-ai/core": "2.19.
|
|
35
|
-
"@mneme-ai/correlator": "2.19.
|
|
36
|
-
"@mneme-ai/embeddings": "2.19.
|
|
37
|
-
"@mneme-ai/mcp": "2.19.
|
|
35
|
+
"@mneme-ai/core": "2.19.69-lite",
|
|
36
|
+
"@mneme-ai/correlator": "2.19.69-lite",
|
|
37
|
+
"@mneme-ai/embeddings": "2.19.69-lite",
|
|
38
|
+
"@mneme-ai/mcp": "2.19.69-lite",
|
|
38
39
|
"commander": "^14.0.3",
|
|
39
40
|
"kleur": "^4.1.5"
|
|
40
41
|
},
|