@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.
- package/dist/index.mjs +286 -38
- 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
|
-
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
|
|
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
|
-
|
|
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
|
-
|
|
445
|
-
|
|
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
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
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
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
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
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
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.
|
|
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/
|
|
25
|
-
"@membank/
|
|
26
|
-
"@membank/mcp": "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",
|