verbative-memory
Advanced tools
+115
-13
@@ -11,3 +11,6 @@ #!/usr/bin/env node | ||
| * | ||
| * Everything runs on-device; the only network use is these one-time downloads. | ||
| * Engine updates ride npm versions: each download is stamped with the package | ||
| * version that fetched it, and a version bump re-downloads (an existence-only | ||
| * gate left early adopters on the first engine build forever). Everything runs | ||
| * on-device; the only network use is these per-version downloads. | ||
| */ | ||
@@ -50,3 +53,3 @@ "use strict"; | ||
| async function download(url, dest) { | ||
| process.stderr.write("verbative-memory: downloading " + path.basename(dest) + " (one-time)...\n"); | ||
| process.stderr.write("verbative-memory: downloading " + path.basename(dest) + "...\n"); | ||
| const res = await fetch(url, { redirect: "follow" }); | ||
@@ -67,5 +70,95 @@ if (!res.ok) throw new Error("download failed (" + res.status + "): " + url); | ||
| function readMarker(file) { | ||
| try { | ||
| return fs.readFileSync(file, "utf8").trim(); | ||
| } catch { | ||
| return ""; | ||
| } | ||
| } | ||
| // Clear a prior install (dir or single file) so the fresh extract lands clean. | ||
| // Windows forbids DELETING a running exe/loaded DLL but allows RENAMING it | ||
| // (same lesson as the extension's binary refresh): when the delete leaves | ||
| // locked survivors, move them into a .trash-* sibling — live processes keep | ||
| // running from the moved files, the extract path is free, and the trash is | ||
| // purged on a later run. | ||
| function clearForReplace(target, binDir) { | ||
| try { | ||
| fs.rmSync(target, { recursive: true, force: true }); | ||
| } catch { | ||
| /* locked leftovers handled below */ | ||
| } | ||
| if (!fs.existsSync(target)) return; | ||
| const trash = path.join(binDir, ".trash-" + Date.now()); | ||
| const sweep = (from, to) => { | ||
| fs.mkdirSync(to, { recursive: true }); | ||
| for (const e of fs.readdirSync(from, { withFileTypes: true })) { | ||
| try { | ||
| if (e.isDirectory()) { | ||
| sweep(path.join(from, e.name), path.join(to, e.name)); | ||
| fs.rmdirSync(path.join(from, e.name)); | ||
| } else { | ||
| fs.renameSync(path.join(from, e.name), path.join(to, e.name)); | ||
| } | ||
| } catch { | ||
| /* a straggler is fine — tar can overwrite what it CAN unlink */ | ||
| } | ||
| } | ||
| }; | ||
| try { | ||
| if (fs.statSync(target).isDirectory()) { | ||
| sweep(target, path.join(trash, path.basename(target))); | ||
| fs.rmdirSync(target); | ||
| } else { | ||
| fs.mkdirSync(trash, { recursive: true }); | ||
| fs.renameSync(target, path.join(trash, path.basename(target))); | ||
| } | ||
| } catch { | ||
| /* best effort */ | ||
| } | ||
| } | ||
| function purgeTrash(binDir) { | ||
| try { | ||
| for (const e of fs.readdirSync(binDir)) { | ||
| if (e.startsWith(".trash-")) { | ||
| try { | ||
| fs.rmSync(path.join(binDir, e), { recursive: true, force: true }); | ||
| } catch { | ||
| /* still pinned by a live process — next time */ | ||
| } | ||
| } | ||
| } | ||
| } catch { | ||
| /* bin dir may not exist yet */ | ||
| } | ||
| } | ||
| // After an engine UPDATE the resident memory server keeps serving from the OLD | ||
| // binary (it owns the shared socket and never exits on its own) — stop it once | ||
| // so the next call respawns from the fresh download. Best-effort and safe: the | ||
| // server restarts on demand, and hook/MCP calls no-op gracefully while it is | ||
| // down. | ||
| function bounceResidentServer() { | ||
| try { | ||
| if (process.platform === "win32") { | ||
| spawnSync( | ||
| path.join(process.env.SystemRoot || "C:\\Windows", "System32", "taskkill.exe"), | ||
| ["/F", "/T", "/IM", "verbative-hooks.exe"], | ||
| { stdio: "ignore" }, | ||
| ); | ||
| } else { | ||
| spawnSync("pkill", ["-f", "verbative-hooks.*__memory-server"], { stdio: "ignore" }); | ||
| } | ||
| } catch { | ||
| /* best effort */ | ||
| } | ||
| } | ||
| async function ensureCore(root) { | ||
| const tag = platformTag(); | ||
| const coreDir = path.join(root, "bin", "verbative-memory-core"); | ||
| const binDir = path.join(root, "bin"); | ||
| const pkgVersion = require("../package.json").version; | ||
| purgeTrash(binDir); | ||
| const coreDir = path.join(binDir, "verbative-memory-core"); | ||
| const core = path.join( | ||
@@ -76,18 +169,28 @@ coreDir, | ||
| ); | ||
| if (!fs.existsSync(core)) { | ||
| const archive = path.join(root, "bin", "core.tar.gz"); | ||
| // Version marker lives INSIDE the replaced dir so it can never go stale | ||
| // independently of the payload. Missing marker (pre-1.1.1 installs) reads as | ||
| // "" and triggers the one catch-up re-download. | ||
| const coreMarker = path.join(coreDir, ".verbative-memory-core.version"); | ||
| if (!fs.existsSync(core) || readMarker(coreMarker) !== pkgVersion) { | ||
| const hadPrior = fs.existsSync(core); | ||
| const archive = path.join(binDir, "core.tar.gz"); | ||
| await download(`${DIST}/verbative-memory-core-${tag}.tar.gz`, archive); | ||
| clearForReplace(coreDir, binDir); | ||
| extractTarGz(archive, coreDir); | ||
| fs.chmodSync(core, 0o755); | ||
| fs.writeFileSync(coreMarker, pkgVersion); | ||
| if (hadPrior) bounceResidentServer(); | ||
| } | ||
| const llm = path.join( | ||
| root, | ||
| "bin", | ||
| binDir, | ||
| process.platform === "win32" ? "verbative-llm.exe" : "verbative-llm", | ||
| ); | ||
| if (!fs.existsSync(llm)) { | ||
| const archive = path.join(root, "bin", "llm.tar.gz"); | ||
| const llmMarker = path.join(binDir, ".verbative-llm.version"); | ||
| if (!fs.existsSync(llm) || readMarker(llmMarker) !== pkgVersion) { | ||
| const archive = path.join(binDir, "llm.tar.gz"); | ||
| await download(`${DIST}/verbative-llm-${tag}.tar.gz`, archive); | ||
| extractTarGz(archive, path.join(root, "bin")); | ||
| clearForReplace(llm, binDir); | ||
| extractTarGz(archive, binDir); | ||
| fs.chmodSync(llm, 0o755); | ||
| fs.writeFileSync(llmMarker, pkgVersion); | ||
| } | ||
@@ -150,4 +253,3 @@ return { core, llm }; | ||
| // session-start warning (same stale rule the extension already migrated away). | ||
| for (const rule of ["Edit(.verbative/**)"]) | ||
| if (!allow.includes(rule)) allow.push(rule); | ||
| for (const rule of ["Edit(.verbative/**)"]) if (!allow.includes(rule)) allow.push(rule); | ||
| fs.mkdirSync(path.dirname(settingsPath), { recursive: true }); | ||
@@ -278,3 +380,3 @@ fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2) + "\n"); | ||
| module.exports = { autoRegisterClaudeHooks, unregisterClaudeHooks, handleCliCommand }; | ||
| module.exports = { autoRegisterClaudeHooks, unregisterClaudeHooks, handleCliCommand, ensureCore }; | ||
| if (require.main !== module) return; | ||
@@ -281,0 +383,0 @@ |
+1
-1
| { | ||
| "name": "verbative-memory", | ||
| "version": "1.1.0", | ||
| "version": "1.1.1", | ||
| "mcpName": "io.github.verbative/verbative-memory", | ||
@@ -5,0 +5,0 @@ "description": "On-device memory system for AI coding agents — dual-layer engine (distilled facts + lossless event ledger), automatically captured and injected, exposed over MCP. Shared across agents, persistent across sessions; files in your repo.", |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Unidentified License
LicenseSomething that seems like a license was found, but its contents could not be matched with a known license.
Unidentified License
LicenseSomething that seems like a license was found, but its contents could not be matched with a known license.
20963
21.62%397
32.78%15
15.38%