@membank/cli 0.0.3 → 0.1.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 +245 -95
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { DatabaseManager, EmbeddingService, MemoryRepository, QueryEngine } from "@membank/core";
|
|
2
|
+
import { DatabaseManager, EmbeddingService, MEMORY_TYPE_VALUES, MemoryRepository, QueryEngine, SessionContextBuilder, resolveScope } from "@membank/core";
|
|
3
3
|
import { startServer } from "@membank/mcp";
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, writeFileSync } from "node:fs";
|
|
@@ -72,13 +72,7 @@ function exportCommand(db, formatter, opts) {
|
|
|
72
72
|
}
|
|
73
73
|
//#endregion
|
|
74
74
|
//#region src/commands/import.ts
|
|
75
|
-
const MEMORY_TYPES = new Set(
|
|
76
|
-
"correction",
|
|
77
|
-
"preference",
|
|
78
|
-
"decision",
|
|
79
|
-
"learning",
|
|
80
|
-
"fact"
|
|
81
|
-
]);
|
|
75
|
+
const MEMORY_TYPES = new Set(MEMORY_TYPE_VALUES);
|
|
82
76
|
function isValidRecord(r) {
|
|
83
77
|
if (typeof r !== "object" || r === null) return false;
|
|
84
78
|
const rec = r;
|
|
@@ -133,6 +127,44 @@ async function importCommand(filePath, db, formatter, prompt) {
|
|
|
133
127
|
else process.stdout.write(`Imported ${count} memories.\n`);
|
|
134
128
|
}
|
|
135
129
|
//#endregion
|
|
130
|
+
//#region src/commands/inject.ts
|
|
131
|
+
const MEMORY_GUIDANCE = "[Memory Guidance]: query_memory before answering on topics where past preferences, corrections, or decisions may apply; save_memory when user corrects you, states a preference, makes a decision, or shares something worth retaining across sessions; update_memory to refine an existing memory (query first to find it) or to set pinned=true/false; delete_memory when a memory is wrong or no longer relevant; pin high-value memories that should always appear at session start";
|
|
132
|
+
function formatContext(ctx) {
|
|
133
|
+
const lines = [];
|
|
134
|
+
const statParts = Object.entries(ctx.stats).filter(([, count]) => count > 0).map(([type, count]) => `${count} ${type}${count !== 1 ? "s" : ""}`);
|
|
135
|
+
if (statParts.length > 0) lines.push(`[Memory Stats]: ${statParts.join(", ")}`);
|
|
136
|
+
else lines.push("[Memory Stats]: no memories saved yet");
|
|
137
|
+
const formatMemory = (m) => `"${m.content}" (${m.type})`;
|
|
138
|
+
for (const m of ctx.pinnedGlobal) lines.push(`[Pinned Global]: ${formatMemory(m)}`);
|
|
139
|
+
for (const m of ctx.pinnedProject) lines.push(`[Pinned Project]: ${formatMemory(m)}`);
|
|
140
|
+
lines.push(MEMORY_GUIDANCE);
|
|
141
|
+
return lines.join("\n");
|
|
142
|
+
}
|
|
143
|
+
async function injectCommand(opts) {
|
|
144
|
+
const projectScope = opts.scope ?? await resolveScope();
|
|
145
|
+
const db = DatabaseManager.open();
|
|
146
|
+
let text;
|
|
147
|
+
try {
|
|
148
|
+
text = formatContext(new SessionContextBuilder(db).getSessionContext(projectScope));
|
|
149
|
+
} finally {
|
|
150
|
+
db.close();
|
|
151
|
+
}
|
|
152
|
+
if (!text) process.exit(0);
|
|
153
|
+
const harness = opts.harness;
|
|
154
|
+
if (harness === "claude-code") {
|
|
155
|
+
process.stdout.write(JSON.stringify({ hookSpecificOutput: {
|
|
156
|
+
hookEventName: "SessionStart",
|
|
157
|
+
additionalContext: text
|
|
158
|
+
} }));
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (harness === "copilot-cli") {
|
|
162
|
+
process.stdout.write(JSON.stringify({ additionalContext: text }));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
process.stdout.write(`${text}\n`);
|
|
166
|
+
}
|
|
167
|
+
//#endregion
|
|
136
168
|
//#region src/commands/list.ts
|
|
137
169
|
async function listCommand(options, formatter) {
|
|
138
170
|
const db = DatabaseManager.open();
|
|
@@ -216,11 +248,8 @@ var Formatter = class Formatter {
|
|
|
216
248
|
constructor(isJson) {
|
|
217
249
|
this.#isJson = isJson;
|
|
218
250
|
}
|
|
219
|
-
static create() {
|
|
220
|
-
return new Formatter(!process.stdout.isTTY);
|
|
221
|
-
}
|
|
222
|
-
withJson(isJson) {
|
|
223
|
-
return new Formatter(isJson);
|
|
251
|
+
static create(forceJson = false) {
|
|
252
|
+
return new Formatter(forceJson || !process.stdout.isTTY);
|
|
224
253
|
}
|
|
225
254
|
get isJson() {
|
|
226
255
|
return this.#isJson;
|
|
@@ -230,13 +259,7 @@ var Formatter = class Formatter {
|
|
|
230
259
|
process.stdout.write(`${JSON.stringify(memory)}\n`);
|
|
231
260
|
return;
|
|
232
261
|
}
|
|
233
|
-
|
|
234
|
-
process.stdout.write(`\n[${memory.type}] ${memory.id}\n`);
|
|
235
|
-
process.stdout.write(` Content : ${memory.content}\n`);
|
|
236
|
-
process.stdout.write(` Tags : ${tags}\n`);
|
|
237
|
-
process.stdout.write(` Scope : ${memory.scope}\n`);
|
|
238
|
-
process.stdout.write(` Pinned : ${memory.pinned}\n`);
|
|
239
|
-
process.stdout.write("\n");
|
|
262
|
+
this.#writeMemoryBlock(memory, ` Pinned : ${memory.pinned}\n`);
|
|
240
263
|
}
|
|
241
264
|
outputMemories(memories) {
|
|
242
265
|
if (this.#isJson) {
|
|
@@ -247,13 +270,7 @@ var Formatter = class Formatter {
|
|
|
247
270
|
process.stdout.write("No memories found.\n");
|
|
248
271
|
return;
|
|
249
272
|
}
|
|
250
|
-
for (const memory of memories)
|
|
251
|
-
const tags = memory.tags.length > 0 ? memory.tags.join(", ") : "(none)";
|
|
252
|
-
process.stdout.write(`\n[${memory.type}] ${memory.id}\n`);
|
|
253
|
-
process.stdout.write(` Content : ${memory.content}\n`);
|
|
254
|
-
process.stdout.write(` Tags : ${tags}\n`);
|
|
255
|
-
process.stdout.write(` Scope : ${memory.scope}\n`);
|
|
256
|
-
}
|
|
273
|
+
for (const memory of memories) this.#writeMemoryBlock(memory);
|
|
257
274
|
process.stdout.write("\n");
|
|
258
275
|
}
|
|
259
276
|
outputStats(stats) {
|
|
@@ -280,20 +297,22 @@ var Formatter = class Formatter {
|
|
|
280
297
|
process.stdout.write("No memories found.\n");
|
|
281
298
|
return;
|
|
282
299
|
}
|
|
283
|
-
for (const result of results) {
|
|
284
|
-
const tags = result.tags.length > 0 ? result.tags.join(", ") : "(none)";
|
|
285
|
-
process.stdout.write(`\n[${result.type}] ${result.id}\n`);
|
|
286
|
-
process.stdout.write(` Content : ${result.content}\n`);
|
|
287
|
-
process.stdout.write(` Tags : ${tags}\n`);
|
|
288
|
-
process.stdout.write(` Scope : ${result.scope}\n`);
|
|
289
|
-
process.stdout.write(` Score : ${result.score.toFixed(4)}\n`);
|
|
290
|
-
}
|
|
300
|
+
for (const result of results) this.#writeMemoryBlock(result, ` Score : ${result.score.toFixed(4)}\n`);
|
|
291
301
|
process.stdout.write("\n");
|
|
292
302
|
}
|
|
293
303
|
error(msg) {
|
|
294
304
|
if (this.#isJson) process.stderr.write(`${JSON.stringify({ error: msg })}\n`);
|
|
295
305
|
else process.stderr.write(`Error: ${msg}\n`);
|
|
296
306
|
}
|
|
307
|
+
#writeMemoryBlock(memory, extra) {
|
|
308
|
+
const tags = memory.tags.length > 0 ? memory.tags.join(", ") : "(none)";
|
|
309
|
+
process.stdout.write(`\n[${memory.type}] ${memory.id}\n`);
|
|
310
|
+
process.stdout.write(` Content : ${memory.content}\n`);
|
|
311
|
+
process.stdout.write(` Tags : ${tags}\n`);
|
|
312
|
+
process.stdout.write(` Scope : ${memory.scope}\n`);
|
|
313
|
+
if (extra !== void 0) process.stdout.write(extra);
|
|
314
|
+
process.stdout.write("\n");
|
|
315
|
+
}
|
|
297
316
|
};
|
|
298
317
|
//#endregion
|
|
299
318
|
//#region src/prompt-helper.ts
|
|
@@ -352,7 +371,7 @@ async function execFileNoThrow(cmd, args) {
|
|
|
352
371
|
}
|
|
353
372
|
//#endregion
|
|
354
373
|
//#region src/setup/harness-config-writer.ts
|
|
355
|
-
const defaultPathResolver = {
|
|
374
|
+
const defaultPathResolver$1 = {
|
|
356
375
|
home: () => {
|
|
357
376
|
const h = process.env.HOME ?? process.env.USERPROFILE;
|
|
358
377
|
if (!h) throw new Error("Cannot determine home directory");
|
|
@@ -360,14 +379,14 @@ const defaultPathResolver = {
|
|
|
360
379
|
},
|
|
361
380
|
cwd: () => process.cwd()
|
|
362
381
|
};
|
|
363
|
-
function readJson(path) {
|
|
382
|
+
function readJson$1(path) {
|
|
364
383
|
try {
|
|
365
384
|
return JSON.parse(readFileSync(path, "utf8"));
|
|
366
385
|
} catch {
|
|
367
386
|
return {};
|
|
368
387
|
}
|
|
369
388
|
}
|
|
370
|
-
function writeJsonAtomic(path, data) {
|
|
389
|
+
function writeJsonAtomic$1(path, data) {
|
|
371
390
|
mkdirSync(dirname(path), { recursive: true });
|
|
372
391
|
const tmp = join(mkdtempSync(join(tmpdir(), "membank-")), "cfg.json");
|
|
373
392
|
writeFileSync(tmp, JSON.stringify(data, null, 2));
|
|
@@ -384,9 +403,9 @@ const MEMBANK_NPX_ARGS = [
|
|
|
384
403
|
"@membank/cli@latest",
|
|
385
404
|
"--mcp"
|
|
386
405
|
];
|
|
387
|
-
const writers = {
|
|
406
|
+
const writers$1 = {
|
|
388
407
|
"claude-code": { async write(resolver, run, { overwrite = false } = {}) {
|
|
389
|
-
const configured = hasKey(readJson(join(resolver.home(), ".claude.json")).mcpServers, "membank");
|
|
408
|
+
const configured = hasKey(readJson$1(join(resolver.home(), ".claude.json")).mcpServers, "membank");
|
|
390
409
|
if (configured && !overwrite) return { status: "already-configured" };
|
|
391
410
|
if (configured) {
|
|
392
411
|
const remove = await run("claude", [
|
|
@@ -412,37 +431,20 @@ const writers = {
|
|
|
412
431
|
if (add.exitCode !== 0) throw new Error(`claude mcp add failed: ${add.stderr || add.stdout}`);
|
|
413
432
|
return { status: "written" };
|
|
414
433
|
} },
|
|
415
|
-
|
|
416
|
-
const cfgPath = join(resolver.
|
|
417
|
-
const cfg = readJson(cfgPath);
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
...cfg,
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
command: "npx",
|
|
427
|
-
args: ["@membank/cli@latest", "--mcp"]
|
|
428
|
-
}
|
|
434
|
+
copilot: { async write(resolver, _run, { overwrite = false } = {}) {
|
|
435
|
+
const cfgPath = join(resolver.home(), ".copilot", "mcp-config.json");
|
|
436
|
+
const cfg = readJson$1(cfgPath);
|
|
437
|
+
if (hasKey(cfg.mcpServers, "membank") && !overwrite) return { status: "already-configured" };
|
|
438
|
+
writeJsonAtomic$1(cfgPath, {
|
|
439
|
+
...cfg,
|
|
440
|
+
mcpServers: {
|
|
441
|
+
...cfg.mcpServers,
|
|
442
|
+
membank: {
|
|
443
|
+
command: "npx",
|
|
444
|
+
args: ["@membank/cli@latest", "--mcp"]
|
|
429
445
|
}
|
|
430
|
-
}
|
|
431
|
-
return { status: "written" };
|
|
432
|
-
}
|
|
433
|
-
const payload = JSON.stringify({
|
|
434
|
-
name: "membank",
|
|
435
|
-
command: "npx",
|
|
436
|
-
args: ["@membank/cli@latest", "--mcp"]
|
|
446
|
+
}
|
|
437
447
|
});
|
|
438
|
-
const result = await run("code", [
|
|
439
|
-
"--folder-uri",
|
|
440
|
-
resolver.cwd(),
|
|
441
|
-
"--add-mcp",
|
|
442
|
-
payload
|
|
443
|
-
]);
|
|
444
|
-
assertCliFound(result, "code");
|
|
445
|
-
if (result.exitCode !== 0) throw new Error(`code --add-mcp failed: ${result.stderr || result.stdout}`);
|
|
446
448
|
return { status: "written" };
|
|
447
449
|
} },
|
|
448
450
|
codex: { async write(_resolver, run, { overwrite = false } = {}) {
|
|
@@ -472,9 +474,9 @@ const writers = {
|
|
|
472
474
|
} },
|
|
473
475
|
opencode: { async write(resolver, _run, { overwrite = false } = {}) {
|
|
474
476
|
const cfgPath = join(resolver.home(), ".config", "opencode", "opencode.json");
|
|
475
|
-
const cfg = readJson(cfgPath);
|
|
477
|
+
const cfg = readJson$1(cfgPath);
|
|
476
478
|
if (hasKey(cfg.mcp, "membank") && !overwrite) return { status: "already-configured" };
|
|
477
|
-
writeJsonAtomic(cfgPath, {
|
|
479
|
+
writeJsonAtomic$1(cfgPath, {
|
|
478
480
|
...cfg,
|
|
479
481
|
mcp: {
|
|
480
482
|
...cfg.mcp,
|
|
@@ -491,21 +493,142 @@ const writers = {
|
|
|
491
493
|
return { status: "written" };
|
|
492
494
|
} }
|
|
493
495
|
};
|
|
494
|
-
const SUPPORTED_HARNESSES = Object.keys(writers);
|
|
496
|
+
const SUPPORTED_HARNESSES = Object.keys(writers$1);
|
|
495
497
|
var HarnessConfigWriter = class {
|
|
496
498
|
#resolver;
|
|
497
499
|
#run;
|
|
498
|
-
constructor(resolver = defaultPathResolver, run = execFileNoThrow) {
|
|
500
|
+
constructor(resolver = defaultPathResolver$1, run = execFileNoThrow) {
|
|
499
501
|
this.#resolver = resolver;
|
|
500
502
|
this.#run = run;
|
|
501
503
|
}
|
|
502
504
|
async write(harness, { overwrite = false } = {}) {
|
|
503
|
-
const writer = writers[harness];
|
|
505
|
+
const writer = writers$1[harness];
|
|
504
506
|
if (!writer) throw new Error(`Unknown harness: ${harness}`);
|
|
505
507
|
return writer.write(this.#resolver, this.#run, { overwrite });
|
|
506
508
|
}
|
|
507
509
|
};
|
|
508
510
|
//#endregion
|
|
511
|
+
//#region src/setup/injection-hook-writer.ts
|
|
512
|
+
const defaultPathResolver = { home: () => {
|
|
513
|
+
const h = process.env.HOME ?? process.env.USERPROFILE;
|
|
514
|
+
if (!h) throw new Error("Cannot determine home directory");
|
|
515
|
+
return h;
|
|
516
|
+
} };
|
|
517
|
+
function readJson(path) {
|
|
518
|
+
try {
|
|
519
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
520
|
+
} catch {
|
|
521
|
+
return {};
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
function writeJsonAtomic(path, data) {
|
|
525
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
526
|
+
const tmp = join(mkdtempSync(join(tmpdir(), "membank-hook-")), "cfg.json");
|
|
527
|
+
writeFileSync(tmp, JSON.stringify(data, null, 2));
|
|
528
|
+
renameSync(tmp, path);
|
|
529
|
+
}
|
|
530
|
+
function containsMembankInject(hooks) {
|
|
531
|
+
if (!Array.isArray(hooks)) return false;
|
|
532
|
+
return hooks.some((h) => typeof h === "object" && h !== null && ("command" in h && typeof h.command === "string" && h.command.includes("@membank/cli inject") || "bash" in h && typeof h.bash === "string" && h.bash.includes("@membank/cli inject")));
|
|
533
|
+
}
|
|
534
|
+
const writers = {
|
|
535
|
+
"claude-code": { write(resolver) {
|
|
536
|
+
const cfgPath = join(resolver.home(), ".claude", "settings.json");
|
|
537
|
+
const cfg = readJson(cfgPath);
|
|
538
|
+
const hooks = cfg.hooks;
|
|
539
|
+
const sessionStart = hooks?.SessionStart;
|
|
540
|
+
if (Array.isArray(sessionStart) && containsMembankInject(sessionStart.flatMap((g) => Array.isArray(g.hooks) ? g.hooks : []))) return { status: "already-configured" };
|
|
541
|
+
const existingSessionStart = Array.isArray(sessionStart) ? sessionStart : [];
|
|
542
|
+
writeJsonAtomic(cfgPath, {
|
|
543
|
+
...cfg,
|
|
544
|
+
hooks: {
|
|
545
|
+
...hooks ?? {},
|
|
546
|
+
SessionStart: [...existingSessionStart, {
|
|
547
|
+
matcher: "",
|
|
548
|
+
hooks: [{
|
|
549
|
+
type: "command",
|
|
550
|
+
command: "npx @membank/cli inject --harness claude-code"
|
|
551
|
+
}]
|
|
552
|
+
}]
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
return { status: "written" };
|
|
556
|
+
} },
|
|
557
|
+
"copilot-cli": { write(resolver) {
|
|
558
|
+
const cfgPath = join(resolver.home(), ".copilot", "settings.json");
|
|
559
|
+
const cfg = readJson(cfgPath);
|
|
560
|
+
const hooks = cfg.hooks;
|
|
561
|
+
const sessionStart = hooks?.sessionStart;
|
|
562
|
+
if (Array.isArray(sessionStart) && containsMembankInject(sessionStart)) return { status: "already-configured" };
|
|
563
|
+
const existingSessionStart = Array.isArray(sessionStart) ? sessionStart : [];
|
|
564
|
+
writeJsonAtomic(cfgPath, {
|
|
565
|
+
version: cfg.version ?? 1,
|
|
566
|
+
...cfg,
|
|
567
|
+
hooks: {
|
|
568
|
+
...hooks ?? {},
|
|
569
|
+
sessionStart: [...existingSessionStart, {
|
|
570
|
+
type: "command",
|
|
571
|
+
bash: "npx @membank/cli inject --harness copilot-cli",
|
|
572
|
+
timeoutSec: 30
|
|
573
|
+
}]
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
return { status: "written" };
|
|
577
|
+
} },
|
|
578
|
+
codex: { write(resolver) {
|
|
579
|
+
const cfgPath = join(resolver.home(), ".codex", "hooks.json");
|
|
580
|
+
const cfg = readJson(cfgPath);
|
|
581
|
+
const hooks = cfg.hooks;
|
|
582
|
+
const sessionStart = hooks?.SessionStart;
|
|
583
|
+
if (Array.isArray(sessionStart) && containsMembankInject(sessionStart.flatMap((g) => Array.isArray(g.hooks) ? g.hooks : []))) return { status: "already-configured" };
|
|
584
|
+
const existingSessionStart = Array.isArray(sessionStart) ? sessionStart : [];
|
|
585
|
+
writeJsonAtomic(cfgPath, {
|
|
586
|
+
...cfg,
|
|
587
|
+
hooks: {
|
|
588
|
+
...hooks ?? {},
|
|
589
|
+
SessionStart: [...existingSessionStart, {
|
|
590
|
+
matcher: "",
|
|
591
|
+
hooks: [{
|
|
592
|
+
type: "command",
|
|
593
|
+
command: "npx @membank/cli inject --harness codex",
|
|
594
|
+
timeout: 30
|
|
595
|
+
}]
|
|
596
|
+
}]
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
return { status: "written" };
|
|
600
|
+
} },
|
|
601
|
+
opencode: { write(resolver) {
|
|
602
|
+
const pluginPath = join(resolver.home(), ".config", "opencode", "plugins", "membank.js");
|
|
603
|
+
if (existsSync(pluginPath)) {
|
|
604
|
+
if (readFileSync(pluginPath, "utf8").includes("@membank/cli inject")) return { status: "already-configured" };
|
|
605
|
+
}
|
|
606
|
+
mkdirSync(dirname(pluginPath), { recursive: true });
|
|
607
|
+
writeFileSync(pluginPath, [
|
|
608
|
+
"export default {",
|
|
609
|
+
" hooks: {",
|
|
610
|
+
" \"session.start\": async ({ $ }) => {",
|
|
611
|
+
" return await $`npx @membank/cli inject`.text();",
|
|
612
|
+
" },",
|
|
613
|
+
" },",
|
|
614
|
+
"};",
|
|
615
|
+
""
|
|
616
|
+
].join("\n"), "utf8");
|
|
617
|
+
return { status: "written" };
|
|
618
|
+
} }
|
|
619
|
+
};
|
|
620
|
+
var InjectionHookWriter = class {
|
|
621
|
+
#resolver;
|
|
622
|
+
constructor(resolver = defaultPathResolver) {
|
|
623
|
+
this.#resolver = resolver;
|
|
624
|
+
}
|
|
625
|
+
write(harness) {
|
|
626
|
+
const writer = writers[harness];
|
|
627
|
+
if (!writer) return { status: "not-supported" };
|
|
628
|
+
return writer.write(this.#resolver);
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
//#endregion
|
|
509
632
|
//#region src/setup/model-downloader.ts
|
|
510
633
|
const MODEL_NAME = "Xenova/bge-small-en-v1.5";
|
|
511
634
|
var ModelDownloadError = class extends Error {
|
|
@@ -571,13 +694,9 @@ var ModelDownloader = class extends EventEmitter {
|
|
|
571
694
|
};
|
|
572
695
|
//#endregion
|
|
573
696
|
//#region src/setup/harness-detector.ts
|
|
574
|
-
const defaultResolver = {
|
|
575
|
-
homeDir: homedir,
|
|
576
|
-
cwd: () => process.cwd()
|
|
577
|
-
};
|
|
697
|
+
const defaultResolver = { homeDir: homedir };
|
|
578
698
|
function harnessConfigs(resolver) {
|
|
579
699
|
const home = resolver.homeDir();
|
|
580
|
-
const cwd = resolver.cwd();
|
|
581
700
|
return [
|
|
582
701
|
{
|
|
583
702
|
name: "claude-code",
|
|
@@ -585,8 +704,8 @@ function harnessConfigs(resolver) {
|
|
|
585
704
|
fallbackPaths: [join(home, ".claude", "settings.json")]
|
|
586
705
|
},
|
|
587
706
|
{
|
|
588
|
-
name: "
|
|
589
|
-
configPath: join(
|
|
707
|
+
name: "copilot",
|
|
708
|
+
configPath: join(home, ".copilot", "mcp-config.json")
|
|
590
709
|
},
|
|
591
710
|
{
|
|
592
711
|
name: "codex",
|
|
@@ -628,6 +747,7 @@ function defaultPrompter(question) {
|
|
|
628
747
|
var SetupOrchestrator = class {
|
|
629
748
|
#detector;
|
|
630
749
|
#writer;
|
|
750
|
+
#hookWriter;
|
|
631
751
|
#prompter;
|
|
632
752
|
#modelDownloader;
|
|
633
753
|
#out;
|
|
@@ -635,6 +755,7 @@ var SetupOrchestrator = class {
|
|
|
635
755
|
constructor(deps) {
|
|
636
756
|
this.#detector = deps.detector ?? (() => detectHarnesses());
|
|
637
757
|
this.#writer = deps.writer;
|
|
758
|
+
this.#hookWriter = deps.hookWriter;
|
|
638
759
|
this.#prompter = deps.prompter ?? defaultPrompter;
|
|
639
760
|
this.#modelDownloader = deps.modelDownloader;
|
|
640
761
|
this.#out = deps.out ?? ((msg) => process.stdout.write(`${msg}\n`));
|
|
@@ -656,6 +777,7 @@ var SetupOrchestrator = class {
|
|
|
656
777
|
if (json) this.#out(JSON.stringify({
|
|
657
778
|
detectedHarnesses: [],
|
|
658
779
|
configuredHarnesses: [],
|
|
780
|
+
injectionHooksConfigured: [],
|
|
659
781
|
modelDownloaded: false
|
|
660
782
|
}));
|
|
661
783
|
return [];
|
|
@@ -667,7 +789,10 @@ var SetupOrchestrator = class {
|
|
|
667
789
|
}
|
|
668
790
|
if (dryRun) {
|
|
669
791
|
out("Planned changes (dry-run — no files written):");
|
|
670
|
-
for (const h of detected)
|
|
792
|
+
for (const h of detected) {
|
|
793
|
+
out(` ⚠ ${h.name}: would write MCP config`);
|
|
794
|
+
if (this.#hookWriter) out(` ⚠ ${h.name}: would write injection hook config`);
|
|
795
|
+
}
|
|
671
796
|
out("");
|
|
672
797
|
out(" ⚠ Model download: skipped (dry-run)");
|
|
673
798
|
return detected.map((h) => ({
|
|
@@ -732,6 +857,21 @@ var SetupOrchestrator = class {
|
|
|
732
857
|
});
|
|
733
858
|
}
|
|
734
859
|
out("");
|
|
860
|
+
const injectionHooksConfigured = [];
|
|
861
|
+
if (this.#hookWriter) {
|
|
862
|
+
for (const h of detected) try {
|
|
863
|
+
const hookResult = this.#hookWriter.write(h.name);
|
|
864
|
+
if (hookResult.status === "not-supported") continue;
|
|
865
|
+
if (hookResult.status === "written") {
|
|
866
|
+
out(` ✓ ${h.name}: injection hook written`);
|
|
867
|
+
injectionHooksConfigured.push(h.name);
|
|
868
|
+
} else out(` ⚠ ${h.name}: injection hook already configured`);
|
|
869
|
+
} catch (err) {
|
|
870
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
871
|
+
out(` ✗ ${h.name} injection hook: ${msg}`);
|
|
872
|
+
}
|
|
873
|
+
out("");
|
|
874
|
+
}
|
|
735
875
|
let modelDownloaded = false;
|
|
736
876
|
if (this.#modelDownloader) modelDownloaded = !(await this.#runModelDownload(this.#modelDownloader, out)).skipped;
|
|
737
877
|
else out("Model download step: see DRA-52");
|
|
@@ -742,6 +882,7 @@ var SetupOrchestrator = class {
|
|
|
742
882
|
const output = {
|
|
743
883
|
detectedHarnesses: detected.map((h) => h.name),
|
|
744
884
|
configuredHarnesses: results.filter((r) => r.status === "written").map((r) => r.harness),
|
|
885
|
+
injectionHooksConfigured,
|
|
745
886
|
modelDownloaded
|
|
746
887
|
};
|
|
747
888
|
this.#out(JSON.stringify(output));
|
|
@@ -782,7 +923,7 @@ const program = new Command();
|
|
|
782
923
|
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)");
|
|
783
924
|
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) => {
|
|
784
925
|
const globalOpts = program.opts();
|
|
785
|
-
const formatter = Formatter.create(
|
|
926
|
+
const formatter = Formatter.create(globalOpts.json === true);
|
|
786
927
|
try {
|
|
787
928
|
await queryCommand(queryText, cmdOptions, formatter);
|
|
788
929
|
} catch (err) {
|
|
@@ -792,7 +933,7 @@ program.command("query <queryText>").description("search memories by semantic si
|
|
|
792
933
|
});
|
|
793
934
|
program.command("list").description("list memories with optional filters").option("--type <type>", "filter by memory type (correction|preference|decision|learning|fact)").option("--pinned", "return only pinned memories").action(async (cmdOptions) => {
|
|
794
935
|
const globalOpts = program.opts();
|
|
795
|
-
const formatter = Formatter.create(
|
|
936
|
+
const formatter = Formatter.create(globalOpts.json === true);
|
|
796
937
|
try {
|
|
797
938
|
await listCommand(cmdOptions, formatter);
|
|
798
939
|
} catch (err) {
|
|
@@ -802,7 +943,7 @@ program.command("list").description("list memories with optional filters").optio
|
|
|
802
943
|
});
|
|
803
944
|
program.command("stats").description("show memory counts by type, total, and needs_review").action(async () => {
|
|
804
945
|
const globalOpts = program.opts();
|
|
805
|
-
const formatter = Formatter.create(
|
|
946
|
+
const formatter = Formatter.create(globalOpts.json === true);
|
|
806
947
|
try {
|
|
807
948
|
await statsCommand(formatter);
|
|
808
949
|
} catch (err) {
|
|
@@ -812,7 +953,7 @@ program.command("stats").description("show memory counts by type, total, and nee
|
|
|
812
953
|
});
|
|
813
954
|
program.command("delete <id>").description("delete a memory by ID").action(async (id) => {
|
|
814
955
|
const globalOpts = program.opts();
|
|
815
|
-
const formatter = Formatter.create(
|
|
956
|
+
const formatter = Formatter.create(globalOpts.json === true);
|
|
816
957
|
const prompt = new PromptHelper(globalOpts.yes === true || !process.stdout.isTTY);
|
|
817
958
|
const db = DatabaseManager.open();
|
|
818
959
|
try {
|
|
@@ -826,7 +967,7 @@ program.command("delete <id>").description("delete a memory by ID").action(async
|
|
|
826
967
|
});
|
|
827
968
|
program.command("add <content>").description("save a new memory").requiredOption("--type <type>", "memory type (correction|preference|decision|learning|fact)").option("--tags <tags>", "comma-separated tags").option("--scope <scope>", "scope (global or project identifier)").action(async (content, cmdOptions) => {
|
|
828
969
|
const globalOpts = program.opts();
|
|
829
|
-
const formatter = Formatter.create(
|
|
970
|
+
const formatter = Formatter.create(globalOpts.json === true);
|
|
830
971
|
try {
|
|
831
972
|
await addCommand(content, cmdOptions, formatter);
|
|
832
973
|
} catch (err) {
|
|
@@ -836,7 +977,7 @@ program.command("add <content>").description("save a new memory").requiredOption
|
|
|
836
977
|
});
|
|
837
978
|
program.command("pin <id>").description("pin a memory by ID").action((id) => {
|
|
838
979
|
const globalOpts = program.opts();
|
|
839
|
-
const formatter = Formatter.create(
|
|
980
|
+
const formatter = Formatter.create(globalOpts.json === true);
|
|
840
981
|
try {
|
|
841
982
|
pinCommand(id, formatter);
|
|
842
983
|
} catch (err) {
|
|
@@ -846,7 +987,7 @@ program.command("pin <id>").description("pin a memory by ID").action((id) => {
|
|
|
846
987
|
});
|
|
847
988
|
program.command("unpin <id>").description("unpin a memory by ID").action((id) => {
|
|
848
989
|
const globalOpts = program.opts();
|
|
849
|
-
const formatter = Formatter.create(
|
|
990
|
+
const formatter = Formatter.create(globalOpts.json === true);
|
|
850
991
|
try {
|
|
851
992
|
unpinCommand(id, formatter);
|
|
852
993
|
} catch (err) {
|
|
@@ -856,7 +997,7 @@ program.command("unpin <id>").description("unpin a memory by ID").action((id) =>
|
|
|
856
997
|
});
|
|
857
998
|
program.command("export").description("export all memories to a JSON file").option("--output <path>", "output file path (default: membank-export-<timestamp>.json in cwd)").action((cmdOptions) => {
|
|
858
999
|
const globalOpts = program.opts();
|
|
859
|
-
const formatter = Formatter.create(
|
|
1000
|
+
const formatter = Formatter.create(globalOpts.json === true);
|
|
860
1001
|
const db = DatabaseManager.open();
|
|
861
1002
|
try {
|
|
862
1003
|
exportCommand(db, formatter, cmdOptions);
|
|
@@ -869,7 +1010,7 @@ program.command("export").description("export all memories to a JSON file").opti
|
|
|
869
1010
|
});
|
|
870
1011
|
program.command("import <file>").description("import memories from a JSON export file").action(async (file) => {
|
|
871
1012
|
const globalOpts = program.opts();
|
|
872
|
-
const formatter = Formatter.create(
|
|
1013
|
+
const formatter = Formatter.create(globalOpts.json === true);
|
|
873
1014
|
const prompt = new PromptHelper(globalOpts.yes === true || !process.stdout.isTTY);
|
|
874
1015
|
const db = DatabaseManager.open();
|
|
875
1016
|
try {
|
|
@@ -881,21 +1022,30 @@ program.command("import <file>").description("import memories from a JSON export
|
|
|
881
1022
|
db.close();
|
|
882
1023
|
}
|
|
883
1024
|
});
|
|
1025
|
+
program.command("inject").description("output session context for harness injection (used by setup hooks)").option("--harness <name>", "format output for a specific harness (claude-code|copilot-cli|codex|opencode)").option("--scope <scope>", "project scope override (default: auto-detect from git remote)").action(async (cmdOptions) => {
|
|
1026
|
+
try {
|
|
1027
|
+
await injectCommand(cmdOptions);
|
|
1028
|
+
} catch (err) {
|
|
1029
|
+
process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
|
|
1030
|
+
process.exit(2);
|
|
1031
|
+
}
|
|
1032
|
+
});
|
|
884
1033
|
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)").action(async (cmdOptions) => {
|
|
885
1034
|
const globalOpts = program.opts();
|
|
886
1035
|
const autoYes = cmdOptions.yes === true || globalOpts.yes === true;
|
|
887
|
-
const
|
|
888
|
-
const formatter = Formatter.create().withJson(useJson);
|
|
1036
|
+
const formatter = Formatter.create(globalOpts.json === true);
|
|
889
1037
|
if (cmdOptions.harness !== void 0) {
|
|
890
|
-
if (!SUPPORTED_HARNESSES.
|
|
1038
|
+
if (!SUPPORTED_HARNESSES.some((h) => h === cmdOptions.harness)) {
|
|
891
1039
|
formatter.error(`Unknown harness: "${cmdOptions.harness}". Supported: ${SUPPORTED_HARNESSES.join(", ")}`);
|
|
892
1040
|
process.exit(1);
|
|
893
1041
|
}
|
|
894
1042
|
}
|
|
895
1043
|
const writer = new HarnessConfigWriter();
|
|
1044
|
+
const hookWriter = new InjectionHookWriter();
|
|
896
1045
|
const promptHelper = new PromptHelper(autoYes);
|
|
897
1046
|
const orchestrator = new SetupOrchestrator({
|
|
898
1047
|
writer,
|
|
1048
|
+
hookWriter,
|
|
899
1049
|
prompter: (question) => promptHelper.confirm(question),
|
|
900
1050
|
modelDownloader: new ModelDownloader()
|
|
901
1051
|
});
|
|
@@ -904,7 +1054,7 @@ program.command("setup").description("detect installed harnesses and write MCP c
|
|
|
904
1054
|
yes: autoYes,
|
|
905
1055
|
dryRun: cmdOptions.dryRun,
|
|
906
1056
|
harness: cmdOptions.harness,
|
|
907
|
-
json:
|
|
1057
|
+
json: formatter.isJson
|
|
908
1058
|
})).some((r) => r.status === "error")) process.exit(1);
|
|
909
1059
|
} catch (err) {
|
|
910
1060
|
formatter.error(err instanceof Error ? err.message : String(err));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@membank/cli",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"@huggingface/transformers": "^4.2.0",
|
|
18
18
|
"commander": "^14.0.3",
|
|
19
19
|
"ora": "^9.4.0",
|
|
20
|
-
"@membank/core": "0.0
|
|
21
|
-
"@membank/mcp": "0.0
|
|
20
|
+
"@membank/core": "0.1.0",
|
|
21
|
+
"@membank/mcp": "0.1.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/node": "^25.6.0",
|