@membank/cli 0.11.2 → 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/README.md +19 -7
- package/dist/index.mjs +263 -231
- 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,
|
|
4
|
-
import { runSynthesis, startServer } from "@membank/mcp";
|
|
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 { runExtraction, 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,
|
|
@@ -195,6 +192,95 @@ function exportCommand(db, formatter, opts) {
|
|
|
195
192
|
else process.stdout.write(`Exported ${memories.length} memories to ${outputPath}\n`);
|
|
196
193
|
}
|
|
197
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
|
|
198
284
|
//#region src/commands/import.ts
|
|
199
285
|
async function importCommand(filePath, db, formatter, prompt) {
|
|
200
286
|
let raw;
|
|
@@ -220,18 +306,23 @@ async function importCommand(filePath, db, formatter, prompt) {
|
|
|
220
306
|
if (formatter.isJson) process.stdout.write(`${JSON.stringify({ found: count })}\n`);
|
|
221
307
|
else process.stdout.write(`Found ${count} memories to import.\n`);
|
|
222
308
|
if (!await prompt.confirm("Import?")) return;
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
309
|
+
const records = parseResult.data.memories.map((rec) => ({
|
|
310
|
+
id: rec.id,
|
|
311
|
+
content: rec.content,
|
|
312
|
+
type: rec.type,
|
|
313
|
+
tags: rec.tags,
|
|
314
|
+
sourceHarness: rec.sourceHarness ?? null,
|
|
315
|
+
accessCount: rec.accessCount ?? 0,
|
|
316
|
+
pinned: rec.pinned ?? false,
|
|
317
|
+
createdAt: rec.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
318
|
+
updatedAt: rec.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
319
|
+
embedding: (() => {
|
|
320
|
+
if (rec.embedding == null) return null;
|
|
321
|
+
const buf = Buffer.from(rec.embedding, "base64");
|
|
322
|
+
return new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4);
|
|
323
|
+
})()
|
|
324
|
+
}));
|
|
325
|
+
createMemoryRepository(db, createProjectRepository(db)).importAll(records);
|
|
235
326
|
if (formatter.isJson) process.stdout.write(`${JSON.stringify({ imported: count })}\n`);
|
|
236
327
|
else process.stdout.write(`Imported ${count} memories.\n`);
|
|
237
328
|
}
|
|
@@ -277,8 +368,8 @@ async function buildText() {
|
|
|
277
368
|
const resolved = await resolveProject();
|
|
278
369
|
const db = DatabaseManager.open();
|
|
279
370
|
try {
|
|
280
|
-
const builder = new SessionContextBuilder(db);
|
|
281
|
-
const synthRepo =
|
|
371
|
+
const builder = new SessionContextBuilder(createMemoryRepository(db, createProjectRepository(db)));
|
|
372
|
+
const synthRepo = createSynthesisRepository(db);
|
|
282
373
|
const globalRow = synthRepo.getSynthesis("global");
|
|
283
374
|
const projectRow = synthRepo.getSynthesis(resolved.hash);
|
|
284
375
|
const synthesis = pickBestSynthesis(globalRow?.inFlightSince === null ? globalRow.content : void 0, projectRow?.inFlightSince === null ? projectRow.content : void 0);
|
|
@@ -288,10 +379,6 @@ async function buildText() {
|
|
|
288
379
|
}
|
|
289
380
|
}
|
|
290
381
|
async function handleEvent(harness, eventName) {
|
|
291
|
-
if (harness === "claude-code" && eventName === "Stop") {
|
|
292
|
-
process.stdout.write("{}");
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
382
|
const text = await buildText().catch((err) => {
|
|
296
383
|
const msg = err instanceof Error ? err.message : String(err);
|
|
297
384
|
process.stderr.write(`membank inject: ${msg}\n`);
|
|
@@ -311,10 +398,6 @@ async function injectCommand(opts) {
|
|
|
311
398
|
await handleEvent(harness, "UserPromptSubmit");
|
|
312
399
|
return;
|
|
313
400
|
}
|
|
314
|
-
if (opts.event === "session-stop" || opts.event === "stop") {
|
|
315
|
-
await handleEvent(harness, "Stop");
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
401
|
process.exit(0);
|
|
319
402
|
}
|
|
320
403
|
//#endregion
|
|
@@ -322,7 +405,7 @@ async function injectCommand(opts) {
|
|
|
322
405
|
async function listCommand(options, formatter) {
|
|
323
406
|
const db = DatabaseManager.open();
|
|
324
407
|
try {
|
|
325
|
-
const memories =
|
|
408
|
+
const memories = createMemoryRepository(db, createProjectRepository(db)).list({
|
|
326
409
|
type: options.type !== void 0 ? MemoryTypeSchema$1.parse(options.type) : void 0,
|
|
327
410
|
pinned: options.pinned
|
|
328
411
|
});
|
|
@@ -353,7 +436,7 @@ async function migrateCommand(mode, name, formatter) {
|
|
|
353
436
|
const db = DatabaseManager.open();
|
|
354
437
|
try {
|
|
355
438
|
if (name === "scope-to-projects") {
|
|
356
|
-
const result = await runScopeToProjectsMigration(
|
|
439
|
+
const result = await runScopeToProjectsMigration(createProjectRepository(db));
|
|
357
440
|
if (result === null) {
|
|
358
441
|
formatter.error("No project found for current directory.");
|
|
359
442
|
return;
|
|
@@ -377,7 +460,7 @@ function pinCommand(id, db) {
|
|
|
377
460
|
const ownDb = db === void 0;
|
|
378
461
|
const resolvedDb = db ?? DatabaseManager.open();
|
|
379
462
|
try {
|
|
380
|
-
|
|
463
|
+
createMemoryRepository(resolvedDb, createProjectRepository(resolvedDb)).setPin(id, true);
|
|
381
464
|
process.stdout.write(`${chalk.green("✓")} Pinned: ${chalk.dim(id)}\n`);
|
|
382
465
|
} finally {
|
|
383
466
|
if (ownDb) resolvedDb.close();
|
|
@@ -388,8 +471,7 @@ function pinCommand(id, db) {
|
|
|
388
471
|
async function queryCommand(queryText, options, formatter) {
|
|
389
472
|
const db = DatabaseManager.open();
|
|
390
473
|
try {
|
|
391
|
-
const
|
|
392
|
-
const engine = new QueryEngine(db, embedding, new MemoryRepository(db, embedding, new ProjectRepository(db)));
|
|
474
|
+
const engine = new QueryEngine(db, new EmbeddingService(), createMemoryRepository(db, createProjectRepository(db)));
|
|
393
475
|
const limit = options.limit !== void 0 ? LimitSchema.parse(options.limit) : 10;
|
|
394
476
|
const spinner = formatter.isJson ? null : ora("Searching memories…").start();
|
|
395
477
|
const results = await engine.query({
|
|
@@ -409,7 +491,7 @@ async function queryCommand(queryText, options, formatter) {
|
|
|
409
491
|
async function reviewCommand(opts, formatter) {
|
|
410
492
|
const db = DatabaseManager.open();
|
|
411
493
|
try {
|
|
412
|
-
const repo =
|
|
494
|
+
const repo = createMemoryRepository(db, createProjectRepository(db));
|
|
413
495
|
if (opts.resolve !== void 0) {
|
|
414
496
|
repo.resolveReviewEvents(opts.resolve);
|
|
415
497
|
if (!formatter.isJson) process.stdout.write(`Resolved review events for memory ${opts.resolve}\n`);
|
|
@@ -427,7 +509,7 @@ async function reviewCommand(opts, formatter) {
|
|
|
427
509
|
async function statsCommand(formatter) {
|
|
428
510
|
const db = DatabaseManager.open();
|
|
429
511
|
try {
|
|
430
|
-
const stats =
|
|
512
|
+
const stats = createMemoryRepository(db, createProjectRepository(db)).stats();
|
|
431
513
|
formatter.outputStats(stats);
|
|
432
514
|
} finally {
|
|
433
515
|
db.close();
|
|
@@ -435,9 +517,6 @@ async function statsCommand(formatter) {
|
|
|
435
517
|
}
|
|
436
518
|
//#endregion
|
|
437
519
|
//#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
520
|
async function synthesizeRunCommand(opts, formatter) {
|
|
442
521
|
const scope = opts.scope ?? "global";
|
|
443
522
|
if (!formatter.isJson) process.stdout.write(`Running synthesis for scope: ${scope}\n`);
|
|
@@ -451,30 +530,25 @@ async function synthesizeRunCommand(opts, formatter) {
|
|
|
451
530
|
function synthesizeShowCommand(opts, formatter) {
|
|
452
531
|
const db = DatabaseManager.open();
|
|
453
532
|
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
533
|
const scope = opts.scope ?? "global";
|
|
460
534
|
let resolvedScope = scope;
|
|
461
535
|
if (scope !== "global" && !/^[0-9a-f]{16}$/.test(scope)) {
|
|
462
|
-
const project =
|
|
536
|
+
const project = createProjectRepository(db).getByName(scope);
|
|
463
537
|
if (project !== void 0) resolvedScope = project.scopeHash;
|
|
464
538
|
}
|
|
465
|
-
const
|
|
466
|
-
if (
|
|
539
|
+
const synthesis = createSynthesisRepository(db).getSynthesis(resolvedScope);
|
|
540
|
+
if (synthesis === void 0) {
|
|
467
541
|
if (formatter.isJson) process.stdout.write(`${JSON.stringify(null)}\n`);
|
|
468
542
|
else process.stdout.write(`No synthesis found for scope: ${scope}\n`);
|
|
469
543
|
return;
|
|
470
544
|
}
|
|
471
|
-
if (formatter.isJson) process.stdout.write(`${JSON.stringify(
|
|
545
|
+
if (formatter.isJson) process.stdout.write(`${JSON.stringify(synthesis)}\n`);
|
|
472
546
|
else {
|
|
473
|
-
process.stdout.write(`\nScope: ${
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
if (
|
|
477
|
-
process.stdout.write(`\n${
|
|
547
|
+
process.stdout.write(`\nScope: ${synthesis.scope}\n`);
|
|
548
|
+
process.stdout.write(`Synthesized: ${new Date(synthesis.synthesizedAt).toLocaleString()}\n`);
|
|
549
|
+
process.stdout.write(`Expires: ${new Date(synthesis.expiresAt).toLocaleString()}\n`);
|
|
550
|
+
if (synthesis.inFlightSince !== null) process.stdout.write(`In-flight since: ${new Date(synthesis.inFlightSince).toLocaleString()}\n`);
|
|
551
|
+
process.stdout.write(`\n${synthesis.content}\n\n`);
|
|
478
552
|
}
|
|
479
553
|
} finally {
|
|
480
554
|
db.close();
|
|
@@ -483,29 +557,22 @@ function synthesizeShowCommand(opts, formatter) {
|
|
|
483
557
|
function synthesizeStatusCommand(formatter) {
|
|
484
558
|
const db = DatabaseManager.open();
|
|
485
559
|
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();
|
|
560
|
+
const syntheses = createSynthesisRepository(db).listAll();
|
|
561
|
+
const projectRepo = createProjectRepository(db);
|
|
495
562
|
if (formatter.isJson) {
|
|
496
|
-
process.stdout.write(`${JSON.stringify(
|
|
563
|
+
process.stdout.write(`${JSON.stringify(syntheses)}\n`);
|
|
497
564
|
return;
|
|
498
565
|
}
|
|
499
|
-
if (
|
|
566
|
+
if (syntheses.length === 0) {
|
|
500
567
|
process.stdout.write("No syntheses found.\n");
|
|
501
568
|
return;
|
|
502
569
|
}
|
|
503
570
|
process.stdout.write("\n");
|
|
504
|
-
for (const
|
|
505
|
-
const displayScope =
|
|
506
|
-
const inFlight =
|
|
507
|
-
const synthesized =
|
|
508
|
-
const expires =
|
|
571
|
+
for (const s of syntheses) {
|
|
572
|
+
const displayScope = (s.scope !== "global" ? projectRepo.getByHash(s.scope) : void 0)?.name ?? s.scope;
|
|
573
|
+
const inFlight = s.inFlightSince !== null ? " [in-flight]" : "";
|
|
574
|
+
const synthesized = new Date(s.synthesizedAt).toLocaleString();
|
|
575
|
+
const expires = new Date(s.expiresAt).toLocaleString();
|
|
509
576
|
process.stdout.write(` ${displayScope}${inFlight}\n`);
|
|
510
577
|
process.stdout.write(` synthesized_at: ${synthesized}\n`);
|
|
511
578
|
process.stdout.write(` expires_at: ${expires}\n`);
|
|
@@ -521,7 +588,7 @@ function unpinCommand(id, db) {
|
|
|
521
588
|
const ownDb = db === void 0;
|
|
522
589
|
const resolvedDb = db ?? DatabaseManager.open();
|
|
523
590
|
try {
|
|
524
|
-
|
|
591
|
+
createMemoryRepository(resolvedDb, createProjectRepository(resolvedDb)).setPin(id, false);
|
|
525
592
|
process.stdout.write(`${chalk.green("✓")} Unpinned: ${chalk.dim(id)}\n`);
|
|
526
593
|
} finally {
|
|
527
594
|
if (ownDb) resolvedDb.close();
|
|
@@ -777,18 +844,20 @@ function hasKey(container, key) {
|
|
|
777
844
|
function assertCliFound(result, cli, command) {
|
|
778
845
|
if (result.exitCode === 127) throw new CommandError(`${cli} CLI not found — install ${cli} first`, command);
|
|
779
846
|
}
|
|
847
|
+
function jsonContainsStalePattern(value) {
|
|
848
|
+
return JSON.stringify(value).includes("@membank/cli");
|
|
849
|
+
}
|
|
780
850
|
const MEMBANK_NPX_ARGS = [
|
|
781
851
|
"npx",
|
|
782
852
|
"-y",
|
|
783
|
-
"@membank/
|
|
784
|
-
"--mcp"
|
|
853
|
+
"@membank/mcp"
|
|
785
854
|
];
|
|
786
855
|
const writers$1 = {
|
|
787
856
|
"claude-code": {
|
|
788
857
|
preview(resolver) {
|
|
789
858
|
return {
|
|
790
859
|
configPath: join(resolver.home(), ".claude.json"),
|
|
791
|
-
cliCommand: "claude mcp add --scope user membank -- npx -y @membank/
|
|
860
|
+
cliCommand: "claude mcp add --scope user membank -- npx -y @membank/mcp"
|
|
792
861
|
};
|
|
793
862
|
},
|
|
794
863
|
async write(resolver, run, { overwrite = false } = {}) {
|
|
@@ -821,6 +890,10 @@ const writers$1 = {
|
|
|
821
890
|
assertCliFound(add, "claude", addCmd);
|
|
822
891
|
if (add.exitCode !== 0) throw new CommandError(`claude mcp add failed: ${add.stderr || add.stdout}`, addCmd);
|
|
823
892
|
return { status: "written" };
|
|
893
|
+
},
|
|
894
|
+
async isStale(resolver) {
|
|
895
|
+
const cfg = readJson(join(resolver.home(), ".claude.json"));
|
|
896
|
+
return hasKey(cfg.mcpServers, "membank") && jsonContainsStalePattern(cfg.mcpServers);
|
|
824
897
|
}
|
|
825
898
|
},
|
|
826
899
|
copilot: {
|
|
@@ -840,22 +913,22 @@ const writers$1 = {
|
|
|
840
913
|
...MaybeJsonObjectSchema.parse(cfg.mcpServers),
|
|
841
914
|
membank: {
|
|
842
915
|
command: "npx",
|
|
843
|
-
args: [
|
|
844
|
-
"-y",
|
|
845
|
-
"@membank/cli",
|
|
846
|
-
"--mcp"
|
|
847
|
-
]
|
|
916
|
+
args: ["-y", "@membank/mcp"]
|
|
848
917
|
}
|
|
849
918
|
}
|
|
850
919
|
});
|
|
851
920
|
return { status: "written" };
|
|
921
|
+
},
|
|
922
|
+
async isStale(resolver) {
|
|
923
|
+
const cfg = readJson(join(resolver.home(), ".copilot", "mcp-config.json"));
|
|
924
|
+
return hasKey(cfg.mcpServers, "membank") && jsonContainsStalePattern(cfg.mcpServers);
|
|
852
925
|
}
|
|
853
926
|
},
|
|
854
927
|
codex: {
|
|
855
928
|
preview(_resolver) {
|
|
856
929
|
return {
|
|
857
930
|
configPath: null,
|
|
858
|
-
cliCommand: "codex mcp add membank -- npx -y @membank/
|
|
931
|
+
cliCommand: "codex mcp add membank -- npx -y @membank/mcp"
|
|
859
932
|
};
|
|
860
933
|
},
|
|
861
934
|
async write(_resolver, run, { overwrite = false } = {}) {
|
|
@@ -887,6 +960,10 @@ const writers$1 = {
|
|
|
887
960
|
assertCliFound(add, "codex", addCmd);
|
|
888
961
|
if (add.exitCode !== 0) throw new CommandError(`codex mcp add failed: ${add.stderr || add.stdout}`, addCmd);
|
|
889
962
|
return { status: "written" };
|
|
963
|
+
},
|
|
964
|
+
async isStale(_resolver, run) {
|
|
965
|
+
const list = await run("codex", ["mcp", "list"]);
|
|
966
|
+
return list.exitCode === 0 && list.stdout.includes("membank") && list.stdout.includes("@membank/cli");
|
|
890
967
|
}
|
|
891
968
|
},
|
|
892
969
|
opencode: {
|
|
@@ -909,13 +986,16 @@ const writers$1 = {
|
|
|
909
986
|
command: [
|
|
910
987
|
"npx",
|
|
911
988
|
"-y",
|
|
912
|
-
"@membank/
|
|
913
|
-
"--mcp"
|
|
989
|
+
"@membank/mcp"
|
|
914
990
|
]
|
|
915
991
|
}
|
|
916
992
|
}
|
|
917
993
|
});
|
|
918
994
|
return { status: "written" };
|
|
995
|
+
},
|
|
996
|
+
async isStale(resolver) {
|
|
997
|
+
const cfg = readJson(join(resolver.home(), ".config", "opencode", "opencode.json"));
|
|
998
|
+
return hasKey(cfg.mcp, "membank") && jsonContainsStalePattern(cfg.mcp);
|
|
919
999
|
}
|
|
920
1000
|
}
|
|
921
1001
|
};
|
|
@@ -937,6 +1017,15 @@ var HarnessConfigWriter = class {
|
|
|
937
1017
|
if (!writer) throw new Error(`Unknown harness: ${harness}`);
|
|
938
1018
|
return writer.write(this.#resolver, this.#run, { overwrite });
|
|
939
1019
|
}
|
|
1020
|
+
async isStale(harness) {
|
|
1021
|
+
const writer = writers$1[harness];
|
|
1022
|
+
if (!writer) return false;
|
|
1023
|
+
try {
|
|
1024
|
+
return await writer.isStale(this.#resolver, this.#run);
|
|
1025
|
+
} catch {
|
|
1026
|
+
return false;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
940
1029
|
};
|
|
941
1030
|
//#endregion
|
|
942
1031
|
//#region src/setup/injection-hook-writer.ts
|
|
@@ -1009,7 +1098,7 @@ const writers = {
|
|
|
1009
1098
|
},
|
|
1010
1099
|
{
|
|
1011
1100
|
event: "Stop",
|
|
1012
|
-
command: "npx -y @membank/cli
|
|
1101
|
+
command: "npx -y @membank/cli extract --harness claude-code",
|
|
1013
1102
|
existingCommand: extractInjectCommand(stopInner) || null
|
|
1014
1103
|
}
|
|
1015
1104
|
]
|
|
@@ -1021,6 +1110,7 @@ const writers = {
|
|
|
1021
1110
|
const hooks = MaybeJsonObjectSchema.parse(cfg.hooks) ?? {};
|
|
1022
1111
|
const newHooks = { ...hooks };
|
|
1023
1112
|
pruneNestedEvent(newHooks, "PostToolUseFailure");
|
|
1113
|
+
pruneNestedEvent(newHooks, "Stop");
|
|
1024
1114
|
if (events.includes("SessionStart")) newHooks.SessionStart = [...filterOutMembank(Array.isArray(hooks.SessionStart) ? hooks.SessionStart : []), {
|
|
1025
1115
|
matcher: "",
|
|
1026
1116
|
hooks: [{
|
|
@@ -1039,7 +1129,9 @@ const writers = {
|
|
|
1039
1129
|
matcher: "",
|
|
1040
1130
|
hooks: [{
|
|
1041
1131
|
type: "command",
|
|
1042
|
-
command: "npx -y @membank/cli
|
|
1132
|
+
command: "npx -y @membank/cli extract --harness claude-code",
|
|
1133
|
+
async: true,
|
|
1134
|
+
timeout: 600
|
|
1043
1135
|
}]
|
|
1044
1136
|
}];
|
|
1045
1137
|
writeJsonAtomic(cfgPath, {
|
|
@@ -1056,27 +1148,18 @@ const writers = {
|
|
|
1056
1148
|
const hooks = MaybeJsonObjectSchema.parse(cfg.hooks) ?? {};
|
|
1057
1149
|
const sessionStart = Array.isArray(hooks.sessionStart) ? hooks.sessionStart : [];
|
|
1058
1150
|
const userPromptSubmitted = Array.isArray(hooks.userPromptSubmitted) ? hooks.userPromptSubmitted : [];
|
|
1059
|
-
const sessionEnd = Array.isArray(hooks.sessionEnd) ? hooks.sessionEnd : [];
|
|
1060
1151
|
return {
|
|
1061
1152
|
status: "ready",
|
|
1062
1153
|
configPath: cfgPath,
|
|
1063
|
-
hooks: [
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
existingCommand: extractInjectCommand(userPromptSubmitted) || null
|
|
1073
|
-
},
|
|
1074
|
-
{
|
|
1075
|
-
event: "sessionEnd",
|
|
1076
|
-
command: "npx -y @membank/cli inject --harness copilot-cli --event session-stop",
|
|
1077
|
-
existingCommand: extractInjectCommand(sessionEnd) || null
|
|
1078
|
-
}
|
|
1079
|
-
]
|
|
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
|
+
}]
|
|
1080
1163
|
};
|
|
1081
1164
|
},
|
|
1082
1165
|
write(resolver, events) {
|
|
@@ -1085,6 +1168,7 @@ const writers = {
|
|
|
1085
1168
|
const hooks = MaybeJsonObjectSchema.parse(cfg.hooks) ?? {};
|
|
1086
1169
|
const newHooks = { ...hooks };
|
|
1087
1170
|
pruneFlatEvent(newHooks, "postToolUseFailure");
|
|
1171
|
+
pruneFlatEvent(newHooks, "sessionEnd");
|
|
1088
1172
|
if (events.includes("sessionStart")) newHooks.sessionStart = [...filterOutMembankFlat(Array.isArray(hooks.sessionStart) ? hooks.sessionStart : []), {
|
|
1089
1173
|
type: "command",
|
|
1090
1174
|
bash: "npx -y @membank/cli inject --harness copilot-cli",
|
|
@@ -1095,11 +1179,6 @@ const writers = {
|
|
|
1095
1179
|
bash: "npx -y @membank/cli inject --harness copilot-cli --event user-prompt-submit",
|
|
1096
1180
|
timeoutSec: 30
|
|
1097
1181
|
}];
|
|
1098
|
-
if (events.includes("sessionEnd")) newHooks.sessionEnd = [...filterOutMembankFlat(Array.isArray(hooks.sessionEnd) ? hooks.sessionEnd : []), {
|
|
1099
|
-
type: "command",
|
|
1100
|
-
bash: "npx -y @membank/cli inject --harness copilot-cli --event session-stop",
|
|
1101
|
-
timeoutSec: 30
|
|
1102
|
-
}];
|
|
1103
1182
|
writeJsonAtomic(cfgPath, {
|
|
1104
1183
|
version: OptionalNumberSchema.parse(cfg.version) ?? 1,
|
|
1105
1184
|
...cfg,
|
|
@@ -1115,27 +1194,18 @@ const writers = {
|
|
|
1115
1194
|
const hooks = MaybeJsonObjectSchema.parse(cfg.hooks) ?? {};
|
|
1116
1195
|
const sessionStartInner = (Array.isArray(hooks.SessionStart) ? hooks.SessionStart : []).flatMap(getHooksArray);
|
|
1117
1196
|
const userPromptSubmitInner = (Array.isArray(hooks.UserPromptSubmit) ? hooks.UserPromptSubmit : []).flatMap(getHooksArray);
|
|
1118
|
-
const stopInner = (Array.isArray(hooks.Stop) ? hooks.Stop : []).flatMap(getHooksArray);
|
|
1119
1197
|
return {
|
|
1120
1198
|
status: "ready",
|
|
1121
1199
|
configPath: cfgPath,
|
|
1122
|
-
hooks: [
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
existingCommand: extractInjectCommand(userPromptSubmitInner) || null
|
|
1132
|
-
},
|
|
1133
|
-
{
|
|
1134
|
-
event: "Stop",
|
|
1135
|
-
command: "npx -y @membank/cli inject --harness codex --event session-stop",
|
|
1136
|
-
existingCommand: extractInjectCommand(stopInner) || null
|
|
1137
|
-
}
|
|
1138
|
-
]
|
|
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
|
+
}]
|
|
1139
1209
|
};
|
|
1140
1210
|
},
|
|
1141
1211
|
write(resolver, events) {
|
|
@@ -1144,6 +1214,7 @@ const writers = {
|
|
|
1144
1214
|
const hooks = MaybeJsonObjectSchema.parse(cfg.hooks) ?? {};
|
|
1145
1215
|
const newHooks = { ...hooks };
|
|
1146
1216
|
pruneNestedEvent(newHooks, "PostToolUse");
|
|
1217
|
+
pruneNestedEvent(newHooks, "Stop");
|
|
1147
1218
|
if (events.includes("SessionStart")) newHooks.SessionStart = [...filterOutMembank(Array.isArray(hooks.SessionStart) ? hooks.SessionStart : []), {
|
|
1148
1219
|
matcher: "",
|
|
1149
1220
|
hooks: [{
|
|
@@ -1160,14 +1231,6 @@ const writers = {
|
|
|
1160
1231
|
timeout: 30
|
|
1161
1232
|
}]
|
|
1162
1233
|
}];
|
|
1163
|
-
if (events.includes("Stop")) newHooks.Stop = [...filterOutMembank(Array.isArray(hooks.Stop) ? hooks.Stop : []), {
|
|
1164
|
-
matcher: "",
|
|
1165
|
-
hooks: [{
|
|
1166
|
-
type: "command",
|
|
1167
|
-
command: "npx -y @membank/cli inject --harness codex --event session-stop",
|
|
1168
|
-
timeout: 30
|
|
1169
|
-
}]
|
|
1170
|
-
}];
|
|
1171
1234
|
writeJsonAtomic(cfgPath, {
|
|
1172
1235
|
...cfg,
|
|
1173
1236
|
hooks: newHooks
|
|
@@ -1229,76 +1292,6 @@ var InjectionHookWriter = class {
|
|
|
1229
1292
|
}
|
|
1230
1293
|
};
|
|
1231
1294
|
//#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
1295
|
//#region src/setup/harness-detector.ts
|
|
1303
1296
|
const defaultResolver = { homeDir: homedir };
|
|
1304
1297
|
function harnessConfigs(resolver) {
|
|
@@ -1586,7 +1579,10 @@ var SetupOrchestrator = class {
|
|
|
1586
1579
|
};
|
|
1587
1580
|
//#endregion
|
|
1588
1581
|
//#region src/index.ts
|
|
1589
|
-
if (process.argv.includes("--mcp"))
|
|
1582
|
+
if (process.argv.includes("--mcp")) {
|
|
1583
|
+
process.stderr.write("[membank] Deprecation: `membank --mcp` is deprecated. Use: npx @membank/mcp\n");
|
|
1584
|
+
await startServer();
|
|
1585
|
+
}
|
|
1590
1586
|
const program = new Command();
|
|
1591
1587
|
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
1588
|
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 +1694,47 @@ program.command("inject").description("output session context for harness inject
|
|
|
1698
1694
|
process.exit(2);
|
|
1699
1695
|
}
|
|
1700
1696
|
});
|
|
1701
|
-
program.command("
|
|
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
|
+
});
|
|
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)");
|
|
1710
|
+
setupCmd.command("upgrade").description("upgrade harness configs from membank --mcp to standalone membank-mcp").action(async () => {
|
|
1711
|
+
const isJson = program.opts().json === true;
|
|
1712
|
+
const writer = new HarnessConfigWriter();
|
|
1713
|
+
const stale = [];
|
|
1714
|
+
for (const harness of SUPPORTED_HARNESSES) if (await writer.isStale(harness)) stale.push(harness);
|
|
1715
|
+
if (stale.length === 0) {
|
|
1716
|
+
if (isJson) process.stdout.write(`${JSON.stringify({ upgraded: [] })}\n`);
|
|
1717
|
+
else process.stdout.write("All harness configs are already up to date.\n");
|
|
1718
|
+
return;
|
|
1719
|
+
}
|
|
1720
|
+
const upgraded = [];
|
|
1721
|
+
const errors = [];
|
|
1722
|
+
for (const harness of stale) try {
|
|
1723
|
+
await writer.write(harness, { overwrite: true });
|
|
1724
|
+
upgraded.push(harness);
|
|
1725
|
+
if (!isJson) process.stdout.write(` ${chalk.green("✓")} ${harness}\n`);
|
|
1726
|
+
} catch (err) {
|
|
1727
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1728
|
+
errors.push(`${harness}: ${msg}`);
|
|
1729
|
+
if (!isJson) process.stderr.write(` ${chalk.red("✗")} ${harness}: ${msg}\n`);
|
|
1730
|
+
}
|
|
1731
|
+
if (isJson) process.stdout.write(`${JSON.stringify({
|
|
1732
|
+
upgraded,
|
|
1733
|
+
errors
|
|
1734
|
+
})}\n`);
|
|
1735
|
+
if (errors.length > 0) process.exit(1);
|
|
1736
|
+
});
|
|
1737
|
+
setupCmd.action(async (cmdOptions) => {
|
|
1702
1738
|
const globalOpts = program.opts();
|
|
1703
1739
|
const autoYes = cmdOptions.yes === true || globalOpts.yes === true;
|
|
1704
1740
|
const formatter = Formatter.create(globalOpts.json === true);
|
|
@@ -1792,13 +1828,9 @@ program.command("migrate <mode> [name]").description("list or run a named data m
|
|
|
1792
1828
|
process.exit(2);
|
|
1793
1829
|
}
|
|
1794
1830
|
});
|
|
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
|
-
}
|
|
1831
|
+
program.command("dashboard").description("(deprecated) open the memory management dashboard").allowUnknownOption().action(() => {
|
|
1832
|
+
process.stderr.write("The dashboard is now a standalone package.\nRun: npx @membank/dashboard\n");
|
|
1833
|
+
process.exit(1);
|
|
1802
1834
|
});
|
|
1803
1835
|
const configCmd = program.command("config").description("manage membank configuration");
|
|
1804
1836
|
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.13.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.11.0",
|
|
25
|
+
"@membank/mcp": "0.14.0"
|
|
27
26
|
},
|
|
28
27
|
"devDependencies": {
|
|
29
28
|
"@types/node": "^25.6.0",
|