@membank/cli 0.14.2 → 0.16.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 (2) hide show
  1. package/dist/index.mjs +331 -56
  2. package/package.json +3 -3
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { cancel, confirm, intro, isCancel, multiselect, note, outro } from "@clack/prompts";
3
- import { ACTIVITY_EVENT_TYPE_VALUES, ActivityEventTypeSchema, DatabaseManager, EmbeddingService, GLOBAL_SCOPE_HASH, MIGRATIONS, MODEL_NAME, MemoryTypeSchema, MemoryTypeSchema as MemoryTypeSchema$1, ModelDownloader, PIN_BUDGET_THRESHOLD, QueryEngine, SessionContextBuilder, createActivityLogger, createActivityRepository, createMemoryRepository, createProjectRepository, createSynthesisRepository, deleteMemory, listEvents, resolveProject, runScopeToProjectsMigration, saveMemory } from "@membank/core";
3
+ import { ACTIVITY_EVENT_TYPE_VALUES, ActivityEventTypeSchema, DatabaseManager, EmbeddingService, GLOBAL_PROJECT_NAME, GLOBAL_SCOPE_HASH, MIGRATIONS, MODEL_NAME, MemoryTypeSchema, MemoryTypeSchema as MemoryTypeSchema$1, ModelDownloader, PIN_BUDGET_THRESHOLD, QueryEngine, SessionContextBuilder, createActivityLogger, createActivityRepository, createMemoryRepository, createProjectRepository, createSynthesisRepository, deleteMemory, listEvents, resolveProject, revertMemory, revertSynthesis, runScopeToProjectsMigration, saveMemory } from "@membank/core";
4
4
  import { runExtraction, runSynthesis, startServer } from "@membank/mcp";
5
5
  import chalk from "chalk";
6
6
  import { Command } from "commander";
@@ -9,6 +9,7 @@ import { z } from "zod";
9
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 { diffLines } from "@membank/core/client";
12
13
  import Table from "cli-table3";
13
14
  import { execFile } from "node:child_process";
14
15
  import { promisify } from "node:util";
@@ -43,8 +44,8 @@ async function activityCommand(options, formatter) {
43
44
  }
44
45
  const events = listEvents({
45
46
  scope,
46
- type: validatedType,
47
- since: options.since,
47
+ ...validatedType !== void 0 && { type: validatedType },
48
+ ...options.since !== void 0 && { since: options.since },
48
49
  limit: options.limit !== void 0 ? parseInt(options.limit, 10) : 50
49
50
  }, activityRepo);
50
51
  if (formatter.isJson) {
@@ -380,11 +381,16 @@ async function importCommand(filePath, db, formatter, prompt) {
380
381
  }
381
382
  //#endregion
382
383
  //#region src/commands/inject.ts
383
- const MEMORY_GUIDANCE = ["Save (call save_memory) when: (1) user states a preference or makes a decision; (2) user corrects you; (3) you discover a working fix after a tool error; (4) you learn a non-obvious project fact. Type ∈ correction|preference|decision|learning|fact. When unsure, save.", "Query (call query_memory) before: answering anything that touches prior decisions, and before exploration tasks (file reads, searches, web lookups) where past corrections or preferences may apply. Skip when clearly irrelevant (e.g. trivial arithmetic). Soft guideline, not a hard rule."].join("\n");
384
+ const SAVE_GUIDANCE = "Save (call save_memory) when: (1) user states a preference or makes a decision; (2) user corrects you; (3) you discover a working fix after a tool error; (4) you learn a non-obvious project fact. Type ∈ correction|preference|decision|learning|fact. When unsure, save.";
385
+ const QUERY_GUIDANCE = "Query (call query_memory) before: answering anything that touches prior decisions, and before exploration tasks (file reads, searches, web lookups) where past corrections or preferences may apply. Skip when clearly irrelevant (e.g. trivial arithmetic). Soft guideline, not a hard rule.";
386
+ const MEMORY_GUIDANCE = [SAVE_GUIDANCE, QUERY_GUIDANCE].join("\n");
387
+ function buildGuidance(harness) {
388
+ return harness === "claude-code" ? QUERY_GUIDANCE : MEMORY_GUIDANCE;
389
+ }
384
390
  function xmlEscape(s) {
385
391
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
386
392
  }
387
- function formatContext(ctx) {
393
+ function formatContext(ctx, guidance) {
388
394
  const parts = [];
389
395
  const statParts = Object.entries(ctx.stats).filter(([, count]) => count > 0).map(([type, count]) => `${count} ${type}${count !== 1 ? "s" : ""}`);
390
396
  if (statParts.length > 0) parts.push(`<memory-stats>\n${statParts.join(", ")}\n</memory-stats>`);
@@ -396,7 +402,7 @@ function formatContext(ctx) {
396
402
  parts.push(`<pinned-memories>\n${memLines.join("\n")}\n</pinned-memories>`);
397
403
  }
398
404
  }
399
- parts.push(`<memory-guidance>\n${MEMORY_GUIDANCE}\n</memory-guidance>`);
405
+ parts.push(`<memory-guidance>\n${guidance}\n</memory-guidance>`);
400
406
  return parts.join("\n");
401
407
  }
402
408
  function outputAdditionalContext(text, harness, eventName) {
@@ -416,7 +422,7 @@ function outputAdditionalContext(text, harness, eventName) {
416
422
  function pickBestSynthesis(globalSynthesis, projectSynthesis) {
417
423
  return projectSynthesis ?? globalSynthesis;
418
424
  }
419
- async function buildText() {
425
+ async function buildText(harness) {
420
426
  const resolved = await resolveProject();
421
427
  const db = DatabaseManager.open();
422
428
  try {
@@ -425,13 +431,13 @@ async function buildText() {
425
431
  const globalRow = synthRepo.getSynthesis(GLOBAL_SCOPE_HASH);
426
432
  const projectRow = synthRepo.getSynthesis(resolved.hash);
427
433
  const synthesis = pickBestSynthesis(globalRow?.inFlightSince === null ? globalRow.content : void 0, projectRow?.inFlightSince === null ? projectRow.content : void 0);
428
- return formatContext(builder.getSessionContext(resolved.hash, synthesis));
434
+ return formatContext(builder.getSessionContext(resolved.hash, synthesis), buildGuidance(harness));
429
435
  } finally {
430
436
  db.close();
431
437
  }
432
438
  }
433
439
  async function handleEvent(harness, eventName) {
434
- const text = await buildText().catch((err) => {
440
+ const text = await buildText(harness).catch((err) => {
435
441
  const msg = err instanceof Error ? err.message : String(err);
436
442
  process.stderr.write(`membank inject: ${msg}\n`);
437
443
  return null;
@@ -447,6 +453,7 @@ async function injectCommand(opts) {
447
453
  return;
448
454
  }
449
455
  if (opts.event === "user-prompt-submit") {
456
+ if (harness === "claude-code") process.exit(0);
450
457
  await handleEvent(harness, "UserPromptSubmit");
451
458
  return;
452
459
  }
@@ -458,8 +465,8 @@ async function listCommand(options, formatter) {
458
465
  const db = DatabaseManager.open();
459
466
  try {
460
467
  const memories = createMemoryRepository(db, createProjectRepository(db)).list({
461
- type: options.type !== void 0 ? MemoryTypeSchema$1.parse(options.type) : void 0,
462
- pinned: options.pinned
468
+ ...options.type !== void 0 && { type: MemoryTypeSchema$1.parse(options.type) },
469
+ ...options.pinned !== void 0 && { pinned: options.pinned }
463
470
  });
464
471
  formatter.outputMemories(memories);
465
472
  } finally {
@@ -467,6 +474,102 @@ async function listCommand(options, formatter) {
467
474
  }
468
475
  }
469
476
  //#endregion
477
+ //#region src/commands/memory/diff.ts
478
+ function memoryDiffCommand(id, v1, v2, db, formatter) {
479
+ const repo = createMemoryRepository(db, createProjectRepository(db));
480
+ const version1 = repo.getVersion(id, v1);
481
+ if (version1 === void 0) {
482
+ formatter.error(`Version ${v1} not found for memory: ${id}`);
483
+ process.exit(1);
484
+ }
485
+ const version2 = repo.getVersion(id, v2);
486
+ if (version2 === void 0) {
487
+ formatter.error(`Version ${v2} not found for memory: ${id}`);
488
+ process.exit(1);
489
+ }
490
+ const diff = diffLines(version1.content, version2.content);
491
+ if (formatter.isJson) {
492
+ process.stdout.write(`${JSON.stringify(diff)}\n`);
493
+ return;
494
+ }
495
+ process.stdout.write(`--- version ${v1}\n+++ version ${v2}\n`);
496
+ for (const entry of diff) if (entry.kind === "added") process.stdout.write(chalk.green(`+ ${entry.line}\n`));
497
+ else if (entry.kind === "removed") process.stdout.write(chalk.red(`- ${entry.line}\n`));
498
+ else process.stdout.write(` ${entry.line}\n`);
499
+ }
500
+ //#endregion
501
+ //#region src/commands/memory/history.ts
502
+ function truncate$2(str, max) {
503
+ return str.length > max ? `${str.slice(0, max - 1)}…` : str;
504
+ }
505
+ function memoryHistoryCommand(id, db, formatter) {
506
+ const versions = createMemoryRepository(db, createProjectRepository(db)).listVersions(id);
507
+ if (versions.length === 0) {
508
+ formatter.error(`No version history found for memory: ${id}`);
509
+ process.exit(1);
510
+ }
511
+ if (formatter.isJson) {
512
+ process.stdout.write(`${JSON.stringify(versions)}\n`);
513
+ return;
514
+ }
515
+ const table = new Table({
516
+ head: [
517
+ chalk.bold("version"),
518
+ chalk.bold("created_at"),
519
+ chalk.bold("preview")
520
+ ],
521
+ style: { head: [] }
522
+ });
523
+ for (const v of versions) table.push([
524
+ String(v.version),
525
+ v.createdAt,
526
+ truncate$2(v.content.replace(/\n/g, " "), 60)
527
+ ]);
528
+ process.stdout.write(`${table.toString()}\n`);
529
+ }
530
+ //#endregion
531
+ //#region src/commands/memory/revert.ts
532
+ async function memoryRevertCommand(id, version, db, formatter, prompt) {
533
+ const repo = createMemoryRepository(db, createProjectRepository(db));
534
+ if (repo.findById(id) === void 0) {
535
+ formatter.error(`Memory not found: ${id}`);
536
+ process.exit(1);
537
+ }
538
+ if (repo.getVersion(id, version) === void 0) {
539
+ formatter.error(`Version ${version} not found for memory: ${id}`);
540
+ process.exit(1);
541
+ }
542
+ if (!await prompt.confirm(`Revert memory ${id} to version ${version}?`)) return;
543
+ await revertMemory(id, version, {
544
+ repo,
545
+ embedder: new EmbeddingService(),
546
+ activityLogger: createActivityLogger(db)
547
+ });
548
+ process.stdout.write(`${chalk.green("✓")} Reverted memory ${chalk.dim(id)} to version ${version}\n`);
549
+ }
550
+ //#endregion
551
+ //#region src/commands/memory/show.ts
552
+ function memoryShowCommand(id, db, formatter, opts) {
553
+ const repo = createMemoryRepository(db, createProjectRepository(db));
554
+ if (opts.version !== void 0) {
555
+ const v = repo.getVersion(id, opts.version);
556
+ if (v === void 0) {
557
+ formatter.error(`Version ${opts.version} not found for memory: ${id}`);
558
+ process.exit(1);
559
+ }
560
+ if (formatter.isJson) process.stdout.write(`${JSON.stringify(v)}\n`);
561
+ else process.stdout.write(`${v.content}\n`);
562
+ return;
563
+ }
564
+ const memory = repo.findById(id);
565
+ if (memory === void 0) {
566
+ formatter.error(`Memory not found: ${id}`);
567
+ process.exit(1);
568
+ }
569
+ if (formatter.isJson) process.stdout.write(`${JSON.stringify(memory)}\n`);
570
+ else process.stdout.write(`${memory.content}\n`);
571
+ }
572
+ //#endregion
470
573
  //#region src/commands/migrate.ts
471
574
  async function migrateCommand(mode, name, formatter) {
472
575
  if (mode === "list") {
@@ -568,9 +671,85 @@ async function statsCommand(formatter) {
568
671
  }
569
672
  }
570
673
  //#endregion
571
- //#region src/commands/synthesize.ts
674
+ //#region src/commands/synthesize/resolve-scope.ts
675
+ function resolveScope(scope, db) {
676
+ if (scope === GLOBAL_PROJECT_NAME) return GLOBAL_SCOPE_HASH;
677
+ if (/^[0-9a-f]{16}$/.test(scope)) return scope;
678
+ const project = createProjectRepository(db).getByName(scope);
679
+ return project !== void 0 ? project.scopeHash : scope;
680
+ }
681
+ //#endregion
682
+ //#region src/commands/synthesize/diff.ts
683
+ function synthesizeDiffCommand(v1, v2, opts, formatter) {
684
+ const db = DatabaseManager.open();
685
+ try {
686
+ const scope = opts.scope ?? GLOBAL_PROJECT_NAME;
687
+ const resolvedScope = resolveScope(scope, db);
688
+ const repo = createSynthesisRepository(db);
689
+ const version1 = repo.getVersion(resolvedScope, v1);
690
+ if (version1 === void 0) {
691
+ formatter.error(`Version ${v1} not found for scope: ${scope}`);
692
+ process.exit(1);
693
+ }
694
+ const version2 = repo.getVersion(resolvedScope, v2);
695
+ if (version2 === void 0) {
696
+ formatter.error(`Version ${v2} not found for scope: ${scope}`);
697
+ process.exit(1);
698
+ }
699
+ const diff = diffLines(version1.content, version2.content);
700
+ if (formatter.isJson) {
701
+ process.stdout.write(`${JSON.stringify(diff)}\n`);
702
+ return;
703
+ }
704
+ process.stdout.write(`--- version ${v1}\n+++ version ${v2}\n`);
705
+ for (const entry of diff) if (entry.kind === "added") process.stdout.write(chalk.green(`+ ${entry.line}\n`));
706
+ else if (entry.kind === "removed") process.stdout.write(chalk.red(`- ${entry.line}\n`));
707
+ else process.stdout.write(` ${entry.line}\n`);
708
+ } finally {
709
+ db.close();
710
+ }
711
+ }
712
+ //#endregion
713
+ //#region src/commands/synthesize/history.ts
714
+ function truncate$1(str, max) {
715
+ return str.length > max ? `${str.slice(0, max - 1)}…` : str;
716
+ }
717
+ function synthesizeHistoryCommand(opts, formatter) {
718
+ const db = DatabaseManager.open();
719
+ try {
720
+ const scope = opts.scope ?? GLOBAL_PROJECT_NAME;
721
+ const resolvedScope = resolveScope(scope, db);
722
+ const versions = createSynthesisRepository(db).listVersions(resolvedScope);
723
+ if (versions.length === 0) {
724
+ formatter.error(`No version history found for scope: ${scope}`);
725
+ process.exit(1);
726
+ }
727
+ if (formatter.isJson) {
728
+ process.stdout.write(`${JSON.stringify(versions)}\n`);
729
+ return;
730
+ }
731
+ const table = new Table({
732
+ head: [
733
+ chalk.bold("version"),
734
+ chalk.bold("synthesized_at"),
735
+ chalk.bold("preview")
736
+ ],
737
+ style: { head: [] }
738
+ });
739
+ for (const v of versions) table.push([
740
+ String(v.version),
741
+ new Date(v.synthesizedAt).toLocaleString(),
742
+ truncate$1(v.content.replace(/\n/g, " "), 60)
743
+ ]);
744
+ process.stdout.write(`${table.toString()}\n`);
745
+ } finally {
746
+ db.close();
747
+ }
748
+ }
749
+ //#endregion
750
+ //#region src/commands/synthesize/index.ts
572
751
  async function synthesizeRunCommand(opts, formatter) {
573
- const scope = opts.scope ?? "global";
752
+ const scope = opts.scope ?? GLOBAL_PROJECT_NAME;
574
753
  if (!formatter.isJson) process.stdout.write(`Running synthesis for scope: ${scope}\n`);
575
754
  const content = await runSynthesis(scope);
576
755
  if (formatter.isJson) process.stdout.write(`${JSON.stringify({
@@ -582,14 +761,26 @@ async function synthesizeRunCommand(opts, formatter) {
582
761
  function synthesizeShowCommand(opts, formatter) {
583
762
  const db = DatabaseManager.open();
584
763
  try {
585
- const scope = opts.scope ?? "global";
586
- let resolvedScope = scope;
587
- if (scope === "global") resolvedScope = GLOBAL_SCOPE_HASH;
588
- else if (!/^[0-9a-f]{16}$/.test(scope)) {
589
- const project = createProjectRepository(db).getByName(scope);
590
- if (project !== void 0) resolvedScope = project.scopeHash;
764
+ const scope = opts.scope ?? GLOBAL_PROJECT_NAME;
765
+ const resolvedScope = resolveScope(scope, db);
766
+ const repo = createSynthesisRepository(db);
767
+ if (opts.version !== void 0) {
768
+ const version = repo.getVersion(resolvedScope, opts.version);
769
+ if (version === void 0) {
770
+ if (formatter.isJson) process.stdout.write(`${JSON.stringify(null)}\n`);
771
+ else process.stdout.write(`Version ${opts.version} not found for scope: ${scope}\n`);
772
+ return;
773
+ }
774
+ if (formatter.isJson) process.stdout.write(`${JSON.stringify(version)}\n`);
775
+ else {
776
+ process.stdout.write(`\nScope: ${resolvedScope} (version ${version.version})\n`);
777
+ process.stdout.write(`Synthesized: ${new Date(version.synthesizedAt).toLocaleString()}\n`);
778
+ process.stdout.write(`Archived: ${new Date(version.createdAt).toLocaleString()}\n`);
779
+ process.stdout.write(`\n${version.content}\n\n`);
780
+ }
781
+ return;
591
782
  }
592
- const synthesis = createSynthesisRepository(db).getSynthesis(resolvedScope);
783
+ const synthesis = repo.getSynthesis(resolvedScope);
593
784
  if (synthesis === void 0) {
594
785
  if (formatter.isJson) process.stdout.write(`${JSON.stringify(null)}\n`);
595
786
  else process.stdout.write(`No synthesis found for scope: ${scope}\n`);
@@ -622,7 +813,7 @@ function synthesizeStatusCommand(formatter) {
622
813
  }
623
814
  process.stdout.write("\n");
624
815
  for (const s of syntheses) {
625
- const displayScope = (s.scope !== GLOBAL_SCOPE_HASH ? projectRepo.getByHash(s.scope) : void 0)?.name ?? (s.scope === GLOBAL_SCOPE_HASH ? "global" : s.scope);
816
+ const displayScope = (s.scope !== GLOBAL_SCOPE_HASH ? projectRepo.getByHash(s.scope) : void 0)?.name ?? (s.scope === GLOBAL_SCOPE_HASH ? GLOBAL_PROJECT_NAME : s.scope);
626
817
  const inFlight = s.inFlightSince !== null ? " [in-flight]" : "";
627
818
  const synthesized = new Date(s.synthesizedAt).toLocaleString();
628
819
  const expires = new Date(s.expiresAt).toLocaleString();
@@ -636,6 +827,25 @@ function synthesizeStatusCommand(formatter) {
636
827
  }
637
828
  }
638
829
  //#endregion
830
+ //#region src/commands/synthesize/revert.ts
831
+ async function synthesizeRevertCommand(version, opts, formatter, prompt) {
832
+ const db = DatabaseManager.open();
833
+ try {
834
+ const scope = opts.scope ?? GLOBAL_PROJECT_NAME;
835
+ const resolvedScope = resolveScope(scope, db);
836
+ const repo = createSynthesisRepository(db);
837
+ if (repo.getVersion(resolvedScope, version) === void 0) {
838
+ formatter.error(`Version ${version} not found for scope: ${scope}`);
839
+ process.exit(1);
840
+ }
841
+ if (!await prompt.confirm(`Revert synthesis for scope "${scope}" to version ${version}?`)) return;
842
+ revertSynthesis(resolvedScope, version, repo);
843
+ process.stdout.write(`${chalk.green("✓")} Reverted synthesis for scope ${chalk.dim(scope)} to version ${version}\n`);
844
+ } finally {
845
+ db.close();
846
+ }
847
+ }
848
+ //#endregion
639
849
  //#region src/commands/unpin.ts
640
850
  function unpinCommand(id, db) {
641
851
  const ownDb = db === void 0;
@@ -1133,28 +1343,19 @@ const writers = {
1133
1343
  const cfg = readJson(cfgPath);
1134
1344
  const hooks = MaybeJsonObjectSchema.parse(cfg.hooks) ?? {};
1135
1345
  const sessionStartInner = (Array.isArray(hooks.SessionStart) ? hooks.SessionStart : []).flatMap(getHooksArray);
1136
- const userPromptSubmitInner = (Array.isArray(hooks.UserPromptSubmit) ? hooks.UserPromptSubmit : []).flatMap(getHooksArray);
1137
1346
  const sessionEndInner = (Array.isArray(hooks.SessionEnd) ? hooks.SessionEnd : []).flatMap(getHooksArray);
1138
1347
  return {
1139
1348
  status: "ready",
1140
1349
  configPath: cfgPath,
1141
- hooks: [
1142
- {
1143
- event: "SessionStart",
1144
- command: "npx -y @membank/cli inject --harness claude-code",
1145
- existingCommand: extractInjectCommand(sessionStartInner) || null
1146
- },
1147
- {
1148
- event: "UserPromptSubmit",
1149
- command: "npx -y @membank/cli inject --harness claude-code --event user-prompt-submit",
1150
- existingCommand: extractInjectCommand(userPromptSubmitInner) || null
1151
- },
1152
- {
1153
- event: "SessionEnd",
1154
- command: "npx -y @membank/cli extract --harness claude-code",
1155
- existingCommand: extractInjectCommand(sessionEndInner) || null
1156
- }
1157
- ]
1350
+ hooks: [{
1351
+ event: "SessionStart",
1352
+ command: "npx -y @membank/cli inject --harness claude-code",
1353
+ existingCommand: extractInjectCommand(sessionStartInner) || null
1354
+ }, {
1355
+ event: "SessionEnd",
1356
+ command: "npx -y @membank/cli extract --harness claude-code",
1357
+ existingCommand: extractInjectCommand(sessionEndInner) || null
1358
+ }]
1158
1359
  };
1159
1360
  },
1160
1361
  write(resolver, events) {
@@ -1164,6 +1365,7 @@ const writers = {
1164
1365
  const newHooks = { ...hooks };
1165
1366
  pruneNestedEvent(newHooks, "PostToolUseFailure");
1166
1367
  pruneNestedEvent(newHooks, "Stop");
1368
+ pruneNestedEvent(newHooks, "UserPromptSubmit");
1167
1369
  if (events.includes("SessionStart")) newHooks.SessionStart = [...filterOutMembank(Array.isArray(hooks.SessionStart) ? hooks.SessionStart : []), {
1168
1370
  matcher: "",
1169
1371
  hooks: [{
@@ -1171,13 +1373,6 @@ const writers = {
1171
1373
  command: "npx -y @membank/cli inject --harness claude-code"
1172
1374
  }]
1173
1375
  }];
1174
- if (events.includes("UserPromptSubmit")) newHooks.UserPromptSubmit = [...filterOutMembank(Array.isArray(hooks.UserPromptSubmit) ? hooks.UserPromptSubmit : []), {
1175
- matcher: "",
1176
- hooks: [{
1177
- type: "command",
1178
- command: "npx -y @membank/cli inject --harness claude-code --event user-prompt-submit"
1179
- }]
1180
- }];
1181
1376
  if (events.includes("SessionEnd")) newHooks.SessionEnd = [...filterOutMembank(Array.isArray(hooks.SessionEnd) ? hooks.SessionEnd : []), {
1182
1377
  matcher: "clear|resume|logout|prompt_input_exit|other",
1183
1378
  hooks: [{
@@ -1750,9 +1945,9 @@ program.command("inject").description("output session context for harness inject
1750
1945
  program.command("extract").description("(internal) run session-end memory extraction; reads the harness's SessionEnd hook payload from stdin").option("--harness <name>", "harness whose session-end hook payload is on stdin (only claude-code is supported today)", "claude-code").option("--session <id>", "session id (otherwise read from stdin)").option("--transcript <path>", "transcript JSONL path (otherwise read from stdin)").action(async (cmdOptions) => {
1751
1946
  try {
1752
1947
  await extractCommand({
1753
- harness: cmdOptions.harness,
1754
- sessionId: cmdOptions.session,
1755
- transcript: cmdOptions.transcript
1948
+ ...cmdOptions.harness !== void 0 && { harness: cmdOptions.harness },
1949
+ ...cmdOptions.session !== void 0 && { sessionId: cmdOptions.session },
1950
+ ...cmdOptions.transcript !== void 0 && { transcript: cmdOptions.transcript }
1756
1951
  });
1757
1952
  } catch (err) {
1758
1953
  process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
@@ -1834,16 +2029,16 @@ setupCmd.action(async (cmdOptions) => {
1834
2029
  writer,
1835
2030
  hookWriter,
1836
2031
  prompter: (question) => promptHelper.confirm(question),
1837
- harnessSelector,
2032
+ ...harnessSelector !== void 0 && { harnessSelector },
1838
2033
  modelDownloader: new ModelDownloader(),
1839
- out: formatter.isJson ? void 0 : decoratedOut,
2034
+ ...!formatter.isJson && { out: decoratedOut },
1840
2035
  synthesisOptIn: true
1841
2036
  });
1842
2037
  try {
1843
2038
  const results = await orchestrator.run({
1844
2039
  yes: autoYes,
1845
- dryRun: cmdOptions.dryRun,
1846
- harness: cmdOptions.harness,
2040
+ ...cmdOptions.dryRun !== void 0 && { dryRun: cmdOptions.dryRun },
2041
+ ...cmdOptions.harness !== void 0 && { harness: cmdOptions.harness },
1847
2042
  json: formatter.isJson
1848
2043
  });
1849
2044
  if (!formatter.isJson && !cmdOptions.dryRun && results.length > 0) {
@@ -1909,11 +2104,14 @@ synthesizeCmd.command("run").description("trigger a synthesis run for a scope").
1909
2104
  process.exit(2);
1910
2105
  }
1911
2106
  });
1912
- synthesizeCmd.command("show").description("display current synthesis for a scope").option("--scope <scope>", "scope to show (default: global)").action((cmdOptions) => {
2107
+ synthesizeCmd.command("show").description("display current synthesis for a scope, or a specific archived version").option("--scope <scope>", "scope to show (default: global)").option("--version <n>", "show this archived version number instead of the active synthesis").action((cmdOptions) => {
1913
2108
  const globalOpts = program.opts();
1914
2109
  const formatter = Formatter.create(globalOpts.json === true);
1915
2110
  try {
1916
- synthesizeShowCommand(cmdOptions, formatter);
2111
+ synthesizeShowCommand({
2112
+ ...cmdOptions.scope !== void 0 && { scope: cmdOptions.scope },
2113
+ ...cmdOptions.version !== void 0 && { version: Number(cmdOptions.version) }
2114
+ }, formatter);
1917
2115
  } catch (err) {
1918
2116
  formatter.error(err instanceof Error ? err.message : String(err));
1919
2117
  process.exit(2);
@@ -1929,13 +2127,90 @@ synthesizeCmd.command("status").description("show all scopes with synthesis stat
1929
2127
  process.exit(2);
1930
2128
  }
1931
2129
  });
2130
+ synthesizeCmd.command("history").description("list archived synthesis versions for a scope").option("--scope <scope>", "scope to list history for (default: global)").action((cmdOptions) => {
2131
+ const globalOpts = program.opts();
2132
+ const formatter = Formatter.create(globalOpts.json === true);
2133
+ try {
2134
+ synthesizeHistoryCommand(cmdOptions, formatter);
2135
+ } catch (err) {
2136
+ formatter.error(err instanceof Error ? err.message : String(err));
2137
+ process.exit(2);
2138
+ }
2139
+ });
2140
+ synthesizeCmd.command("diff <v1> <v2>").description("show a line diff between two archived synthesis versions").option("--scope <scope>", "scope to diff (default: global)").action((v1, v2, cmdOptions) => {
2141
+ const globalOpts = program.opts();
2142
+ const formatter = Formatter.create(globalOpts.json === true);
2143
+ try {
2144
+ synthesizeDiffCommand(Number(v1), Number(v2), cmdOptions, formatter);
2145
+ } catch (err) {
2146
+ formatter.error(err instanceof Error ? err.message : String(err));
2147
+ process.exit(2);
2148
+ }
2149
+ });
2150
+ synthesizeCmd.command("revert <version>").description("revert the active synthesis to a previous archived version (records a new version)").option("--scope <scope>", "scope to revert (default: global)").action(async (version, cmdOptions) => {
2151
+ const globalOpts = program.opts();
2152
+ const formatter = Formatter.create(globalOpts.json === true);
2153
+ const prompt = new PromptHelper(globalOpts.yes === true);
2154
+ try {
2155
+ await synthesizeRevertCommand(Number(version), cmdOptions, formatter, prompt);
2156
+ } catch (err) {
2157
+ formatter.error(err instanceof Error ? err.message : String(err));
2158
+ process.exit(2);
2159
+ }
2160
+ });
2161
+ const memoryCmd = program.command("memory").description("view and manage memory version history");
2162
+ memoryCmd.command("history <id>").description("list version history for a memory").action((id) => {
2163
+ const globalOpts = program.opts();
2164
+ const formatter = Formatter.create(globalOpts.json === true);
2165
+ const db = DatabaseManager.open();
2166
+ try {
2167
+ memoryHistoryCommand(id, db, formatter);
2168
+ } catch (err) {
2169
+ formatter.error(err instanceof Error ? err.message : String(err));
2170
+ process.exit(2);
2171
+ }
2172
+ });
2173
+ memoryCmd.command("show <id>").description("show content of a memory, optionally at a specific version").option("--version <n>", "show this version number instead of current content").action((id, cmdOptions) => {
2174
+ const globalOpts = program.opts();
2175
+ const formatter = Formatter.create(globalOpts.json === true);
2176
+ const db = DatabaseManager.open();
2177
+ try {
2178
+ memoryShowCommand(id, db, formatter, { ...cmdOptions.version !== void 0 && { version: Number(cmdOptions.version) } });
2179
+ } catch (err) {
2180
+ formatter.error(err instanceof Error ? err.message : String(err));
2181
+ process.exit(2);
2182
+ }
2183
+ });
2184
+ memoryCmd.command("diff <id> <v1> <v2>").description("show a line diff between two versions of a memory").action((id, v1, v2) => {
2185
+ const globalOpts = program.opts();
2186
+ const formatter = Formatter.create(globalOpts.json === true);
2187
+ const db = DatabaseManager.open();
2188
+ try {
2189
+ memoryDiffCommand(id, Number(v1), Number(v2), db, formatter);
2190
+ } catch (err) {
2191
+ formatter.error(err instanceof Error ? err.message : String(err));
2192
+ process.exit(2);
2193
+ }
2194
+ });
2195
+ memoryCmd.command("revert <id> <version>").description("revert a memory to a previous version (records the revert as a new version)").action(async (id, version) => {
2196
+ const globalOpts = program.opts();
2197
+ const formatter = Formatter.create(globalOpts.json === true);
2198
+ const db = DatabaseManager.open();
2199
+ const prompt = new PromptHelper(globalOpts.yes === true);
2200
+ try {
2201
+ await memoryRevertCommand(id, Number(version), db, formatter, prompt);
2202
+ } catch (err) {
2203
+ formatter.error(err instanceof Error ? err.message : String(err));
2204
+ process.exit(2);
2205
+ }
2206
+ });
1932
2207
  program.command("activity").description("list activity events for the current project (or --global for global memories)").option("--type <event_type>", "filter by event type (memory.created|updated|deleted|flagged|queried)").option("--since <date>", "return events after this ISO date/time").option("--memory-id <id>", "filter by memory id").option("--limit <n>", "maximum number of results (default 50)").option("--global", "show activity for global (sentinel) project").option("--scope <hash>", "show activity for a specific scope hash (advanced)").action(async (cmdOptions) => {
1933
2208
  const globalOpts = program.opts();
1934
2209
  const formatter = Formatter.create(globalOpts.json === true);
1935
2210
  try {
1936
2211
  await activityCommand({
1937
2212
  ...cmdOptions,
1938
- json: globalOpts.json
2213
+ ...globalOpts.json !== void 0 && { json: globalOpts.json }
1939
2214
  }, formatter);
1940
2215
  } catch (err) {
1941
2216
  formatter.error(err instanceof Error ? err.message : String(err));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@membank/cli",
3
- "version": "0.14.2",
3
+ "version": "0.16.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,8 +21,8 @@
21
21
  "commander": "^14.0.3",
22
22
  "ora": "^9.4.0",
23
23
  "zod": "^4.4.3",
24
- "@membank/mcp": "0.14.3",
25
- "@membank/core": "0.12.1"
24
+ "@membank/core": "0.14.0",
25
+ "@membank/mcp": "0.16.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/node": "^25.6.0",