@membank/cli 0.11.1 → 0.12.0

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 +19 -7
  2. package/dist/index.mjs +136 -171
  3. package/package.json +5 -6
package/README.md CHANGED
@@ -35,6 +35,16 @@ Options:
35
35
 
36
36
  Supported harnesses: `claude-code`, `copilot`, `codex`, `opencode` (see `membank setup` for harness-specific setup instructions)
37
37
 
38
+ ### `membank setup upgrade`
39
+
40
+ Migrate existing harness configs from the old `npx @membank/cli --mcp` pattern to the standalone `npx @membank/mcp` binary:
41
+
42
+ ```bash
43
+ membank setup upgrade
44
+ ```
45
+
46
+ Run this once after upgrading to align all configured harnesses with the new standalone MCP package.
47
+
38
48
  ## Commands
39
49
 
40
50
  ### `membank query <text>`
@@ -167,31 +177,33 @@ membank inject --harness claude-code --event session-stop
167
177
 
168
178
  Options: `--harness <name>` (claude-code|copilot-cli|codex|opencode), `--event <event>` (session-start|user-prompt-submit|session-stop)
169
179
 
170
- ### `membank dashboard`
180
+ ### `membank dashboard` (deprecated)
171
181
 
172
- Start the web dashboard for browsing and managing memories.
182
+ The dashboard is now a standalone package. Run it directly:
173
183
 
174
184
  ```bash
175
- membank dashboard
185
+ npx @membank/dashboard
176
186
  ```
177
187
 
178
- Opens http://localhost:3847 by default. Features: full-text search, filtering by type/scope/pin status, edit memory metadata, view dedup reviews, and storage statistics.
188
+ See [`@membank/dashboard`](../dashboard/README.md) for options.
179
189
 
180
190
  ## Global flags
181
191
 
182
192
  ```
183
193
  --json Output machine-readable JSON
184
194
  --yes, -y Skip confirmation prompts
185
- --mcp Start MCP stdio server (used by harness config)
195
+ --mcp Start MCP stdio server (deprecated use npx @membank/mcp)
186
196
  ```
187
197
 
188
198
  ## MCP server mode
189
199
 
200
+ The preferred way to run the MCP server is via the standalone package:
201
+
190
202
  ```bash
191
- membank --mcp
203
+ npx @membank/mcp
192
204
  ```
193
205
 
194
- Starts the stdio MCP server. This is what harnesses connect to `setup` writes this command into harness configs automatically.
206
+ `membank setup` writes this command into harness configs automatically. The legacy `membank --mcp` flag still works but emits a deprecation warning. Run `membank setup upgrade` to migrate existing harness configs.
195
207
 
196
208
  ## Session hooks
197
209
 
package/dist/index.mjs CHANGED
@@ -1,20 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import { cancel, confirm, intro, isCancel, multiselect, note, outro } from "@clack/prompts";
3
- import { DatabaseManager, EmbeddingService, MIGRATIONS, MemoryRepository, MemoryTypeSchema, MemoryTypeSchema as MemoryTypeSchema$1, PIN_BUDGET_THRESHOLD, ProjectRepository, QueryEngine, SessionContextBuilder, SynthesisRepository, TagsJsonSchema as TagsRowSchema, resolveProject, runScopeToProjectsMigration } from "@membank/core";
3
+ import { DatabaseManager, EmbeddingService, MIGRATIONS, MODEL_NAME, MemoryTypeSchema, MemoryTypeSchema as MemoryTypeSchema$1, ModelDownloader, PIN_BUDGET_THRESHOLD, QueryEngine, SessionContextBuilder, createMemoryRepository, createProjectRepository, createSynthesisRepository, resolveProject, runScopeToProjectsMigration, saveMemory } from "@membank/core";
4
4
  import { runSynthesis, startServer } from "@membank/mcp";
5
5
  import chalk from "chalk";
6
6
  import { Command } from "commander";
7
7
  import ora from "ora";
8
8
  import { z } from "zod";
9
- import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, writeFileSync } from "node:fs";
9
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, writeFileSync } from "node:fs";
10
10
  import { homedir, tmpdir } from "node:os";
11
11
  import { dirname, join } from "node:path";
12
- import { startDashboard } from "@membank/dashboard";
13
12
  import Table from "cli-table3";
14
13
  import { execFile } from "node:child_process";
15
14
  import { promisify } from "node:util";
16
- import { EventEmitter } from "node:events";
17
- import { pipeline } from "@huggingface/transformers";
18
15
  import { createInterface } from "node:readline";
19
16
  //#region src/schemas.ts
20
17
  const SETUP_HARNESS_VALUES = [
@@ -32,7 +29,7 @@ const InjectionHarnessSchema = z.enum([
32
29
  ]);
33
30
  const MigrateModeSchema = z.enum(["list", "run"]);
34
31
  const LimitSchema = z.coerce.number().int().positive();
35
- const PortSchema = z.coerce.number().int().min(1).max(65535);
32
+ z.coerce.number().int().min(1).max(65535);
36
33
  const OptionalNumberSchema = z.number().optional().catch(void 0);
37
34
  const MutableJsonObjectSchema = z.record(z.string(), z.unknown());
38
35
  const MaybeJsonObjectSchema = z.record(z.string(), z.unknown()).optional().catch(void 0);
@@ -60,15 +57,19 @@ async function addCommand(content, options, formatter, db, embeddingService) {
60
57
  const ownDb = db === void 0;
61
58
  const resolvedDb = db ?? DatabaseManager.open();
62
59
  try {
63
- const repo = new MemoryRepository(resolvedDb, embeddingService ?? new EmbeddingService(), new ProjectRepository(resolvedDb));
60
+ const embedder = embeddingService ?? new EmbeddingService();
61
+ const repo = createMemoryRepository(resolvedDb, createProjectRepository(resolvedDb));
64
62
  const tags = options.tags !== void 0 ? options.tags.split(",").map((t) => t.trim()) : [];
65
63
  const projectScope = options.global ? void 0 : await resolveProject();
66
64
  const spinner = formatter.isJson ? null : ora("Saving memory…").start();
67
- const memory = await repo.save({
65
+ const memory = await saveMemory({
68
66
  content,
69
67
  type: MemoryTypeSchema$1.parse(options.type),
70
68
  tags,
71
69
  projectScope
70
+ }, {
71
+ repo,
72
+ embedder
72
73
  });
73
74
  spinner?.succeed("Memory saved");
74
75
  formatter.outputMemory(memory);
@@ -151,35 +152,31 @@ function configShowCommand(formatter) {
151
152
  process.stdout.write(`${JSON.stringify(config, null, formatter.isJson ? 0 : 2)}\n`);
152
153
  }
153
154
  //#endregion
154
- //#region src/commands/dashboard.ts
155
- async function dashboardCommand(opts) {
156
- await startDashboard({ port: opts.port !== void 0 ? PortSchema.parse(opts.port) : void 0 });
157
- }
158
- //#endregion
159
155
  //#region src/commands/delete.ts
160
156
  async function deleteCommand(id, db, formatter, prompt) {
161
- if (db.db.prepare(`SELECT id FROM memories WHERE id = ?`).get(id) === void 0) {
157
+ const repo = createMemoryRepository(db, createProjectRepository(db));
158
+ if (repo.findById(id) === void 0) {
162
159
  formatter.error(`Memory not found: ${id}`);
163
160
  process.exit(1);
164
161
  }
165
162
  if (!await prompt.confirm(`Delete memory ${id}?`)) return;
166
- await new MemoryRepository(db, new EmbeddingService(), new ProjectRepository(db)).delete(id);
163
+ repo.delete(id);
167
164
  process.stdout.write(`${chalk.green("✓")} Deleted memory: ${chalk.dim(id)}\n`);
168
165
  }
169
166
  //#endregion
170
167
  //#region src/commands/export.ts
171
168
  function exportCommand(db, formatter, opts) {
172
- const memories = db.db.prepare(`SELECT m.*, e.embedding FROM memories m LEFT JOIN embeddings e ON e.rowid = m.rowid ORDER BY m.created_at DESC`).all().map((row) => ({
173
- id: row.id,
174
- content: row.content,
175
- type: row.type,
176
- tags: TagsRowSchema.parse(JSON.parse(row.tags)),
177
- sourceHarness: row.source,
178
- accessCount: row.access_count,
179
- pinned: row.pinned !== 0,
180
- createdAt: row.created_at,
181
- updatedAt: row.updated_at,
182
- embedding: row.embedding !== null ? Buffer.from(row.embedding).toString("base64") : null
169
+ const memories = createMemoryRepository(db, createProjectRepository(db)).exportAll().map((rec) => ({
170
+ id: rec.id,
171
+ content: rec.content,
172
+ type: rec.type,
173
+ tags: rec.tags,
174
+ sourceHarness: rec.sourceHarness,
175
+ accessCount: rec.accessCount,
176
+ pinned: rec.pinned,
177
+ createdAt: rec.createdAt,
178
+ updatedAt: rec.updatedAt,
179
+ embedding: rec.embedding !== null ? Buffer.from(rec.embedding.buffer, rec.embedding.byteOffset, rec.embedding.byteLength).toString("base64") : null
183
180
  }));
184
181
  const data = {
185
182
  version: 1,
@@ -220,18 +217,23 @@ async function importCommand(filePath, db, formatter, prompt) {
220
217
  if (formatter.isJson) process.stdout.write(`${JSON.stringify({ found: count })}\n`);
221
218
  else process.stdout.write(`Found ${count} memories to import.\n`);
222
219
  if (!await prompt.confirm("Import?")) return;
223
- const insertMemory = db.db.prepare(`INSERT OR REPLACE INTO memories (id, content, type, tags, source, access_count, pinned, created_at, updated_at)
224
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`);
225
- const insertEmbedding = db.db.prepare(`INSERT OR REPLACE INTO embeddings (rowid, embedding) SELECT m.rowid, ? FROM memories m WHERE m.id = ?`);
226
- db.db.transaction(() => {
227
- for (const rec of parseResult.data.memories) {
228
- insertMemory.run(rec.id, rec.content, rec.type, JSON.stringify(rec.tags ?? []), rec.sourceHarness ?? null, rec.accessCount ?? 0, rec.pinned ? 1 : 0, rec.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(), rec.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString());
229
- if (rec.embedding !== null && rec.embedding !== void 0) {
230
- const buf = Buffer.from(rec.embedding, "base64");
231
- insertEmbedding.run(buf, rec.id);
232
- }
233
- }
234
- })();
220
+ const records = parseResult.data.memories.map((rec) => ({
221
+ id: rec.id,
222
+ content: rec.content,
223
+ type: rec.type,
224
+ tags: rec.tags,
225
+ sourceHarness: rec.sourceHarness ?? null,
226
+ accessCount: rec.accessCount ?? 0,
227
+ pinned: rec.pinned ?? false,
228
+ createdAt: rec.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
229
+ updatedAt: rec.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
230
+ embedding: (() => {
231
+ if (rec.embedding == null) return null;
232
+ const buf = Buffer.from(rec.embedding, "base64");
233
+ return new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4);
234
+ })()
235
+ }));
236
+ createMemoryRepository(db, createProjectRepository(db)).importAll(records);
235
237
  if (formatter.isJson) process.stdout.write(`${JSON.stringify({ imported: count })}\n`);
236
238
  else process.stdout.write(`Imported ${count} memories.\n`);
237
239
  }
@@ -277,8 +279,8 @@ async function buildText() {
277
279
  const resolved = await resolveProject();
278
280
  const db = DatabaseManager.open();
279
281
  try {
280
- const builder = new SessionContextBuilder(db);
281
- const synthRepo = new SynthesisRepository(db);
282
+ const builder = new SessionContextBuilder(createMemoryRepository(db, createProjectRepository(db)));
283
+ const synthRepo = createSynthesisRepository(db);
282
284
  const globalRow = synthRepo.getSynthesis("global");
283
285
  const projectRow = synthRepo.getSynthesis(resolved.hash);
284
286
  const synthesis = pickBestSynthesis(globalRow?.inFlightSince === null ? globalRow.content : void 0, projectRow?.inFlightSince === null ? projectRow.content : void 0);
@@ -322,7 +324,7 @@ async function injectCommand(opts) {
322
324
  async function listCommand(options, formatter) {
323
325
  const db = DatabaseManager.open();
324
326
  try {
325
- const memories = new MemoryRepository(db, new EmbeddingService(), new ProjectRepository(db)).list({
327
+ const memories = createMemoryRepository(db, createProjectRepository(db)).list({
326
328
  type: options.type !== void 0 ? MemoryTypeSchema$1.parse(options.type) : void 0,
327
329
  pinned: options.pinned
328
330
  });
@@ -353,7 +355,7 @@ async function migrateCommand(mode, name, formatter) {
353
355
  const db = DatabaseManager.open();
354
356
  try {
355
357
  if (name === "scope-to-projects") {
356
- const result = await runScopeToProjectsMigration(new ProjectRepository(db));
358
+ const result = await runScopeToProjectsMigration(createProjectRepository(db));
357
359
  if (result === null) {
358
360
  formatter.error("No project found for current directory.");
359
361
  return;
@@ -377,7 +379,7 @@ function pinCommand(id, db) {
377
379
  const ownDb = db === void 0;
378
380
  const resolvedDb = db ?? DatabaseManager.open();
379
381
  try {
380
- new MemoryRepository(resolvedDb, new EmbeddingService(), new ProjectRepository(resolvedDb)).setPin(id, true);
382
+ createMemoryRepository(resolvedDb, createProjectRepository(resolvedDb)).setPin(id, true);
381
383
  process.stdout.write(`${chalk.green("✓")} Pinned: ${chalk.dim(id)}\n`);
382
384
  } finally {
383
385
  if (ownDb) resolvedDb.close();
@@ -388,8 +390,7 @@ function pinCommand(id, db) {
388
390
  async function queryCommand(queryText, options, formatter) {
389
391
  const db = DatabaseManager.open();
390
392
  try {
391
- const embedding = new EmbeddingService();
392
- const engine = new QueryEngine(db, embedding, new MemoryRepository(db, embedding, new ProjectRepository(db)));
393
+ const engine = new QueryEngine(db, new EmbeddingService(), createMemoryRepository(db, createProjectRepository(db)));
393
394
  const limit = options.limit !== void 0 ? LimitSchema.parse(options.limit) : 10;
394
395
  const spinner = formatter.isJson ? null : ora("Searching memories…").start();
395
396
  const results = await engine.query({
@@ -409,7 +410,7 @@ async function queryCommand(queryText, options, formatter) {
409
410
  async function reviewCommand(opts, formatter) {
410
411
  const db = DatabaseManager.open();
411
412
  try {
412
- const repo = new MemoryRepository(db, new EmbeddingService(), new ProjectRepository(db));
413
+ const repo = createMemoryRepository(db, createProjectRepository(db));
413
414
  if (opts.resolve !== void 0) {
414
415
  repo.resolveReviewEvents(opts.resolve);
415
416
  if (!formatter.isJson) process.stdout.write(`Resolved review events for memory ${opts.resolve}\n`);
@@ -427,7 +428,7 @@ async function reviewCommand(opts, formatter) {
427
428
  async function statsCommand(formatter) {
428
429
  const db = DatabaseManager.open();
429
430
  try {
430
- const stats = new MemoryRepository(db, new EmbeddingService(), new ProjectRepository(db)).stats();
431
+ const stats = createMemoryRepository(db, createProjectRepository(db)).stats();
431
432
  formatter.outputStats(stats);
432
433
  } finally {
433
434
  db.close();
@@ -435,9 +436,6 @@ async function statsCommand(formatter) {
435
436
  }
436
437
  //#endregion
437
438
  //#region src/commands/synthesize.ts
438
- function hasSynthesesTable(db) {
439
- return db.db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='syntheses'").get() !== void 0;
440
- }
441
439
  async function synthesizeRunCommand(opts, formatter) {
442
440
  const scope = opts.scope ?? "global";
443
441
  if (!formatter.isJson) process.stdout.write(`Running synthesis for scope: ${scope}\n`);
@@ -451,30 +449,25 @@ async function synthesizeRunCommand(opts, formatter) {
451
449
  function synthesizeShowCommand(opts, formatter) {
452
450
  const db = DatabaseManager.open();
453
451
  try {
454
- if (!hasSynthesesTable(db)) {
455
- if (formatter.isJson) process.stdout.write(`${JSON.stringify(null)}\n`);
456
- else process.stdout.write("No synthesis data available.\n");
457
- return;
458
- }
459
452
  const scope = opts.scope ?? "global";
460
453
  let resolvedScope = scope;
461
454
  if (scope !== "global" && !/^[0-9a-f]{16}$/.test(scope)) {
462
- const project = new ProjectRepository(db).getByName(scope);
455
+ const project = createProjectRepository(db).getByName(scope);
463
456
  if (project !== void 0) resolvedScope = project.scopeHash;
464
457
  }
465
- const row = db.db.prepare("SELECT * FROM syntheses WHERE scope = ? ORDER BY synthesized_at DESC LIMIT 1").get(resolvedScope);
466
- if (row === void 0) {
458
+ const synthesis = createSynthesisRepository(db).getSynthesis(resolvedScope);
459
+ if (synthesis === void 0) {
467
460
  if (formatter.isJson) process.stdout.write(`${JSON.stringify(null)}\n`);
468
461
  else process.stdout.write(`No synthesis found for scope: ${scope}\n`);
469
462
  return;
470
463
  }
471
- if (formatter.isJson) process.stdout.write(`${JSON.stringify(row)}\n`);
464
+ if (formatter.isJson) process.stdout.write(`${JSON.stringify(synthesis)}\n`);
472
465
  else {
473
- process.stdout.write(`\nScope: ${row.scope}\n`);
474
- if (row.synthesized_at !== null) process.stdout.write(`Synthesized: ${new Date(row.synthesized_at).toLocaleString()}\n`);
475
- if (row.expires_at !== null) process.stdout.write(`Expires: ${new Date(row.expires_at).toLocaleString()}\n`);
476
- if (row.in_flight_since !== null) process.stdout.write(`In-flight since: ${new Date(row.in_flight_since).toLocaleString()}\n`);
477
- process.stdout.write(`\n${row.content}\n\n`);
466
+ process.stdout.write(`\nScope: ${synthesis.scope}\n`);
467
+ process.stdout.write(`Synthesized: ${new Date(synthesis.synthesizedAt).toLocaleString()}\n`);
468
+ process.stdout.write(`Expires: ${new Date(synthesis.expiresAt).toLocaleString()}\n`);
469
+ if (synthesis.inFlightSince !== null) process.stdout.write(`In-flight since: ${new Date(synthesis.inFlightSince).toLocaleString()}\n`);
470
+ process.stdout.write(`\n${synthesis.content}\n\n`);
478
471
  }
479
472
  } finally {
480
473
  db.close();
@@ -483,29 +476,22 @@ function synthesizeShowCommand(opts, formatter) {
483
476
  function synthesizeStatusCommand(formatter) {
484
477
  const db = DatabaseManager.open();
485
478
  try {
486
- if (!hasSynthesesTable(db)) {
487
- if (formatter.isJson) process.stdout.write(`${JSON.stringify([])}\n`);
488
- else process.stdout.write("No synthesis data available.\n");
489
- return;
490
- }
491
- const rows = db.db.prepare(`SELECT s.*, p.name AS project_name
492
- FROM syntheses s
493
- LEFT JOIN projects p ON p.scope_hash = s.scope
494
- ORDER BY COALESCE(p.name, s.scope)`).all();
479
+ const syntheses = createSynthesisRepository(db).listAll();
480
+ const projectRepo = createProjectRepository(db);
495
481
  if (formatter.isJson) {
496
- process.stdout.write(`${JSON.stringify(rows)}\n`);
482
+ process.stdout.write(`${JSON.stringify(syntheses)}\n`);
497
483
  return;
498
484
  }
499
- if (rows.length === 0) {
485
+ if (syntheses.length === 0) {
500
486
  process.stdout.write("No syntheses found.\n");
501
487
  return;
502
488
  }
503
489
  process.stdout.write("\n");
504
- for (const row of rows) {
505
- const displayScope = row.project_name ?? row.scope;
506
- const inFlight = row.in_flight_since !== null ? " [in-flight]" : "";
507
- const synthesized = row.synthesized_at !== null ? new Date(row.synthesized_at).toLocaleString() : "(never)";
508
- const expires = row.expires_at !== null ? new Date(row.expires_at).toLocaleString() : "(none)";
490
+ for (const s of syntheses) {
491
+ const displayScope = (s.scope !== "global" ? projectRepo.getByHash(s.scope) : void 0)?.name ?? s.scope;
492
+ const inFlight = s.inFlightSince !== null ? " [in-flight]" : "";
493
+ const synthesized = new Date(s.synthesizedAt).toLocaleString();
494
+ const expires = new Date(s.expiresAt).toLocaleString();
509
495
  process.stdout.write(` ${displayScope}${inFlight}\n`);
510
496
  process.stdout.write(` synthesized_at: ${synthesized}\n`);
511
497
  process.stdout.write(` expires_at: ${expires}\n`);
@@ -521,7 +507,7 @@ function unpinCommand(id, db) {
521
507
  const ownDb = db === void 0;
522
508
  const resolvedDb = db ?? DatabaseManager.open();
523
509
  try {
524
- new MemoryRepository(resolvedDb, new EmbeddingService(), new ProjectRepository(resolvedDb)).setPin(id, false);
510
+ createMemoryRepository(resolvedDb, createProjectRepository(resolvedDb)).setPin(id, false);
525
511
  process.stdout.write(`${chalk.green("✓")} Unpinned: ${chalk.dim(id)}\n`);
526
512
  } finally {
527
513
  if (ownDb) resolvedDb.close();
@@ -777,18 +763,20 @@ function hasKey(container, key) {
777
763
  function assertCliFound(result, cli, command) {
778
764
  if (result.exitCode === 127) throw new CommandError(`${cli} CLI not found — install ${cli} first`, command);
779
765
  }
766
+ function jsonContainsStalePattern(value) {
767
+ return JSON.stringify(value).includes("@membank/cli");
768
+ }
780
769
  const MEMBANK_NPX_ARGS = [
781
770
  "npx",
782
771
  "-y",
783
- "@membank/cli",
784
- "--mcp"
772
+ "@membank/mcp"
785
773
  ];
786
774
  const writers$1 = {
787
775
  "claude-code": {
788
776
  preview(resolver) {
789
777
  return {
790
778
  configPath: join(resolver.home(), ".claude.json"),
791
- cliCommand: "claude mcp add --scope user membank -- npx -y @membank/cli --mcp"
779
+ cliCommand: "claude mcp add --scope user membank -- npx -y @membank/mcp"
792
780
  };
793
781
  },
794
782
  async write(resolver, run, { overwrite = false } = {}) {
@@ -821,6 +809,10 @@ const writers$1 = {
821
809
  assertCliFound(add, "claude", addCmd);
822
810
  if (add.exitCode !== 0) throw new CommandError(`claude mcp add failed: ${add.stderr || add.stdout}`, addCmd);
823
811
  return { status: "written" };
812
+ },
813
+ async isStale(resolver) {
814
+ const cfg = readJson(join(resolver.home(), ".claude.json"));
815
+ return hasKey(cfg.mcpServers, "membank") && jsonContainsStalePattern(cfg.mcpServers);
824
816
  }
825
817
  },
826
818
  copilot: {
@@ -840,22 +832,22 @@ const writers$1 = {
840
832
  ...MaybeJsonObjectSchema.parse(cfg.mcpServers),
841
833
  membank: {
842
834
  command: "npx",
843
- args: [
844
- "-y",
845
- "@membank/cli",
846
- "--mcp"
847
- ]
835
+ args: ["-y", "@membank/mcp"]
848
836
  }
849
837
  }
850
838
  });
851
839
  return { status: "written" };
840
+ },
841
+ async isStale(resolver) {
842
+ const cfg = readJson(join(resolver.home(), ".copilot", "mcp-config.json"));
843
+ return hasKey(cfg.mcpServers, "membank") && jsonContainsStalePattern(cfg.mcpServers);
852
844
  }
853
845
  },
854
846
  codex: {
855
847
  preview(_resolver) {
856
848
  return {
857
849
  configPath: null,
858
- cliCommand: "codex mcp add membank -- npx -y @membank/cli --mcp"
850
+ cliCommand: "codex mcp add membank -- npx -y @membank/mcp"
859
851
  };
860
852
  },
861
853
  async write(_resolver, run, { overwrite = false } = {}) {
@@ -887,6 +879,10 @@ const writers$1 = {
887
879
  assertCliFound(add, "codex", addCmd);
888
880
  if (add.exitCode !== 0) throw new CommandError(`codex mcp add failed: ${add.stderr || add.stdout}`, addCmd);
889
881
  return { status: "written" };
882
+ },
883
+ async isStale(_resolver, run) {
884
+ const list = await run("codex", ["mcp", "list"]);
885
+ return list.exitCode === 0 && list.stdout.includes("membank") && list.stdout.includes("@membank/cli");
890
886
  }
891
887
  },
892
888
  opencode: {
@@ -909,13 +905,16 @@ const writers$1 = {
909
905
  command: [
910
906
  "npx",
911
907
  "-y",
912
- "@membank/cli",
913
- "--mcp"
908
+ "@membank/mcp"
914
909
  ]
915
910
  }
916
911
  }
917
912
  });
918
913
  return { status: "written" };
914
+ },
915
+ async isStale(resolver) {
916
+ const cfg = readJson(join(resolver.home(), ".config", "opencode", "opencode.json"));
917
+ return hasKey(cfg.mcp, "membank") && jsonContainsStalePattern(cfg.mcp);
919
918
  }
920
919
  }
921
920
  };
@@ -937,6 +936,15 @@ var HarnessConfigWriter = class {
937
936
  if (!writer) throw new Error(`Unknown harness: ${harness}`);
938
937
  return writer.write(this.#resolver, this.#run, { overwrite });
939
938
  }
939
+ async isStale(harness) {
940
+ const writer = writers$1[harness];
941
+ if (!writer) return false;
942
+ try {
943
+ return await writer.isStale(this.#resolver, this.#run);
944
+ } catch {
945
+ return false;
946
+ }
947
+ }
940
948
  };
941
949
  //#endregion
942
950
  //#region src/setup/injection-hook-writer.ts
@@ -1229,76 +1237,6 @@ var InjectionHookWriter = class {
1229
1237
  }
1230
1238
  };
1231
1239
  //#endregion
1232
- //#region src/setup/model-downloader.ts
1233
- const MODEL_NAME = "Xenova/bge-small-en-v1.5";
1234
- var ModelDownloadError = class extends Error {
1235
- constructor(message, options) {
1236
- super(message, options);
1237
- this.name = "ModelDownloadError";
1238
- }
1239
- };
1240
- function defaultModelPath() {
1241
- return join(homedir(), ".membank", "models");
1242
- }
1243
- function isCached(modelPath) {
1244
- if (!existsSync(modelPath)) return false;
1245
- try {
1246
- return readdirSync(modelPath).length > 0;
1247
- } catch {
1248
- return false;
1249
- }
1250
- }
1251
- var ModelDownloader = class extends EventEmitter {
1252
- modelPath;
1253
- constructor(modelPath) {
1254
- super();
1255
- this.modelPath = modelPath ?? defaultModelPath();
1256
- }
1257
- isAlreadyCached() {
1258
- return isCached(this.modelPath);
1259
- }
1260
- get cachePath() {
1261
- return this.modelPath;
1262
- }
1263
- async download() {
1264
- if (isCached(this.modelPath)) return { skipped: true };
1265
- const startTime = Date.now();
1266
- let lastDownloadedBytes = 0;
1267
- let lastTimestamp = startTime;
1268
- try {
1269
- await pipeline("feature-extraction", MODEL_NAME, {
1270
- cache_dir: this.modelPath,
1271
- progress_callback: (event) => {
1272
- if (event.status !== "progress" || event.total == null || event.loaded == null) return;
1273
- const totalBytes = event.total;
1274
- const downloadedBytes = event.loaded;
1275
- const percentage = totalBytes > 0 ? downloadedBytes / totalBytes * 100 : 0;
1276
- const now = Date.now();
1277
- const elapsedSinceLastMs = now - lastTimestamp;
1278
- const bytesSinceLast = downloadedBytes - lastDownloadedBytes;
1279
- let estimatedSecondsRemaining = 0;
1280
- if (elapsedSinceLastMs > 0 && bytesSinceLast > 0) {
1281
- const bytesPerMs = bytesSinceLast / elapsedSinceLastMs;
1282
- estimatedSecondsRemaining = (totalBytes - downloadedBytes) / bytesPerMs / 1e3;
1283
- }
1284
- lastDownloadedBytes = downloadedBytes;
1285
- lastTimestamp = now;
1286
- const progress = {
1287
- totalBytes,
1288
- downloadedBytes,
1289
- percentage,
1290
- estimatedSecondsRemaining
1291
- };
1292
- this.emit("progress", progress);
1293
- }
1294
- });
1295
- } catch (err) {
1296
- throw new ModelDownloadError("Failed to download model", { cause: err });
1297
- }
1298
- return { skipped: false };
1299
- }
1300
- };
1301
- //#endregion
1302
1240
  //#region src/setup/harness-detector.ts
1303
1241
  const defaultResolver = { homeDir: homedir };
1304
1242
  function harnessConfigs(resolver) {
@@ -1586,7 +1524,10 @@ var SetupOrchestrator = class {
1586
1524
  };
1587
1525
  //#endregion
1588
1526
  //#region src/index.ts
1589
- if (process.argv.includes("--mcp")) await startServer();
1527
+ if (process.argv.includes("--mcp")) {
1528
+ process.stderr.write("[membank] Deprecation: `membank --mcp` is deprecated. Use: npx @membank/mcp\n");
1529
+ await startServer();
1530
+ }
1590
1531
  const program = new Command();
1591
1532
  program.name("membank").description("LLM memory management system").option("--json", "emit machine-readable JSON only").option("-y, --yes", "skip all confirmation prompts").option("--mcp", "start the MCP stdio server (for harness integration)");
1592
1533
  program.command("query <queryText>").description("search memories by semantic similarity").option("--type <type>", "filter by memory type (correction|preference|decision|learning|fact)").option("--limit <n>", "maximum number of results", "10").option("--include-pinned", "include pinned memories in results (excluded by default)").action(async (queryText, cmdOptions) => {
@@ -1698,7 +1639,35 @@ program.command("inject").description("output session context for harness inject
1698
1639
  process.exit(2);
1699
1640
  }
1700
1641
  });
1701
- program.command("setup").description("detect installed harnesses and write MCP config for each").option("--yes", "skip all confirmation prompts").option("--dry-run", "print planned changes without writing any file").option("--harness <name>", "target only the named harness (skip detection)").action(async (cmdOptions) => {
1642
+ const setupCmd = program.command("setup").description("detect installed harnesses and write MCP config for each").option("--yes", "skip all confirmation prompts").option("--dry-run", "print planned changes without writing any file").option("--harness <name>", "target only the named harness (skip detection)");
1643
+ setupCmd.command("upgrade").description("upgrade harness configs from membank --mcp to standalone membank-mcp").action(async () => {
1644
+ const isJson = program.opts().json === true;
1645
+ const writer = new HarnessConfigWriter();
1646
+ const stale = [];
1647
+ for (const harness of SUPPORTED_HARNESSES) if (await writer.isStale(harness)) stale.push(harness);
1648
+ if (stale.length === 0) {
1649
+ if (isJson) process.stdout.write(`${JSON.stringify({ upgraded: [] })}\n`);
1650
+ else process.stdout.write("All harness configs are already up to date.\n");
1651
+ return;
1652
+ }
1653
+ const upgraded = [];
1654
+ const errors = [];
1655
+ for (const harness of stale) try {
1656
+ await writer.write(harness, { overwrite: true });
1657
+ upgraded.push(harness);
1658
+ if (!isJson) process.stdout.write(` ${chalk.green("✓")} ${harness}\n`);
1659
+ } catch (err) {
1660
+ const msg = err instanceof Error ? err.message : String(err);
1661
+ errors.push(`${harness}: ${msg}`);
1662
+ if (!isJson) process.stderr.write(` ${chalk.red("✗")} ${harness}: ${msg}\n`);
1663
+ }
1664
+ if (isJson) process.stdout.write(`${JSON.stringify({
1665
+ upgraded,
1666
+ errors
1667
+ })}\n`);
1668
+ if (errors.length > 0) process.exit(1);
1669
+ });
1670
+ setupCmd.action(async (cmdOptions) => {
1702
1671
  const globalOpts = program.opts();
1703
1672
  const autoYes = cmdOptions.yes === true || globalOpts.yes === true;
1704
1673
  const formatter = Formatter.create(globalOpts.json === true);
@@ -1792,13 +1761,9 @@ program.command("migrate <mode> [name]").description("list or run a named data m
1792
1761
  process.exit(2);
1793
1762
  }
1794
1763
  });
1795
- program.command("dashboard").description("open the memory management dashboard in the browser").option("--port <port>", "port to listen on (default: 3847, fallback to random)").action(async (cmdOptions) => {
1796
- try {
1797
- await dashboardCommand(cmdOptions);
1798
- } catch (err) {
1799
- process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
1800
- process.exit(2);
1801
- }
1764
+ program.command("dashboard").description("(deprecated) open the memory management dashboard").allowUnknownOption().action(() => {
1765
+ process.stderr.write("The dashboard is now a standalone package.\nRun: npx @membank/dashboard\n");
1766
+ process.exit(1);
1802
1767
  });
1803
1768
  const configCmd = program.command("config").description("manage membank configuration");
1804
1769
  configCmd.command("get <key>").description("print a config value as JSON").action((key) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@membank/cli",
3
- "version": "0.11.1",
3
+ "version": "0.12.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -11,19 +11,18 @@
11
11
  "membank": "./dist/index.mjs"
12
12
  },
13
13
  "files": [
14
- "dist"
14
+ "dist",
15
+ "!dist/**/*.map"
15
16
  ],
16
17
  "dependencies": {
17
18
  "@clack/prompts": "^1.3.0",
18
- "@huggingface/transformers": "^4.2.0",
19
19
  "chalk": "^5.6.2",
20
20
  "cli-table3": "^0.6.5",
21
21
  "commander": "^14.0.3",
22
22
  "ora": "^9.4.0",
23
23
  "zod": "^4.4.3",
24
- "@membank/core": "0.9.3",
25
- "@membank/dashboard": "0.5.4",
26
- "@membank/mcp": "0.12.1"
24
+ "@membank/core": "0.10.0",
25
+ "@membank/mcp": "0.13.0"
27
26
  },
28
27
  "devDependencies": {
29
28
  "@types/node": "^25.6.0",