@membank/cli 0.11.2 → 0.12.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/README.md +19 -7
- package/dist/index.mjs +136 -171
- package/package.json +5 -6
package/README.md
CHANGED
|
@@ -35,6 +35,16 @@ Options:
|
|
|
35
35
|
|
|
36
36
|
Supported harnesses: `claude-code`, `copilot`, `codex`, `opencode` (see `membank setup` for harness-specific setup instructions)
|
|
37
37
|
|
|
38
|
+
### `membank setup upgrade`
|
|
39
|
+
|
|
40
|
+
Migrate existing harness configs from the old `npx @membank/cli --mcp` pattern to the standalone `npx @membank/mcp` binary:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
membank setup upgrade
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Run this once after upgrading to align all configured harnesses with the new standalone MCP package.
|
|
47
|
+
|
|
38
48
|
## Commands
|
|
39
49
|
|
|
40
50
|
### `membank query <text>`
|
|
@@ -167,31 +177,33 @@ membank inject --harness claude-code --event session-stop
|
|
|
167
177
|
|
|
168
178
|
Options: `--harness <name>` (claude-code|copilot-cli|codex|opencode), `--event <event>` (session-start|user-prompt-submit|session-stop)
|
|
169
179
|
|
|
170
|
-
### `membank dashboard`
|
|
180
|
+
### `membank dashboard` (deprecated)
|
|
171
181
|
|
|
172
|
-
|
|
182
|
+
The dashboard is now a standalone package. Run it directly:
|
|
173
183
|
|
|
174
184
|
```bash
|
|
175
|
-
membank
|
|
185
|
+
npx @membank/dashboard
|
|
176
186
|
```
|
|
177
187
|
|
|
178
|
-
|
|
188
|
+
See [`@membank/dashboard`](../dashboard/README.md) for options.
|
|
179
189
|
|
|
180
190
|
## Global flags
|
|
181
191
|
|
|
182
192
|
```
|
|
183
193
|
--json Output machine-readable JSON
|
|
184
194
|
--yes, -y Skip confirmation prompts
|
|
185
|
-
--mcp Start MCP stdio server (
|
|
195
|
+
--mcp Start MCP stdio server (deprecated — use npx @membank/mcp)
|
|
186
196
|
```
|
|
187
197
|
|
|
188
198
|
## MCP server mode
|
|
189
199
|
|
|
200
|
+
The preferred way to run the MCP server is via the standalone package:
|
|
201
|
+
|
|
190
202
|
```bash
|
|
191
|
-
membank
|
|
203
|
+
npx @membank/mcp
|
|
192
204
|
```
|
|
193
205
|
|
|
194
|
-
|
|
206
|
+
`membank setup` writes this command into harness configs automatically. The legacy `membank --mcp` flag still works but emits a deprecation warning. Run `membank setup upgrade` to migrate existing harness configs.
|
|
195
207
|
|
|
196
208
|
## Session hooks
|
|
197
209
|
|
package/dist/index.mjs
CHANGED
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { cancel, confirm, intro, isCancel, multiselect, note, outro } from "@clack/prompts";
|
|
3
|
-
import { DatabaseManager, EmbeddingService, MIGRATIONS,
|
|
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
4
|
import { runSynthesis, 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 { existsSync, mkdirSync, mkdtempSync, readFileSync,
|
|
9
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
10
10
|
import { homedir, tmpdir } from "node:os";
|
|
11
11
|
import { dirname, join } from "node:path";
|
|
12
|
-
import { startDashboard } from "@membank/dashboard";
|
|
13
12
|
import Table from "cli-table3";
|
|
14
13
|
import { execFile } from "node:child_process";
|
|
15
14
|
import { promisify } from "node:util";
|
|
16
|
-
import { EventEmitter } from "node:events";
|
|
17
|
-
import { pipeline } from "@huggingface/transformers";
|
|
18
15
|
import { createInterface } from "node:readline";
|
|
19
16
|
//#region src/schemas.ts
|
|
20
17
|
const SETUP_HARNESS_VALUES = [
|
|
@@ -32,7 +29,7 @@ const InjectionHarnessSchema = z.enum([
|
|
|
32
29
|
]);
|
|
33
30
|
const MigrateModeSchema = z.enum(["list", "run"]);
|
|
34
31
|
const LimitSchema = z.coerce.number().int().positive();
|
|
35
|
-
|
|
32
|
+
z.coerce.number().int().min(1).max(65535);
|
|
36
33
|
const OptionalNumberSchema = z.number().optional().catch(void 0);
|
|
37
34
|
const MutableJsonObjectSchema = z.record(z.string(), z.unknown());
|
|
38
35
|
const MaybeJsonObjectSchema = z.record(z.string(), z.unknown()).optional().catch(void 0);
|
|
@@ -60,15 +57,19 @@ async function addCommand(content, options, formatter, db, embeddingService) {
|
|
|
60
57
|
const ownDb = db === void 0;
|
|
61
58
|
const resolvedDb = db ?? DatabaseManager.open();
|
|
62
59
|
try {
|
|
63
|
-
const
|
|
60
|
+
const embedder = embeddingService ?? new EmbeddingService();
|
|
61
|
+
const repo = createMemoryRepository(resolvedDb, createProjectRepository(resolvedDb));
|
|
64
62
|
const tags = options.tags !== void 0 ? options.tags.split(",").map((t) => t.trim()) : [];
|
|
65
63
|
const projectScope = options.global ? void 0 : await resolveProject();
|
|
66
64
|
const spinner = formatter.isJson ? null : ora("Saving memory…").start();
|
|
67
|
-
const memory = await
|
|
65
|
+
const memory = await saveMemory({
|
|
68
66
|
content,
|
|
69
67
|
type: MemoryTypeSchema$1.parse(options.type),
|
|
70
68
|
tags,
|
|
71
69
|
projectScope
|
|
70
|
+
}, {
|
|
71
|
+
repo,
|
|
72
|
+
embedder
|
|
72
73
|
});
|
|
73
74
|
spinner?.succeed("Memory saved");
|
|
74
75
|
formatter.outputMemory(memory);
|
|
@@ -151,35 +152,31 @@ function configShowCommand(formatter) {
|
|
|
151
152
|
process.stdout.write(`${JSON.stringify(config, null, formatter.isJson ? 0 : 2)}\n`);
|
|
152
153
|
}
|
|
153
154
|
//#endregion
|
|
154
|
-
//#region src/commands/dashboard.ts
|
|
155
|
-
async function dashboardCommand(opts) {
|
|
156
|
-
await startDashboard({ port: opts.port !== void 0 ? PortSchema.parse(opts.port) : void 0 });
|
|
157
|
-
}
|
|
158
|
-
//#endregion
|
|
159
155
|
//#region src/commands/delete.ts
|
|
160
156
|
async function deleteCommand(id, db, formatter, prompt) {
|
|
161
|
-
|
|
157
|
+
const repo = createMemoryRepository(db, createProjectRepository(db));
|
|
158
|
+
if (repo.findById(id) === void 0) {
|
|
162
159
|
formatter.error(`Memory not found: ${id}`);
|
|
163
160
|
process.exit(1);
|
|
164
161
|
}
|
|
165
162
|
if (!await prompt.confirm(`Delete memory ${id}?`)) return;
|
|
166
|
-
|
|
163
|
+
repo.delete(id);
|
|
167
164
|
process.stdout.write(`${chalk.green("✓")} Deleted memory: ${chalk.dim(id)}\n`);
|
|
168
165
|
}
|
|
169
166
|
//#endregion
|
|
170
167
|
//#region src/commands/export.ts
|
|
171
168
|
function exportCommand(db, formatter, opts) {
|
|
172
|
-
const memories = db
|
|
173
|
-
id:
|
|
174
|
-
content:
|
|
175
|
-
type:
|
|
176
|
-
tags:
|
|
177
|
-
sourceHarness:
|
|
178
|
-
accessCount:
|
|
179
|
-
pinned:
|
|
180
|
-
createdAt:
|
|
181
|
-
updatedAt:
|
|
182
|
-
embedding:
|
|
169
|
+
const memories = createMemoryRepository(db, createProjectRepository(db)).exportAll().map((rec) => ({
|
|
170
|
+
id: rec.id,
|
|
171
|
+
content: rec.content,
|
|
172
|
+
type: rec.type,
|
|
173
|
+
tags: rec.tags,
|
|
174
|
+
sourceHarness: rec.sourceHarness,
|
|
175
|
+
accessCount: rec.accessCount,
|
|
176
|
+
pinned: rec.pinned,
|
|
177
|
+
createdAt: rec.createdAt,
|
|
178
|
+
updatedAt: rec.updatedAt,
|
|
179
|
+
embedding: rec.embedding !== null ? Buffer.from(rec.embedding.buffer, rec.embedding.byteOffset, rec.embedding.byteLength).toString("base64") : null
|
|
183
180
|
}));
|
|
184
181
|
const data = {
|
|
185
182
|
version: 1,
|
|
@@ -220,18 +217,23 @@ async function importCommand(filePath, db, formatter, prompt) {
|
|
|
220
217
|
if (formatter.isJson) process.stdout.write(`${JSON.stringify({ found: count })}\n`);
|
|
221
218
|
else process.stdout.write(`Found ${count} memories to import.\n`);
|
|
222
219
|
if (!await prompt.confirm("Import?")) return;
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
220
|
+
const records = parseResult.data.memories.map((rec) => ({
|
|
221
|
+
id: rec.id,
|
|
222
|
+
content: rec.content,
|
|
223
|
+
type: rec.type,
|
|
224
|
+
tags: rec.tags,
|
|
225
|
+
sourceHarness: rec.sourceHarness ?? null,
|
|
226
|
+
accessCount: rec.accessCount ?? 0,
|
|
227
|
+
pinned: rec.pinned ?? false,
|
|
228
|
+
createdAt: rec.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
229
|
+
updatedAt: rec.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
230
|
+
embedding: (() => {
|
|
231
|
+
if (rec.embedding == null) return null;
|
|
232
|
+
const buf = Buffer.from(rec.embedding, "base64");
|
|
233
|
+
return new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4);
|
|
234
|
+
})()
|
|
235
|
+
}));
|
|
236
|
+
createMemoryRepository(db, createProjectRepository(db)).importAll(records);
|
|
235
237
|
if (formatter.isJson) process.stdout.write(`${JSON.stringify({ imported: count })}\n`);
|
|
236
238
|
else process.stdout.write(`Imported ${count} memories.\n`);
|
|
237
239
|
}
|
|
@@ -277,8 +279,8 @@ async function buildText() {
|
|
|
277
279
|
const resolved = await resolveProject();
|
|
278
280
|
const db = DatabaseManager.open();
|
|
279
281
|
try {
|
|
280
|
-
const builder = new SessionContextBuilder(db);
|
|
281
|
-
const synthRepo =
|
|
282
|
+
const builder = new SessionContextBuilder(createMemoryRepository(db, createProjectRepository(db)));
|
|
283
|
+
const synthRepo = createSynthesisRepository(db);
|
|
282
284
|
const globalRow = synthRepo.getSynthesis("global");
|
|
283
285
|
const projectRow = synthRepo.getSynthesis(resolved.hash);
|
|
284
286
|
const synthesis = pickBestSynthesis(globalRow?.inFlightSince === null ? globalRow.content : void 0, projectRow?.inFlightSince === null ? projectRow.content : void 0);
|
|
@@ -322,7 +324,7 @@ async function injectCommand(opts) {
|
|
|
322
324
|
async function listCommand(options, formatter) {
|
|
323
325
|
const db = DatabaseManager.open();
|
|
324
326
|
try {
|
|
325
|
-
const memories =
|
|
327
|
+
const memories = createMemoryRepository(db, createProjectRepository(db)).list({
|
|
326
328
|
type: options.type !== void 0 ? MemoryTypeSchema$1.parse(options.type) : void 0,
|
|
327
329
|
pinned: options.pinned
|
|
328
330
|
});
|
|
@@ -353,7 +355,7 @@ async function migrateCommand(mode, name, formatter) {
|
|
|
353
355
|
const db = DatabaseManager.open();
|
|
354
356
|
try {
|
|
355
357
|
if (name === "scope-to-projects") {
|
|
356
|
-
const result = await runScopeToProjectsMigration(
|
|
358
|
+
const result = await runScopeToProjectsMigration(createProjectRepository(db));
|
|
357
359
|
if (result === null) {
|
|
358
360
|
formatter.error("No project found for current directory.");
|
|
359
361
|
return;
|
|
@@ -377,7 +379,7 @@ function pinCommand(id, db) {
|
|
|
377
379
|
const ownDb = db === void 0;
|
|
378
380
|
const resolvedDb = db ?? DatabaseManager.open();
|
|
379
381
|
try {
|
|
380
|
-
|
|
382
|
+
createMemoryRepository(resolvedDb, createProjectRepository(resolvedDb)).setPin(id, true);
|
|
381
383
|
process.stdout.write(`${chalk.green("✓")} Pinned: ${chalk.dim(id)}\n`);
|
|
382
384
|
} finally {
|
|
383
385
|
if (ownDb) resolvedDb.close();
|
|
@@ -388,8 +390,7 @@ function pinCommand(id, db) {
|
|
|
388
390
|
async function queryCommand(queryText, options, formatter) {
|
|
389
391
|
const db = DatabaseManager.open();
|
|
390
392
|
try {
|
|
391
|
-
const
|
|
392
|
-
const engine = new QueryEngine(db, embedding, new MemoryRepository(db, embedding, new ProjectRepository(db)));
|
|
393
|
+
const engine = new QueryEngine(db, new EmbeddingService(), createMemoryRepository(db, createProjectRepository(db)));
|
|
393
394
|
const limit = options.limit !== void 0 ? LimitSchema.parse(options.limit) : 10;
|
|
394
395
|
const spinner = formatter.isJson ? null : ora("Searching memories…").start();
|
|
395
396
|
const results = await engine.query({
|
|
@@ -409,7 +410,7 @@ async function queryCommand(queryText, options, formatter) {
|
|
|
409
410
|
async function reviewCommand(opts, formatter) {
|
|
410
411
|
const db = DatabaseManager.open();
|
|
411
412
|
try {
|
|
412
|
-
const repo =
|
|
413
|
+
const repo = createMemoryRepository(db, createProjectRepository(db));
|
|
413
414
|
if (opts.resolve !== void 0) {
|
|
414
415
|
repo.resolveReviewEvents(opts.resolve);
|
|
415
416
|
if (!formatter.isJson) process.stdout.write(`Resolved review events for memory ${opts.resolve}\n`);
|
|
@@ -427,7 +428,7 @@ async function reviewCommand(opts, formatter) {
|
|
|
427
428
|
async function statsCommand(formatter) {
|
|
428
429
|
const db = DatabaseManager.open();
|
|
429
430
|
try {
|
|
430
|
-
const stats =
|
|
431
|
+
const stats = createMemoryRepository(db, createProjectRepository(db)).stats();
|
|
431
432
|
formatter.outputStats(stats);
|
|
432
433
|
} finally {
|
|
433
434
|
db.close();
|
|
@@ -435,9 +436,6 @@ async function statsCommand(formatter) {
|
|
|
435
436
|
}
|
|
436
437
|
//#endregion
|
|
437
438
|
//#region src/commands/synthesize.ts
|
|
438
|
-
function hasSynthesesTable(db) {
|
|
439
|
-
return db.db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='syntheses'").get() !== void 0;
|
|
440
|
-
}
|
|
441
439
|
async function synthesizeRunCommand(opts, formatter) {
|
|
442
440
|
const scope = opts.scope ?? "global";
|
|
443
441
|
if (!formatter.isJson) process.stdout.write(`Running synthesis for scope: ${scope}\n`);
|
|
@@ -451,30 +449,25 @@ async function synthesizeRunCommand(opts, formatter) {
|
|
|
451
449
|
function synthesizeShowCommand(opts, formatter) {
|
|
452
450
|
const db = DatabaseManager.open();
|
|
453
451
|
try {
|
|
454
|
-
if (!hasSynthesesTable(db)) {
|
|
455
|
-
if (formatter.isJson) process.stdout.write(`${JSON.stringify(null)}\n`);
|
|
456
|
-
else process.stdout.write("No synthesis data available.\n");
|
|
457
|
-
return;
|
|
458
|
-
}
|
|
459
452
|
const scope = opts.scope ?? "global";
|
|
460
453
|
let resolvedScope = scope;
|
|
461
454
|
if (scope !== "global" && !/^[0-9a-f]{16}$/.test(scope)) {
|
|
462
|
-
const project =
|
|
455
|
+
const project = createProjectRepository(db).getByName(scope);
|
|
463
456
|
if (project !== void 0) resolvedScope = project.scopeHash;
|
|
464
457
|
}
|
|
465
|
-
const
|
|
466
|
-
if (
|
|
458
|
+
const synthesis = createSynthesisRepository(db).getSynthesis(resolvedScope);
|
|
459
|
+
if (synthesis === void 0) {
|
|
467
460
|
if (formatter.isJson) process.stdout.write(`${JSON.stringify(null)}\n`);
|
|
468
461
|
else process.stdout.write(`No synthesis found for scope: ${scope}\n`);
|
|
469
462
|
return;
|
|
470
463
|
}
|
|
471
|
-
if (formatter.isJson) process.stdout.write(`${JSON.stringify(
|
|
464
|
+
if (formatter.isJson) process.stdout.write(`${JSON.stringify(synthesis)}\n`);
|
|
472
465
|
else {
|
|
473
|
-
process.stdout.write(`\nScope: ${
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
if (
|
|
477
|
-
process.stdout.write(`\n${
|
|
466
|
+
process.stdout.write(`\nScope: ${synthesis.scope}\n`);
|
|
467
|
+
process.stdout.write(`Synthesized: ${new Date(synthesis.synthesizedAt).toLocaleString()}\n`);
|
|
468
|
+
process.stdout.write(`Expires: ${new Date(synthesis.expiresAt).toLocaleString()}\n`);
|
|
469
|
+
if (synthesis.inFlightSince !== null) process.stdout.write(`In-flight since: ${new Date(synthesis.inFlightSince).toLocaleString()}\n`);
|
|
470
|
+
process.stdout.write(`\n${synthesis.content}\n\n`);
|
|
478
471
|
}
|
|
479
472
|
} finally {
|
|
480
473
|
db.close();
|
|
@@ -483,29 +476,22 @@ function synthesizeShowCommand(opts, formatter) {
|
|
|
483
476
|
function synthesizeStatusCommand(formatter) {
|
|
484
477
|
const db = DatabaseManager.open();
|
|
485
478
|
try {
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
else process.stdout.write("No synthesis data available.\n");
|
|
489
|
-
return;
|
|
490
|
-
}
|
|
491
|
-
const rows = db.db.prepare(`SELECT s.*, p.name AS project_name
|
|
492
|
-
FROM syntheses s
|
|
493
|
-
LEFT JOIN projects p ON p.scope_hash = s.scope
|
|
494
|
-
ORDER BY COALESCE(p.name, s.scope)`).all();
|
|
479
|
+
const syntheses = createSynthesisRepository(db).listAll();
|
|
480
|
+
const projectRepo = createProjectRepository(db);
|
|
495
481
|
if (formatter.isJson) {
|
|
496
|
-
process.stdout.write(`${JSON.stringify(
|
|
482
|
+
process.stdout.write(`${JSON.stringify(syntheses)}\n`);
|
|
497
483
|
return;
|
|
498
484
|
}
|
|
499
|
-
if (
|
|
485
|
+
if (syntheses.length === 0) {
|
|
500
486
|
process.stdout.write("No syntheses found.\n");
|
|
501
487
|
return;
|
|
502
488
|
}
|
|
503
489
|
process.stdout.write("\n");
|
|
504
|
-
for (const
|
|
505
|
-
const displayScope =
|
|
506
|
-
const inFlight =
|
|
507
|
-
const synthesized =
|
|
508
|
-
const expires =
|
|
490
|
+
for (const s of syntheses) {
|
|
491
|
+
const displayScope = (s.scope !== "global" ? projectRepo.getByHash(s.scope) : void 0)?.name ?? s.scope;
|
|
492
|
+
const inFlight = s.inFlightSince !== null ? " [in-flight]" : "";
|
|
493
|
+
const synthesized = new Date(s.synthesizedAt).toLocaleString();
|
|
494
|
+
const expires = new Date(s.expiresAt).toLocaleString();
|
|
509
495
|
process.stdout.write(` ${displayScope}${inFlight}\n`);
|
|
510
496
|
process.stdout.write(` synthesized_at: ${synthesized}\n`);
|
|
511
497
|
process.stdout.write(` expires_at: ${expires}\n`);
|
|
@@ -521,7 +507,7 @@ function unpinCommand(id, db) {
|
|
|
521
507
|
const ownDb = db === void 0;
|
|
522
508
|
const resolvedDb = db ?? DatabaseManager.open();
|
|
523
509
|
try {
|
|
524
|
-
|
|
510
|
+
createMemoryRepository(resolvedDb, createProjectRepository(resolvedDb)).setPin(id, false);
|
|
525
511
|
process.stdout.write(`${chalk.green("✓")} Unpinned: ${chalk.dim(id)}\n`);
|
|
526
512
|
} finally {
|
|
527
513
|
if (ownDb) resolvedDb.close();
|
|
@@ -777,18 +763,20 @@ function hasKey(container, key) {
|
|
|
777
763
|
function assertCliFound(result, cli, command) {
|
|
778
764
|
if (result.exitCode === 127) throw new CommandError(`${cli} CLI not found — install ${cli} first`, command);
|
|
779
765
|
}
|
|
766
|
+
function jsonContainsStalePattern(value) {
|
|
767
|
+
return JSON.stringify(value).includes("@membank/cli");
|
|
768
|
+
}
|
|
780
769
|
const MEMBANK_NPX_ARGS = [
|
|
781
770
|
"npx",
|
|
782
771
|
"-y",
|
|
783
|
-
"@membank/
|
|
784
|
-
"--mcp"
|
|
772
|
+
"@membank/mcp"
|
|
785
773
|
];
|
|
786
774
|
const writers$1 = {
|
|
787
775
|
"claude-code": {
|
|
788
776
|
preview(resolver) {
|
|
789
777
|
return {
|
|
790
778
|
configPath: join(resolver.home(), ".claude.json"),
|
|
791
|
-
cliCommand: "claude mcp add --scope user membank -- npx -y @membank/
|
|
779
|
+
cliCommand: "claude mcp add --scope user membank -- npx -y @membank/mcp"
|
|
792
780
|
};
|
|
793
781
|
},
|
|
794
782
|
async write(resolver, run, { overwrite = false } = {}) {
|
|
@@ -821,6 +809,10 @@ const writers$1 = {
|
|
|
821
809
|
assertCliFound(add, "claude", addCmd);
|
|
822
810
|
if (add.exitCode !== 0) throw new CommandError(`claude mcp add failed: ${add.stderr || add.stdout}`, addCmd);
|
|
823
811
|
return { status: "written" };
|
|
812
|
+
},
|
|
813
|
+
async isStale(resolver) {
|
|
814
|
+
const cfg = readJson(join(resolver.home(), ".claude.json"));
|
|
815
|
+
return hasKey(cfg.mcpServers, "membank") && jsonContainsStalePattern(cfg.mcpServers);
|
|
824
816
|
}
|
|
825
817
|
},
|
|
826
818
|
copilot: {
|
|
@@ -840,22 +832,22 @@ const writers$1 = {
|
|
|
840
832
|
...MaybeJsonObjectSchema.parse(cfg.mcpServers),
|
|
841
833
|
membank: {
|
|
842
834
|
command: "npx",
|
|
843
|
-
args: [
|
|
844
|
-
"-y",
|
|
845
|
-
"@membank/cli",
|
|
846
|
-
"--mcp"
|
|
847
|
-
]
|
|
835
|
+
args: ["-y", "@membank/mcp"]
|
|
848
836
|
}
|
|
849
837
|
}
|
|
850
838
|
});
|
|
851
839
|
return { status: "written" };
|
|
840
|
+
},
|
|
841
|
+
async isStale(resolver) {
|
|
842
|
+
const cfg = readJson(join(resolver.home(), ".copilot", "mcp-config.json"));
|
|
843
|
+
return hasKey(cfg.mcpServers, "membank") && jsonContainsStalePattern(cfg.mcpServers);
|
|
852
844
|
}
|
|
853
845
|
},
|
|
854
846
|
codex: {
|
|
855
847
|
preview(_resolver) {
|
|
856
848
|
return {
|
|
857
849
|
configPath: null,
|
|
858
|
-
cliCommand: "codex mcp add membank -- npx -y @membank/
|
|
850
|
+
cliCommand: "codex mcp add membank -- npx -y @membank/mcp"
|
|
859
851
|
};
|
|
860
852
|
},
|
|
861
853
|
async write(_resolver, run, { overwrite = false } = {}) {
|
|
@@ -887,6 +879,10 @@ const writers$1 = {
|
|
|
887
879
|
assertCliFound(add, "codex", addCmd);
|
|
888
880
|
if (add.exitCode !== 0) throw new CommandError(`codex mcp add failed: ${add.stderr || add.stdout}`, addCmd);
|
|
889
881
|
return { status: "written" };
|
|
882
|
+
},
|
|
883
|
+
async isStale(_resolver, run) {
|
|
884
|
+
const list = await run("codex", ["mcp", "list"]);
|
|
885
|
+
return list.exitCode === 0 && list.stdout.includes("membank") && list.stdout.includes("@membank/cli");
|
|
890
886
|
}
|
|
891
887
|
},
|
|
892
888
|
opencode: {
|
|
@@ -909,13 +905,16 @@ const writers$1 = {
|
|
|
909
905
|
command: [
|
|
910
906
|
"npx",
|
|
911
907
|
"-y",
|
|
912
|
-
"@membank/
|
|
913
|
-
"--mcp"
|
|
908
|
+
"@membank/mcp"
|
|
914
909
|
]
|
|
915
910
|
}
|
|
916
911
|
}
|
|
917
912
|
});
|
|
918
913
|
return { status: "written" };
|
|
914
|
+
},
|
|
915
|
+
async isStale(resolver) {
|
|
916
|
+
const cfg = readJson(join(resolver.home(), ".config", "opencode", "opencode.json"));
|
|
917
|
+
return hasKey(cfg.mcp, "membank") && jsonContainsStalePattern(cfg.mcp);
|
|
919
918
|
}
|
|
920
919
|
}
|
|
921
920
|
};
|
|
@@ -937,6 +936,15 @@ var HarnessConfigWriter = class {
|
|
|
937
936
|
if (!writer) throw new Error(`Unknown harness: ${harness}`);
|
|
938
937
|
return writer.write(this.#resolver, this.#run, { overwrite });
|
|
939
938
|
}
|
|
939
|
+
async isStale(harness) {
|
|
940
|
+
const writer = writers$1[harness];
|
|
941
|
+
if (!writer) return false;
|
|
942
|
+
try {
|
|
943
|
+
return await writer.isStale(this.#resolver, this.#run);
|
|
944
|
+
} catch {
|
|
945
|
+
return false;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
940
948
|
};
|
|
941
949
|
//#endregion
|
|
942
950
|
//#region src/setup/injection-hook-writer.ts
|
|
@@ -1229,76 +1237,6 @@ var InjectionHookWriter = class {
|
|
|
1229
1237
|
}
|
|
1230
1238
|
};
|
|
1231
1239
|
//#endregion
|
|
1232
|
-
//#region src/setup/model-downloader.ts
|
|
1233
|
-
const MODEL_NAME = "Xenova/bge-small-en-v1.5";
|
|
1234
|
-
var ModelDownloadError = class extends Error {
|
|
1235
|
-
constructor(message, options) {
|
|
1236
|
-
super(message, options);
|
|
1237
|
-
this.name = "ModelDownloadError";
|
|
1238
|
-
}
|
|
1239
|
-
};
|
|
1240
|
-
function defaultModelPath() {
|
|
1241
|
-
return join(homedir(), ".membank", "models");
|
|
1242
|
-
}
|
|
1243
|
-
function isCached(modelPath) {
|
|
1244
|
-
if (!existsSync(modelPath)) return false;
|
|
1245
|
-
try {
|
|
1246
|
-
return readdirSync(modelPath).length > 0;
|
|
1247
|
-
} catch {
|
|
1248
|
-
return false;
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
var ModelDownloader = class extends EventEmitter {
|
|
1252
|
-
modelPath;
|
|
1253
|
-
constructor(modelPath) {
|
|
1254
|
-
super();
|
|
1255
|
-
this.modelPath = modelPath ?? defaultModelPath();
|
|
1256
|
-
}
|
|
1257
|
-
isAlreadyCached() {
|
|
1258
|
-
return isCached(this.modelPath);
|
|
1259
|
-
}
|
|
1260
|
-
get cachePath() {
|
|
1261
|
-
return this.modelPath;
|
|
1262
|
-
}
|
|
1263
|
-
async download() {
|
|
1264
|
-
if (isCached(this.modelPath)) return { skipped: true };
|
|
1265
|
-
const startTime = Date.now();
|
|
1266
|
-
let lastDownloadedBytes = 0;
|
|
1267
|
-
let lastTimestamp = startTime;
|
|
1268
|
-
try {
|
|
1269
|
-
await pipeline("feature-extraction", MODEL_NAME, {
|
|
1270
|
-
cache_dir: this.modelPath,
|
|
1271
|
-
progress_callback: (event) => {
|
|
1272
|
-
if (event.status !== "progress" || event.total == null || event.loaded == null) return;
|
|
1273
|
-
const totalBytes = event.total;
|
|
1274
|
-
const downloadedBytes = event.loaded;
|
|
1275
|
-
const percentage = totalBytes > 0 ? downloadedBytes / totalBytes * 100 : 0;
|
|
1276
|
-
const now = Date.now();
|
|
1277
|
-
const elapsedSinceLastMs = now - lastTimestamp;
|
|
1278
|
-
const bytesSinceLast = downloadedBytes - lastDownloadedBytes;
|
|
1279
|
-
let estimatedSecondsRemaining = 0;
|
|
1280
|
-
if (elapsedSinceLastMs > 0 && bytesSinceLast > 0) {
|
|
1281
|
-
const bytesPerMs = bytesSinceLast / elapsedSinceLastMs;
|
|
1282
|
-
estimatedSecondsRemaining = (totalBytes - downloadedBytes) / bytesPerMs / 1e3;
|
|
1283
|
-
}
|
|
1284
|
-
lastDownloadedBytes = downloadedBytes;
|
|
1285
|
-
lastTimestamp = now;
|
|
1286
|
-
const progress = {
|
|
1287
|
-
totalBytes,
|
|
1288
|
-
downloadedBytes,
|
|
1289
|
-
percentage,
|
|
1290
|
-
estimatedSecondsRemaining
|
|
1291
|
-
};
|
|
1292
|
-
this.emit("progress", progress);
|
|
1293
|
-
}
|
|
1294
|
-
});
|
|
1295
|
-
} catch (err) {
|
|
1296
|
-
throw new ModelDownloadError("Failed to download model", { cause: err });
|
|
1297
|
-
}
|
|
1298
|
-
return { skipped: false };
|
|
1299
|
-
}
|
|
1300
|
-
};
|
|
1301
|
-
//#endregion
|
|
1302
1240
|
//#region src/setup/harness-detector.ts
|
|
1303
1241
|
const defaultResolver = { homeDir: homedir };
|
|
1304
1242
|
function harnessConfigs(resolver) {
|
|
@@ -1586,7 +1524,10 @@ var SetupOrchestrator = class {
|
|
|
1586
1524
|
};
|
|
1587
1525
|
//#endregion
|
|
1588
1526
|
//#region src/index.ts
|
|
1589
|
-
if (process.argv.includes("--mcp"))
|
|
1527
|
+
if (process.argv.includes("--mcp")) {
|
|
1528
|
+
process.stderr.write("[membank] Deprecation: `membank --mcp` is deprecated. Use: npx @membank/mcp\n");
|
|
1529
|
+
await startServer();
|
|
1530
|
+
}
|
|
1590
1531
|
const program = new Command();
|
|
1591
1532
|
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)");
|
|
1592
1533
|
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) => {
|
|
@@ -1698,7 +1639,35 @@ program.command("inject").description("output session context for harness inject
|
|
|
1698
1639
|
process.exit(2);
|
|
1699
1640
|
}
|
|
1700
1641
|
});
|
|
1701
|
-
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)")
|
|
1642
|
+
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
|
+
setupCmd.command("upgrade").description("upgrade harness configs from membank --mcp to standalone membank-mcp").action(async () => {
|
|
1644
|
+
const isJson = program.opts().json === true;
|
|
1645
|
+
const writer = new HarnessConfigWriter();
|
|
1646
|
+
const stale = [];
|
|
1647
|
+
for (const harness of SUPPORTED_HARNESSES) if (await writer.isStale(harness)) stale.push(harness);
|
|
1648
|
+
if (stale.length === 0) {
|
|
1649
|
+
if (isJson) process.stdout.write(`${JSON.stringify({ upgraded: [] })}\n`);
|
|
1650
|
+
else process.stdout.write("All harness configs are already up to date.\n");
|
|
1651
|
+
return;
|
|
1652
|
+
}
|
|
1653
|
+
const upgraded = [];
|
|
1654
|
+
const errors = [];
|
|
1655
|
+
for (const harness of stale) try {
|
|
1656
|
+
await writer.write(harness, { overwrite: true });
|
|
1657
|
+
upgraded.push(harness);
|
|
1658
|
+
if (!isJson) process.stdout.write(` ${chalk.green("✓")} ${harness}\n`);
|
|
1659
|
+
} catch (err) {
|
|
1660
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1661
|
+
errors.push(`${harness}: ${msg}`);
|
|
1662
|
+
if (!isJson) process.stderr.write(` ${chalk.red("✗")} ${harness}: ${msg}\n`);
|
|
1663
|
+
}
|
|
1664
|
+
if (isJson) process.stdout.write(`${JSON.stringify({
|
|
1665
|
+
upgraded,
|
|
1666
|
+
errors
|
|
1667
|
+
})}\n`);
|
|
1668
|
+
if (errors.length > 0) process.exit(1);
|
|
1669
|
+
});
|
|
1670
|
+
setupCmd.action(async (cmdOptions) => {
|
|
1702
1671
|
const globalOpts = program.opts();
|
|
1703
1672
|
const autoYes = cmdOptions.yes === true || globalOpts.yes === true;
|
|
1704
1673
|
const formatter = Formatter.create(globalOpts.json === true);
|
|
@@ -1792,13 +1761,9 @@ program.command("migrate <mode> [name]").description("list or run a named data m
|
|
|
1792
1761
|
process.exit(2);
|
|
1793
1762
|
}
|
|
1794
1763
|
});
|
|
1795
|
-
program.command("dashboard").description("open the memory management dashboard
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
} catch (err) {
|
|
1799
|
-
process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
|
|
1800
|
-
process.exit(2);
|
|
1801
|
-
}
|
|
1764
|
+
program.command("dashboard").description("(deprecated) open the memory management dashboard").allowUnknownOption().action(() => {
|
|
1765
|
+
process.stderr.write("The dashboard is now a standalone package.\nRun: npx @membank/dashboard\n");
|
|
1766
|
+
process.exit(1);
|
|
1802
1767
|
});
|
|
1803
1768
|
const configCmd = program.command("config").description("manage membank configuration");
|
|
1804
1769
|
configCmd.command("get <key>").description("print a config value as JSON").action((key) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@membank/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -11,19 +11,18 @@
|
|
|
11
11
|
"membank": "./dist/index.mjs"
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
|
-
"dist"
|
|
14
|
+
"dist",
|
|
15
|
+
"!dist/**/*.map"
|
|
15
16
|
],
|
|
16
17
|
"dependencies": {
|
|
17
18
|
"@clack/prompts": "^1.3.0",
|
|
18
|
-
"@huggingface/transformers": "^4.2.0",
|
|
19
19
|
"chalk": "^5.6.2",
|
|
20
20
|
"cli-table3": "^0.6.5",
|
|
21
21
|
"commander": "^14.0.3",
|
|
22
22
|
"ora": "^9.4.0",
|
|
23
23
|
"zod": "^4.4.3",
|
|
24
|
-
"@membank/core": "0.
|
|
25
|
-
"@membank/
|
|
26
|
-
"@membank/mcp": "0.12.2"
|
|
24
|
+
"@membank/core": "0.10.0",
|
|
25
|
+
"@membank/mcp": "0.13.0"
|
|
27
26
|
},
|
|
28
27
|
"devDependencies": {
|
|
29
28
|
"@types/node": "^25.6.0",
|