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.
Files changed (3) hide show
  1. package/README.md +31 -17
  2. package/package.json +1 -1
  3. 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.** Recall is gated by a measured noise floor.
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
- **Claude Code** (plugin — no npm needed, installs straight from this repo):
40
+ **Any MCP client, one line** (Claude Code shown):
41
41
 
42
42
  ```
43
- /plugin marketplace add t-crew/memory-pulse
44
- /plugin install memory-pulse@memory-pulse
43
+ claude mcp add memory-pulse -- npx -y memory-pulse
45
44
  ```
46
45
 
47
- **Codex CLI / Cursor / any MCP client** — clone this repo and point at it
48
- (zero dependencies, nothing to build):
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
- git clone https://github.com/t-crew/memory-pulse
51
+ npx memory-pulse install-hook
52
52
  ```
53
53
 
54
- ```toml
55
- # Codex: ~/.codex/config.toml
56
- [mcp_servers.memory-pulse]
57
- command = "node"
58
- args = ["/path/to/memory-pulse/server.mjs"]
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
- For other clients: stdio server, command `node /path/to/memory-pulse/server.mjs`.
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
- Then just tell your agent to remember things, and start sessions with "pulse
64
- the memory". It figures the rest out from the tool descriptions.
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
- - This client is the entire client: ~300 lines, zero dependencies, read it in
76
- one sitting.
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",
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
- "wavefront (pulse), or when an edge was strongest (when). Returns nothing rather than guessing " +
118
- "when the answer is below the noise floor.",
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.3" },
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(`[![memory-pulse](https://img.shields.io/badge/memory--pulse-${label}-6366f1)](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 && sub !== "serve") { console.error(`unknown command: ${sub} (try: brief, install-hook)`); process.exit(1); }
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