@membank/cli 0.12.0 → 0.13.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 +127 -60
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { cancel, confirm, intro, isCancel, multiselect, note, outro } from "@clack/prompts";
|
|
3
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
|
-
import { runSynthesis, startServer } from "@membank/mcp";
|
|
4
|
+
import { runExtraction, runSynthesis, startServer } from "@membank/mcp";
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
import { Command } from "commander";
|
|
7
7
|
import ora from "ora";
|
|
@@ -192,6 +192,95 @@ function exportCommand(db, formatter, opts) {
|
|
|
192
192
|
else process.stdout.write(`Exported ${memories.length} memories to ${outputPath}\n`);
|
|
193
193
|
}
|
|
194
194
|
//#endregion
|
|
195
|
+
//#region src/commands/extract.ts
|
|
196
|
+
const ExtractionHarnessSchema = z.enum(["claude-code"]);
|
|
197
|
+
const ClaudeCodeStopInputSchema = z.object({
|
|
198
|
+
session_id: z.string(),
|
|
199
|
+
transcript_path: z.string(),
|
|
200
|
+
cwd: z.string().optional(),
|
|
201
|
+
hook_event_name: z.string().optional(),
|
|
202
|
+
stop_hook_active: z.boolean().optional()
|
|
203
|
+
});
|
|
204
|
+
function parseHookPayload(harness, raw) {
|
|
205
|
+
let parsedJson;
|
|
206
|
+
try {
|
|
207
|
+
parsedJson = JSON.parse(raw);
|
|
208
|
+
} catch {
|
|
209
|
+
return {
|
|
210
|
+
ok: false,
|
|
211
|
+
reason: "stdin is not valid JSON"
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
if (harness === "claude-code") {
|
|
215
|
+
const parsed = ClaudeCodeStopInputSchema.safeParse(parsedJson);
|
|
216
|
+
if (!parsed.success) return {
|
|
217
|
+
ok: false,
|
|
218
|
+
reason: `invalid hook payload: ${parsed.error.message}`
|
|
219
|
+
};
|
|
220
|
+
return {
|
|
221
|
+
ok: true,
|
|
222
|
+
value: {
|
|
223
|
+
sessionId: parsed.data.session_id,
|
|
224
|
+
transcriptPath: parsed.data.transcript_path,
|
|
225
|
+
stopHookActive: parsed.data.stop_hook_active === true
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
ok: false,
|
|
231
|
+
reason: `unsupported harness: ${harness}`
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
async function readStdin() {
|
|
235
|
+
if (process.stdin.isTTY) return "";
|
|
236
|
+
const chunks = [];
|
|
237
|
+
for await (const chunk of process.stdin) chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
238
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
239
|
+
}
|
|
240
|
+
async function extractCommand(opts) {
|
|
241
|
+
const harnessResult = ExtractionHarnessSchema.safeParse(opts.harness ?? "claude-code");
|
|
242
|
+
if (!harnessResult.success) {
|
|
243
|
+
process.stderr.write(`membank extract: unsupported harness "${opts.harness}". Supported: ${ExtractionHarnessSchema.options.join(", ")}\n`);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const harness = harnessResult.data;
|
|
247
|
+
let sessionId = opts.sessionId;
|
|
248
|
+
let transcriptPath = opts.transcript;
|
|
249
|
+
let stopHookActive = false;
|
|
250
|
+
if (sessionId === void 0 || transcriptPath === void 0) {
|
|
251
|
+
const raw = await readStdin();
|
|
252
|
+
if (raw.trim().length > 0) {
|
|
253
|
+
const parsed = parseHookPayload(harness, raw);
|
|
254
|
+
if (!parsed.ok) {
|
|
255
|
+
process.stderr.write(`membank extract: ${parsed.reason}; skipping.\n`);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
sessionId = sessionId ?? parsed.value.sessionId;
|
|
259
|
+
transcriptPath = transcriptPath ?? parsed.value.transcriptPath;
|
|
260
|
+
stopHookActive = parsed.value.stopHookActive;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (sessionId === void 0 || transcriptPath === void 0) {
|
|
264
|
+
process.stderr.write("membank extract: missing session_id or transcript_path (provide via stdin or --session/--transcript).\n");
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (stopHookActive) {
|
|
268
|
+
process.stderr.write("membank extract: stop_hook_active=true; skipping to avoid recursion.\n");
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
const result = await runExtraction({
|
|
273
|
+
sessionId,
|
|
274
|
+
transcriptPath
|
|
275
|
+
});
|
|
276
|
+
if (result.status === "skipped") process.stderr.write(`membank extract: skipped (${result.reason})\n`);
|
|
277
|
+
else if (result.status === "failed") process.stderr.write(`membank extract: failed: ${result.error}\n`);
|
|
278
|
+
} catch (err) {
|
|
279
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
280
|
+
process.stderr.write(`membank extract: ${msg}\n`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
//#endregion
|
|
195
284
|
//#region src/commands/import.ts
|
|
196
285
|
async function importCommand(filePath, db, formatter, prompt) {
|
|
197
286
|
let raw;
|
|
@@ -290,10 +379,6 @@ async function buildText() {
|
|
|
290
379
|
}
|
|
291
380
|
}
|
|
292
381
|
async function handleEvent(harness, eventName) {
|
|
293
|
-
if (harness === "claude-code" && eventName === "Stop") {
|
|
294
|
-
process.stdout.write("{}");
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
382
|
const text = await buildText().catch((err) => {
|
|
298
383
|
const msg = err instanceof Error ? err.message : String(err);
|
|
299
384
|
process.stderr.write(`membank inject: ${msg}\n`);
|
|
@@ -313,10 +398,6 @@ async function injectCommand(opts) {
|
|
|
313
398
|
await handleEvent(harness, "UserPromptSubmit");
|
|
314
399
|
return;
|
|
315
400
|
}
|
|
316
|
-
if (opts.event === "session-stop" || opts.event === "stop") {
|
|
317
|
-
await handleEvent(harness, "Stop");
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
401
|
process.exit(0);
|
|
321
402
|
}
|
|
322
403
|
//#endregion
|
|
@@ -1017,7 +1098,7 @@ const writers = {
|
|
|
1017
1098
|
},
|
|
1018
1099
|
{
|
|
1019
1100
|
event: "Stop",
|
|
1020
|
-
command: "npx -y @membank/cli
|
|
1101
|
+
command: "npx -y @membank/cli extract --harness claude-code",
|
|
1021
1102
|
existingCommand: extractInjectCommand(stopInner) || null
|
|
1022
1103
|
}
|
|
1023
1104
|
]
|
|
@@ -1029,6 +1110,7 @@ const writers = {
|
|
|
1029
1110
|
const hooks = MaybeJsonObjectSchema.parse(cfg.hooks) ?? {};
|
|
1030
1111
|
const newHooks = { ...hooks };
|
|
1031
1112
|
pruneNestedEvent(newHooks, "PostToolUseFailure");
|
|
1113
|
+
pruneNestedEvent(newHooks, "Stop");
|
|
1032
1114
|
if (events.includes("SessionStart")) newHooks.SessionStart = [...filterOutMembank(Array.isArray(hooks.SessionStart) ? hooks.SessionStart : []), {
|
|
1033
1115
|
matcher: "",
|
|
1034
1116
|
hooks: [{
|
|
@@ -1047,7 +1129,9 @@ const writers = {
|
|
|
1047
1129
|
matcher: "",
|
|
1048
1130
|
hooks: [{
|
|
1049
1131
|
type: "command",
|
|
1050
|
-
command: "npx -y @membank/cli
|
|
1132
|
+
command: "npx -y @membank/cli extract --harness claude-code",
|
|
1133
|
+
async: true,
|
|
1134
|
+
timeout: 600
|
|
1051
1135
|
}]
|
|
1052
1136
|
}];
|
|
1053
1137
|
writeJsonAtomic(cfgPath, {
|
|
@@ -1064,27 +1148,18 @@ const writers = {
|
|
|
1064
1148
|
const hooks = MaybeJsonObjectSchema.parse(cfg.hooks) ?? {};
|
|
1065
1149
|
const sessionStart = Array.isArray(hooks.sessionStart) ? hooks.sessionStart : [];
|
|
1066
1150
|
const userPromptSubmitted = Array.isArray(hooks.userPromptSubmitted) ? hooks.userPromptSubmitted : [];
|
|
1067
|
-
const sessionEnd = Array.isArray(hooks.sessionEnd) ? hooks.sessionEnd : [];
|
|
1068
1151
|
return {
|
|
1069
1152
|
status: "ready",
|
|
1070
1153
|
configPath: cfgPath,
|
|
1071
|
-
hooks: [
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
existingCommand: extractInjectCommand(userPromptSubmitted) || null
|
|
1081
|
-
},
|
|
1082
|
-
{
|
|
1083
|
-
event: "sessionEnd",
|
|
1084
|
-
command: "npx -y @membank/cli inject --harness copilot-cli --event session-stop",
|
|
1085
|
-
existingCommand: extractInjectCommand(sessionEnd) || null
|
|
1086
|
-
}
|
|
1087
|
-
]
|
|
1154
|
+
hooks: [{
|
|
1155
|
+
event: "sessionStart",
|
|
1156
|
+
command: "npx -y @membank/cli inject --harness copilot-cli",
|
|
1157
|
+
existingCommand: extractInjectCommand(sessionStart) || null
|
|
1158
|
+
}, {
|
|
1159
|
+
event: "userPromptSubmitted",
|
|
1160
|
+
command: "npx -y @membank/cli inject --harness copilot-cli --event user-prompt-submit",
|
|
1161
|
+
existingCommand: extractInjectCommand(userPromptSubmitted) || null
|
|
1162
|
+
}]
|
|
1088
1163
|
};
|
|
1089
1164
|
},
|
|
1090
1165
|
write(resolver, events) {
|
|
@@ -1093,6 +1168,7 @@ const writers = {
|
|
|
1093
1168
|
const hooks = MaybeJsonObjectSchema.parse(cfg.hooks) ?? {};
|
|
1094
1169
|
const newHooks = { ...hooks };
|
|
1095
1170
|
pruneFlatEvent(newHooks, "postToolUseFailure");
|
|
1171
|
+
pruneFlatEvent(newHooks, "sessionEnd");
|
|
1096
1172
|
if (events.includes("sessionStart")) newHooks.sessionStart = [...filterOutMembankFlat(Array.isArray(hooks.sessionStart) ? hooks.sessionStart : []), {
|
|
1097
1173
|
type: "command",
|
|
1098
1174
|
bash: "npx -y @membank/cli inject --harness copilot-cli",
|
|
@@ -1103,11 +1179,6 @@ const writers = {
|
|
|
1103
1179
|
bash: "npx -y @membank/cli inject --harness copilot-cli --event user-prompt-submit",
|
|
1104
1180
|
timeoutSec: 30
|
|
1105
1181
|
}];
|
|
1106
|
-
if (events.includes("sessionEnd")) newHooks.sessionEnd = [...filterOutMembankFlat(Array.isArray(hooks.sessionEnd) ? hooks.sessionEnd : []), {
|
|
1107
|
-
type: "command",
|
|
1108
|
-
bash: "npx -y @membank/cli inject --harness copilot-cli --event session-stop",
|
|
1109
|
-
timeoutSec: 30
|
|
1110
|
-
}];
|
|
1111
1182
|
writeJsonAtomic(cfgPath, {
|
|
1112
1183
|
version: OptionalNumberSchema.parse(cfg.version) ?? 1,
|
|
1113
1184
|
...cfg,
|
|
@@ -1123,27 +1194,18 @@ const writers = {
|
|
|
1123
1194
|
const hooks = MaybeJsonObjectSchema.parse(cfg.hooks) ?? {};
|
|
1124
1195
|
const sessionStartInner = (Array.isArray(hooks.SessionStart) ? hooks.SessionStart : []).flatMap(getHooksArray);
|
|
1125
1196
|
const userPromptSubmitInner = (Array.isArray(hooks.UserPromptSubmit) ? hooks.UserPromptSubmit : []).flatMap(getHooksArray);
|
|
1126
|
-
const stopInner = (Array.isArray(hooks.Stop) ? hooks.Stop : []).flatMap(getHooksArray);
|
|
1127
1197
|
return {
|
|
1128
1198
|
status: "ready",
|
|
1129
1199
|
configPath: cfgPath,
|
|
1130
|
-
hooks: [
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
existingCommand: extractInjectCommand(userPromptSubmitInner) || null
|
|
1140
|
-
},
|
|
1141
|
-
{
|
|
1142
|
-
event: "Stop",
|
|
1143
|
-
command: "npx -y @membank/cli inject --harness codex --event session-stop",
|
|
1144
|
-
existingCommand: extractInjectCommand(stopInner) || null
|
|
1145
|
-
}
|
|
1146
|
-
]
|
|
1200
|
+
hooks: [{
|
|
1201
|
+
event: "SessionStart",
|
|
1202
|
+
command: "npx -y @membank/cli inject --harness codex",
|
|
1203
|
+
existingCommand: extractInjectCommand(sessionStartInner) || null
|
|
1204
|
+
}, {
|
|
1205
|
+
event: "UserPromptSubmit",
|
|
1206
|
+
command: "npx -y @membank/cli inject --harness codex --event user-prompt-submit",
|
|
1207
|
+
existingCommand: extractInjectCommand(userPromptSubmitInner) || null
|
|
1208
|
+
}]
|
|
1147
1209
|
};
|
|
1148
1210
|
},
|
|
1149
1211
|
write(resolver, events) {
|
|
@@ -1152,6 +1214,7 @@ const writers = {
|
|
|
1152
1214
|
const hooks = MaybeJsonObjectSchema.parse(cfg.hooks) ?? {};
|
|
1153
1215
|
const newHooks = { ...hooks };
|
|
1154
1216
|
pruneNestedEvent(newHooks, "PostToolUse");
|
|
1217
|
+
pruneNestedEvent(newHooks, "Stop");
|
|
1155
1218
|
if (events.includes("SessionStart")) newHooks.SessionStart = [...filterOutMembank(Array.isArray(hooks.SessionStart) ? hooks.SessionStart : []), {
|
|
1156
1219
|
matcher: "",
|
|
1157
1220
|
hooks: [{
|
|
@@ -1168,14 +1231,6 @@ const writers = {
|
|
|
1168
1231
|
timeout: 30
|
|
1169
1232
|
}]
|
|
1170
1233
|
}];
|
|
1171
|
-
if (events.includes("Stop")) newHooks.Stop = [...filterOutMembank(Array.isArray(hooks.Stop) ? hooks.Stop : []), {
|
|
1172
|
-
matcher: "",
|
|
1173
|
-
hooks: [{
|
|
1174
|
-
type: "command",
|
|
1175
|
-
command: "npx -y @membank/cli inject --harness codex --event session-stop",
|
|
1176
|
-
timeout: 30
|
|
1177
|
-
}]
|
|
1178
|
-
}];
|
|
1179
1234
|
writeJsonAtomic(cfgPath, {
|
|
1180
1235
|
...cfg,
|
|
1181
1236
|
hooks: newHooks
|
|
@@ -1639,6 +1694,18 @@ program.command("inject").description("output session context for harness inject
|
|
|
1639
1694
|
process.exit(2);
|
|
1640
1695
|
}
|
|
1641
1696
|
});
|
|
1697
|
+
program.command("extract").description("(internal) run session-end memory extraction; reads the harness's Stop hook payload from stdin").option("--harness <name>", "harness whose stop-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) => {
|
|
1698
|
+
try {
|
|
1699
|
+
await extractCommand({
|
|
1700
|
+
harness: cmdOptions.harness,
|
|
1701
|
+
sessionId: cmdOptions.session,
|
|
1702
|
+
transcript: cmdOptions.transcript
|
|
1703
|
+
});
|
|
1704
|
+
} catch (err) {
|
|
1705
|
+
process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
|
|
1706
|
+
}
|
|
1707
|
+
process.exit(0);
|
|
1708
|
+
});
|
|
1642
1709
|
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
1710
|
setupCmd.command("upgrade").description("upgrade harness configs from membank --mcp to standalone membank-mcp").action(async () => {
|
|
1644
1711
|
const isJson = program.opts().json === true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@membank/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.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/core": "0.
|
|
25
|
-
"@membank/mcp": "0.
|
|
24
|
+
"@membank/core": "0.11.0",
|
|
25
|
+
"@membank/mcp": "0.14.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^25.6.0",
|