memory-pulse 0.1.3 → 0.1.4
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/package.json +1 -1
- package/server.mjs +61 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memory-pulse",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Causal memory for coding agents that costs ~670 tokens, not your context window. Four MCP tools: re-enter a project, recall what caused what, record findings, run code against memory.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/server.mjs
CHANGED
|
@@ -69,8 +69,33 @@ function appendEvent({ cause, effect, note, kind, tags, pinned }) {
|
|
|
69
69
|
return { written: true, t, ledger: path, stored: event };
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
// ------------------------------------------------------------- telemetry ----
|
|
73
|
+
// The engine keeps no database, so telemetry lives HERE, beside your ledger,
|
|
74
|
+
// as a signed capsule the engine advances on every read call and hands back.
|
|
75
|
+
// You own it; delete the file and it restarts from zero.
|
|
76
|
+
const telemetryPath = () => join(dirname(ledgerPath()), "telemetry.rain");
|
|
77
|
+
function readTelemetry() {
|
|
78
|
+
try { return JSON.parse(readFileSync(telemetryPath(), "utf8")); } catch { return null; }
|
|
79
|
+
}
|
|
80
|
+
function writeTelemetry(capsule) {
|
|
81
|
+
if (!capsule || typeof capsule !== "object") return;
|
|
82
|
+
try {
|
|
83
|
+
mkdirSync(dirname(telemetryPath()), { recursive: true });
|
|
84
|
+
writeFileSync(telemetryPath(), JSON.stringify(capsule, null, 2) + "\n");
|
|
85
|
+
} catch { /* telemetry is a convenience; a read-only checkout must not break a read call */ }
|
|
86
|
+
}
|
|
87
|
+
const projectName = () => process.env.MEMORY_PULSE_PROJECT || process.cwd().split(/[\\/]/).filter(Boolean).pop() || "project";
|
|
88
|
+
const fmtK = (n) => (n >= 1_000_000 ? `${(n / 1_000_000).toFixed(1)}M` : n >= 1000 ? `${Math.round(n / 1000)}k` : String(n));
|
|
89
|
+
function telemetryFooter(c) {
|
|
90
|
+
const k = c?.counters;
|
|
91
|
+
if (!k || !k.calls) return "";
|
|
92
|
+
return `— memory-pulse · ${k.pulse} re-entries · ${k.correctionsSurfaced} corrections surfaced · ~${fmtK(k.tokensSavedEst)} tokens saved (est., signed)`;
|
|
93
|
+
}
|
|
94
|
+
|
|
72
95
|
// ------------------------------------------------------------------- api ----
|
|
73
96
|
async function callApi(route, body) {
|
|
97
|
+
const prior = readTelemetry();
|
|
98
|
+
body = { ...body, project: projectName(), ...(prior ? { telemetry: prior } : {}) };
|
|
74
99
|
let res;
|
|
75
100
|
try {
|
|
76
101
|
res = await fetch(`${API}${route}`, {
|
|
@@ -85,6 +110,7 @@ async function callApi(route, body) {
|
|
|
85
110
|
);
|
|
86
111
|
}
|
|
87
112
|
const out = await res.json().catch(() => ({}));
|
|
113
|
+
if (res.ok && out.telemetry) { writeTelemetry(out.telemetry); }
|
|
88
114
|
if (!res.ok) {
|
|
89
115
|
let msg = out.error ?? `API error ${res.status}`;
|
|
90
116
|
if (out.upgrade) msg += ` — upgrade: ${out.upgrade}`;
|
|
@@ -190,7 +216,7 @@ async function dispatch(msg) {
|
|
|
190
216
|
return ok(id, {
|
|
191
217
|
protocolVersion: SUPPORTED.includes(wanted) ? wanted : SUPPORTED[0],
|
|
192
218
|
capabilities: { tools: {} },
|
|
193
|
-
serverInfo: { name: "memory-pulse", version: "0.1.
|
|
219
|
+
serverInfo: { name: "memory-pulse", version: "0.1.4" },
|
|
194
220
|
});
|
|
195
221
|
}
|
|
196
222
|
if (method === "notifications/initialized" || method === "initialized") return;
|
|
@@ -218,12 +244,43 @@ async function cliBrief() {
|
|
|
218
244
|
try {
|
|
219
245
|
const out = await callApi("/v1/pulse", { events, tier: process.env.MEMORY_PULSE_BRIEF_TIER || "brief" });
|
|
220
246
|
if (out.text) process.stdout.write(out.text + "\n");
|
|
247
|
+
const foot = telemetryFooter(out.telemetry);
|
|
248
|
+
if (foot) process.stdout.write(foot + "\n");
|
|
221
249
|
} catch (e) {
|
|
222
250
|
// A dead network must not break session start — say so in one line.
|
|
223
251
|
process.stdout.write(`memory-pulse: brief unavailable (${String(e?.message ?? e).split(".")[0]})\n`);
|
|
224
252
|
}
|
|
225
253
|
}
|
|
226
254
|
|
|
255
|
+
// `npx memory-pulse stats` — the signed telemetry capsule, verified keylessly
|
|
256
|
+
// against the engine so the numbers you share are numbers we signed.
|
|
257
|
+
async function cliStats() {
|
|
258
|
+
const c = readTelemetry();
|
|
259
|
+
if (!c) { console.log("no telemetry yet — run a pulse first"); return; }
|
|
260
|
+
const k = c.counters;
|
|
261
|
+
console.log(`memory-pulse telemetry for "${c.project}" (${c.since.slice(0, 10)} → ${c.updated.slice(0, 10)})`);
|
|
262
|
+
console.log(` re-entries (pulse) ${k.pulse}`);
|
|
263
|
+
console.log(` recall / execute ${k.recall} / ${k.execute}`);
|
|
264
|
+
console.log(` corrections recorded ${k.correctionsRecorded}`);
|
|
265
|
+
console.log(` corrections surfaced ${k.correctionsSurfaced}`);
|
|
266
|
+
console.log(` largest ledger seen ${k.maxEvents} events`);
|
|
267
|
+
console.log(` tokens saved (est.) ~${k.tokensSavedEst.toLocaleString()}`);
|
|
268
|
+
if (c.reset) console.log(` note: ${c.reset}`);
|
|
269
|
+
try {
|
|
270
|
+
const res = await fetch(`${API}/v1/verify-telemetry`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ telemetry: c }) });
|
|
271
|
+
const v = await res.json();
|
|
272
|
+
console.log(v.valid ? " signature ✓ verified by the engine (keyless check anyone can repeat)" : ` signature ✗ ${v.reason ?? "invalid"}`);
|
|
273
|
+
} catch { console.log(" signature ? engine unreachable"); }
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// `npx memory-pulse badge` — a README badge from your own signed numbers.
|
|
277
|
+
function cliBadge() {
|
|
278
|
+
const c = readTelemetry();
|
|
279
|
+
const n = c?.counters?.tokensSavedEst ?? 0;
|
|
280
|
+
const label = `${fmtK(n)}_tokens_saved`.replace(/-/g, "--");
|
|
281
|
+
console.log(`[](https://pulse.strategic-innovations.ai)`);
|
|
282
|
+
}
|
|
283
|
+
|
|
227
284
|
// `npx memory-pulse install-hook` — make re-entry automatic: a Claude Code
|
|
228
285
|
// SessionStart hook that runs the brief. Idempotent; merges, never clobbers.
|
|
229
286
|
function cliInstallHook() {
|
|
@@ -260,7 +317,9 @@ if (isMain) {
|
|
|
260
317
|
const sub = process.argv[2];
|
|
261
318
|
if (sub === "brief") { await cliBrief(); process.exit(0); }
|
|
262
319
|
if (sub === "install-hook") { cliInstallHook(); process.exit(0); }
|
|
263
|
-
if (sub
|
|
320
|
+
if (sub === "stats") { await cliStats(); process.exit(0); }
|
|
321
|
+
if (sub === "badge") { cliBadge(); process.exit(0); }
|
|
322
|
+
if (sub && sub !== "serve") { console.error(`unknown command: ${sub} (try: brief, stats, badge, install-hook)`); process.exit(1); }
|
|
264
323
|
process.stderr.write(`memory-pulse: ledger ${ledgerPath()} — api ${API}\n`);
|
|
265
324
|
const rl = readline.createInterface({ input: process.stdin, terminal: false });
|
|
266
325
|
// In-flight calls are drained before exit. Exiting the moment stdin closes
|