@membank/cli 0.9.0 → 0.10.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 +286 -38
  2. package/package.json +4 -4
package/dist/index.mjs CHANGED
@@ -1,18 +1,18 @@
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, ProjectRepository, QueryEngine, SessionContextBuilder, TagsJsonSchema as TagsRowSchema, resolveProject, runScopeToProjectsMigration } from "@membank/core";
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";
4
4
  import { 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 { startDashboard } from "@membank/dashboard";
10
9
  import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, writeFileSync } from "node:fs";
10
+ import { homedir, tmpdir } from "node:os";
11
11
  import { dirname, join } from "node:path";
12
+ import { startDashboard } from "@membank/dashboard";
12
13
  import Table from "cli-table3";
13
14
  import { execFile } from "node:child_process";
14
15
  import { promisify } from "node:util";
15
- import { homedir, tmpdir } from "node:os";
16
16
  import { EventEmitter } from "node:events";
17
17
  import { pipeline } from "@huggingface/transformers";
18
18
  import { createInterface } from "node:readline";
@@ -77,6 +77,80 @@ async function addCommand(content, options, formatter, db, embeddingService) {
77
77
  }
78
78
  }
79
79
  //#endregion
80
+ //#region src/config/manager.ts
81
+ function defaultConfigPath() {
82
+ return join(homedir(), ".membank", "config.json");
83
+ }
84
+ function readConfigFile(path) {
85
+ if (!existsSync(path)) return {};
86
+ try {
87
+ const raw = readFileSync(path, "utf-8");
88
+ return JSON.parse(raw);
89
+ } catch {
90
+ return {};
91
+ }
92
+ }
93
+ const ConfigManager = {
94
+ getConfigPath() {
95
+ return defaultConfigPath();
96
+ },
97
+ load() {
98
+ return readConfigFile(ConfigManager.getConfigPath());
99
+ },
100
+ get(key) {
101
+ const config = ConfigManager.load();
102
+ return key.split(".").reduce((obj, part) => {
103
+ if (obj !== null && typeof obj === "object" && part in obj) return obj[part];
104
+ }, config);
105
+ },
106
+ set(key, value) {
107
+ const config = ConfigManager.load();
108
+ const parts = key.split(".");
109
+ let cursor = config;
110
+ for (let i = 0; i < parts.length - 1; i++) {
111
+ const part = parts[i];
112
+ if (!(part in cursor) || typeof cursor[part] !== "object" || cursor[part] === null) cursor[part] = {};
113
+ cursor = cursor[part];
114
+ }
115
+ const lastPart = parts[parts.length - 1];
116
+ cursor[lastPart] = value;
117
+ ConfigManager.write(config);
118
+ },
119
+ write(config) {
120
+ writeFileSync(ConfigManager.getConfigPath(), JSON.stringify(config, null, 2), "utf-8");
121
+ }
122
+ };
123
+ //#endregion
124
+ //#region src/commands/config.ts
125
+ function parseValue(raw) {
126
+ if (raw === "true") return true;
127
+ if (raw === "false") return false;
128
+ const n = Number(raw);
129
+ if (!Number.isNaN(n) && raw.trim() !== "") return n;
130
+ return raw;
131
+ }
132
+ function configGetCommand(key, formatter) {
133
+ const value = ConfigManager.get(key);
134
+ if (formatter.isJson) process.stdout.write(`${JSON.stringify({
135
+ key,
136
+ value
137
+ })}\n`);
138
+ else process.stdout.write(`${JSON.stringify(value)}\n`);
139
+ }
140
+ function configSetCommand(key, rawValue, formatter) {
141
+ const value = parseValue(rawValue);
142
+ ConfigManager.set(key, value);
143
+ if (formatter.isJson) process.stdout.write(`${JSON.stringify({
144
+ key,
145
+ value
146
+ })}\n`);
147
+ else process.stdout.write(`Set ${key} = ${JSON.stringify(value)}\n`);
148
+ }
149
+ function configShowCommand(formatter) {
150
+ const config = ConfigManager.load();
151
+ process.stdout.write(`${JSON.stringify(config, null, formatter.isJson ? 0 : 2)}\n`);
152
+ }
153
+ //#endregion
80
154
  //#region src/commands/dashboard.ts
81
155
  async function dashboardCommand(opts) {
82
156
  await startDashboard({ port: opts.port !== void 0 ? PortSchema.parse(opts.port) : void 0 });
@@ -171,10 +245,13 @@ function formatContext(ctx) {
171
245
  const parts = [];
172
246
  const statParts = Object.entries(ctx.stats).filter(([, count]) => count > 0).map(([type, count]) => `${count} ${type}${count !== 1 ? "s" : ""}`);
173
247
  if (statParts.length > 0) parts.push(`<memory-stats>\n${statParts.join(", ")}\n</memory-stats>`);
174
- const allPinned = [...ctx.pinnedGlobal, ...ctx.pinnedProject];
175
- if (allPinned.length > 0) {
176
- const memLines = allPinned.map((m) => ` <memory type="${m.type}">${xmlEscape(m.content)}</memory>`);
177
- parts.push(`<pinned-memories>\n${memLines.join("\n")}\n</pinned-memories>`);
248
+ if (ctx.synthesis !== void 0 && ctx.synthesis.length > 0) parts.push(`<synthesis>\n${ctx.synthesis}\n</synthesis>`);
249
+ else {
250
+ const allPinned = [...ctx.pinnedGlobal, ...ctx.pinnedProject];
251
+ if (allPinned.length > 0) {
252
+ const memLines = allPinned.map((m) => ` <memory type="${m.type}">${xmlEscape(m.content)}</memory>`);
253
+ parts.push(`<pinned-memories>\n${memLines.join("\n")}\n</pinned-memories>`);
254
+ }
178
255
  }
179
256
  parts.push(`<memory-guidance>\n${MEMORY_GUIDANCE}\n</memory-guidance>`);
180
257
  return parts.join("\n");
@@ -193,11 +270,19 @@ function outputAdditionalContext(text, harness, eventName) {
193
270
  }
194
271
  process.stdout.write(`${text}\n`);
195
272
  }
273
+ function pickBestSynthesis(globalSynthesis, projectSynthesis) {
274
+ return projectSynthesis ?? globalSynthesis;
275
+ }
196
276
  async function buildText() {
197
277
  const resolved = await resolveProject();
198
278
  const db = DatabaseManager.open();
199
279
  try {
200
- return formatContext(new SessionContextBuilder(db).getSessionContext(resolved.hash));
280
+ const builder = new SessionContextBuilder(db);
281
+ const synthRepo = new SynthesisRepository(db);
282
+ const globalRow = synthRepo.getSynthesis("global");
283
+ const projectRow = synthRepo.getSynthesis(resolved.hash);
284
+ const synthesis = pickBestSynthesis(globalRow?.inFlightSince === null ? globalRow.content : void 0, projectRow?.inFlightSince === null ? projectRow.content : void 0);
285
+ return formatContext(builder.getSessionContext(resolved.hash, synthesis));
201
286
  } finally {
202
287
  db.close();
203
288
  }
@@ -222,6 +307,10 @@ async function injectCommand(opts) {
222
307
  await handleEvent(harness, "UserPromptSubmit");
223
308
  return;
224
309
  }
310
+ if (opts.event === "session-stop" || opts.event === "stop") {
311
+ await handleEvent(harness, "Stop");
312
+ return;
313
+ }
225
314
  process.exit(0);
226
315
  }
227
316
  //#endregion
@@ -341,6 +430,69 @@ async function statsCommand(formatter) {
341
430
  }
342
431
  }
343
432
  //#endregion
433
+ //#region src/commands/synthesize.ts
434
+ function hasSynthesesTable(db) {
435
+ return db.db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='syntheses'").get() !== void 0;
436
+ }
437
+ function synthesizeShowCommand(opts, formatter) {
438
+ const db = DatabaseManager.open();
439
+ try {
440
+ if (!hasSynthesesTable(db)) {
441
+ if (formatter.isJson) process.stdout.write(`${JSON.stringify(null)}\n`);
442
+ else process.stdout.write("No synthesis data available.\n");
443
+ return;
444
+ }
445
+ const scope = opts.scope ?? "global";
446
+ const row = db.db.prepare("SELECT * FROM syntheses WHERE scope = ? ORDER BY synthesized_at DESC LIMIT 1").get(scope);
447
+ if (row === void 0) {
448
+ if (formatter.isJson) process.stdout.write(`${JSON.stringify(null)}\n`);
449
+ else process.stdout.write(`No synthesis found for scope: ${scope}\n`);
450
+ return;
451
+ }
452
+ if (formatter.isJson) process.stdout.write(`${JSON.stringify(row)}\n`);
453
+ else {
454
+ process.stdout.write(`\nScope: ${row.scope}\n`);
455
+ if (row.synthesized_at !== null) process.stdout.write(`Synthesized: ${new Date(row.synthesized_at).toLocaleString()}\n`);
456
+ if (row.expires_at !== null) process.stdout.write(`Expires: ${new Date(row.expires_at).toLocaleString()}\n`);
457
+ if (row.in_flight_since !== null) process.stdout.write(`In-flight since: ${new Date(row.in_flight_since).toLocaleString()}\n`);
458
+ process.stdout.write(`\n${row.content}\n\n`);
459
+ }
460
+ } finally {
461
+ db.close();
462
+ }
463
+ }
464
+ function synthesizeStatusCommand(formatter) {
465
+ const db = DatabaseManager.open();
466
+ try {
467
+ if (!hasSynthesesTable(db)) {
468
+ if (formatter.isJson) process.stdout.write(`${JSON.stringify([])}\n`);
469
+ else process.stdout.write("No synthesis data available.\n");
470
+ return;
471
+ }
472
+ const rows = db.db.prepare("SELECT * FROM syntheses ORDER BY scope").all();
473
+ if (formatter.isJson) {
474
+ process.stdout.write(`${JSON.stringify(rows)}\n`);
475
+ return;
476
+ }
477
+ if (rows.length === 0) {
478
+ process.stdout.write("No syntheses found.\n");
479
+ return;
480
+ }
481
+ process.stdout.write("\n");
482
+ for (const row of rows) {
483
+ const inFlight = row.in_flight_since !== null ? " [in-flight]" : "";
484
+ const synthesized = row.synthesized_at !== null ? new Date(row.synthesized_at).toLocaleString() : "(never)";
485
+ const expires = row.expires_at !== null ? new Date(row.expires_at).toLocaleString() : "(none)";
486
+ process.stdout.write(` ${row.scope}${inFlight}\n`);
487
+ process.stdout.write(` synthesized_at: ${synthesized}\n`);
488
+ process.stdout.write(` expires_at: ${expires}\n`);
489
+ }
490
+ process.stdout.write("\n");
491
+ } finally {
492
+ db.close();
493
+ }
494
+ }
495
+ //#endregion
344
496
  //#region src/commands/unpin.ts
345
497
  function unpinCommand(id, db) {
346
498
  const ownDb = db === void 0;
@@ -364,6 +516,10 @@ const TYPE_COLORS = {
364
516
  function colorType(type) {
365
517
  return TYPE_COLORS[type](type);
366
518
  }
519
+ function statsRow(label, value, warn) {
520
+ if (warn) process.stdout.write(` ${chalk.yellow("⚠")} ${label.padEnd(12)} ${value}\n`);
521
+ else process.stdout.write(` ${" ".concat(label).padEnd(14)} ${value}\n`);
522
+ }
367
523
  function truncate(str, max) {
368
524
  return str.length > max ? `${str.slice(0, max - 1)}…` : str;
369
525
  }
@@ -441,8 +597,9 @@ var Formatter = class Formatter {
441
597
  for (const type of types) process.stdout.write(` ${TYPE_COLORS[type](type.padEnd(14))} ${stats.byType[type]}\n`);
442
598
  process.stdout.write(`\n ${chalk.dim("─".repeat(24))}\n`);
443
599
  process.stdout.write(` ${"total".padEnd(14)} ${stats.total}\n`);
444
- if (stats.needsReview > 0) process.stdout.write(` ${chalk.yellow("⚠")} ${"needs_review".padEnd(12)} ${stats.needsReview}\n\n`);
445
- else process.stdout.write(` ${" needs_review".padEnd(14)} ${stats.needsReview}\n\n`);
600
+ statsRow("needs_review", String(stats.needsReview), stats.needsReview > 0);
601
+ statsRow("pin_budget", `${stats.pinBudgetChars} / ${PIN_BUDGET_THRESHOLD} chars`, stats.pinBudgetChars > PIN_BUDGET_THRESHOLD);
602
+ process.stdout.write("\n");
446
603
  }
447
604
  outputQueryResults(results) {
448
605
  if (this.#isJson) {
@@ -512,6 +669,7 @@ var Formatter = class Formatter {
512
669
  //#endregion
513
670
  //#region src/prompt-helper.ts
514
671
  var PromptHelper = class {
672
+ autoConfirm;
515
673
  constructor(autoConfirm) {
516
674
  this.autoConfirm = autoConfirm;
517
675
  }
@@ -811,18 +969,27 @@ const writers = {
811
969
  const hooks = MaybeJsonObjectSchema.parse(cfg.hooks) ?? {};
812
970
  const sessionStartInner = (Array.isArray(hooks.SessionStart) ? hooks.SessionStart : []).flatMap(getHooksArray);
813
971
  const userPromptSubmitInner = (Array.isArray(hooks.UserPromptSubmit) ? hooks.UserPromptSubmit : []).flatMap(getHooksArray);
972
+ const stopInner = (Array.isArray(hooks.Stop) ? hooks.Stop : []).flatMap(getHooksArray);
814
973
  return {
815
974
  status: "ready",
816
975
  configPath: cfgPath,
817
- hooks: [{
818
- event: "SessionStart",
819
- command: "npx -y @membank/cli inject --harness claude-code",
820
- existingCommand: extractInjectCommand(sessionStartInner) || null
821
- }, {
822
- event: "UserPromptSubmit",
823
- command: "npx -y @membank/cli inject --harness claude-code --event user-prompt-submit",
824
- existingCommand: extractInjectCommand(userPromptSubmitInner) || null
825
- }]
976
+ hooks: [
977
+ {
978
+ event: "SessionStart",
979
+ command: "npx -y @membank/cli inject --harness claude-code",
980
+ existingCommand: extractInjectCommand(sessionStartInner) || null
981
+ },
982
+ {
983
+ event: "UserPromptSubmit",
984
+ command: "npx -y @membank/cli inject --harness claude-code --event user-prompt-submit",
985
+ existingCommand: extractInjectCommand(userPromptSubmitInner) || null
986
+ },
987
+ {
988
+ event: "Stop",
989
+ command: "npx -y @membank/cli inject --harness claude-code --event session-stop",
990
+ existingCommand: extractInjectCommand(stopInner) || null
991
+ }
992
+ ]
826
993
  };
827
994
  },
828
995
  write(resolver, events) {
@@ -845,6 +1012,13 @@ const writers = {
845
1012
  command: "npx -y @membank/cli inject --harness claude-code --event user-prompt-submit"
846
1013
  }]
847
1014
  }];
1015
+ if (events.includes("Stop")) newHooks.Stop = [...filterOutMembank(Array.isArray(hooks.Stop) ? hooks.Stop : []), {
1016
+ matcher: "",
1017
+ hooks: [{
1018
+ type: "command",
1019
+ command: "npx -y @membank/cli inject --harness claude-code --event session-stop"
1020
+ }]
1021
+ }];
848
1022
  writeJsonAtomic(cfgPath, {
849
1023
  ...cfg,
850
1024
  hooks: newHooks
@@ -859,18 +1033,27 @@ const writers = {
859
1033
  const hooks = MaybeJsonObjectSchema.parse(cfg.hooks) ?? {};
860
1034
  const sessionStart = Array.isArray(hooks.sessionStart) ? hooks.sessionStart : [];
861
1035
  const userPromptSubmitted = Array.isArray(hooks.userPromptSubmitted) ? hooks.userPromptSubmitted : [];
1036
+ const sessionEnd = Array.isArray(hooks.sessionEnd) ? hooks.sessionEnd : [];
862
1037
  return {
863
1038
  status: "ready",
864
1039
  configPath: cfgPath,
865
- hooks: [{
866
- event: "sessionStart",
867
- command: "npx -y @membank/cli inject --harness copilot-cli",
868
- existingCommand: extractInjectCommand(sessionStart) || null
869
- }, {
870
- event: "userPromptSubmitted",
871
- command: "npx -y @membank/cli inject --harness copilot-cli --event user-prompt-submit",
872
- existingCommand: extractInjectCommand(userPromptSubmitted) || null
873
- }]
1040
+ hooks: [
1041
+ {
1042
+ event: "sessionStart",
1043
+ command: "npx -y @membank/cli inject --harness copilot-cli",
1044
+ existingCommand: extractInjectCommand(sessionStart) || null
1045
+ },
1046
+ {
1047
+ event: "userPromptSubmitted",
1048
+ command: "npx -y @membank/cli inject --harness copilot-cli --event user-prompt-submit",
1049
+ existingCommand: extractInjectCommand(userPromptSubmitted) || null
1050
+ },
1051
+ {
1052
+ event: "sessionEnd",
1053
+ command: "npx -y @membank/cli inject --harness copilot-cli --event session-stop",
1054
+ existingCommand: extractInjectCommand(sessionEnd) || null
1055
+ }
1056
+ ]
874
1057
  };
875
1058
  },
876
1059
  write(resolver, events) {
@@ -889,6 +1072,11 @@ const writers = {
889
1072
  bash: "npx -y @membank/cli inject --harness copilot-cli --event user-prompt-submit",
890
1073
  timeoutSec: 30
891
1074
  }];
1075
+ if (events.includes("sessionEnd")) newHooks.sessionEnd = [...filterOutMembankFlat(Array.isArray(hooks.sessionEnd) ? hooks.sessionEnd : []), {
1076
+ type: "command",
1077
+ bash: "npx -y @membank/cli inject --harness copilot-cli --event session-stop",
1078
+ timeoutSec: 30
1079
+ }];
892
1080
  writeJsonAtomic(cfgPath, {
893
1081
  version: OptionalNumberSchema.parse(cfg.version) ?? 1,
894
1082
  ...cfg,
@@ -904,18 +1092,27 @@ const writers = {
904
1092
  const hooks = MaybeJsonObjectSchema.parse(cfg.hooks) ?? {};
905
1093
  const sessionStartInner = (Array.isArray(hooks.SessionStart) ? hooks.SessionStart : []).flatMap(getHooksArray);
906
1094
  const userPromptSubmitInner = (Array.isArray(hooks.UserPromptSubmit) ? hooks.UserPromptSubmit : []).flatMap(getHooksArray);
1095
+ const stopInner = (Array.isArray(hooks.Stop) ? hooks.Stop : []).flatMap(getHooksArray);
907
1096
  return {
908
1097
  status: "ready",
909
1098
  configPath: cfgPath,
910
- hooks: [{
911
- event: "SessionStart",
912
- command: "npx -y @membank/cli inject --harness codex",
913
- existingCommand: extractInjectCommand(sessionStartInner) || null
914
- }, {
915
- event: "UserPromptSubmit",
916
- command: "npx -y @membank/cli inject --harness codex --event user-prompt-submit",
917
- existingCommand: extractInjectCommand(userPromptSubmitInner) || null
918
- }]
1099
+ hooks: [
1100
+ {
1101
+ event: "SessionStart",
1102
+ command: "npx -y @membank/cli inject --harness codex",
1103
+ existingCommand: extractInjectCommand(sessionStartInner) || null
1104
+ },
1105
+ {
1106
+ event: "UserPromptSubmit",
1107
+ command: "npx -y @membank/cli inject --harness codex --event user-prompt-submit",
1108
+ existingCommand: extractInjectCommand(userPromptSubmitInner) || null
1109
+ },
1110
+ {
1111
+ event: "Stop",
1112
+ command: "npx -y @membank/cli inject --harness codex --event session-stop",
1113
+ existingCommand: extractInjectCommand(stopInner) || null
1114
+ }
1115
+ ]
919
1116
  };
920
1117
  },
921
1118
  write(resolver, events) {
@@ -940,6 +1137,14 @@ const writers = {
940
1137
  timeout: 30
941
1138
  }]
942
1139
  }];
1140
+ if (events.includes("Stop")) newHooks.Stop = [...filterOutMembank(Array.isArray(hooks.Stop) ? hooks.Stop : []), {
1141
+ matcher: "",
1142
+ hooks: [{
1143
+ type: "command",
1144
+ command: "npx -y @membank/cli inject --harness codex --event session-stop",
1145
+ timeout: 30
1146
+ }]
1147
+ }];
943
1148
  writeJsonAtomic(cfgPath, {
944
1149
  ...cfg,
945
1150
  hooks: newHooks
@@ -1131,6 +1336,7 @@ var SetupOrchestrator = class {
1131
1336
  #modelDownloader;
1132
1337
  #out;
1133
1338
  #progressWrite;
1339
+ #synthesisOptIn;
1134
1340
  constructor(deps) {
1135
1341
  this.#detector = deps.detector ?? (() => detectHarnesses());
1136
1342
  this.#writer = deps.writer;
@@ -1139,6 +1345,7 @@ var SetupOrchestrator = class {
1139
1345
  this.#harnessSelector = deps.harnessSelector;
1140
1346
  this.#modelDownloader = deps.modelDownloader;
1141
1347
  this.#out = deps.out ?? ((msg) => process.stdout.write(`${msg}\n`));
1348
+ this.#synthesisOptIn = deps.synthesisOptIn ?? false;
1142
1349
  this.#progressWrite = deps.progressWrite ?? ((text) => process.stdout.write(text));
1143
1350
  }
1144
1351
  async run(opts = {}) {
@@ -1275,6 +1482,12 @@ var SetupOrchestrator = class {
1275
1482
  out(`Step ${this.#hookWriter !== void 0 ? 3 : 2}/${totalSteps} Embedding model`);
1276
1483
  modelDownloaded = !(await this.#runModelDownload(this.#modelDownloader, out)).skipped;
1277
1484
  } else out("Model download step: see DRA-52");
1485
+ if (this.#synthesisOptIn && !yes && !json) {
1486
+ if (await this.#prompter("Enable memory synthesis? (experimental — summarizes memories at session start using Claude Haiku, requires ANTHROPIC_API_KEY)")) {
1487
+ ConfigManager.set("synthesis.enabled", true);
1488
+ out(" ✓ Memory synthesis enabled.");
1489
+ }
1490
+ }
1278
1491
  const written = results.filter((r) => r.status === "written").length;
1279
1492
  const skipped = results.filter((r) => r.status === "already-configured").length;
1280
1493
  const errors = results.filter((r) => r.status === "error").length;
@@ -1511,7 +1724,8 @@ program.command("setup").description("detect installed harnesses and write MCP c
1511
1724
  prompter: (question) => promptHelper.confirm(question),
1512
1725
  harnessSelector,
1513
1726
  modelDownloader: new ModelDownloader(),
1514
- out: formatter.isJson ? void 0 : decoratedOut
1727
+ out: formatter.isJson ? void 0 : decoratedOut,
1728
+ synthesisOptIn: true
1515
1729
  });
1516
1730
  try {
1517
1731
  const results = await orchestrator.run({
@@ -1563,6 +1777,40 @@ program.command("dashboard").description("open the memory management dashboard i
1563
1777
  process.exit(2);
1564
1778
  }
1565
1779
  });
1780
+ const configCmd = program.command("config").description("manage membank configuration");
1781
+ configCmd.command("get <key>").description("print a config value as JSON").action((key) => {
1782
+ const globalOpts = program.opts();
1783
+ configGetCommand(key, Formatter.create(globalOpts.json === true));
1784
+ });
1785
+ configCmd.command("set <key> <value>").description("set a config value and persist").action((key, value) => {
1786
+ const globalOpts = program.opts();
1787
+ configSetCommand(key, value, Formatter.create(globalOpts.json === true));
1788
+ });
1789
+ configCmd.command("show").description("print the entire config as formatted JSON").action(() => {
1790
+ const globalOpts = program.opts();
1791
+ configShowCommand(Formatter.create(globalOpts.json === true));
1792
+ });
1793
+ const synthesizeCmd = program.command("synthesize").description("view synthesis state");
1794
+ synthesizeCmd.command("show").description("display current synthesis for a scope").option("--scope <scope>", "scope to show (default: global)").action((cmdOptions) => {
1795
+ const globalOpts = program.opts();
1796
+ const formatter = Formatter.create(globalOpts.json === true);
1797
+ try {
1798
+ synthesizeShowCommand(cmdOptions, formatter);
1799
+ } catch (err) {
1800
+ formatter.error(err instanceof Error ? err.message : String(err));
1801
+ process.exit(2);
1802
+ }
1803
+ });
1804
+ synthesizeCmd.command("status").description("show all scopes with synthesis status").action(() => {
1805
+ const globalOpts = program.opts();
1806
+ const formatter = Formatter.create(globalOpts.json === true);
1807
+ try {
1808
+ synthesizeStatusCommand(formatter);
1809
+ } catch (err) {
1810
+ formatter.error(err instanceof Error ? err.message : String(err));
1811
+ process.exit(2);
1812
+ }
1813
+ });
1566
1814
  program.on("command:*", () => {
1567
1815
  program.outputHelp();
1568
1816
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@membank/cli",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,9 +21,9 @@
21
21
  "commander": "^14.0.3",
22
22
  "ora": "^9.4.0",
23
23
  "zod": "^4.4.3",
24
- "@membank/dashboard": "0.5.0",
25
- "@membank/core": "0.8.0",
26
- "@membank/mcp": "0.10.0"
24
+ "@membank/core": "0.9.0",
25
+ "@membank/dashboard": "0.5.1",
26
+ "@membank/mcp": "0.11.0"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/node": "^25.6.0",