@eleboucher/pi-memini 0.6.8 → 0.6.9

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 +57 -22
  2. package/dist/index.js +568 -29
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -17,8 +17,8 @@ What it wires:
17
17
  - **`agent_end`** — once the agent finishes a prompt, stores the completed
18
18
  user/assistant turn back into memini (episodic, tagged `pi`, with the session
19
19
  id) so it can be recalled later.
20
- - **Explicit tools** — the same set Claude Code gets from memini's MCP server,
21
- registered natively via `pi.registerTool`: `memory_recall`, `memory_list`,
20
+ - **Explicit tools** — modeled on the tool set Claude Code gets from memini's
21
+ MCP server, registered natively via `pi.registerTool`: `memory_recall`, `memory_list`,
22
22
  `memory_remember`, `memory_forget`. The model can call them on demand even
23
23
  though the automatic loop already runs.
24
24
 
@@ -53,33 +53,68 @@ pi -e ./integrations/pi/plugin/dist/index.js
53
53
  All config is via environment variables in the shell that launches Pi (secrets
54
54
  stay out of any file):
55
55
 
56
- | Env var | Default | Purpose |
57
- | -------------------------------- | ----------------------- | --------------------------------------------------------------------------------------------- |
58
- | `MEMINI_BASE_URL` | `http://localhost:8080` | memini REST base URL (alias: `MEMINI_URL`) |
59
- | `MEMINI_NAMESPACE` | cwd basename | project the memory is scoped to (`X-Memini-Namespace`) |
60
- | `MEMINI_HOME` | unset | caller's personal namespace, sent as `X-Memini-Home`; unset = no home leg |
61
- | `MEMINI_RECALL` | on | `0`/`false` disables recall-before-turn |
62
- | `MEMINI_CAPTURE` | on | `0`/`false` disables capture-after-turn |
63
- | `MEMINI_RECALL_LIMIT` | `3` | max memories injected per turn |
64
- | `MEMINI_INJECT_RECALL_MAX_TOK` | `0` | hard ceiling on recall-block tokens (`0` = unbounded); the tail is dropped with a footer |
65
- | `MEMINI_INJECT_RECALL_MIN_SCORE` | `0` | fused-score floor (>=) sent as `min_score` to `/v1/search` |
66
- | `MEMINI_INJECT_LABELS` | — | comma-separated bullet labels: `tier`, `confidence`, `age` |
67
- | `MEMINI_TIMEOUT_MS` | `30000` | per-request timeout |
68
- | `MEMINI_FALLBACK` | on | `0`/`false` surfaces errors instead of degrading silently |
69
- | `MEMINI_API_KEY` | — | bearer token, if memini needs auth (sent as `Authorization: Bearer …`; alias: `MEMINI_TOKEN`) |
70
- | `MEMINI_REQUIRE_HTTPS` | — | `1` refuses to send the token over plaintext HTTP |
71
-
72
- Unset, the namespace is derived from the working-directory basename and sent as
73
- the `X-Memini-Namespace` header set it to share one memory pool with your
56
+ | Env var | Default | Purpose |
57
+ | -------------------------------- | -------------------------------- | --------------------------------------------------------------------------------------------- |
58
+ | `MEMINI_BASE_URL` | `http://localhost:8080` | memini REST base URL (alias: `MEMINI_URL`) |
59
+ | `MEMINI_NAMESPACE` | git repo name, else cwd basename | project the memory is scoped to (`X-Memini-Namespace`) |
60
+ | `MEMINI_HOME` | unset | caller's personal namespace, sent as `X-Memini-Home`; unset = no home leg |
61
+ | `MEMINI_RECALL` | on | `0`/`false` disables recall-before-turn |
62
+ | `MEMINI_CAPTURE` | on | `0`/`false` disables capture-after-turn |
63
+ | `MEMINI_RECALL_LIMIT` | `3` | max memories injected per turn |
64
+ | `MEMINI_INJECT_RECALL_MAX_TOK` | `0` | hard ceiling on recall-block tokens (`0` = unbounded); the tail is dropped with a footer |
65
+ | `MEMINI_INJECT_RECALL_MIN_SCORE` | `0` | fused-score floor (>=) sent as `min_score` to `/v1/search` |
66
+ | `MEMINI_INJECT_LABELS` | — | comma-separated bullet labels: `tier`, `confidence`, `age` |
67
+ | `MEMINI_TIMEOUT_MS` | `30000` | per-request timeout |
68
+ | `MEMINI_FALLBACK` | on | `0`/`false` surfaces errors instead of degrading silently |
69
+ | `MEMINI_API_KEY` | — | bearer token, if memini needs auth (sent as `Authorization: Bearer …`; alias: `MEMINI_TOKEN`) |
70
+ | `MEMINI_REQUIRE_HTTPS` | — | `1` refuses to send the token over plaintext HTTP |
71
+
72
+ Unset, the namespace is derived from the git repo (remote name, then toplevel
73
+ basename), falling back to the working-directory basename, and sent as the
74
+ `X-Memini-Namespace` header — set it to share one memory pool with your
74
75
  other agents (Claude Code, opencode, …).
75
76
 
77
+ ### Commands
78
+
79
+ | Command | What it does |
80
+ | ------------------ | ------------------------------------------------------------------------------- |
81
+ | `memini:status` | Effective settings, the resolved namespace **and where it came from**, warnings |
82
+ | `memini:namespace` | Show, set, or clear the namespace override for this project |
83
+
84
+ `memini:status` exists because a list of values is not enough to debug a namespace
85
+ problem. It shows provenance (`<- env` vs `(default)`), so a `MEMINI_NAMESPACE`
86
+ exported once from a shell profile — which pins _every_ repo on the machine to one
87
+ namespace — shows up as a warning rather than as a mystery. Secrets are redacted.
88
+
89
+ ### The namespace override
90
+
91
+ ```
92
+ memini:namespace # show the namespace and where it came from
93
+ memini:namespace acme/api # override it for this project
94
+ memini:namespace --clear # back to automatic resolution
95
+ ```
96
+
97
+ Precedence is **override > `MEMINI_NAMESPACE` > config file > git > cwd**. The
98
+ override deliberately beats the environment: a globally exported
99
+ `MEMINI_NAMESPACE` is exactly the problem an override exists to solve, so if the
100
+ environment won, the command would silently do nothing on the machines that need
101
+ it.
102
+
103
+ The override is stored in `~/.config/memini/overrides.json` and shared with every
104
+ other memini client, so one set in Claude Code applies here too, and `memini
105
+ doctor` reports the same value. See
106
+ [env-vars](../../docs/reference/env-vars.md#the-overrides-file) for the format.
107
+
108
+ Unlike the Claude Code plugin, setting an override here takes effect immediately —
109
+ the namespace is re-read per request, so there is no reconnect to wait for.
110
+
76
111
  ### Build & test
77
112
 
78
113
  ```sh
79
114
  cd integrations/pi/plugin
80
115
  npm install
81
- npm run build # tsc -> dist/index.js
82
- npm test # pure-helper unit tests (tsx --test)
116
+ npm run build # esbuild bundle -> dist/index.js
117
+ npm test # bundle test (node --test) + pure-helper unit tests (tsx --test)
83
118
  ```
84
119
 
85
120
  ## Alternative: MCP wire
package/dist/index.js CHANGED
@@ -158,11 +158,245 @@ function resolveNamespace(opts) {
158
158
  return { namespace, segments, source, home, homeSource };
159
159
  }
160
160
 
161
+ // ../../../packages/memini-client/src/redact.ts
162
+ var SENSITIVE = /(^|_)(KEY|TOKEN|SECRET|PASSWORD|PASS|BEARER|DSN|CREDENTIALS?)$/i;
163
+ function isSensitive(name) {
164
+ return SENSITIVE.test(name);
165
+ }
166
+ function redactValue(value) {
167
+ if (!value) return "";
168
+ if (value.length <= 12) return "***";
169
+ return `${value.slice(0, 3)}\u2026${value.slice(-4)}`;
170
+ }
171
+
172
+ // ../../../packages/memini-client/src/namespace-validate.ts
173
+ var MAX_NAMESPACE_BYTES = 256;
174
+ function normalizeNamespace(ns) {
175
+ let out = String(ns ?? "").trim();
176
+ out = out.replace(/^\/+|\/+$/g, "");
177
+ while (out.includes("//")) out = out.replace(/\/\/+/g, "/");
178
+ return out;
179
+ }
180
+ function validateNamespace(ns) {
181
+ if (!ns) return "namespace is empty";
182
+ if (Buffer.byteLength(ns, "utf8") > MAX_NAMESPACE_BYTES) {
183
+ return `namespace exceeds ${MAX_NAMESPACE_BYTES} bytes`;
184
+ }
185
+ if (/[\r\n]/.test(ns)) return "namespace contains a newline";
186
+ if (/[\x00-\x1F\x7F]/.test(ns)) return "namespace contains a control character";
187
+ if (/[^\x20-\x7E]/.test(ns)) return "namespace contains a non-ASCII character";
188
+ return null;
189
+ }
190
+
191
+ // ../../../packages/memini-client/src/override.ts
192
+ import { execSync as execSync2 } from "node:child_process";
193
+ import { homedir as homedir2 } from "node:os";
194
+ import fs2 from "node:fs";
195
+ import path2 from "node:path";
196
+ var OVERRIDES_VERSION = 1;
197
+ var EMPTY = { version: OVERRIDES_VERSION, overrides: {} };
198
+ function defaultOverridesPath(env = process.env) {
199
+ const xdg = env["XDG_CONFIG_HOME"];
200
+ const base = xdg && xdg.trim() ? xdg : path2.join(homedir2(), ".config");
201
+ return path2.join(base, "memini", "overrides.json");
202
+ }
203
+ function overrideKey(cwd) {
204
+ const dir = cwd && cwd.trim() ? cwd : process.cwd();
205
+ try {
206
+ const top = execSync2("git rev-parse --show-toplevel", {
207
+ cwd: dir,
208
+ stdio: ["ignore", "pipe", "ignore"],
209
+ timeout: 500
210
+ }).toString().trim();
211
+ if (top) return path2.resolve(top);
212
+ } catch {
213
+ }
214
+ return path2.resolve(dir);
215
+ }
216
+ function readOverrides(opts = {}) {
217
+ const p = opts.overridesPath || defaultOverridesPath(opts.env);
218
+ try {
219
+ const parsed = JSON.parse(fs2.readFileSync(p, "utf8"));
220
+ if (!parsed || typeof parsed !== "object" || typeof parsed.overrides !== "object") {
221
+ return { ...EMPTY, overrides: {} };
222
+ }
223
+ return {
224
+ version: typeof parsed.version === "number" ? parsed.version : OVERRIDES_VERSION,
225
+ overrides: parsed.overrides || {}
226
+ };
227
+ } catch {
228
+ return { ...EMPTY, overrides: {} };
229
+ }
230
+ }
231
+ function readOverride(cwd, opts = {}) {
232
+ const file = readOverrides(opts);
233
+ const keys = Object.keys(file.overrides);
234
+ if (keys.length === 0) return void 0;
235
+ const entry = file.overrides[overrideKey(cwd)];
236
+ if (!entry || typeof entry.namespace !== "string" || !entry.namespace.trim()) return void 0;
237
+ return entry;
238
+ }
239
+ function writeOverride(cwd, namespace, opts = {}) {
240
+ const ns = normalizeNamespace(namespace);
241
+ const bad = validateNamespace(ns);
242
+ if (bad) throw new Error(`invalid namespace ${JSON.stringify(namespace)}: ${bad}`);
243
+ const p = opts.overridesPath || defaultOverridesPath(opts.env);
244
+ const file = readOverrides(opts);
245
+ const entry = {
246
+ namespace: ns,
247
+ setAt: (opts.now ? opts.now() : /* @__PURE__ */ new Date()).toISOString()
248
+ };
249
+ file.version = OVERRIDES_VERSION;
250
+ file.overrides[overrideKey(cwd)] = entry;
251
+ fs2.mkdirSync(path2.dirname(p), { recursive: true });
252
+ fs2.writeFileSync(p, JSON.stringify(file, null, 2) + "\n");
253
+ return entry;
254
+ }
255
+ function clearOverride(cwd, opts = {}) {
256
+ const p = opts.overridesPath || defaultOverridesPath(opts.env);
257
+ const file = readOverrides(opts);
258
+ const key = overrideKey(cwd);
259
+ if (!(key in file.overrides)) return false;
260
+ delete file.overrides[key];
261
+ try {
262
+ fs2.mkdirSync(path2.dirname(p), { recursive: true });
263
+ fs2.writeFileSync(p, JSON.stringify(file, null, 2) + "\n");
264
+ } catch {
265
+ return false;
266
+ }
267
+ return true;
268
+ }
269
+
270
+ // ../../../packages/memini-client/src/session.ts
271
+ var SESSION_CWD_TTL_MS = 6 * 60 * 60 * 1e3;
272
+
273
+ // ../../../packages/memini-client/src/settings.ts
274
+ var CLIENT_KNOBS = [
275
+ // Connection
276
+ { name: "MEMINI_BASE_URL", kind: "string", default: "http://localhost:8080", usedBy: "hooks + MCP", description: "memini base URL (alias: MEMINI_URL)" },
277
+ { name: "MEMINI_MCP_URL", kind: "string", default: "${MEMINI_BASE_URL}/mcp", usedBy: "MCP", description: "MCP endpoint; derived from the base URL unless set" },
278
+ { name: "MEMINI_API_KEY", kind: "string", default: "", usedBy: "hooks + MCP", description: "bearer token sent to the server (alias: MEMINI_TOKEN)" },
279
+ { name: "MEMINI_REQUIRE_HTTPS", kind: "bool", default: "0", usedBy: "hooks + MCP", description: "refuse to send a bearer token over plaintext HTTP" },
280
+ // Namespace
281
+ { name: "MEMINI_NAMESPACE", kind: "string", default: "(auto: git/cwd)", usedBy: "hooks + MCP", description: "pin the namespace; overrides git and directory detection" },
282
+ { name: "MEMINI_NAMESPACE_SCOPE", kind: "string", default: "repo", usedBy: "hooks", description: "owner-repo derives owner-repo slugs from the git remote" },
283
+ { name: "MEMINI_AGENT", kind: "string", default: "", usedBy: "hooks + MCP", description: "nest the namespace under a per-agent segment" },
284
+ { name: "MEMINI_HOME", kind: "string", default: "", usedBy: "hooks + MCP", description: 'personal namespace; required for visibility:"personal" writes' },
285
+ // Capture
286
+ { name: "MEMINI_CAPTURE_TURNS", kind: "bool", default: "on", usedBy: "hooks", description: "capture each user\u2192assistant turn as episodic memory" },
287
+ { name: "MEMINI_SESSION_DIGEST", kind: "bool", default: "on", usedBy: "hooks", description: "record session digests (files edited, commands run); 0 to keep memory to durable facts only" },
288
+ { name: "MEMINI_INLINE_EXTRACT", kind: "bool", default: "on", usedBy: "hooks", description: "inject the memory-save directive at SessionStart" },
289
+ { name: "MEMINI_AUTO_SAVE", kind: "bool", default: "on", usedBy: "hooks", description: "periodic auto-save nudge on Stop" },
290
+ { name: "MEMINI_AUTO_SAVE_INTERVAL", kind: "int", default: "10", usedBy: "hooks", description: "user messages between auto-save nudges" },
291
+ // Injection budgets
292
+ { name: "MEMINI_INJECT_BRIEFING_PINNED", kind: "int", default: "5", usedBy: "hooks", description: "max pinned memories at SessionStart (0 disables)" },
293
+ { name: "MEMINI_INJECT_BRIEFING_FACTS", kind: "int", default: "5", usedBy: "hooks", description: "max durable facts at SessionStart (0 disables)" },
294
+ { name: "MEMINI_INJECT_BRIEFING_PROCEDURES", kind: "int", default: "5", usedBy: "hooks", description: "max procedural how-tos at SessionStart (0 disables)" },
295
+ { name: "MEMINI_INJECT_BRIEFING_RECENT", kind: "int", default: "3", usedBy: "hooks", description: "max recent episodic entries at SessionStart (0 disables)" },
296
+ { name: "MEMINI_INJECT_BRIEFING_MAX_TOK", kind: "int", default: "uncapped", usedBy: "hooks", description: "token ceiling on the SessionStart briefing" },
297
+ { name: "MEMINI_INJECT_PRETOOL_ITEMS", kind: "int", default: "3", usedBy: "hooks", description: "max hits surfaced per file on PreToolUse" },
298
+ { name: "MEMINI_INJECT_PRETOOL_MAX_TOK", kind: "int", default: "uncapped", usedBy: "hooks", description: "token ceiling per file on PreToolUse" },
299
+ { name: "MEMINI_INJECT_PRETOOL_MIN_SCORE", kind: "float", default: "0", usedBy: "hooks", description: "relevance floor for PreToolUse hits" },
300
+ { name: "MEMINI_INJECT_PRETOOL_TOOLS", kind: "list", default: "Read|Write|Edit|Glob|Grep", usedBy: "hooks", description: "tool allowlist for PreToolUse recall" },
301
+ { name: "MEMINI_INJECT_LABELS", kind: "list", default: "", usedBy: "hooks", description: "annotate injected bullets: tier, confidence, age, reason" },
302
+ // Diagnostics
303
+ { name: "MEMINI_DEBUG", kind: "bool", default: "0", usedBy: "hooks + MCP", description: "verbose hook logging to stderr" }
304
+ ];
305
+ function describeKnob(spec, env) {
306
+ const raw = env[spec.name];
307
+ const set = raw != null && raw !== "";
308
+ const sensitive = isSensitive(spec.name);
309
+ let value;
310
+ if (set) {
311
+ value = sensitive ? redactValue(raw) : raw;
312
+ } else {
313
+ value = spec.default === "" ? "(unset)" : spec.default;
314
+ }
315
+ return {
316
+ name: spec.name,
317
+ value,
318
+ source: set ? "env" : "default",
319
+ isDefault: !set,
320
+ sensitive,
321
+ usedBy: spec.usedBy,
322
+ description: spec.description
323
+ };
324
+ }
325
+ var LOOPBACK = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
326
+ function isPlaintextToNonLoopback(baseUrl) {
327
+ try {
328
+ const u = new URL(baseUrl);
329
+ return u.protocol === "http:" && !LOOPBACK.has(u.hostname.replace(/^\[|\]$/g, "").toLowerCase());
330
+ } catch {
331
+ return false;
332
+ }
333
+ }
334
+ function describeSettings(opts) {
335
+ const env = opts.env || process.env;
336
+ const cwd = opts.cwd;
337
+ const override = readOverride(cwd, { env, overridesPath: opts.overridesPath });
338
+ const withoutOverride = opts.resolve(env, { ignoreOverride: true });
339
+ const envSansPin = { ...env };
340
+ delete envSansPin["MEMINI_NAMESPACE"];
341
+ const derived = opts.resolve(envSansPin, { ignoreOverride: true });
342
+ const effective = override ? override.namespace : withoutOverride.namespace;
343
+ const source = override ? "override" : withoutOverride.source;
344
+ const home = (env["MEMINI_HOME"] || "").trim() || void 0;
345
+ const settings = CLIENT_KNOBS.map((k) => describeKnob(k, env));
346
+ const warnings = [];
347
+ if (override) {
348
+ warnings.push({
349
+ level: "note",
350
+ code: "override-active",
351
+ message: `namespace is overridden to "${override.namespace}" for this project (set ${override.setAt}); without it this project would use "${withoutOverride.namespace}".`,
352
+ fix: "Run the namespace command with --clear to return to automatic resolution."
353
+ });
354
+ }
355
+ const pin = (env["MEMINI_NAMESPACE"] || "").trim();
356
+ if (pin && !override && derived.namespace && derived.namespace !== pin) {
357
+ warnings.push({
358
+ level: "warn",
359
+ code: "global-namespace-pin",
360
+ message: `MEMINI_NAMESPACE is set to "${pin}", which pins EVERY project on this machine to one namespace. This project would otherwise resolve to "${derived.namespace}". If this variable is exported from a shell rc (or a fish universal variable), every repo you work in is sharing one memory pool.`,
361
+ fix: `Unset MEMINI_NAMESPACE and let each repo resolve on its own, or set a per-project override instead.`
362
+ });
363
+ }
364
+ if (!home) {
365
+ warnings.push({
366
+ level: "warn",
367
+ code: "home-unset",
368
+ message: 'MEMINI_HOME is unset: there is no personal namespace, so visibility:"personal" writes will error and no personal leg merges into recall.',
369
+ fix: "Export MEMINI_HOME=personal/<you>."
370
+ });
371
+ }
372
+ const baseUrl = env["MEMINI_BASE_URL"] || env["MEMINI_URL"] || "http://localhost:8080";
373
+ const token = env["MEMINI_API_KEY"] || env["MEMINI_TOKEN"] || "";
374
+ if (token && isPlaintextToNonLoopback(baseUrl)) {
375
+ warnings.push({
376
+ level: "warn",
377
+ code: "plaintext-bearer",
378
+ message: `a bearer token is configured for plaintext HTTP to ${baseUrl}; the token and your memory payloads can be observed on the network.`,
379
+ fix: "Use HTTPS, or tunnel over SSH. Set MEMINI_REQUIRE_HTTPS=1 to make this an error."
380
+ });
381
+ }
382
+ return {
383
+ cwd,
384
+ namespace: { effective, source, override, withoutOverride, derived, home },
385
+ settings,
386
+ paths: {
387
+ overrides: opts.overridesPath || defaultOverridesPath(env),
388
+ cache: opts.cacheDir
389
+ },
390
+ warnings
391
+ };
392
+ }
393
+
161
394
  // src/index.ts
162
395
  var DEFAULT_BASE_URL = "http://localhost:8080";
163
396
  var DEFAULT_TIMEOUT_MS = 3e4;
164
397
  var DEFAULT_RECALL_LIMIT = 3;
165
398
  var DEFAULT_NAMESPACE = "pi";
399
+ var STATUS_TIMEOUT_MS = 4e3;
166
400
  var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
167
401
  function envBool(value, fallback) {
168
402
  if (value === void 0 || value === null || value === "") return fallback;
@@ -200,22 +434,33 @@ function deriveNamespace(cwd) {
200
434
  const base = cwd.replace(/[\\/]+$/, "").split(/[\\/]/).pop() || "";
201
435
  return sanitizeNamespace(base);
202
436
  }
203
- function resolveConfig(env, cwd) {
437
+ function resolverSource(source) {
438
+ return source === "git" ? "git-remote" : source;
439
+ }
440
+ function resolveProjectNamespace(env, cwd, opts = {}) {
204
441
  const e = env || {};
442
+ if (!opts.ignoreOverride && cwd) {
443
+ const override = readOverride(cwd, { env: e });
444
+ if (override) return { namespace: override.namespace, source: "override" };
445
+ }
205
446
  const nsEnv = (e.MEMINI_NAMESPACE || "").trim();
206
- let namespace;
207
447
  if (nsEnv) {
208
- namespace = nsEnv;
209
- } else if (cwd) {
210
- const { namespace: resolvedNs } = resolveNamespace({
448
+ return { namespace: nsEnv, source: "env" };
449
+ }
450
+ if (cwd) {
451
+ const { namespace: resolvedNs, source } = resolveNamespace({
211
452
  cwd,
212
453
  env: e,
213
454
  integration: "pi"
214
455
  });
215
- namespace = sanitizeNamespacePath(resolvedNs) || DEFAULT_NAMESPACE;
216
- } else {
217
- namespace = DEFAULT_NAMESPACE;
456
+ const sanitized = sanitizeNamespacePath(resolvedNs);
457
+ if (sanitized) return { namespace: sanitized, source: resolverSource(source) };
218
458
  }
459
+ return { namespace: DEFAULT_NAMESPACE, source: "default" };
460
+ }
461
+ function resolveConfig(env, cwd) {
462
+ const e = env || {};
463
+ const { namespace } = resolveProjectNamespace(e, cwd);
219
464
  const recall_limit = (() => {
220
465
  const n = Number(e.MEMINI_RECALL_LIMIT);
221
466
  return Number.isFinite(n) && n >= 0 ? n : DEFAULT_RECALL_LIMIT;
@@ -223,7 +468,7 @@ function resolveConfig(env, cwd) {
223
468
  const homeEnv = (e.MEMINI_HOME || "").trim();
224
469
  return {
225
470
  base_url: e.MEMINI_BASE_URL || e.MEMINI_URL || DEFAULT_BASE_URL,
226
- // namespace is already resolved above (raw-trimmed on the env path,
471
+ // namespace is already resolved above (verbatim on the override/env paths,
227
472
  // per-segment sanitized on the resolver path); re-sanitizing here would
228
473
  // flatten tenant separators.
229
474
  namespace: namespace || DEFAULT_NAMESPACE,
@@ -328,10 +573,10 @@ function createClient(cfg, warn) {
328
573
  if (cfg.home) h["X-Memini-Home"] = cfg.home;
329
574
  return h;
330
575
  }
331
- async function request(method, path2, body) {
576
+ async function request(method, path3, body) {
332
577
  guard(baseUrl, secret);
333
578
  try {
334
- const res = await fetch(`${baseUrl}${path2}`, {
579
+ const res = await fetch(`${baseUrl}${path3}`, {
335
580
  method,
336
581
  headers: headers(body ? { "Content-Type": "application/json" } : void 0),
337
582
  body: body ? JSON.stringify(body) : void 0,
@@ -339,11 +584,11 @@ function createClient(cfg, warn) {
339
584
  });
340
585
  if (!res.ok) {
341
586
  if (cfg.fallback_on_error) {
342
- warn(`memini ${method} ${path2} failed: ${res.status}`);
587
+ warn(`memini ${method} ${path3} failed: ${res.status}`);
343
588
  return null;
344
589
  }
345
590
  const text = await res.text().catch(() => "");
346
- throw new Error(`memini ${method} ${path2} failed: ${res.status} ${text}`);
591
+ throw new Error(`memini ${method} ${path3} failed: ${res.status} ${text}`);
347
592
  }
348
593
  return await res.json().catch(() => ({ ok: true }));
349
594
  } catch (error) {
@@ -352,10 +597,31 @@ function createClient(cfg, warn) {
352
597
  return null;
353
598
  }
354
599
  }
600
+ async function requestResult(method, path3, body) {
601
+ try {
602
+ guard(baseUrl, secret);
603
+ const res = await fetch(`${baseUrl}${path3}`, {
604
+ method,
605
+ headers: headers(body ? { "Content-Type": "application/json" } : void 0),
606
+ body: body ? JSON.stringify(body) : void 0,
607
+ signal: AbortSignal.timeout(cfg.timeout_ms)
608
+ });
609
+ if (!res.ok) {
610
+ const detail = (await res.text().catch(() => "")).trim();
611
+ warn(`memini ${method} ${path3} failed: ${res.status} ${detail}`);
612
+ return { ok: false, error: detail || `HTTP ${res.status}` };
613
+ }
614
+ return { ok: true, data: await res.json().catch(() => ({})) };
615
+ } catch (error) {
616
+ warn(`memini: ${String(error)}`);
617
+ return { ok: false, error: String(error) };
618
+ }
619
+ }
355
620
  return {
356
- postJson: (path2, payload) => request("POST", path2, payload),
357
- getJson: (path2) => request("GET", path2),
358
- deleteJson: (path2) => request("DELETE", path2)
621
+ postJson: (path3, payload) => request("POST", path3, payload),
622
+ getJson: (path3) => request("GET", path3),
623
+ deleteJson: (path3) => request("DELETE", path3),
624
+ postJsonResult: (path3, payload) => requestResult("POST", path3, payload)
359
625
  };
360
626
  }
361
627
  function meminiListPath(args) {
@@ -394,8 +660,223 @@ function buildTurnContent(userText, assistantText) {
394
660
 
395
661
  ${String(assistantText).slice(0, 3e3)}`;
396
662
  }
397
- var TOOL_NAMES = ["memory_recall", "memory_list", "memory_remember", "memory_forget"];
663
+ async function statusGet(cfg, namespace, path3, warn, quiet = false) {
664
+ const baseUrl = String(cfg.base_url).replace(/\/+$/, "");
665
+ const secret = process.env.MEMINI_API_KEY || process.env.MEMINI_TOKEN;
666
+ const headers = { "X-Memini-Namespace": namespace };
667
+ if (secret) headers.Authorization = `Bearer ${secret}`;
668
+ if (cfg.home) headers["X-Memini-Home"] = cfg.home;
669
+ try {
670
+ const res = await fetch(`${baseUrl}${path3}`, {
671
+ method: "GET",
672
+ headers,
673
+ signal: AbortSignal.timeout(STATUS_TIMEOUT_MS)
674
+ });
675
+ if (!res.ok) {
676
+ if (!quiet) warn(`GET ${path3} -> ${res.status}`);
677
+ return null;
678
+ }
679
+ return await res.json();
680
+ } catch (error) {
681
+ if (!quiet) warn(`GET ${path3} failed: ${String(error)}`);
682
+ return null;
683
+ }
684
+ }
685
+ async function fetchServer(cfg, namespace, warn) {
686
+ const started = Date.now();
687
+ const readSet = await statusGet(cfg, namespace, "/v1/namespaces/read-set", warn);
688
+ const out = {
689
+ reachable: readSet != null,
690
+ latencyMs: Date.now() - started,
691
+ readSet
692
+ };
693
+ const health = await statusGet(cfg, namespace, "/healthz?verbose=1", warn, true);
694
+ if (health) {
695
+ out.version = health.version;
696
+ out.status = health.status;
697
+ out.deps = health.deps;
698
+ } else {
699
+ out.healthExposed = false;
700
+ }
701
+ return out;
702
+ }
703
+ var pad = (s, n) => String(s).padEnd(n);
704
+ function renderStatus(settings, cfg, server) {
705
+ const ns = settings.namespace;
706
+ const L = [];
707
+ L.push(`memini \u2014 effective settings (pi)`);
708
+ L.push(`cwd: ${settings.cwd}`);
709
+ L.push("");
710
+ L.push(`NAMESPACE`);
711
+ L.push(` ${pad("effective", 28)} ${pad(ns.effective, 34)} <- ${ns.source}`);
712
+ if (ns.override) {
713
+ L.push(
714
+ ` ${pad("without the override", 28)} ${pad(ns.withoutOverride.namespace, 34)} <- ${ns.withoutOverride.source}`
715
+ );
716
+ }
717
+ if (ns.derived.namespace !== ns.effective) {
718
+ L.push(` ${pad("git/cwd would give", 28)} ${pad(ns.derived.namespace, 34)} <- ${ns.derived.source}`);
719
+ }
720
+ L.push(` ${pad("home (personal)", 28)} ${ns.home || "(unset)"}`);
721
+ L.push("");
722
+ const groups = [
723
+ ["CONNECTION", ["MEMINI_BASE_URL", "MEMINI_API_KEY", "MEMINI_REQUIRE_HTTPS"]],
724
+ ["NAMESPACE INPUTS", ["MEMINI_NAMESPACE", "MEMINI_NAMESPACE_SCOPE", "MEMINI_AGENT", "MEMINI_HOME"]]
725
+ ];
726
+ for (const [group, names] of groups) {
727
+ const rows = (settings.settings || []).filter((s) => names.includes(s.name));
728
+ if (!rows.length) continue;
729
+ L.push(group);
730
+ for (const r of rows) {
731
+ const origin = r.source === "env" ? `<- env` : `(default)`;
732
+ L.push(` ${pad(r.name.replace(/^MEMINI_/, "").toLowerCase(), 28)} ${pad(r.value, 34)} ${origin}`);
733
+ }
734
+ L.push("");
735
+ }
736
+ const secret = process.env.MEMINI_API_KEY || process.env.MEMINI_TOKEN || "";
737
+ L.push(`EXTENSION`);
738
+ L.push(` ${pad("recall", 28)} ${cfg.recall ? "on" : "off"}`);
739
+ L.push(` ${pad("capture", 28)} ${cfg.capture ? "on" : "off"}`);
740
+ L.push(` ${pad("recall_limit", 28)} ${cfg.recall_limit}`);
741
+ L.push(` ${pad("timeout_ms", 28)} ${cfg.timeout_ms}`);
742
+ L.push(` ${pad("bearer", 28)} ${secret ? redactValue(secret) : "(none)"}`);
743
+ L.push("");
744
+ L.push(`SERVER`);
745
+ if (!server.reachable) {
746
+ L.push(` ${pad("reachable", 28)} NO \u2014 could not reach ${cfg.base_url}`);
747
+ } else {
748
+ const ver = server.version ? `, ${server.version}` : "";
749
+ L.push(` ${pad("reachable", 28)} yes (${server.latencyMs}ms${ver})`);
750
+ const d = server.deps || {};
751
+ if (d.store) L.push(` ${pad("store", 28)} ${d.store.ok ? "ok" : `FAILING \u2014 ${d.store.last_error || "?"}`}`);
752
+ if (d.embedder) {
753
+ L.push(` ${pad("embedder", 28)} ${d.embedder.ok ? "ok" : `FAILING \u2014 ${d.embedder.last_error || "?"}`}`);
754
+ }
755
+ if (server.healthExposed === false) {
756
+ L.push(` ${pad("dependency detail", 28)} unavailable (/healthz not routed \u2014 normal behind an ingress)`);
757
+ }
758
+ }
759
+ L.push("");
760
+ if (server.readSet?.entries?.length) {
761
+ L.push(`READ SET for "${ns.effective}" \u2014 where a plain recall looks`);
762
+ L.push(` ${pad("NAMESPACE", 34)} ${pad("ORIGIN", 12)} TIERS`);
763
+ for (const e of server.readSet.entries) {
764
+ const tiers = Array.isArray(e.tiers) && e.tiers.length ? e.tiers.join(",") : "all";
765
+ L.push(` ${pad(e.namespace, 34)} ${pad(e.origin, 12)} ${tiers}`);
766
+ }
767
+ L.push("");
768
+ }
769
+ L.push(`PATHS`);
770
+ L.push(` ${pad("overrides", 28)} ${settings.paths.overrides}`);
771
+ L.push("");
772
+ if (settings.warnings.length) {
773
+ L.push(`WARNINGS`);
774
+ for (const w of settings.warnings) {
775
+ L.push(` [${w.level === "warn" ? "!" : "i"}] ${w.code}: ${w.message}`);
776
+ if (w.fix) L.push(` fix: ${w.fix}`);
777
+ }
778
+ } else {
779
+ L.push(`No problems detected.`);
780
+ }
781
+ return L.join("\n");
782
+ }
783
+ function registerMeminiCommands(pi, cfg, warn) {
784
+ const show = (content) => {
785
+ pi.sendMessage({ customType: "memini-status", content, display: true });
786
+ };
787
+ pi.registerCommand("memini:status", {
788
+ description: "Show memini's effective settings: namespace + provenance, connection, server read set",
789
+ handler: async (_args, ctx) => {
790
+ try {
791
+ const cwd = ctx.cwd || process.cwd();
792
+ const settings = describeSettings({
793
+ cwd,
794
+ env: process.env,
795
+ // Hand describeSettings THIS harness's resolver, so what it reports is
796
+ // what the extension actually does. The opts pass-through carries
797
+ // ignoreOverride, which is how the counterfactual lines see past an
798
+ // override (it lives in a file, so no env-doctoring would remove it).
799
+ resolve: (env, o) => resolveProjectNamespace(env, cwd, o)
800
+ });
801
+ const server = await fetchServer(cfg, settings.namespace.effective, warn);
802
+ show(renderStatus(settings, cfg, server));
803
+ } catch (error) {
804
+ ctx.ui.notify(`memini: status failed: ${String(error)}`, "error");
805
+ }
806
+ }
807
+ });
808
+ pi.registerCommand("memini:namespace", {
809
+ description: "Show, set, or --clear the memini namespace override for this project",
810
+ handler: async (args, ctx) => {
811
+ try {
812
+ const cwd = ctx.cwd || process.cwd();
813
+ const arg = String(args || "").trim();
814
+ const before = resolveProjectNamespace(process.env, cwd);
815
+ if (!arg) {
816
+ const current = readOverride(cwd, { env: process.env });
817
+ const out = [
818
+ `namespace: ${before.namespace} (source: ${before.source})`,
819
+ `project: ${overrideKey(cwd)}`,
820
+ ``
821
+ ];
822
+ if (current) {
823
+ out.push(`An override is active (set ${current.setAt}).`);
824
+ out.push(`Clear it with: /memini:namespace --clear`);
825
+ } else {
826
+ out.push(`No override \u2014 resolving automatically.`);
827
+ out.push(`Set one with: /memini:namespace <namespace>`);
828
+ }
829
+ out.push(`Overrides file: ${defaultOverridesPath(process.env)}`);
830
+ show(out.join("\n"));
831
+ return;
832
+ }
833
+ if (arg === "--clear" || arg === "clear") {
834
+ const removed = clearOverride(cwd, { env: process.env });
835
+ if (!removed) {
836
+ show(`No override was set for ${overrideKey(cwd)} \u2014 nothing to clear.`);
837
+ return;
838
+ }
839
+ const after2 = resolveProjectNamespace(process.env, cwd);
840
+ cfg.namespace = after2.namespace;
841
+ show(
842
+ [
843
+ `namespace override cleared: ${before.namespace} -> ${after2.namespace} (source: ${after2.source})`,
844
+ ``,
845
+ `Recall and capture use the new namespace from the next turn.`
846
+ ].join("\n")
847
+ );
848
+ return;
849
+ }
850
+ const ns = normalizeNamespace(arg);
851
+ const bad = validateNamespace(ns);
852
+ if (bad) {
853
+ ctx.ui.notify(`memini: invalid namespace ${JSON.stringify(arg)}: ${bad}`, "error");
854
+ return;
855
+ }
856
+ writeOverride(cwd, ns, { env: process.env });
857
+ const after = resolveProjectNamespace(process.env, cwd);
858
+ cfg.namespace = after.namespace;
859
+ show(
860
+ [
861
+ `namespace override set: ${before.namespace} -> ${after.namespace}`,
862
+ `project: ${overrideKey(cwd)}`,
863
+ ``,
864
+ `The override wins over MEMINI_NAMESPACE. Recall and capture use it from the next turn.`
865
+ ].join("\n")
866
+ );
867
+ } catch (error) {
868
+ ctx.ui.notify(`memini: namespace failed: ${String(error)}`, "error");
869
+ }
870
+ }
871
+ });
872
+ }
873
+ var TOOL_NAMES = ["memory_recall", "memory_briefing", "memory_list", "memory_remember", "memory_forget"];
398
874
  var VALID_TIERS = ["working", "episodic", "semantic", "procedural"];
875
+ var VALID_SCOPES = ["project", "full", "everywhere"];
876
+ function briefingPath(args) {
877
+ const scope = String(args?.scope || "").trim();
878
+ return VALID_SCOPES.includes(scope) ? `/v1/namespaces/briefing?scope=${encodeURIComponent(scope)}` : "/v1/namespaces/briefing";
879
+ }
399
880
  function sessionIdOf(ctx) {
400
881
  try {
401
882
  return String(ctx?.sessionManager?.getSessionId?.() ?? "");
@@ -419,6 +900,11 @@ function meminiExtension(pi) {
419
900
  };
420
901
  const cfg = resolveConfig(process.env, process.cwd());
421
902
  const client = createClient(cfg, warn);
903
+ try {
904
+ if (typeof pi.registerCommand === "function") registerMeminiCommands(pi, cfg, warn);
905
+ } catch (error) {
906
+ warn(`command registration skipped: ${String(error)}`);
907
+ }
422
908
  const pendingUser = /* @__PURE__ */ new Map();
423
909
  const captured = /* @__PURE__ */ new Set();
424
910
  const injectedBySession = /* @__PURE__ */ new Map();
@@ -506,31 +992,70 @@ function meminiExtension(pi) {
506
992
  description: 'Match memories whose top-level metadata contains each key=value pair, e.g. {"category":"bug_fixes"}.'
507
993
  })
508
994
  );
995
+ const Scope = Type.Optional(
996
+ Type.String({
997
+ enum: VALID_SCOPES,
998
+ description: "How wide to read: 'project' = just this project's own memories; 'full' (default) = project plus inherited context (ancestors, your personal namespace, links); 'everywhere' = full plus nested sub-projects."
999
+ })
1000
+ );
509
1001
  pi.registerTool({
510
1002
  name: "memory_recall",
511
1003
  label: "Recall memory",
512
- description: `Recall relevant memories from long-term memory (memini) via hybrid (semantic + keyword) search. Call before starting work that may have history: editing an unfamiliar file, debugging a recurring issue, or when asked what's known about something. Empty results mean nothing is known \u2014 proceed from first principles, never invent a remembered fact. A degraded:"keyword_only" field in the result means semantic search was unavailable and results came from keyword matching alone \u2014 treat as incomplete, not exhaustive.`,
1004
+ description: `Search prior context in long-term memory (memini) via hybrid (semantic + keyword) retrieval, ranked by relevance, recency, and corroboration. Call BEFORE starting work that may have history: editing an unfamiliar file, debugging a recurring issue, making a non-obvious decision, or when asked what's known about something. Prefer a short descriptive query ('JWT auth setup'). scope picks how wide to read: 'project' (just this project), 'full' (default: project plus inherited ancestor/personal/link context), or 'everywhere' (full plus nested sub-projects). Each result's namespace/from fields are provenance, not a choice \u2014 an absent 'from' means this project's own memory, otherwise it names the ancestor or personal namespace the memory came from; read them to learn where knowledge lives, never construct a namespace path. Empty results mean nothing is known \u2014 proceed from first principles, never invent a remembered fact. A degraded:"keyword_only" field in the result means semantic search was unavailable and results came from keyword matching alone \u2014 treat as incomplete, not exhaustive.`,
513
1005
  parameters: Type.Object({
514
1006
  query: Type.String({ description: "What to search for" }),
515
1007
  limit: Type.Optional(Type.Number({ description: "Max results (default 3)" })),
516
1008
  tags: Tags,
517
- metadata: Metadata
1009
+ metadata: Metadata,
1010
+ scope: Scope
518
1011
  }),
519
1012
  async execute(_toolCallId, params) {
520
1013
  const body = { query: params.query, limit: params.limit || DEFAULT_RECALL_LIMIT };
521
1014
  if (params.tags?.length) body.tags = params.tags;
522
1015
  if (params.metadata && Object.keys(params.metadata).length) body.metadata = params.metadata;
1016
+ if (VALID_SCOPES.includes(params.scope)) body.scope = params.scope;
523
1017
  const res = await client.postJson("/v1/search", body);
524
- const results = (res?.results || []).map((r) => ({
525
- id: r?.memory?.id || "",
526
- content: r?.memory?.content || "",
527
- summary: r?.memory?.summary || "",
528
- tier: r?.memory?.tier || "",
529
- score: typeof r?.score === "number" ? r.score : 0
530
- }));
1018
+ const results = (res?.results || []).map((r) => {
1019
+ const mem = r?.memory || {};
1020
+ const out = {
1021
+ id: mem.id || "",
1022
+ content: mem.content || "",
1023
+ summary: mem.summary || "",
1024
+ tier: mem.tier || "",
1025
+ score: typeof r?.score === "number" ? r.score : 0
1026
+ };
1027
+ if (mem.namespace) out.namespace = mem.namespace;
1028
+ if (r?.from) out.from = r.from;
1029
+ return out;
1030
+ });
531
1031
  return text(res?.degraded ? { results, degraded: res.degraded, note: res.note } : { results });
532
1032
  }
533
1033
  });
1034
+ pi.registerTool({
1035
+ name: "memory_briefing",
1036
+ label: "Session briefing",
1037
+ description: "Layered session-start briefing for this project from long-term memory (memini) \u2014 pinned context, durable facts, how-to procedures, and recent activity \u2014 in one query-less call. Call it when a session opens to orient yourself; prefer it over broad recall queries at session start. The scope_header line ('Scope: acme/phoenix/api \u2190 acme/phoenix(3) \u2190 acme(4) \u2190 personal(2)') spells out the ancestor chain you inherit from \u2014 read it instead of guessing namespace paths, and name one of those ancestors as memory_remember's visibility to share a fact up that chain. scope='everywhere' also briefs nested sub-projects.",
1038
+ parameters: Type.Object({ scope: Scope }),
1039
+ async execute(_toolCallId, params) {
1040
+ const res = await client.getJson(briefingPath(params));
1041
+ if (!res) return text({ briefing: null, error: "memini unavailable" });
1042
+ const section = (items) => (items || []).map((b) => {
1043
+ const mem = b?.memory || {};
1044
+ const out = { id: mem.id || "", content: mem.content || "", tier: mem.tier || "" };
1045
+ if (mem.namespace) out.namespace = mem.namespace;
1046
+ if (b?.from) out.from = b.from;
1047
+ return out;
1048
+ });
1049
+ return text({
1050
+ namespace: res.namespace || "",
1051
+ scope_header: res.scope_header || "",
1052
+ pinned: section(res.pinned),
1053
+ facts: section(res.facts),
1054
+ procedures: section(res.procedures),
1055
+ recent: section(res.recent)
1056
+ });
1057
+ }
1058
+ });
534
1059
  pi.registerTool({
535
1060
  name: "memory_list",
536
1061
  label: "List memory",
@@ -560,7 +1085,7 @@ function meminiExtension(pi) {
560
1085
  pi.registerTool({
561
1086
  name: "memory_remember",
562
1087
  label: "Remember",
563
- description: "Store a durable fact, decision, or preference in long-term memory (memini). Call proactively when the user says 'remember this', after an architectural decision (capture the why), or after discovering a non-obvious bug or convention. Keep memories atomic \u2014 one self-contained fact per call. Don't store what's already in project docs or trivially recoverable from code. To correct an existing memory, pass its id \u2014 the write updates it in place.",
1088
+ description: "Store a durable fact, decision, or preference in long-term memory (memini). Call proactively when the user says 'remember this', after an architectural decision (capture the why), or after discovering a non-obvious bug or convention. Keep memories atomic \u2014 one self-contained fact per call. Don't store what's already in project docs or trivially recoverable from code. To correct an existing memory, pass its id \u2014 the write updates it in place. visibility decides who should know: 'project' (default) keeps it here; 'personal' follows the user everywhere; or name an ancestor from the memory_briefing Scope line to share it up that chain. reinforced=true in the result means the fact was ALREADY KNOWN: no new memory was created, the existing one was strengthened, and `id` names that pre-existing memory rather than anything you just wrote \u2014 do not report it to the user as a new save.",
564
1089
  parameters: Type.Object({
565
1090
  content: Type.String({ description: "The fact to remember \u2014 atomic and self-contained." }),
566
1091
  id: Type.Optional(
@@ -582,6 +1107,11 @@ function meminiExtension(pi) {
582
1107
  Type.String({
583
1108
  description: "Optional topic bucket stored as metadata.category (e.g. bug_fixes, architecture_decisions) for browsing by subject later."
584
1109
  })
1110
+ ),
1111
+ visibility: Type.Optional(
1112
+ Type.String({
1113
+ description: "Who should remember this: 'project' (default, this project only), 'personal' (about the user, follows them everywhere), or an ancestor namespace name read off the memory_briefing Scope line (e.g. the team or org level) to share it up that chain. On a durable write an unrecognized name errors listing the valid options. Episodic/working writes always stay in the project regardless."
1114
+ })
585
1115
  )
586
1116
  }),
587
1117
  async execute(_toolCallId, params) {
@@ -590,8 +1120,13 @@ function meminiExtension(pi) {
590
1120
  if (params.tier && VALID_TIERS.includes(params.tier)) body.tier = params.tier;
591
1121
  if (params.tags?.length) body.tags = params.tags;
592
1122
  if (params.category) body.metadata = { category: params.category };
593
- const res = await client.postJson("/v1/memories", body);
594
- return text({ id: res?.id || null, success: res != null });
1123
+ const visibility = String(params.visibility || "").trim();
1124
+ if (visibility) body.visibility = visibility;
1125
+ const res = await client.postJsonResult("/v1/memories", body);
1126
+ if (!res.ok) return text({ id: null, success: false, error: res.error });
1127
+ const out = { id: res.data?.id || null, success: true };
1128
+ if (res.data?.reinforced) out.reinforced = true;
1129
+ return text(out);
595
1130
  }
596
1131
  });
597
1132
  pi.registerTool({
@@ -611,6 +1146,7 @@ function meminiExtension(pi) {
611
1146
  }
612
1147
  export {
613
1148
  approxTokens,
1149
+ briefingPath,
614
1150
  buildTurnContent,
615
1151
  createPlaintextBearerAuthGuard,
616
1152
  meminiExtension as default,
@@ -623,7 +1159,10 @@ export {
623
1159
  intEnv,
624
1160
  labelsEnv,
625
1161
  meminiListPath,
1162
+ registerMeminiCommands,
1163
+ renderStatus,
626
1164
  resolveConfig,
1165
+ resolveProjectNamespace,
627
1166
  sanitizeNamespace,
628
1167
  sanitizeNamespacePath,
629
1168
  truncate
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@eleboucher/pi-memini",
3
- "version": "0.6.8",
3
+ "version": "0.6.9",
4
4
  "description": "Shared cross-session memory for the Pi coding agent, backed by a memini service.",
5
5
  "files": [
6
6
  "dist"
7
7
  ],
8
8
  "type": "module",
9
9
  "scripts": {
10
- "build": "esbuild src/index.ts --bundle --platform=node --format=esm --target=node20 --external:typebox --external:@earendil-works/pi-coding-agent --alias:@memini/namespace-resolver=../../../packages/namespace-resolver/src/index.ts --outfile=dist/index.js",
10
+ "build": "esbuild src/index.ts --bundle --platform=node --format=esm --target=node20 --external:typebox --external:@earendil-works/pi-coding-agent --alias:@memini/namespace-resolver=../../../packages/namespace-resolver/src/index.ts --alias:@memini/client=../../../packages/memini-client/src/index.ts --outfile=dist/index.js",
11
11
  "test": "node --test test/bundle.test.mjs && npx tsx --test test/helpers.test.ts",
12
12
  "typecheck": "tsc -p tsconfig.test.json"
13
13
  },