@lotargo/memory_plugin 1.1.7 → 1.1.9

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/mcp-server/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  import readline from "readline";
3
3
  import { join } from "node:path";
4
4
  import { getConfig, updateConfig, resetConfig } from "./config/config_manager.js";
@@ -629,6 +629,24 @@ function waitForEnter() {
629
629
  }
630
630
 
631
631
  export async function runCli() {
632
+ const cliArgs = process.argv.slice(2);
633
+ if (cliArgs.includes("--enable-prompt") || cliArgs.includes("enable-prompt")) {
634
+ const { enableGlobalPrompt } = await import("./prompt_manager.js");
635
+ const results = await enableGlobalPrompt();
636
+ console.log("\n [OK] Global prompt enabled across client configurations:\n");
637
+ results.forEach((r) => console.log(` - ${r.name}: ${r.filePath} (${r.status})`));
638
+ console.log("");
639
+ return;
640
+ }
641
+ if (cliArgs.includes("--disable-prompt") || cliArgs.includes("disable-prompt")) {
642
+ const { disableGlobalPrompt } = await import("./prompt_manager.js");
643
+ const results = await disableGlobalPrompt();
644
+ console.log("\n [OK] Global prompt disabled across client configurations:\n");
645
+ results.forEach((r) => console.log(` - ${r.name}: ${r.filePath} (${r.status})`));
646
+ console.log("");
647
+ return;
648
+ }
649
+
632
650
  let running = true;
633
651
  let selectedIndex = 0;
634
652
 
@@ -731,6 +749,21 @@ export async function runCli() {
731
749
  },
732
750
  ],
733
751
  },
752
+ {
753
+ title: "Global Prompt & Integration Management",
754
+ items: [
755
+ {
756
+ label: "[PROMPT ENABLE] Enable Global Prompt (Antigravity / Codex / Claude)",
757
+ value: "enable_prompt",
758
+ info: "Inject memory instructions into ~/.gemini/config/AGENTS.md, ~/.codex/AGENTS.md, ~/.claude/CLAUDE.md",
759
+ },
760
+ {
761
+ label: "[PROMPT DISABLE] Disable Global Prompt",
762
+ value: "disable_prompt",
763
+ info: "Remove memory instructions from global AGENTS.md / CLAUDE.md files",
764
+ },
765
+ ],
766
+ },
734
767
  {
735
768
  title: "Diagnostics & System Actions",
736
769
  items: [
@@ -1590,6 +1623,24 @@ export async function runCli() {
1590
1623
  }
1591
1624
  break;
1592
1625
  }
1626
+ case "enable_prompt": {
1627
+ const { enableGlobalPrompt } = await import("./prompt_manager.js");
1628
+ const results = await enableGlobalPrompt();
1629
+ console.clear();
1630
+ console.log("\n [OK] Global prompt enabled across client configurations:\n");
1631
+ results.forEach((r) => console.log(` - ${r.name}: ${r.filePath} (${r.status})`));
1632
+ await waitForEnter();
1633
+ break;
1634
+ }
1635
+ case "disable_prompt": {
1636
+ const { disableGlobalPrompt } = await import("./prompt_manager.js");
1637
+ const results = await disableGlobalPrompt();
1638
+ console.clear();
1639
+ console.log("\n [OK] Global prompt disabled across client configurations:\n");
1640
+ results.forEach((r) => console.log(` - ${r.name}: ${r.filePath} (${r.status})`));
1641
+ await waitForEnter();
1642
+ break;
1643
+ }
1593
1644
  case "reset": {
1594
1645
  resetConfig();
1595
1646
  console.clear();
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import * as z from "zod/v4";
@@ -0,0 +1,116 @@
1
+ import { readFile, writeFile, mkdir, unlink } from "fs/promises";
2
+ import { existsSync } from "fs";
3
+ import { join } from "path";
4
+ import { homedir } from "os";
5
+
6
+ const START_MARKER = "<!-- START MEMORY AGENT PROMPT -->";
7
+ const END_MARKER = "<!-- END MEMORY AGENT PROMPT -->";
8
+
9
+ export const PROMPT_BLOCK = `${START_MARKER}
10
+ [SYSTEM INSTRUCTION: PERSONAL CONTEXT & MEMORY AGENT]
11
+ When starting a session or when personal/project context is relevant, use \`recall\` from \`memory-agent\` to load saved facts, user preferences, and project guidelines. Use \`remember\` to save any durable high-signal facts provided by the user.
12
+ ${END_MARKER}`;
13
+
14
+ export function getGlobalPromptTargets() {
15
+ const home = homedir();
16
+ return [
17
+ {
18
+ name: "Antigravity",
19
+ filePath: join(home, ".gemini", "config", "AGENTS.md"),
20
+ },
21
+ {
22
+ name: "Codex",
23
+ filePath: join(home, ".codex", "AGENTS.md"),
24
+ },
25
+ {
26
+ name: "Claude Code",
27
+ filePath: join(home, ".claude", "CLAUDE.md"),
28
+ },
29
+ ];
30
+ }
31
+
32
+ function stripPromptBlock(content) {
33
+ const startIndex = content.indexOf(START_MARKER);
34
+ const endIndex = content.indexOf(END_MARKER);
35
+
36
+ if (startIndex !== -1 && endIndex !== -1 && endIndex > startIndex) {
37
+ const before = content.substring(0, startIndex);
38
+ const after = content.substring(endIndex + END_MARKER.length);
39
+ return (before + after).replace(/\n{3,}/g, "\n\n").trim();
40
+ }
41
+ return content.trim();
42
+ }
43
+
44
+ export async function enableGlobalPrompt() {
45
+ const targets = getGlobalPromptTargets();
46
+ const results = [];
47
+
48
+ for (const target of targets) {
49
+ try {
50
+ const parentDir = join(target.filePath, "..");
51
+ if (!existsSync(parentDir)) {
52
+ await mkdir(parentDir, { recursive: true });
53
+ }
54
+
55
+ let existing = "";
56
+ if (existsSync(target.filePath)) {
57
+ existing = await readFile(target.filePath, "utf-8");
58
+ }
59
+
60
+ const clean = stripPromptBlock(existing);
61
+ const updated = clean ? `${clean}\n\n${PROMPT_BLOCK}\n` : `${PROMPT_BLOCK}\n`;
62
+
63
+ await writeFile(target.filePath, updated, "utf-8");
64
+ results.push({ name: target.name, filePath: target.filePath, status: "enabled" });
65
+ } catch (err) {
66
+ results.push({ name: target.name, filePath: target.filePath, status: "failed", error: err.message });
67
+ }
68
+ }
69
+
70
+ return results;
71
+ }
72
+
73
+ export async function disableGlobalPrompt() {
74
+ const targets = getGlobalPromptTargets();
75
+ const results = [];
76
+
77
+ for (const target of targets) {
78
+ try {
79
+ if (!existsSync(target.filePath)) {
80
+ results.push({ name: target.name, filePath: target.filePath, status: "skipped" });
81
+ continue;
82
+ }
83
+
84
+ const existing = await readFile(target.filePath, "utf-8");
85
+ const clean = stripPromptBlock(existing);
86
+
87
+ if (clean.length === 0) {
88
+ await unlink(target.filePath);
89
+ results.push({ name: target.name, filePath: target.filePath, status: "removed_file" });
90
+ } else {
91
+ await writeFile(target.filePath, clean + "\n", "utf-8");
92
+ results.push({ name: target.name, filePath: target.filePath, status: "disabled" });
93
+ }
94
+ } catch (err) {
95
+ results.push({ name: target.name, filePath: target.filePath, status: "failed", error: err.message });
96
+ }
97
+ }
98
+
99
+ return results;
100
+ }
101
+
102
+ export async function getGlobalPromptStatus() {
103
+ const targets = getGlobalPromptTargets();
104
+ const status = [];
105
+
106
+ for (const target of targets) {
107
+ let enabled = false;
108
+ if (existsSync(target.filePath)) {
109
+ const content = await readFile(target.filePath, "utf-8");
110
+ enabled = content.includes(START_MARKER) && content.includes(END_MARKER);
111
+ }
112
+ status.push({ name: target.name, filePath: target.filePath, enabled });
113
+ }
114
+
115
+ return status;
116
+ }
@@ -32,12 +32,18 @@ export async function runSetup() {
32
32
  } catch (e) {}
33
33
  }
34
34
  if (!Array.isArray(config.plugin)) config.plugin = [];
35
- // Clean up legacy / incorrect plugin entry names
36
- const obsoleteNames = ["opencode-memory-plugin", "memory_plugin", "memory-plugin"];
37
- config.plugin = config.plugin.filter((p) => !obsoleteNames.includes(p));
38
- if (!config.plugin.includes("@lotargo/memory_plugin")) {
39
- config.plugin.push("@lotargo/memory_plugin");
40
- }
35
+ // Clean up legacy / obsolete / duplicate entries of OUR plugin only
36
+ const obsoleteNames = ["opencode-memory-plugin", "memory_plugin", "memory-plugin", "@lotargo/memory_plugin"];
37
+ config.plugin = config.plugin.filter((p) => {
38
+ if (typeof p !== "string") return true;
39
+ if (obsoleteNames.includes(p)) return false;
40
+ const normalized = p.replace(/\\/g, "/").toLowerCase();
41
+ if (normalized.endsWith("/memory") || normalized.endsWith("/memory_plugin") || normalized.endsWith("/memory-plugin")) {
42
+ return false;
43
+ }
44
+ return true;
45
+ });
46
+ config.plugin.push("@lotargo/memory_plugin");
41
47
  // Clean up legacy mcp-helper.js standalone file plugin if present
42
48
  const legacyPluginFile = join(opencodeDir, "plugins", "mcp-helper.js");
43
49
  if (existsSync(legacyPluginFile)) {
@@ -144,5 +150,19 @@ export async function runSetup() {
144
150
  }
145
151
  }
146
152
 
153
+ // 5. Global Prompt Instructions (Antigravity, Codex, Claude Code)
154
+ try {
155
+ const { enableGlobalPrompt } = await import("./prompt_manager.js");
156
+ const promptResults = await enableGlobalPrompt();
157
+ promptResults.forEach((r) => {
158
+ if (r.status === "enabled") {
159
+ console.log(` [OK] ${r.name}: enabled global prompt instruction in ${r.filePath}`);
160
+ }
161
+ });
162
+ } catch (err) {
163
+ console.log(" [SKIP] Global prompt setup skipped:", err.message);
164
+ }
165
+
147
166
  console.log(`\nSetup complete. Configured ${configuredCount} environment(s).\n`);
148
167
  }
168
+
@@ -153,7 +153,12 @@ export const MemoryPlugin = async ({ directory, worktree, client }) => {
153
153
  },
154
154
  },
155
155
  "remember": {
156
- description: "Save an important, durable fact to memory. Only use for high-signal information (name, goals, constraints, tech preferences, project conventions). Translate the fact into English before saving. scope: 'project' (default) or 'global'",
156
+ description:
157
+ "Save an important, durable fact to memory. Only use for high-signal information " +
158
+ "(name, goals, constraints, tech preferences, project conventions). " +
159
+ "Optionally link the fact to a Knowledge Base document or exact line range (docId, startLine, endLine). " +
160
+ "Translate the fact into English and keep it concise. " +
161
+ "scope: 'project' (default) or 'global'",
157
162
  args: {
158
163
  fact: { type: "string", description: "The fact to remember, written in English" },
159
164
  scope: {
@@ -161,25 +166,52 @@ export const MemoryPlugin = async ({ directory, worktree, client }) => {
161
166
  description: "'project' (default) or 'global'",
162
167
  default: "project",
163
168
  },
169
+ docId: { type: "string", description: "Optional document ID, title, or path to link this fact to" },
170
+ startLine: { type: "number", description: "Optional starting line number in target document" },
171
+ endLine: { type: "number", description: "Optional ending line number in target document" },
172
+ relationType: {
173
+ type: "string",
174
+ description: "Relation type (e.g. 'RULES_FOR', 'IMPLEMENTS', 'REFERENCES')",
175
+ default: "LINKS_TO",
176
+ },
164
177
  },
165
- async execute({ fact, scope }, { worktree, directory }) {
178
+ async execute({ fact, scope, docId, startLine, endLine, relationType }, { worktree, directory }) {
166
179
  const key = scopeKey(scope || "project", worktree, directory);
167
180
  const entries = await readMemory(key);
168
181
  const factNormalized = fact.toLowerCase().trim();
169
- if (entries.some((e) => {
182
+ if (!entries.some((e) => {
170
183
  const idx = e.indexOf("] ");
171
184
  return idx !== -1 && e.slice(idx + 2).toLowerCase().trim() === factNormalized;
172
185
  })) {
173
- return "Already saved";
186
+ entries.push(`- [${today()}] ${fact}`);
187
+ await writeMemory(key, entries);
174
188
  }
175
- entries.push(`- [${today()}] ${fact}`);
176
- await writeMemory(key, entries);
177
- await notify(client, "Memory updated");
178
- return "Memory updated";
189
+
190
+ let linkInfo = "";
191
+ if (docId) {
192
+ try {
193
+ const { linkFactToDocument } = await import("../mcp-server/graph/knowledge_linker.js");
194
+ const linkRes = linkFactToDocument({
195
+ factKey: key,
196
+ factText: fact,
197
+ docId,
198
+ startLine,
199
+ endLine,
200
+ relationType: relationType || "LINKS_TO",
201
+ });
202
+ const linesStr = startLine ? `:L${startLine}${endLine ? `-${endLine}` : ""}` : "";
203
+ linkInfo = ` [Linked to Doc: "${linkRes.docTitle}"${linesStr}]`;
204
+ } catch (err) {
205
+ linkInfo = ` (Note: Fact saved, but document link failed: ${err.message})`;
206
+ }
207
+ }
208
+
209
+ await notify(client, "Memory updated" + linkInfo);
210
+ return "Memory updated" + linkInfo;
179
211
  },
180
212
  },
181
213
  "recall": {
182
- description: "Показать запомненные факты (scope: project | global | all, по умолчанию все)",
214
+ description: "Show saved facts with any Agent-linked Knowledge Base documents/lines. scope: 'project', 'global', or 'all' (default)",
183
215
  args: {
184
216
  scope: {
185
217
  type: "string",
@@ -190,11 +222,37 @@ export const MemoryPlugin = async ({ directory, worktree, client }) => {
190
222
  async execute({ scope }, { worktree, directory }) {
191
223
  const project = projectName(worktree, directory);
192
224
  const results = [];
225
+
226
+ let getLinksForFact;
227
+ try {
228
+ const linker = await import("../mcp-server/graph/knowledge_linker.js");
229
+ getLinksForFact = linker.getLinksForFact;
230
+ } catch (e) {}
231
+
232
+ const formatFactWithLinks = (factText, key) => {
233
+ let line = factText;
234
+ if (getLinksForFact) {
235
+ try {
236
+ const links = getLinksForFact(key, factText);
237
+ if (links && links.length > 0) {
238
+ const docStr = links
239
+ .map((l) => {
240
+ const range = l.start_line ? `:L${l.start_line}${l.end_line ? `-${l.end_line}` : ""}` : "";
241
+ return `${l.doc_title || l.doc_path}${range}`;
242
+ })
243
+ .join(", ");
244
+ line += ` 🔗 [Linked Docs: ${docStr}]`;
245
+ }
246
+ } catch (e) {}
247
+ }
248
+ return line;
249
+ };
250
+
193
251
  if (scope !== "project") {
194
252
  const global = await readMemoryRaw(GLOBAL_KEY);
195
253
  if (global.length) {
196
254
  results.push("--- Global ---");
197
- global.forEach((e, i) => results.push(`${i + 1}. ${e}`));
255
+ global.forEach((e, i) => results.push(`${i + 1}. ${formatFactWithLinks(e, GLOBAL_KEY)}`));
198
256
  }
199
257
  }
200
258
  if (scope !== "global") {
@@ -202,7 +260,7 @@ export const MemoryPlugin = async ({ directory, worktree, client }) => {
202
260
  if (local.length) {
203
261
  if (results.length) results.push("");
204
262
  results.push(`--- ${project} ---`);
205
- local.forEach((e, i) => results.push(`${i + 1}. ${e}`));
263
+ local.forEach((e, i) => results.push(`${i + 1}. ${formatFactWithLinks(e, project)}`));
206
264
  }
207
265
  }
208
266
  return results.length ? results.join("\n") : "Memory is empty.";
@@ -237,6 +295,232 @@ export const MemoryPlugin = async ({ directory, worktree, client }) => {
237
295
  return result;
238
296
  },
239
297
  },
298
+ "link_knowledge": {
299
+ description:
300
+ "Explicitly link a Notebook memory fact to a Knowledge Base document, section, or line range. " +
301
+ "Creates Agent-driven Graph Edges connecting memory to RAG documents.",
302
+ args: {
303
+ action: {
304
+ type: "string",
305
+ description: "Action type: 'link' (default), 'list_links', 'get_doc_links'",
306
+ default: "link",
307
+ },
308
+ factText: { type: "string", description: "Memory fact text or keyword" },
309
+ docId: { type: "string", description: "Document ID, title, or file path" },
310
+ scope: { type: "string", description: "'project' (default) or 'global'", default: "project" },
311
+ startLine: { type: "number", description: "Starting line number in target document" },
312
+ endLine: { type: "number", description: "Ending line number in target document" },
313
+ relationType: {
314
+ type: "string",
315
+ description: "Relation type (e.g. 'RULES_FOR', 'IMPLEMENTS', 'EXPLAINS')",
316
+ default: "LINKS_TO",
317
+ },
318
+ },
319
+ async execute({ action, factText, docId, scope, startLine, endLine, relationType }, { worktree, directory }) {
320
+ const { linkFactToDocument, getLinksForDoc, listAllLinks } = await import("../mcp-server/graph/knowledge_linker.js");
321
+ const key = scopeKey(scope || "project", worktree, directory);
322
+ const act = action || "link";
323
+
324
+ if (act === "link") {
325
+ if (!factText || !docId) {
326
+ throw new Error("factText and docId are required parameters for link action");
327
+ }
328
+ const res = linkFactToDocument({
329
+ factKey: key,
330
+ factText,
331
+ docId,
332
+ startLine,
333
+ endLine,
334
+ relationType: relationType || "LINKS_TO",
335
+ });
336
+ return JSON.stringify(res, null, 2);
337
+ }
338
+
339
+ if (act === "get_doc_links") {
340
+ if (!docId) throw new Error("docId parameter is required for get_doc_links action");
341
+ const links = getLinksForDoc(docId);
342
+ return JSON.stringify(links, null, 2);
343
+ }
344
+
345
+ if (act === "list_links") {
346
+ const links = listAllLinks(key);
347
+ return JSON.stringify(links, null, 2);
348
+ }
349
+
350
+ throw new Error(`Unknown action: ${act}`);
351
+ },
352
+ },
353
+ "ingest_document": {
354
+ description:
355
+ "Ingest a document into the RAG knowledge base. " +
356
+ "Accepts local file paths, web URLs, or raw Markdown/text content. " +
357
+ "Processes document through 3-tier hierarchy chunking (Big/Medium/Small), " +
358
+ "computes dense vectors, and extracts GraphRAG code symbols.",
359
+ args: {
360
+ content: { type: "string", description: "Raw text content, file path, or web URL" },
361
+ type: { type: "string", description: "Input content type: 'text', 'file', 'url'", default: "text" },
362
+ title: { type: "string", description: "Document title" },
363
+ path: { type: "string", description: "Original document file path" },
364
+ generateEmbeddings: { type: "boolean", description: "Compute dense vector embeddings", default: true },
365
+ },
366
+ async execute({ content, type, title, path, generateEmbeddings }) {
367
+ const { ingestDocument } = await import("../mcp-server/ingest/pipeline.js");
368
+ const result = await ingestDocument({
369
+ content,
370
+ type: type || "text",
371
+ title: title || null,
372
+ path: path || null,
373
+ generateEmbeddings: generateEmbeddings !== false,
374
+ });
375
+ return JSON.stringify(
376
+ {
377
+ status: "success",
378
+ docId: result.docId,
379
+ title: result.title,
380
+ sectionsCount: result.sectionsCount,
381
+ microChunksCount: result.microChunksCount,
382
+ deduplicated: result.deduplicated,
383
+ },
384
+ null,
385
+ 2
386
+ );
387
+ },
388
+ },
389
+ "query_knowledge_base": {
390
+ description:
391
+ "Perform hybrid search (RSF/RRF BM25 full-text + dense vector similarity) across the RAG knowledge base. " +
392
+ "Returns top-ranked candidate document sections with breadcrumbs, GraphRAG defined code symbols, and relevance scores.",
393
+ args: {
394
+ query: { type: "string", description: "Search query in natural language or symbol name" },
395
+ limit: { type: "number", description: "Maximum number of sections to return", default: 5 },
396
+ instruction: {
397
+ type: "string",
398
+ description: "Optional task-specific retrieval instruction shaping embedding focus",
399
+ },
400
+ generateEmbeddings: { type: "boolean", description: "Use vector search alongside BM25", default: true },
401
+ },
402
+ async execute({ query, limit, instruction, generateEmbeddings }) {
403
+ const { hybridQuery } = await import("../mcp-server/retrieval/retriever.js");
404
+ const { getConfig } = await import("../mcp-server/config/config_manager.js");
405
+ const activeConfig = getConfig();
406
+
407
+ const results = await hybridQuery({
408
+ query,
409
+ limit: limit || 5,
410
+ generateEmbeddings: generateEmbeddings !== false,
411
+ instruction: instruction || null,
412
+ });
413
+
414
+ if (!results || results.length === 0) {
415
+ return `[Active Model: ${activeConfig.embeddingModel}]\nNo matching knowledge found for query.`;
416
+ }
417
+
418
+ const headerNote = `[Active Model: ${activeConfig.embeddingModel} | Fusion: ${activeConfig.fusionAlgorithm.toUpperCase()}]\n\n`;
419
+
420
+ const formatted = results
421
+ .map((r, i) => {
422
+ let header = `### [${i + 1}] ${r.doc_title || "Untitled"}`;
423
+ if (r.heading) header += ` > ${r.heading}`;
424
+ if (r.breadcrumbs) header += ` (${r.breadcrumbs})`;
425
+ let body = `Score: ${(r.score || 0).toFixed(4)}\n`;
426
+ if (r.defined_symbols && r.defined_symbols.length > 0) {
427
+ body += `Defined Symbols: ${r.defined_symbols.join(", ")}\n`;
428
+ }
429
+ body += `\n${r.snippet || r.full_section_content || ""}`;
430
+ return `${header}\n${body}`;
431
+ })
432
+ .join("\n\n---\n\n");
433
+
434
+ return headerNote + formatted;
435
+ },
436
+ },
437
+ "manage_knowledge_base": {
438
+ description:
439
+ "Manage the RAG knowledge base: inspect stats, list documents, read full raw document, delete documents, or export/import snapshots.",
440
+ args: {
441
+ action: {
442
+ type: "string",
443
+ description: "Management action: 'stats', 'list', 'read_document', 'delete', 'export_snapshot', 'import_snapshot'",
444
+ },
445
+ docId: { type: "string", description: "Document ID, title, or path (required for read_document and delete)" },
446
+ snapshotPath: { type: "string", description: "File path for snapshot export/import" },
447
+ },
448
+ async execute({ action, docId, snapshotPath }) {
449
+ const { getDatabase } = await import("../mcp-server/db/database.js");
450
+ const db = getDatabase();
451
+
452
+ if (action === "stats") {
453
+ const docCount = db.prepare("SELECT COUNT(*) as cnt FROM documents").get().cnt;
454
+ const secCount = db.prepare("SELECT COUNT(*) as cnt FROM sections").get().cnt;
455
+ const chunkCount = db.prepare("SELECT COUNT(*) as cnt FROM micro_chunks").get().cnt;
456
+ const edgeCount = db.prepare("SELECT COUNT(*) as cnt FROM graph_edges").get().cnt;
457
+ return JSON.stringify(
458
+ {
459
+ documents: docCount,
460
+ sections: secCount,
461
+ micro_chunks: chunkCount,
462
+ graph_edges: edgeCount,
463
+ },
464
+ null,
465
+ 2
466
+ );
467
+ }
468
+
469
+ if (action === "list") {
470
+ const docs = db
471
+ .prepare("SELECT id, title, path, blob_hash, created_at FROM documents ORDER BY created_at DESC")
472
+ .all();
473
+ return JSON.stringify(docs, null, 2);
474
+ }
475
+
476
+ if (action === "read_document") {
477
+ if (!docId) throw new Error("docId parameter is required for read_document action");
478
+ const doc = db
479
+ .prepare("SELECT id, title, path, blob_hash, created_at FROM documents WHERE id = ? OR path = ? OR title = ?")
480
+ .get(docId, docId, docId);
481
+ if (!doc) {
482
+ throw new Error(`Document not found in knowledge base for docId: ${docId}`);
483
+ }
484
+ const { readBlob } = await import("../mcp-server/storage/blob_store.js");
485
+ const rawContent = await readBlob(doc.blob_hash);
486
+ return JSON.stringify(
487
+ {
488
+ id: doc.id,
489
+ title: doc.title,
490
+ path: doc.path,
491
+ created_at: doc.created_at,
492
+ content: rawContent,
493
+ },
494
+ null,
495
+ 2
496
+ );
497
+ }
498
+
499
+ if (action === "delete") {
500
+ if (!docId) throw new Error("docId parameter is required for delete action");
501
+ const { deleteDocument } = await import("../mcp-server/ingest/pipeline.js");
502
+ const result = await deleteDocument(docId, db);
503
+ return JSON.stringify(result, null, 2);
504
+ }
505
+
506
+ if (action === "export_snapshot") {
507
+ const { exportSnapshot } = await import("../mcp-server/admin/snapshot.js");
508
+ const result = await exportSnapshot({ customDb: db, outputPath: snapshotPath || null });
509
+ return snapshotPath
510
+ ? `Snapshot exported successfully to ${snapshotPath}`
511
+ : JSON.stringify(result, null, 2);
512
+ }
513
+
514
+ if (action === "import_snapshot") {
515
+ if (!snapshotPath) throw new Error("snapshotPath parameter is required for import_snapshot action");
516
+ const { importSnapshot } = await import("../mcp-server/admin/snapshot.js");
517
+ const result = await importSnapshot({ customDb: db, snapshotPathOrData: snapshotPath });
518
+ return JSON.stringify(result, null, 2);
519
+ }
520
+
521
+ throw new Error(`Unknown action: ${action}`);
522
+ },
523
+ },
240
524
  },
241
525
  };
242
526
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotargo/memory_plugin",
3
- "version": "1.1.7",
3
+ "version": "1.1.9",
4
4
  "description": "Persistent memory agent for coding AI tools — remembers user preferences and project context across sessions. Works with Antigravity, OpenCode, Claude Code, and Codex.",
5
5
  "type": "module",
6
6
  "main": "opencode-plugin/index.js",
@@ -28,6 +28,7 @@
28
28
  "mcp-server/memory.js",
29
29
  "mcp-server/setup.js",
30
30
  "mcp-server/preinstall.js",
31
+ "mcp-server/prompt_manager.js",
31
32
  "skills"
32
33
  ],
33
34
  "keywords": [