memory-pulse 0.1.3 → 0.1.5
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/README.md +31 -17
- package/package.json +1 -1
- package/server.mjs +63 -4
package/README.md
CHANGED
|
@@ -32,36 +32,47 @@ Two design decisions do the heavy lifting:
|
|
|
32
32
|
The failure this prevents: your agent confidently quotes the benchmark number
|
|
33
33
|
you withdrew three sessions ago.
|
|
34
34
|
|
|
35
|
-
**Silence beats a wrong answer.**
|
|
35
|
+
**Silence beats a wrong answer.** When recall isn't confident enough, it returns nothing rather than guessing.
|
|
36
36
|
When the answer isn't there, you get nothing — not a plausible guess.
|
|
37
37
|
|
|
38
38
|
## Install
|
|
39
39
|
|
|
40
|
-
**
|
|
40
|
+
**Any MCP client, one line** (Claude Code shown):
|
|
41
41
|
|
|
42
42
|
```
|
|
43
|
-
|
|
44
|
-
/plugin install memory-pulse@memory-pulse
|
|
43
|
+
claude mcp add memory-pulse -- npx -y memory-pulse
|
|
45
44
|
```
|
|
46
45
|
|
|
47
|
-
**
|
|
48
|
-
(
|
|
46
|
+
**Make re-entry automatic** — a Claude Code SessionStart hook that runs the
|
|
47
|
+
brief before your first prompt (idempotent; merges into your settings, never
|
|
48
|
+
clobbers them; silent in projects that have no ledger):
|
|
49
49
|
|
|
50
50
|
```
|
|
51
|
-
|
|
51
|
+
npx memory-pulse install-hook
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
**Claude Code plugin** (installs straight from this repo, hook included):
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
/plugin marketplace add t-crew/memory-pulse
|
|
58
|
+
/plugin install memory-pulse@memory-pulse
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
**Codex CLI / Cursor / other clients** — stdio server, command
|
|
62
|
+
`npx -y memory-pulse` (or clone this repo and run `node /path/to/server.mjs`).
|
|
63
|
+
|
|
64
|
+
Then tell your agent to remember things. Record a withdrawn number with
|
|
65
|
+
`kind: "correction"` and it will outrank the history that contained it — at
|
|
66
|
+
every brief size, in every session.
|
|
67
|
+
|
|
68
|
+
### Commands
|
|
62
69
|
|
|
63
|
-
|
|
64
|
-
|
|
70
|
+
```
|
|
71
|
+
npx memory-pulse brief # the re-entry brief (what the hook prints)
|
|
72
|
+
npx memory-pulse stats # your telemetry capsule, signature verified by the engine
|
|
73
|
+
npx memory-pulse badge # README badge markdown from your own signed numbers
|
|
74
|
+
npx memory-pulse install-hook # Claude Code SessionStart hook
|
|
75
|
+
```
|
|
65
76
|
|
|
66
77
|
## What runs where (the privacy contract)
|
|
67
78
|
|
|
@@ -72,8 +83,11 @@ the memory". It figures the rest out from the tool descriptions.
|
|
|
72
83
|
which computes the answer and forgets the request. **The service keeps no
|
|
73
84
|
database of your memory** — state arrives in the request and leaves in the
|
|
74
85
|
response.
|
|
75
|
-
-
|
|
76
|
-
|
|
86
|
+
- Telemetry is a **signed capsule beside your ledger**
|
|
87
|
+
(`.memory-pulse/telemetry.rain`): the engine advances it on each read call
|
|
88
|
+
and hands it back — it never stores it. `stats` verifies the signature;
|
|
89
|
+
`badge` turns it into a README badge. Delete the file and it restarts.
|
|
90
|
+
- This client is the entire client, zero dependencies, read it in one sitting.
|
|
77
91
|
|
|
78
92
|
## Pricing
|
|
79
93
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memory-pulse",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
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}`;
|
|
@@ -114,8 +140,8 @@ export const TOOLS = [
|
|
|
114
140
|
name: "recall",
|
|
115
141
|
description:
|
|
116
142
|
"Query the causal graph: what an event caused (effects), what caused it (causes), a multi-hop " +
|
|
117
|
-
"
|
|
118
|
-
"when the answer is
|
|
143
|
+
"chain (pulse), or when a link was strongest (when). Returns nothing rather than guessing " +
|
|
144
|
+
"when the answer is not confident enough.",
|
|
119
145
|
inputSchema: {
|
|
120
146
|
type: "object",
|
|
121
147
|
properties: {
|
|
@@ -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.5" },
|
|
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
|