@membank/cli 0.7.1 → 0.8.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 +174 -102
- package/package.json +5 -4
package/dist/index.mjs
CHANGED
|
@@ -1,20 +1,60 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { cancel, confirm, intro, isCancel, multiselect, note, outro } from "@clack/prompts";
|
|
3
|
-
import { DatabaseManager, EmbeddingService,
|
|
3
|
+
import { DatabaseManager, EmbeddingService, MIGRATIONS, MemoryRepository, MemoryTypeSchema, MemoryTypeSchema as MemoryTypeSchema$1, ProjectRepository, QueryEngine, SessionContextBuilder, 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
|
+
import { z } from "zod";
|
|
8
9
|
import { startDashboard } from "@membank/dashboard";
|
|
9
10
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, writeFileSync } from "node:fs";
|
|
10
11
|
import { dirname, join } from "node:path";
|
|
11
12
|
import Table from "cli-table3";
|
|
12
|
-
import { homedir, tmpdir } from "node:os";
|
|
13
13
|
import { execFile } from "node:child_process";
|
|
14
14
|
import { promisify } from "node:util";
|
|
15
|
+
import { homedir, tmpdir } from "node:os";
|
|
15
16
|
import { EventEmitter } from "node:events";
|
|
16
17
|
import { pipeline } from "@huggingface/transformers";
|
|
17
18
|
import { createInterface } from "node:readline";
|
|
19
|
+
//#region src/schemas.ts
|
|
20
|
+
const SETUP_HARNESS_VALUES = [
|
|
21
|
+
"claude-code",
|
|
22
|
+
"copilot",
|
|
23
|
+
"codex",
|
|
24
|
+
"opencode"
|
|
25
|
+
];
|
|
26
|
+
const SetupHarnessSchema = z.enum(SETUP_HARNESS_VALUES);
|
|
27
|
+
const InjectionHarnessSchema = z.enum([
|
|
28
|
+
"claude-code",
|
|
29
|
+
"copilot-cli",
|
|
30
|
+
"codex",
|
|
31
|
+
"opencode"
|
|
32
|
+
]);
|
|
33
|
+
const MigrateModeSchema = z.enum(["list", "run"]);
|
|
34
|
+
const LimitSchema = z.coerce.number().int().positive();
|
|
35
|
+
const PortSchema = z.coerce.number().int().min(1).max(65535);
|
|
36
|
+
const OptionalNumberSchema = z.number().optional().catch(void 0);
|
|
37
|
+
const MutableJsonObjectSchema = z.record(z.string(), z.unknown());
|
|
38
|
+
const MaybeJsonObjectSchema = z.record(z.string(), z.unknown()).optional().catch(void 0);
|
|
39
|
+
const ExportRecordSchema = z.object({
|
|
40
|
+
id: z.string().min(1),
|
|
41
|
+
content: z.string(),
|
|
42
|
+
type: MemoryTypeSchema,
|
|
43
|
+
tags: z.array(z.string()).optional().default([]),
|
|
44
|
+
sourceHarness: z.string().nullable().optional().default(null),
|
|
45
|
+
accessCount: z.number().optional().default(0),
|
|
46
|
+
pinned: z.boolean().optional().default(false),
|
|
47
|
+
needsReview: z.boolean().optional().default(false),
|
|
48
|
+
createdAt: z.string().optional(),
|
|
49
|
+
updatedAt: z.string().optional(),
|
|
50
|
+
embedding: z.string().nullable().optional().default(null)
|
|
51
|
+
});
|
|
52
|
+
const ExportFileSchema = z.object({
|
|
53
|
+
version: z.literal(1),
|
|
54
|
+
exportedAt: z.string().optional(),
|
|
55
|
+
memories: z.array(ExportRecordSchema)
|
|
56
|
+
});
|
|
57
|
+
//#endregion
|
|
18
58
|
//#region src/commands/add.ts
|
|
19
59
|
async function addCommand(content, options, formatter, db, embeddingService) {
|
|
20
60
|
const ownDb = db === void 0;
|
|
@@ -26,7 +66,7 @@ async function addCommand(content, options, formatter, db, embeddingService) {
|
|
|
26
66
|
const spinner = formatter.isJson ? null : ora("Saving memory…").start();
|
|
27
67
|
const memory = await repo.save({
|
|
28
68
|
content,
|
|
29
|
-
type: options.type,
|
|
69
|
+
type: MemoryTypeSchema$1.parse(options.type),
|
|
30
70
|
tags,
|
|
31
71
|
projectScope
|
|
32
72
|
});
|
|
@@ -39,7 +79,7 @@ async function addCommand(content, options, formatter, db, embeddingService) {
|
|
|
39
79
|
//#endregion
|
|
40
80
|
//#region src/commands/dashboard.ts
|
|
41
81
|
async function dashboardCommand(opts) {
|
|
42
|
-
await startDashboard({ port: opts.port !== void 0 ?
|
|
82
|
+
await startDashboard({ port: opts.port !== void 0 ? PortSchema.parse(opts.port) : void 0 });
|
|
43
83
|
}
|
|
44
84
|
//#endregion
|
|
45
85
|
//#region src/commands/delete.ts
|
|
@@ -59,7 +99,7 @@ function exportCommand(db, formatter, opts) {
|
|
|
59
99
|
id: row.id,
|
|
60
100
|
content: row.content,
|
|
61
101
|
type: row.type,
|
|
62
|
-
tags: JSON.parse(row.tags),
|
|
102
|
+
tags: TagsRowSchema.parse(JSON.parse(row.tags)),
|
|
63
103
|
sourceHarness: row.source,
|
|
64
104
|
accessCount: row.access_count,
|
|
65
105
|
pinned: row.pinned !== 0,
|
|
@@ -83,17 +123,6 @@ function exportCommand(db, formatter, opts) {
|
|
|
83
123
|
}
|
|
84
124
|
//#endregion
|
|
85
125
|
//#region src/commands/import.ts
|
|
86
|
-
const MEMORY_TYPES = new Set(MEMORY_TYPE_VALUES);
|
|
87
|
-
function isValidRecord(r) {
|
|
88
|
-
if (typeof r !== "object" || r === null) return false;
|
|
89
|
-
const rec = r;
|
|
90
|
-
return typeof rec.id === "string" && rec.id.length > 0 && typeof rec.content === "string" && typeof rec.type === "string" && MEMORY_TYPES.has(rec.type);
|
|
91
|
-
}
|
|
92
|
-
function isExportFile(parsed) {
|
|
93
|
-
if (typeof parsed !== "object" || parsed === null) return false;
|
|
94
|
-
const obj = parsed;
|
|
95
|
-
return obj.version === 1 && Array.isArray(obj.memories);
|
|
96
|
-
}
|
|
97
126
|
async function importCommand(filePath, db, formatter, prompt) {
|
|
98
127
|
let raw;
|
|
99
128
|
try {
|
|
@@ -109,16 +138,12 @@ async function importCommand(filePath, db, formatter, prompt) {
|
|
|
109
138
|
formatter.error(`Invalid JSON in file: ${filePath}`);
|
|
110
139
|
process.exit(1);
|
|
111
140
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
const invalidIndex = parsed.memories.findIndex((r) => !isValidRecord(r));
|
|
117
|
-
if (invalidIndex !== -1) {
|
|
118
|
-
formatter.error(`Invalid memory record at index ${invalidIndex}: must have id, content, and type`);
|
|
141
|
+
const parseResult = ExportFileSchema.safeParse(parsed);
|
|
142
|
+
if (!parseResult.success) {
|
|
143
|
+
formatter.error(`Invalid export file: ${parseResult.error.issues[0]?.message ?? "unknown error"}`);
|
|
119
144
|
process.exit(1);
|
|
120
145
|
}
|
|
121
|
-
const count =
|
|
146
|
+
const count = parseResult.data.memories.length;
|
|
122
147
|
if (formatter.isJson) process.stdout.write(`${JSON.stringify({ found: count })}\n`);
|
|
123
148
|
else process.stdout.write(`Found ${count} memories to import.\n`);
|
|
124
149
|
if (!await prompt.confirm("Import?")) return;
|
|
@@ -126,7 +151,7 @@ async function importCommand(filePath, db, formatter, prompt) {
|
|
|
126
151
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
127
152
|
const insertEmbedding = db.db.prepare(`INSERT OR REPLACE INTO embeddings (rowid, embedding) SELECT m.rowid, ? FROM memories m WHERE m.id = ?`);
|
|
128
153
|
db.db.transaction(() => {
|
|
129
|
-
for (const rec of
|
|
154
|
+
for (const rec of parseResult.data.memories) {
|
|
130
155
|
insertMemory.run(rec.id, rec.content, rec.type, JSON.stringify(rec.tags ?? []), rec.sourceHarness ?? null, rec.accessCount ?? 0, rec.pinned ? 1 : 0, rec.needsReview ? 1 : 0, rec.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(), rec.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString());
|
|
131
156
|
if (rec.embedding !== null && rec.embedding !== void 0) {
|
|
132
157
|
const buf = Buffer.from(rec.embedding, "base64");
|
|
@@ -139,17 +164,21 @@ async function importCommand(filePath, db, formatter, prompt) {
|
|
|
139
164
|
}
|
|
140
165
|
//#endregion
|
|
141
166
|
//#region src/commands/inject.ts
|
|
142
|
-
const MEMORY_GUIDANCE = "
|
|
167
|
+
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");
|
|
168
|
+
function xmlEscape(s) {
|
|
169
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
170
|
+
}
|
|
143
171
|
function formatContext(ctx) {
|
|
144
|
-
const
|
|
172
|
+
const parts = [];
|
|
145
173
|
const statParts = Object.entries(ctx.stats).filter(([, count]) => count > 0).map(([type, count]) => `${count} ${type}${count !== 1 ? "s" : ""}`);
|
|
146
|
-
if (statParts.length > 0)
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
174
|
+
if (statParts.length > 0) parts.push(`<memory-stats>\n${statParts.join(", ")}\n</memory-stats>`);
|
|
175
|
+
const allPinned = [...ctx.pinnedGlobal, ...ctx.pinnedProject];
|
|
176
|
+
if (allPinned.length > 0) {
|
|
177
|
+
const memLines = allPinned.map((m) => ` <memory type="${m.type}">${xmlEscape(m.content)}</memory>`);
|
|
178
|
+
parts.push(`<pinned-memories>\n${memLines.join("\n")}\n</pinned-memories>`);
|
|
179
|
+
}
|
|
180
|
+
parts.push(`<memory-guidance>\n${MEMORY_GUIDANCE}\n</memory-guidance>`);
|
|
181
|
+
return parts.join("\n");
|
|
153
182
|
}
|
|
154
183
|
function outputAdditionalContext(text, harness, eventName) {
|
|
155
184
|
if (harness === "claude-code") {
|
|
@@ -165,22 +194,36 @@ function outputAdditionalContext(text, harness, eventName) {
|
|
|
165
194
|
}
|
|
166
195
|
process.stdout.write(`${text}\n`);
|
|
167
196
|
}
|
|
168
|
-
async function
|
|
169
|
-
const
|
|
197
|
+
async function buildText() {
|
|
198
|
+
const resolved = await resolveProject();
|
|
170
199
|
const db = DatabaseManager.open();
|
|
171
|
-
let text;
|
|
172
200
|
try {
|
|
173
|
-
|
|
201
|
+
return formatContext(new SessionContextBuilder(db).getSessionContext(resolved.hash));
|
|
174
202
|
} finally {
|
|
175
203
|
db.close();
|
|
176
204
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
205
|
+
}
|
|
206
|
+
async function handleEvent(harness, eventName) {
|
|
207
|
+
const text = await buildText().catch((err) => {
|
|
208
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
209
|
+
process.stderr.write(`membank inject: ${msg}\n`);
|
|
210
|
+
return null;
|
|
211
|
+
});
|
|
212
|
+
if (text === null) process.exit(0);
|
|
213
|
+
outputAdditionalContext(text, harness, eventName);
|
|
180
214
|
}
|
|
181
215
|
async function injectCommand(opts) {
|
|
182
|
-
|
|
183
|
-
|
|
216
|
+
const harnessResult = InjectionHarnessSchema.safeParse(opts.harness);
|
|
217
|
+
const harness = harnessResult.success ? harnessResult.data : void 0;
|
|
218
|
+
if (opts.event === "session-start" || opts.event === void 0) {
|
|
219
|
+
await handleEvent(harness, "SessionStart");
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (opts.event === "user-prompt-submit") {
|
|
223
|
+
await handleEvent(harness, "UserPromptSubmit");
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
process.exit(0);
|
|
184
227
|
}
|
|
185
228
|
//#endregion
|
|
186
229
|
//#region src/commands/list.ts
|
|
@@ -188,7 +231,7 @@ async function listCommand(options, formatter) {
|
|
|
188
231
|
const db = DatabaseManager.open();
|
|
189
232
|
try {
|
|
190
233
|
const memories = new MemoryRepository(db, new EmbeddingService(), new ProjectRepository(db)).list({
|
|
191
|
-
type: options.type,
|
|
234
|
+
type: options.type !== void 0 ? MemoryTypeSchema$1.parse(options.type) : void 0,
|
|
192
235
|
pinned: options.pinned
|
|
193
236
|
});
|
|
194
237
|
formatter.outputMemories(memories);
|
|
@@ -255,12 +298,13 @@ async function queryCommand(queryText, options, formatter) {
|
|
|
255
298
|
try {
|
|
256
299
|
const embedding = new EmbeddingService();
|
|
257
300
|
const engine = new QueryEngine(db, embedding, new MemoryRepository(db, embedding, new ProjectRepository(db)));
|
|
258
|
-
const limit = options.limit !== void 0 ?
|
|
301
|
+
const limit = options.limit !== void 0 ? LimitSchema.parse(options.limit) : 10;
|
|
259
302
|
const spinner = formatter.isJson ? null : ora("Searching memories…").start();
|
|
260
303
|
const results = await engine.query({
|
|
261
304
|
query: queryText,
|
|
262
|
-
type: options.type,
|
|
263
|
-
limit
|
|
305
|
+
type: options.type !== void 0 ? MemoryTypeSchema$1.parse(options.type) : void 0,
|
|
306
|
+
limit,
|
|
307
|
+
includePinned: options.includePinned
|
|
264
308
|
});
|
|
265
309
|
spinner?.succeed(`${results.length} result${results.length === 1 ? "" : "s"} found`);
|
|
266
310
|
formatter.outputQueryResults(results);
|
|
@@ -473,6 +517,21 @@ async function execFileNoThrow(cmd, args) {
|
|
|
473
517
|
}
|
|
474
518
|
}
|
|
475
519
|
//#endregion
|
|
520
|
+
//#region src/utils/json.ts
|
|
521
|
+
function readJson(path) {
|
|
522
|
+
try {
|
|
523
|
+
return MutableJsonObjectSchema.parse(JSON.parse(readFileSync(path, "utf8")));
|
|
524
|
+
} catch {
|
|
525
|
+
return {};
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
function writeJsonAtomic(path, data) {
|
|
529
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
530
|
+
const tmp = join(mkdtempSync(join(tmpdir(), "membank-")), "cfg.json");
|
|
531
|
+
writeFileSync(tmp, JSON.stringify(data, null, 2));
|
|
532
|
+
renameSync(tmp, path);
|
|
533
|
+
}
|
|
534
|
+
//#endregion
|
|
476
535
|
//#region src/setup/harness-config-writer.ts
|
|
477
536
|
var CommandError = class extends Error {
|
|
478
537
|
command;
|
|
@@ -490,21 +549,8 @@ const defaultPathResolver$1 = {
|
|
|
490
549
|
},
|
|
491
550
|
cwd: () => process.cwd()
|
|
492
551
|
};
|
|
493
|
-
function readJson$1(path) {
|
|
494
|
-
try {
|
|
495
|
-
return JSON.parse(readFileSync(path, "utf8"));
|
|
496
|
-
} catch {
|
|
497
|
-
return {};
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
function writeJsonAtomic$1(path, data) {
|
|
501
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
502
|
-
const tmp = join(mkdtempSync(join(tmpdir(), "membank-")), "cfg.json");
|
|
503
|
-
writeFileSync(tmp, JSON.stringify(data, null, 2));
|
|
504
|
-
renameSync(tmp, path);
|
|
505
|
-
}
|
|
506
552
|
function hasKey(container, key) {
|
|
507
|
-
return container !== null && typeof container === "object" && key
|
|
553
|
+
return container !== null && typeof container === "object" && Object.hasOwn(container, key);
|
|
508
554
|
}
|
|
509
555
|
function assertCliFound(result, cli, command) {
|
|
510
556
|
if (result.exitCode === 127) throw new CommandError(`${cli} CLI not found — install ${cli} first`, command);
|
|
@@ -524,7 +570,7 @@ const writers$1 = {
|
|
|
524
570
|
};
|
|
525
571
|
},
|
|
526
572
|
async write(resolver, run, { overwrite = false } = {}) {
|
|
527
|
-
const configured = hasKey(readJson
|
|
573
|
+
const configured = hasKey(readJson(join(resolver.home(), ".claude.json")).mcpServers, "membank");
|
|
528
574
|
if (configured && !overwrite) return { status: "already-configured" };
|
|
529
575
|
if (configured) {
|
|
530
576
|
const removeArgs = [
|
|
@@ -564,12 +610,12 @@ const writers$1 = {
|
|
|
564
610
|
},
|
|
565
611
|
async write(resolver, _run, { overwrite = false } = {}) {
|
|
566
612
|
const cfgPath = join(resolver.home(), ".copilot", "mcp-config.json");
|
|
567
|
-
const cfg = readJson
|
|
613
|
+
const cfg = readJson(cfgPath);
|
|
568
614
|
if (hasKey(cfg.mcpServers, "membank") && !overwrite) return { status: "already-configured" };
|
|
569
|
-
writeJsonAtomic
|
|
615
|
+
writeJsonAtomic(cfgPath, {
|
|
570
616
|
...cfg,
|
|
571
617
|
mcpServers: {
|
|
572
|
-
...cfg.mcpServers,
|
|
618
|
+
...MaybeJsonObjectSchema.parse(cfg.mcpServers),
|
|
573
619
|
membank: {
|
|
574
620
|
command: "npx",
|
|
575
621
|
args: [
|
|
@@ -630,12 +676,12 @@ const writers$1 = {
|
|
|
630
676
|
},
|
|
631
677
|
async write(resolver, _run, { overwrite = false } = {}) {
|
|
632
678
|
const cfgPath = join(resolver.home(), ".config", "opencode", "opencode.json");
|
|
633
|
-
const cfg = readJson
|
|
679
|
+
const cfg = readJson(cfgPath);
|
|
634
680
|
if (hasKey(cfg.mcp, "membank") && !overwrite) return { status: "already-configured" };
|
|
635
|
-
writeJsonAtomic
|
|
681
|
+
writeJsonAtomic(cfgPath, {
|
|
636
682
|
...cfg,
|
|
637
683
|
mcp: {
|
|
638
|
-
...cfg.mcp,
|
|
684
|
+
...MaybeJsonObjectSchema.parse(cfg.mcp),
|
|
639
685
|
membank: {
|
|
640
686
|
type: "local",
|
|
641
687
|
command: [
|
|
@@ -651,7 +697,7 @@ const writers$1 = {
|
|
|
651
697
|
}
|
|
652
698
|
}
|
|
653
699
|
};
|
|
654
|
-
const SUPPORTED_HARNESSES =
|
|
700
|
+
const SUPPORTED_HARNESSES = SETUP_HARNESS_VALUES;
|
|
655
701
|
var HarnessConfigWriter = class {
|
|
656
702
|
#resolver;
|
|
657
703
|
#run;
|
|
@@ -677,23 +723,10 @@ const defaultPathResolver = { home: () => {
|
|
|
677
723
|
if (!h) throw new Error("Cannot determine home directory");
|
|
678
724
|
return h;
|
|
679
725
|
} };
|
|
680
|
-
function readJson(path) {
|
|
681
|
-
try {
|
|
682
|
-
return JSON.parse(readFileSync(path, "utf8"));
|
|
683
|
-
} catch {
|
|
684
|
-
return {};
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
function writeJsonAtomic(path, data) {
|
|
688
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
689
|
-
const tmp = join(mkdtempSync(join(tmpdir(), "membank-hook-")), "cfg.json");
|
|
690
|
-
writeFileSync(tmp, JSON.stringify(data, null, 2));
|
|
691
|
-
renameSync(tmp, path);
|
|
692
|
-
}
|
|
693
726
|
function getHooksArray(group) {
|
|
694
727
|
if (typeof group !== "object" || group === null) return [];
|
|
695
|
-
|
|
696
|
-
return Array.isArray(
|
|
728
|
+
if (!("hooks" in group)) return [];
|
|
729
|
+
return Array.isArray(group.hooks) ? group.hooks : [];
|
|
697
730
|
}
|
|
698
731
|
function findMembankHookCommand(hooks, pattern) {
|
|
699
732
|
for (const h of hooks) {
|
|
@@ -733,23 +766,29 @@ const writers = {
|
|
|
733
766
|
"claude-code": {
|
|
734
767
|
inspect(resolver) {
|
|
735
768
|
const cfgPath = join(resolver.home(), ".claude", "settings.json");
|
|
736
|
-
const
|
|
769
|
+
const cfg = readJson(cfgPath);
|
|
770
|
+
const hooks = MaybeJsonObjectSchema.parse(cfg.hooks) ?? {};
|
|
771
|
+
const sessionStartInner = (Array.isArray(hooks.SessionStart) ? hooks.SessionStart : []).flatMap(getHooksArray);
|
|
772
|
+
const userPromptSubmitInner = (Array.isArray(hooks.UserPromptSubmit) ? hooks.UserPromptSubmit : []).flatMap(getHooksArray);
|
|
737
773
|
return {
|
|
738
774
|
status: "ready",
|
|
739
775
|
configPath: cfgPath,
|
|
740
776
|
hooks: [{
|
|
741
777
|
event: "SessionStart",
|
|
742
778
|
command: "npx -y @membank/cli inject --harness claude-code",
|
|
743
|
-
existingCommand: extractInjectCommand(
|
|
779
|
+
existingCommand: extractInjectCommand(sessionStartInner) || null
|
|
780
|
+
}, {
|
|
781
|
+
event: "UserPromptSubmit",
|
|
782
|
+
command: "npx -y @membank/cli inject --harness claude-code --event user-prompt-submit",
|
|
783
|
+
existingCommand: extractInjectCommand(userPromptSubmitInner) || null
|
|
744
784
|
}]
|
|
745
785
|
};
|
|
746
786
|
},
|
|
747
787
|
write(resolver, events) {
|
|
748
788
|
const cfgPath = join(resolver.home(), ".claude", "settings.json");
|
|
749
789
|
const cfg = readJson(cfgPath);
|
|
750
|
-
const hooks = cfg.hooks ?? {};
|
|
790
|
+
const hooks = MaybeJsonObjectSchema.parse(cfg.hooks) ?? {};
|
|
751
791
|
const newHooks = { ...hooks };
|
|
752
|
-
pruneNestedEvent(newHooks, "UserPromptSubmit");
|
|
753
792
|
pruneNestedEvent(newHooks, "PostToolUseFailure");
|
|
754
793
|
if (events.includes("SessionStart")) newHooks.SessionStart = [...filterOutMembank(Array.isArray(hooks.SessionStart) ? hooks.SessionStart : []), {
|
|
755
794
|
matcher: "",
|
|
@@ -758,6 +797,13 @@ const writers = {
|
|
|
758
797
|
command: "npx -y @membank/cli inject --harness claude-code"
|
|
759
798
|
}]
|
|
760
799
|
}];
|
|
800
|
+
if (events.includes("UserPromptSubmit")) newHooks.UserPromptSubmit = [...filterOutMembank(Array.isArray(hooks.UserPromptSubmit) ? hooks.UserPromptSubmit : []), {
|
|
801
|
+
matcher: "",
|
|
802
|
+
hooks: [{
|
|
803
|
+
type: "command",
|
|
804
|
+
command: "npx -y @membank/cli inject --harness claude-code --event user-prompt-submit"
|
|
805
|
+
}]
|
|
806
|
+
}];
|
|
761
807
|
writeJsonAtomic(cfgPath, {
|
|
762
808
|
...cfg,
|
|
763
809
|
hooks: newHooks
|
|
@@ -768,31 +814,42 @@ const writers = {
|
|
|
768
814
|
"copilot-cli": {
|
|
769
815
|
inspect(resolver) {
|
|
770
816
|
const cfgPath = join(resolver.home(), ".copilot", "settings.json");
|
|
771
|
-
const
|
|
817
|
+
const cfg = readJson(cfgPath);
|
|
818
|
+
const hooks = MaybeJsonObjectSchema.parse(cfg.hooks) ?? {};
|
|
819
|
+
const sessionStart = Array.isArray(hooks.sessionStart) ? hooks.sessionStart : [];
|
|
820
|
+
const userPromptSubmitted = Array.isArray(hooks.userPromptSubmitted) ? hooks.userPromptSubmitted : [];
|
|
772
821
|
return {
|
|
773
822
|
status: "ready",
|
|
774
823
|
configPath: cfgPath,
|
|
775
824
|
hooks: [{
|
|
776
825
|
event: "sessionStart",
|
|
777
826
|
command: "npx -y @membank/cli inject --harness copilot-cli",
|
|
778
|
-
existingCommand: extractInjectCommand(
|
|
827
|
+
existingCommand: extractInjectCommand(sessionStart) || null
|
|
828
|
+
}, {
|
|
829
|
+
event: "userPromptSubmitted",
|
|
830
|
+
command: "npx -y @membank/cli inject --harness copilot-cli --event user-prompt-submit",
|
|
831
|
+
existingCommand: extractInjectCommand(userPromptSubmitted) || null
|
|
779
832
|
}]
|
|
780
833
|
};
|
|
781
834
|
},
|
|
782
835
|
write(resolver, events) {
|
|
783
836
|
const cfgPath = join(resolver.home(), ".copilot", "settings.json");
|
|
784
837
|
const cfg = readJson(cfgPath);
|
|
785
|
-
const hooks = cfg.hooks ?? {};
|
|
838
|
+
const hooks = MaybeJsonObjectSchema.parse(cfg.hooks) ?? {};
|
|
786
839
|
const newHooks = { ...hooks };
|
|
787
|
-
pruneFlatEvent(newHooks, "userPromptSubmitted");
|
|
788
840
|
pruneFlatEvent(newHooks, "postToolUseFailure");
|
|
789
841
|
if (events.includes("sessionStart")) newHooks.sessionStart = [...filterOutMembankFlat(Array.isArray(hooks.sessionStart) ? hooks.sessionStart : []), {
|
|
790
842
|
type: "command",
|
|
791
843
|
bash: "npx -y @membank/cli inject --harness copilot-cli",
|
|
792
844
|
timeoutSec: 30
|
|
793
845
|
}];
|
|
846
|
+
if (events.includes("userPromptSubmitted")) newHooks.userPromptSubmitted = [...filterOutMembankFlat(Array.isArray(hooks.userPromptSubmitted) ? hooks.userPromptSubmitted : []), {
|
|
847
|
+
type: "command",
|
|
848
|
+
bash: "npx -y @membank/cli inject --harness copilot-cli --event user-prompt-submit",
|
|
849
|
+
timeoutSec: 30
|
|
850
|
+
}];
|
|
794
851
|
writeJsonAtomic(cfgPath, {
|
|
795
|
-
version: cfg.version ?? 1,
|
|
852
|
+
version: OptionalNumberSchema.parse(cfg.version) ?? 1,
|
|
796
853
|
...cfg,
|
|
797
854
|
hooks: newHooks
|
|
798
855
|
});
|
|
@@ -802,23 +859,29 @@ const writers = {
|
|
|
802
859
|
codex: {
|
|
803
860
|
inspect(resolver) {
|
|
804
861
|
const cfgPath = join(resolver.home(), ".codex", "hooks.json");
|
|
805
|
-
const
|
|
862
|
+
const cfg = readJson(cfgPath);
|
|
863
|
+
const hooks = MaybeJsonObjectSchema.parse(cfg.hooks) ?? {};
|
|
864
|
+
const sessionStartInner = (Array.isArray(hooks.SessionStart) ? hooks.SessionStart : []).flatMap(getHooksArray);
|
|
865
|
+
const userPromptSubmitInner = (Array.isArray(hooks.UserPromptSubmit) ? hooks.UserPromptSubmit : []).flatMap(getHooksArray);
|
|
806
866
|
return {
|
|
807
867
|
status: "ready",
|
|
808
868
|
configPath: cfgPath,
|
|
809
869
|
hooks: [{
|
|
810
870
|
event: "SessionStart",
|
|
811
871
|
command: "npx -y @membank/cli inject --harness codex",
|
|
812
|
-
existingCommand: extractInjectCommand(
|
|
872
|
+
existingCommand: extractInjectCommand(sessionStartInner) || null
|
|
873
|
+
}, {
|
|
874
|
+
event: "UserPromptSubmit",
|
|
875
|
+
command: "npx -y @membank/cli inject --harness codex --event user-prompt-submit",
|
|
876
|
+
existingCommand: extractInjectCommand(userPromptSubmitInner) || null
|
|
813
877
|
}]
|
|
814
878
|
};
|
|
815
879
|
},
|
|
816
880
|
write(resolver, events) {
|
|
817
881
|
const cfgPath = join(resolver.home(), ".codex", "hooks.json");
|
|
818
882
|
const cfg = readJson(cfgPath);
|
|
819
|
-
const hooks = cfg.hooks ?? {};
|
|
883
|
+
const hooks = MaybeJsonObjectSchema.parse(cfg.hooks) ?? {};
|
|
820
884
|
const newHooks = { ...hooks };
|
|
821
|
-
pruneNestedEvent(newHooks, "UserPromptSubmit");
|
|
822
885
|
pruneNestedEvent(newHooks, "PostToolUse");
|
|
823
886
|
if (events.includes("SessionStart")) newHooks.SessionStart = [...filterOutMembank(Array.isArray(hooks.SessionStart) ? hooks.SessionStart : []), {
|
|
824
887
|
matcher: "",
|
|
@@ -828,6 +891,14 @@ const writers = {
|
|
|
828
891
|
timeout: 30
|
|
829
892
|
}]
|
|
830
893
|
}];
|
|
894
|
+
if (events.includes("UserPromptSubmit")) newHooks.UserPromptSubmit = [...filterOutMembank(Array.isArray(hooks.UserPromptSubmit) ? hooks.UserPromptSubmit : []), {
|
|
895
|
+
matcher: "",
|
|
896
|
+
hooks: [{
|
|
897
|
+
type: "command",
|
|
898
|
+
command: "npx -y @membank/cli inject --harness codex --event user-prompt-submit",
|
|
899
|
+
timeout: 30
|
|
900
|
+
}]
|
|
901
|
+
}];
|
|
831
902
|
writeJsonAtomic(cfgPath, {
|
|
832
903
|
...cfg,
|
|
833
904
|
hooks: newHooks
|
|
@@ -1034,7 +1105,7 @@ var SetupOrchestrator = class {
|
|
|
1034
1105
|
const out = json ? () => {} : this.#out;
|
|
1035
1106
|
let detected;
|
|
1036
1107
|
if (harness !== void 0) detected = [{
|
|
1037
|
-
name: harness,
|
|
1108
|
+
name: SetupHarnessSchema.parse(harness),
|
|
1038
1109
|
configPath: ""
|
|
1039
1110
|
}];
|
|
1040
1111
|
else detected = this.#detector();
|
|
@@ -1241,7 +1312,7 @@ var SetupOrchestrator = class {
|
|
|
1241
1312
|
if (process.argv.includes("--mcp")) await startServer();
|
|
1242
1313
|
const program = new Command();
|
|
1243
1314
|
program.name("membank").description("LLM memory management system").option("--json", "emit machine-readable JSON only").option("-y, --yes", "skip all confirmation prompts").option("--mcp", "start the MCP stdio server (for harness integration)");
|
|
1244
|
-
program.command("query <queryText>").description("search memories by semantic similarity").option("--type <type>", "filter by memory type (correction|preference|decision|learning|fact)").option("--limit <n>", "maximum number of results", "10").action(async (queryText, cmdOptions) => {
|
|
1315
|
+
program.command("query <queryText>").description("search memories by semantic similarity").option("--type <type>", "filter by memory type (correction|preference|decision|learning|fact)").option("--limit <n>", "maximum number of results", "10").option("--include-pinned", "include pinned memories in results (excluded by default)").action(async (queryText, cmdOptions) => {
|
|
1245
1316
|
const globalOpts = program.opts();
|
|
1246
1317
|
const formatter = Formatter.create(globalOpts.json === true);
|
|
1247
1318
|
try {
|
|
@@ -1356,7 +1427,7 @@ program.command("setup").description("detect installed harnesses and write MCP c
|
|
|
1356
1427
|
const formatter = Formatter.create(globalOpts.json === true);
|
|
1357
1428
|
const interactive = !formatter.isJson && !autoYes && cmdOptions.harness === void 0;
|
|
1358
1429
|
if (cmdOptions.harness !== void 0) {
|
|
1359
|
-
if (!
|
|
1430
|
+
if (!SetupHarnessSchema.safeParse(cmdOptions.harness).success) {
|
|
1360
1431
|
formatter.error(`Unknown harness: "${cmdOptions.harness}". Supported: ${SUPPORTED_HARNESSES.join(", ")}`);
|
|
1361
1432
|
process.exit(1);
|
|
1362
1433
|
}
|
|
@@ -1419,14 +1490,15 @@ program.command("setup").description("detect installed harnesses and write MCP c
|
|
|
1419
1490
|
}
|
|
1420
1491
|
});
|
|
1421
1492
|
program.command("migrate <mode> [name]").description("list or run a named data migration (modes: list, run)").action(async (mode, name) => {
|
|
1422
|
-
|
|
1493
|
+
const modeResult = MigrateModeSchema.safeParse(mode);
|
|
1494
|
+
if (!modeResult.success) {
|
|
1423
1495
|
process.stderr.write(`Error: mode must be "list" or "run"\n`);
|
|
1424
1496
|
process.exit(1);
|
|
1425
1497
|
}
|
|
1426
1498
|
const globalOpts = program.opts();
|
|
1427
1499
|
const formatter = Formatter.create(globalOpts.json === true);
|
|
1428
1500
|
try {
|
|
1429
|
-
await migrateCommand(
|
|
1501
|
+
await migrateCommand(modeResult.data, name, formatter);
|
|
1430
1502
|
} catch (err) {
|
|
1431
1503
|
formatter.error(err instanceof Error ? err.message : String(err));
|
|
1432
1504
|
process.exit(2);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@membank/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -20,9 +20,10 @@
|
|
|
20
20
|
"cli-table3": "^0.6.5",
|
|
21
21
|
"commander": "^14.0.3",
|
|
22
22
|
"ora": "^9.4.0",
|
|
23
|
-
"
|
|
24
|
-
"@membank/
|
|
25
|
-
"@membank/
|
|
23
|
+
"zod": "^4.4.3",
|
|
24
|
+
"@membank/core": "0.7.0",
|
|
25
|
+
"@membank/mcp": "0.9.0",
|
|
26
|
+
"@membank/dashboard": "0.4.1"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
28
29
|
"@types/node": "^25.6.0",
|