@membank/cli 0.5.1 → 0.7.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 +84 -37
- package/package.json +4 -4
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { cancel, confirm, intro, isCancel, multiselect, note, outro } from "@clack/prompts";
|
|
3
|
-
import { DatabaseManager, EmbeddingService, MEMORY_TYPE_VALUES, MemoryRepository, QueryEngine, SessionContextBuilder,
|
|
3
|
+
import { DatabaseManager, EmbeddingService, MEMORY_TYPE_VALUES, MIGRATIONS, MemoryRepository, ProjectRepository, QueryEngine, SessionContextBuilder, resolveProject, runScopeToProjectsMigration } from "@membank/core";
|
|
4
4
|
import { startServer } from "@membank/mcp";
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
import { Command } from "commander";
|
|
@@ -20,14 +20,15 @@ async function addCommand(content, options, formatter, db, embeddingService) {
|
|
|
20
20
|
const ownDb = db === void 0;
|
|
21
21
|
const resolvedDb = db ?? DatabaseManager.open();
|
|
22
22
|
try {
|
|
23
|
-
const repo = new MemoryRepository(resolvedDb, embeddingService ?? new EmbeddingService());
|
|
23
|
+
const repo = new MemoryRepository(resolvedDb, embeddingService ?? new EmbeddingService(), new ProjectRepository(resolvedDb));
|
|
24
24
|
const tags = options.tags !== void 0 ? options.tags.split(",").map((t) => t.trim()) : [];
|
|
25
|
+
const projectScope = options.global ? void 0 : await resolveProject();
|
|
25
26
|
const spinner = formatter.isJson ? null : ora("Saving memory…").start();
|
|
26
27
|
const memory = await repo.save({
|
|
27
28
|
content,
|
|
28
29
|
type: options.type,
|
|
29
30
|
tags,
|
|
30
|
-
|
|
31
|
+
projectScope
|
|
31
32
|
});
|
|
32
33
|
spinner?.succeed("Memory saved");
|
|
33
34
|
formatter.outputMemory(memory);
|
|
@@ -48,7 +49,7 @@ async function deleteCommand(id, db, formatter, prompt) {
|
|
|
48
49
|
process.exit(1);
|
|
49
50
|
}
|
|
50
51
|
if (!await prompt.confirm(`Delete memory ${id}?`)) return;
|
|
51
|
-
await new MemoryRepository(db, new EmbeddingService()).delete(id);
|
|
52
|
+
await new MemoryRepository(db, new EmbeddingService(), new ProjectRepository(db)).delete(id);
|
|
52
53
|
process.stdout.write(`${chalk.green("✓")} Deleted memory: ${chalk.dim(id)}\n`);
|
|
53
54
|
}
|
|
54
55
|
//#endregion
|
|
@@ -59,7 +60,6 @@ function exportCommand(db, formatter, opts) {
|
|
|
59
60
|
content: row.content,
|
|
60
61
|
type: row.type,
|
|
61
62
|
tags: JSON.parse(row.tags),
|
|
62
|
-
scope: row.scope,
|
|
63
63
|
sourceHarness: row.source,
|
|
64
64
|
accessCount: row.access_count,
|
|
65
65
|
pinned: row.pinned !== 0,
|
|
@@ -87,7 +87,7 @@ const MEMORY_TYPES = new Set(MEMORY_TYPE_VALUES);
|
|
|
87
87
|
function isValidRecord(r) {
|
|
88
88
|
if (typeof r !== "object" || r === null) return false;
|
|
89
89
|
const rec = r;
|
|
90
|
-
return typeof rec.id === "string" && rec.id.length > 0 && typeof rec.content === "string" && typeof rec.type === "string" && MEMORY_TYPES.has(rec.type)
|
|
90
|
+
return typeof rec.id === "string" && rec.id.length > 0 && typeof rec.content === "string" && typeof rec.type === "string" && MEMORY_TYPES.has(rec.type);
|
|
91
91
|
}
|
|
92
92
|
function isExportFile(parsed) {
|
|
93
93
|
if (typeof parsed !== "object" || parsed === null) return false;
|
|
@@ -115,19 +115,19 @@ async function importCommand(filePath, db, formatter, prompt) {
|
|
|
115
115
|
}
|
|
116
116
|
const invalidIndex = parsed.memories.findIndex((r) => !isValidRecord(r));
|
|
117
117
|
if (invalidIndex !== -1) {
|
|
118
|
-
formatter.error(`Invalid memory record at index ${invalidIndex}: must have id, content,
|
|
118
|
+
formatter.error(`Invalid memory record at index ${invalidIndex}: must have id, content, and type`);
|
|
119
119
|
process.exit(1);
|
|
120
120
|
}
|
|
121
121
|
const count = parsed.memories.length;
|
|
122
122
|
if (formatter.isJson) process.stdout.write(`${JSON.stringify({ found: count })}\n`);
|
|
123
123
|
else process.stdout.write(`Found ${count} memories to import.\n`);
|
|
124
124
|
if (!await prompt.confirm("Import?")) return;
|
|
125
|
-
const insertMemory = db.db.prepare(`INSERT OR REPLACE INTO memories (id, content, type, tags,
|
|
126
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?,
|
|
125
|
+
const insertMemory = db.db.prepare(`INSERT OR REPLACE INTO memories (id, content, type, tags, source, access_count, pinned, needs_review, created_at, updated_at)
|
|
126
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
127
127
|
const insertEmbedding = db.db.prepare(`INSERT OR REPLACE INTO embeddings (rowid, embedding) SELECT m.rowid, ? FROM memories m WHERE m.id = ?`);
|
|
128
128
|
db.db.transaction(() => {
|
|
129
129
|
for (const rec of parsed.memories) {
|
|
130
|
-
insertMemory.run(rec.id, rec.content, rec.type, JSON.stringify(rec.tags ?? []), rec.
|
|
130
|
+
insertMemory.run(rec.id, rec.content, rec.type, JSON.stringify(rec.tags ?? []), rec.sourceHarness ?? null, rec.accessCount ?? 0, rec.pinned ? 1 : 0, rec.needsReview ? 1 : 0, rec.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(), rec.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString());
|
|
131
131
|
if (rec.embedding !== null && rec.embedding !== void 0) {
|
|
132
132
|
const buf = Buffer.from(rec.embedding, "base64");
|
|
133
133
|
insertEmbedding.run(buf, rec.id);
|
|
@@ -166,7 +166,7 @@ function outputAdditionalContext(text, harness, eventName) {
|
|
|
166
166
|
process.stdout.write(`${text}\n`);
|
|
167
167
|
}
|
|
168
168
|
async function handleSessionStart(opts) {
|
|
169
|
-
const projectScope =
|
|
169
|
+
const projectScope = (await resolveProject()).hash;
|
|
170
170
|
const db = DatabaseManager.open();
|
|
171
171
|
let text;
|
|
172
172
|
try {
|
|
@@ -187,7 +187,7 @@ async function injectCommand(opts) {
|
|
|
187
187
|
async function listCommand(options, formatter) {
|
|
188
188
|
const db = DatabaseManager.open();
|
|
189
189
|
try {
|
|
190
|
-
const memories = new MemoryRepository(db, new EmbeddingService()).list({
|
|
190
|
+
const memories = new MemoryRepository(db, new EmbeddingService(), new ProjectRepository(db)).list({
|
|
191
191
|
type: options.type,
|
|
192
192
|
pinned: options.pinned
|
|
193
193
|
});
|
|
@@ -197,18 +197,53 @@ async function listCommand(options, formatter) {
|
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
//#endregion
|
|
200
|
+
//#region src/commands/migrate.ts
|
|
201
|
+
async function migrateCommand(mode, name, formatter) {
|
|
202
|
+
if (mode === "list") {
|
|
203
|
+
if (formatter.isJson) process.stdout.write(`${JSON.stringify(MIGRATIONS.map((m) => ({
|
|
204
|
+
name: m.name,
|
|
205
|
+
description: m.description
|
|
206
|
+
})))}\n`);
|
|
207
|
+
else for (const m of MIGRATIONS) process.stdout.write(`${m.name}\n ${m.description}\n`);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (name === void 0) {
|
|
211
|
+
formatter.error("Migration name is required for run mode.");
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
if (!MIGRATIONS.some((m) => m.name === name)) {
|
|
215
|
+
formatter.error(`Unknown migration: "${name}". Available: ${MIGRATIONS.map((m) => m.name).join(", ")}`);
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
const db = DatabaseManager.open();
|
|
219
|
+
try {
|
|
220
|
+
if (name === "scope-to-projects") {
|
|
221
|
+
const result = await runScopeToProjectsMigration(new ProjectRepository(db));
|
|
222
|
+
if (result === null) {
|
|
223
|
+
formatter.error("No project found for current directory.");
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (formatter.isJson) process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
227
|
+
else {
|
|
228
|
+
const { oldName, newName, memoryCount } = result;
|
|
229
|
+
process.stdout.write(`Found project: ${oldName} (${memoryCount} ${memoryCount === 1 ? "memory" : "memories"})\n`);
|
|
230
|
+
process.stdout.write(`Resolved name: ${newName}\n`);
|
|
231
|
+
process.stdout.write(`Renamed → ${newName}\n`);
|
|
232
|
+
process.stdout.write("Done.\n");
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
} finally {
|
|
236
|
+
db.close();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
//#endregion
|
|
200
240
|
//#region src/commands/pin.ts
|
|
201
|
-
function pinCommand(id,
|
|
241
|
+
function pinCommand(id, db) {
|
|
202
242
|
const ownDb = db === void 0;
|
|
203
243
|
const resolvedDb = db ?? DatabaseManager.open();
|
|
204
244
|
try {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
process.exit(2);
|
|
208
|
-
} else {
|
|
209
|
-
resolvedDb.db.prepare("UPDATE memories SET pinned = 1 WHERE id = ?").run(id);
|
|
210
|
-
process.stdout.write(`${chalk.green("✓")} Pinned: ${chalk.dim(id)}\n`);
|
|
211
|
-
}
|
|
245
|
+
new MemoryRepository(resolvedDb, new EmbeddingService(), new ProjectRepository(resolvedDb)).setPin(id, true);
|
|
246
|
+
process.stdout.write(`${chalk.green("✓")} Pinned: ${chalk.dim(id)}\n`);
|
|
212
247
|
} finally {
|
|
213
248
|
if (ownDb) resolvedDb.close();
|
|
214
249
|
}
|
|
@@ -219,7 +254,7 @@ async function queryCommand(queryText, options, formatter) {
|
|
|
219
254
|
const db = DatabaseManager.open();
|
|
220
255
|
try {
|
|
221
256
|
const embedding = new EmbeddingService();
|
|
222
|
-
const engine = new QueryEngine(db, embedding, new MemoryRepository(db, embedding));
|
|
257
|
+
const engine = new QueryEngine(db, embedding, new MemoryRepository(db, embedding, new ProjectRepository(db)));
|
|
223
258
|
const limit = options.limit !== void 0 ? Number.parseInt(options.limit, 10) : 10;
|
|
224
259
|
const spinner = formatter.isJson ? null : ora("Searching memories…").start();
|
|
225
260
|
const results = await engine.query({
|
|
@@ -238,7 +273,7 @@ async function queryCommand(queryText, options, formatter) {
|
|
|
238
273
|
async function statsCommand(formatter) {
|
|
239
274
|
const db = DatabaseManager.open();
|
|
240
275
|
try {
|
|
241
|
-
const stats = new MemoryRepository(db, new EmbeddingService()).stats();
|
|
276
|
+
const stats = new MemoryRepository(db, new EmbeddingService(), new ProjectRepository(db)).stats();
|
|
242
277
|
formatter.outputStats(stats);
|
|
243
278
|
} finally {
|
|
244
279
|
db.close();
|
|
@@ -246,17 +281,12 @@ async function statsCommand(formatter) {
|
|
|
246
281
|
}
|
|
247
282
|
//#endregion
|
|
248
283
|
//#region src/commands/unpin.ts
|
|
249
|
-
function unpinCommand(id,
|
|
284
|
+
function unpinCommand(id, db) {
|
|
250
285
|
const ownDb = db === void 0;
|
|
251
286
|
const resolvedDb = db ?? DatabaseManager.open();
|
|
252
287
|
try {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
process.exit(2);
|
|
256
|
-
} else {
|
|
257
|
-
resolvedDb.db.prepare("UPDATE memories SET pinned = 0 WHERE id = ?").run(id);
|
|
258
|
-
process.stdout.write(`${chalk.green("✓")} Unpinned: ${chalk.dim(id)}\n`);
|
|
259
|
-
}
|
|
288
|
+
new MemoryRepository(resolvedDb, new EmbeddingService(), new ProjectRepository(resolvedDb)).setPin(id, false);
|
|
289
|
+
process.stdout.write(`${chalk.green("✓")} Unpinned: ${chalk.dim(id)}\n`);
|
|
260
290
|
} finally {
|
|
261
291
|
if (ownDb) resolvedDb.close();
|
|
262
292
|
}
|
|
@@ -296,7 +326,8 @@ var Formatter = class Formatter {
|
|
|
296
326
|
process.stdout.write("\n");
|
|
297
327
|
process.stdout.write(` ${colorType(memory.type)} ${chalk.dim(memory.id)}\n`);
|
|
298
328
|
process.stdout.write(` ${memory.content}\n`);
|
|
299
|
-
|
|
329
|
+
const scope = memory.projects.length > 0 ? memory.projects.map((p) => p.name).join(", ") : "global";
|
|
330
|
+
process.stdout.write(` ${chalk.dim("Tags:")} ${tags} ${chalk.dim("Project:")} ${scope}\n`);
|
|
300
331
|
process.stdout.write(`\n ${chalk.dim(`Hint: pin with membank pin ${memory.id}`)}\n\n`);
|
|
301
332
|
}
|
|
302
333
|
outputMemories(memories) {
|
|
@@ -322,7 +353,8 @@ var Formatter = class Formatter {
|
|
|
322
353
|
});
|
|
323
354
|
for (const m of memories) {
|
|
324
355
|
const tags = m.tags.length > 0 ? m.tags.join(", ") : "(none)";
|
|
325
|
-
const
|
|
356
|
+
const mScope = m.projects.length > 0 ? m.projects.map((p) => p.name).join(", ") : "global";
|
|
357
|
+
const meta = `${truncate(m.content, 45)}\n${chalk.dim(`${tags} · ${mScope}`)}`;
|
|
326
358
|
table.push([
|
|
327
359
|
colorType(m.type),
|
|
328
360
|
chalk.dim(m.id),
|
|
@@ -376,7 +408,8 @@ var Formatter = class Formatter {
|
|
|
376
408
|
const scoreStr = r.score.toFixed(4);
|
|
377
409
|
const score = r.score >= .85 ? chalk.bold(scoreStr) : r.score < .75 ? chalk.dim(scoreStr) : scoreStr;
|
|
378
410
|
const tags = r.tags.length > 0 ? r.tags.join(", ") : "(none)";
|
|
379
|
-
const
|
|
411
|
+
const rScope = r.projects.length > 0 ? r.projects.map((p) => p.name).join(", ") : "global";
|
|
412
|
+
const meta = `${truncate(r.content, 45)}\n${chalk.dim(`${tags} · ${rScope}`)}`;
|
|
380
413
|
table.push([
|
|
381
414
|
colorType(r.type),
|
|
382
415
|
chalk.dim(r.id),
|
|
@@ -1252,7 +1285,7 @@ program.command("delete <id>").description("delete a memory by ID").action(async
|
|
|
1252
1285
|
db.close();
|
|
1253
1286
|
}
|
|
1254
1287
|
});
|
|
1255
|
-
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("--
|
|
1288
|
+
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("--global", "save as a global memory, not tied to any project").action(async (content, cmdOptions) => {
|
|
1256
1289
|
const globalOpts = program.opts();
|
|
1257
1290
|
const formatter = Formatter.create(globalOpts.json === true);
|
|
1258
1291
|
try {
|
|
@@ -1266,7 +1299,7 @@ program.command("pin <id>").description("pin a memory by ID").action((id) => {
|
|
|
1266
1299
|
const globalOpts = program.opts();
|
|
1267
1300
|
const formatter = Formatter.create(globalOpts.json === true);
|
|
1268
1301
|
try {
|
|
1269
|
-
pinCommand(id
|
|
1302
|
+
pinCommand(id);
|
|
1270
1303
|
} catch (err) {
|
|
1271
1304
|
formatter.error(err instanceof Error ? err.message : String(err));
|
|
1272
1305
|
process.exit(2);
|
|
@@ -1276,7 +1309,7 @@ program.command("unpin <id>").description("unpin a memory by ID").action((id) =>
|
|
|
1276
1309
|
const globalOpts = program.opts();
|
|
1277
1310
|
const formatter = Formatter.create(globalOpts.json === true);
|
|
1278
1311
|
try {
|
|
1279
|
-
unpinCommand(id
|
|
1312
|
+
unpinCommand(id);
|
|
1280
1313
|
} catch (err) {
|
|
1281
1314
|
formatter.error(err instanceof Error ? err.message : String(err));
|
|
1282
1315
|
process.exit(2);
|
|
@@ -1309,7 +1342,7 @@ program.command("import <file>").description("import memories from a JSON export
|
|
|
1309
1342
|
db.close();
|
|
1310
1343
|
}
|
|
1311
1344
|
});
|
|
1312
|
-
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("--
|
|
1345
|
+
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("--event <event>", "hook event type (only session-start is supported; other values no-op for legacy hook compatibility)", "session-start").action(async (cmdOptions) => {
|
|
1313
1346
|
try {
|
|
1314
1347
|
await injectCommand(cmdOptions);
|
|
1315
1348
|
} catch (err) {
|
|
@@ -1385,6 +1418,20 @@ program.command("setup").description("detect installed harnesses and write MCP c
|
|
|
1385
1418
|
process.exit(2);
|
|
1386
1419
|
}
|
|
1387
1420
|
});
|
|
1421
|
+
program.command("migrate <mode> [name]").description("list or run a named data migration (modes: list, run)").action(async (mode, name) => {
|
|
1422
|
+
if (mode !== "list" && mode !== "run") {
|
|
1423
|
+
process.stderr.write(`Error: mode must be "list" or "run"\n`);
|
|
1424
|
+
process.exit(1);
|
|
1425
|
+
}
|
|
1426
|
+
const globalOpts = program.opts();
|
|
1427
|
+
const formatter = Formatter.create(globalOpts.json === true);
|
|
1428
|
+
try {
|
|
1429
|
+
await migrateCommand(mode, name, formatter);
|
|
1430
|
+
} catch (err) {
|
|
1431
|
+
formatter.error(err instanceof Error ? err.message : String(err));
|
|
1432
|
+
process.exit(2);
|
|
1433
|
+
}
|
|
1434
|
+
});
|
|
1388
1435
|
program.command("dashboard").description("open the memory management dashboard in the browser").option("--port <port>", "port to listen on (default: 3847, fallback to random)").action(async (cmdOptions) => {
|
|
1389
1436
|
try {
|
|
1390
1437
|
await dashboardCommand(cmdOptions);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@membank/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"cli-table3": "^0.6.5",
|
|
21
21
|
"commander": "^14.0.3",
|
|
22
22
|
"ora": "^9.4.0",
|
|
23
|
-
"@membank/core": "0.
|
|
24
|
-
"@membank/dashboard": "0.
|
|
25
|
-
"@membank/mcp": "0.
|
|
23
|
+
"@membank/core": "0.6.1",
|
|
24
|
+
"@membank/dashboard": "0.3.1",
|
|
25
|
+
"@membank/mcp": "0.8.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^25.6.0",
|