@lotargo/memory_plugin 1.6.6 → 1.6.7
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/CHANGELOG.md +28 -0
- package/README.md +576 -443
- package/mcp-server/cli/direct_commands.js +39 -0
- package/mcp-server/cli.js +16 -5
- package/mcp-server/cli_boot.js +4 -1
- package/mcp-server/client_cli.js +73 -0
- package/mcp-server/client_paths.js +44 -0
- package/mcp-server/client_registration.js +38 -0
- package/mcp-server/codex_config.js +86 -8
- package/mcp-server/db/database.js +14 -21
- package/mcp-server/db/migrations.js +66 -77
- package/mcp-server/db/rag_blob_transport.js +143 -0
- package/mcp-server/db/rag_sync.js +284 -0
- package/mcp-server/db/sync_queue.js +219 -307
- package/mcp-server/dev_link.js +142 -0
- package/mcp-server/fact_format.js +44 -12
- package/mcp-server/index.js +17 -7
- package/mcp-server/ingest/exporter.js +44 -38
- package/mcp-server/ingest/pipeline.js +260 -248
- package/mcp-server/persona_migration.js +39 -0
- package/mcp-server/prompt_manager.js +162 -55
- package/mcp-server/retrieval/retriever.js +99 -64
- package/mcp-server/setup.js +150 -100
- package/mcp-server/storage/blob_store.js +53 -1
- package/mcp-server/tools/core/knowledge_read_core.js +163 -0
- package/mcp-server/tools/core/memory_core.js +24 -4
- package/mcp-server/tools/core/memory_routing.js +10 -0
- package/mcp-server/tools/core/note_core.js +53 -0
- package/mcp-server/tools/core/rag_query_core.js +169 -0
- package/mcp-server/tools/index.js +11 -9
- package/mcp-server/tools/memory_tools.js +4 -1
- package/mcp-server/tools/note_tools.js +35 -0
- package/mcp-server/tools/rag_tools.js +211 -364
- package/mcp-server/uninstall.js +627 -0
- package/opencode-plugin/index.js +80 -12
- package/opencode-plugin/main.js +136 -0
- package/package.json +16 -12
- package/skills/using-memory/SKILL.md +28 -19
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir, rm, realpath, readdir } from "node:fs/promises";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { isAbsolute, join, parse, relative, resolve } from "node:path";
|
|
4
|
+
import readline from "node:readline";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { resolveClientPaths } from "./client_paths.js";
|
|
7
|
+
import { isMemoryMcpServerEntry, isMemoryPluginEntry, readJsonConfig } from "./client_registration.js";
|
|
8
|
+
import { cliFailureMessage, runClientCli } from "./client_cli.js";
|
|
9
|
+
|
|
10
|
+
export { isMemoryMcpServerEntry, isMemoryPluginEntry } from "./client_registration.js";
|
|
11
|
+
|
|
12
|
+
export function openCodeCacheTargets(cachePackages) {
|
|
13
|
+
return [
|
|
14
|
+
join(cachePackages, "@lotargo", "memory_plugin"),
|
|
15
|
+
join(cachePackages, "@lotargo", "memory_plugin@latest"),
|
|
16
|
+
join(cachePackages, "memory_plugin"),
|
|
17
|
+
join(cachePackages, "memory_plugin@latest"),
|
|
18
|
+
join(cachePackages, "opencode-memory-plugin"),
|
|
19
|
+
join(cachePackages, "opencode-memory-plugin@latest"),
|
|
20
|
+
];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function discoverOpenCodeCacheTargets(cachePackages) {
|
|
24
|
+
const targets = new Set(openCodeCacheTargets(cachePackages));
|
|
25
|
+
const locations = [
|
|
26
|
+
{ dir: cachePackages, match: /^(?:memory_plugin|opencode-memory-plugin)(?:@[^/]+)?$/i },
|
|
27
|
+
{ dir: join(cachePackages, "@lotargo"), match: /^memory_plugin(?:@[^/]+)?$/i },
|
|
28
|
+
];
|
|
29
|
+
for (const location of locations) {
|
|
30
|
+
try {
|
|
31
|
+
const entries = await readdir(location.dir, { withFileTypes: true });
|
|
32
|
+
for (const entry of entries) {
|
|
33
|
+
if ((entry.isDirectory() || entry.isSymbolicLink()) && location.match.test(entry.name)) {
|
|
34
|
+
targets.add(join(location.dir, entry.name));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
} catch {}
|
|
38
|
+
}
|
|
39
|
+
return [...targets];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const PACKAGED_SKILL_FILE = fileURLToPath(new URL("../skills/using-memory/SKILL.md", import.meta.url));
|
|
43
|
+
|
|
44
|
+
export async function isOwnedSkillDir(skillDir) {
|
|
45
|
+
try {
|
|
46
|
+
const entries = await readdir(skillDir, { withFileTypes: true });
|
|
47
|
+
if (entries.length !== 1 || !entries[0].isFile() || entries[0].name !== "SKILL.md") return false;
|
|
48
|
+
const [installed, packaged] = await Promise.all([
|
|
49
|
+
readFile(join(skillDir, "SKILL.md")),
|
|
50
|
+
readFile(PACKAGED_SKILL_FILE),
|
|
51
|
+
]);
|
|
52
|
+
return installed.equals(packaged);
|
|
53
|
+
} catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isSameOrAncestor(candidate, protectedPath) {
|
|
59
|
+
const rel = relative(candidate, protectedPath);
|
|
60
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function assertSafePurgeTarget(target, { protectedPaths = [] } = {}) {
|
|
64
|
+
if (!target || !String(target).trim()) throw new Error("Refusing to purge an empty path");
|
|
65
|
+
const resolved = resolve(String(target));
|
|
66
|
+
const root = parse(resolved).root;
|
|
67
|
+
if (resolved === root) throw new Error(`Refusing to purge filesystem root: ${resolved}`);
|
|
68
|
+
const depth = relative(root, resolved).split(/[\\/]+/).filter(Boolean).length;
|
|
69
|
+
if (depth < 2) throw new Error(`Refusing to purge broad top-level path: ${resolved}`);
|
|
70
|
+
for (const protectedPath of protectedPaths.filter(Boolean)) {
|
|
71
|
+
const protectedResolved = resolve(String(protectedPath));
|
|
72
|
+
if (isSameOrAncestor(resolved, protectedResolved)) {
|
|
73
|
+
throw new Error(`Refusing to purge ${resolved}; it contains protected path ${protectedResolved}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return resolved;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function parseArgs(rawArgs) {
|
|
80
|
+
const args = rawArgs.map((a) => String(a));
|
|
81
|
+
const lower = args.map((a) => a.toLowerCase());
|
|
82
|
+
const has = (name) => lower.includes(name.toLowerCase());
|
|
83
|
+
const hasSpecificFlag = ["--opencode", "--claude", "--codex", "--antigravity", "--gemini"].some((f) => has(f));
|
|
84
|
+
const doOpenCode = !hasSpecificFlag || has("--opencode");
|
|
85
|
+
const doClaude = !hasSpecificFlag || has("--claude");
|
|
86
|
+
const doAntigravity = !hasSpecificFlag || has("--antigravity");
|
|
87
|
+
const doGemini = !hasSpecificFlag || has("--gemini");
|
|
88
|
+
const doCodex = !hasSpecificFlag || has("--codex");
|
|
89
|
+
const purge = has("--purge") || has("--hard") || has("--with-data") || has("--purge-data") || has("--hard-purge");
|
|
90
|
+
const purgeCache = has("--purge-cache");
|
|
91
|
+
const dryRun = has("--dry-run") || has("--dry");
|
|
92
|
+
const yes = has("--yes") || has("-y") || has("--force") || has("-f") || has("--assume-yes");
|
|
93
|
+
const help = has("--help") || has("-h") || has("help");
|
|
94
|
+
return { args, lower, has, hasSpecificFlag, doOpenCode, doClaude, doAntigravity, doGemini, doCodex, purge, purgeCache, dryRun, yes, help };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function printHelp() {
|
|
98
|
+
console.log(`
|
|
99
|
+
memory_plugin uninstall — remove @lotargo/memory_plugin from all clients
|
|
100
|
+
|
|
101
|
+
Usage:
|
|
102
|
+
memory_plugin uninstall [options]
|
|
103
|
+
memory_plugin setup --uninstall [options]
|
|
104
|
+
memory-cli uninstall [options]
|
|
105
|
+
npx @lotargo/memory_plugin uninstall [options]
|
|
106
|
+
|
|
107
|
+
Options:
|
|
108
|
+
--opencode Only remove OpenCode plugin entry
|
|
109
|
+
--claude Only remove Claude Code MCP entry
|
|
110
|
+
--codex Only remove Codex MCP entry
|
|
111
|
+
--antigravity Only remove Antigravity MCP entry
|
|
112
|
+
--gemini Only remove Gemini CLI MCP entry
|
|
113
|
+
(no flag) Remove from all detected clients
|
|
114
|
+
--purge Also delete local data (MEMORY_DIR, memory-agent prompt state, blobs, SQLite)
|
|
115
|
+
Without --purge, Notebook facts / RAG / models are kept on disk.
|
|
116
|
+
Unsafe broad targets are rejected even with --yes.
|
|
117
|
+
--purge-cache Also delete only this plugin's OpenCode package-cache directories
|
|
118
|
+
--dry-run Preview what would be removed without writing
|
|
119
|
+
--yes, -y, --force Skip confirmation prompt for --purge
|
|
120
|
+
-h, --help Show this help
|
|
121
|
+
|
|
122
|
+
What is removed by default (without --purge):
|
|
123
|
+
• OpenCode: plugin entry from ~/.config/opencode/opencode.json (incl. file:// dev link)
|
|
124
|
+
• Claude: mcpServers.memory-agent from ~/.claude.json
|
|
125
|
+
• Gemini CLI: mcpServers.memory-agent from ~/.gemini/settings.json
|
|
126
|
+
• Antigravity: mcpServers.memory-agent from ~/.gemini/config/mcp_config.json + .agents/mcp_config.json
|
|
127
|
+
• Codex: [mcp_servers.memory-agent] from ~/.codex/config.toml (only if owned by this plugin)
|
|
128
|
+
• Prompts: managed blocks from Codex, Claude, Gemini CLI, and Antigravity global instruction files
|
|
129
|
+
• Skills: using-memory skill from each client's skills/ directory
|
|
130
|
+
|
|
131
|
+
With --purge also removes:
|
|
132
|
+
• Local Notebook & RAG storage (MEMORY_DIR) and prompt state (XDG_CONFIG_HOME/memory-agent)
|
|
133
|
+
With --purge-cache also removes:
|
|
134
|
+
• Exact OpenCode cache directories belonging to @lotargo/memory_plugin
|
|
135
|
+
|
|
136
|
+
The npm package itself is removed separately:
|
|
137
|
+
npm uninstall -g @lotargo/memory_plugin
|
|
138
|
+
|
|
139
|
+
Examples:
|
|
140
|
+
memory_plugin uninstall --dry-run
|
|
141
|
+
memory_plugin uninstall --purge --yes
|
|
142
|
+
memory_plugin uninstall --opencode --purge-cache
|
|
143
|
+
memory_plugin uninstall --opencode --claude
|
|
144
|
+
`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function confirmPurge(yes, targets = []) {
|
|
148
|
+
if (yes) return true;
|
|
149
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
150
|
+
const answer = await new Promise((resolve) => {
|
|
151
|
+
const targetList = targets.map((target) => `\n - ${target}`).join("");
|
|
152
|
+
rl.question(` This will PERMANENTLY delete:${targetList}\n Continue? [y/N] `, (a) => {
|
|
153
|
+
rl.close();
|
|
154
|
+
resolve(String(a).trim().toLowerCase());
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
return answer === "y" || answer === "yes";
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function removeJsonMcpClient({ label, configPath, cliName, cliArgs, dry }) {
|
|
161
|
+
if (!existsSync(configPath)) return { status: "not_found" };
|
|
162
|
+
let config = await readJsonConfig(configPath);
|
|
163
|
+
const entry = config.mcpServers?.["memory-agent"];
|
|
164
|
+
if (!entry) return { status: "not_found" };
|
|
165
|
+
if (!isMemoryMcpServerEntry(entry)) return { status: "conflict" };
|
|
166
|
+
if (dry) return { status: "removed", method: "preview" };
|
|
167
|
+
|
|
168
|
+
const native = runClientCli(cliName, cliArgs);
|
|
169
|
+
if (native.ok) {
|
|
170
|
+
config = await readJsonConfig(configPath);
|
|
171
|
+
if (!config.mcpServers?.["memory-agent"]) {
|
|
172
|
+
return { status: "removed", method: "native" };
|
|
173
|
+
}
|
|
174
|
+
if (!isMemoryMcpServerEntry(config.mcpServers["memory-agent"])) {
|
|
175
|
+
return { status: "conflict", reason: `native ${label} command left an unrecognized entry` };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
config = await readJsonConfig(configPath);
|
|
180
|
+
const afterNative = config.mcpServers?.["memory-agent"];
|
|
181
|
+
if (!afterNative) return { status: "removed", method: "native" };
|
|
182
|
+
if (!isMemoryMcpServerEntry(afterNative)) return { status: "conflict" };
|
|
183
|
+
delete config.mcpServers["memory-agent"];
|
|
184
|
+
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
185
|
+
return {
|
|
186
|
+
status: "removed",
|
|
187
|
+
method: "fallback",
|
|
188
|
+
nativeReason: native.ok ? "native command did not remove the expected entry" : cliFailureMessage(native),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export async function runUninstall() {
|
|
193
|
+
const rawArgs = process.argv.slice(2);
|
|
194
|
+
// strip leading "uninstall" / "setup" / "--uninstall" tokens so parsing is uniform
|
|
195
|
+
// e.g. ["uninstall","--purge"] or ["setup","--uninstall","--purge"] or ["--uninstall"]
|
|
196
|
+
const filtered = rawArgs.filter((a, idx) => {
|
|
197
|
+
const low = String(a).toLowerCase();
|
|
198
|
+
if (low === "uninstall" || low === "--uninstall") return false;
|
|
199
|
+
if (low === "setup" && idx === 0) return false;
|
|
200
|
+
if (low === "install" && idx === 0) return false;
|
|
201
|
+
return true;
|
|
202
|
+
});
|
|
203
|
+
const opts = parseArgs(filtered);
|
|
204
|
+
|
|
205
|
+
if (opts.help) {
|
|
206
|
+
printHelp();
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const clientPaths = resolveClientPaths();
|
|
211
|
+
const { home } = clientPaths;
|
|
212
|
+
const dry = opts.dryRun;
|
|
213
|
+
const tag = dry ? "[DRY-RUN]" : "[OK]";
|
|
214
|
+
|
|
215
|
+
let purgeDirs = [];
|
|
216
|
+
if (opts.purge) {
|
|
217
|
+
const { MEMORY_DIR } = await import("./memory.js");
|
|
218
|
+
const candidates = [
|
|
219
|
+
{ path: MEMORY_DIR, label: "Local data (MEMORY_DIR)" },
|
|
220
|
+
{ path: clientPaths.agentConfigDir, label: "Prompt state (memory-agent config)" },
|
|
221
|
+
];
|
|
222
|
+
const seen = new Set();
|
|
223
|
+
for (const candidate of candidates) {
|
|
224
|
+
const protectedPaths = [home, clientPaths.cwd, clientPaths.configHome, clientPaths.opencodeDir, clientPaths.cacheHome];
|
|
225
|
+
let safePath = assertSafePurgeTarget(candidate.path, { protectedPaths });
|
|
226
|
+
if (existsSync(safePath)) {
|
|
227
|
+
const real = await realpath(safePath);
|
|
228
|
+
assertSafePurgeTarget(real, { protectedPaths });
|
|
229
|
+
}
|
|
230
|
+
const key = process.platform === "win32" ? safePath.toLowerCase() : safePath;
|
|
231
|
+
if (!seen.has(key)) {
|
|
232
|
+
seen.add(key);
|
|
233
|
+
purgeDirs.push({ ...candidate, path: safePath });
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
console.log(`\n${dry ? "Previewing" : "Removing"} @lotargo/memory_plugin${opts.purge ? " (with --purge)" : ""}...\n`);
|
|
239
|
+
|
|
240
|
+
if (opts.purge) {
|
|
241
|
+
console.log(" Validated purge targets:");
|
|
242
|
+
for (const target of purgeDirs) console.log(` - ${target.path}`);
|
|
243
|
+
console.log("");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (opts.purge && !dry) {
|
|
247
|
+
const ok = await confirmPurge(opts.yes, purgeDirs.map((item) => item.path));
|
|
248
|
+
if (!ok) {
|
|
249
|
+
console.log(" [CANCELLED] Purge aborted by user.\n");
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
let removedCount = 0;
|
|
255
|
+
let skippedCount = 0;
|
|
256
|
+
let failureCount = 0;
|
|
257
|
+
|
|
258
|
+
// 1. OpenCode
|
|
259
|
+
if (opts.doOpenCode) {
|
|
260
|
+
try {
|
|
261
|
+
const { opencodeDir, opencodeConfigPath } = clientPaths;
|
|
262
|
+
if (!existsSync(opencodeConfigPath)) {
|
|
263
|
+
console.log(` [SKIP] OpenCode: no config at ${opencodeConfigPath}`);
|
|
264
|
+
skippedCount++;
|
|
265
|
+
} else {
|
|
266
|
+
let config = await readJsonConfig(opencodeConfigPath);
|
|
267
|
+
const fields = ["plugin", "plugins"];
|
|
268
|
+
const ownedCount = fields.reduce((count, field) => count + (
|
|
269
|
+
Array.isArray(config[field]) ? config[field].filter(isMemoryPluginEntry).length : 0
|
|
270
|
+
), 0);
|
|
271
|
+
if (ownedCount === 0) {
|
|
272
|
+
console.log(` [SKIP] OpenCode: no plugin entries in ${opencodeConfigPath}`);
|
|
273
|
+
skippedCount++;
|
|
274
|
+
} else {
|
|
275
|
+
let method = "preview";
|
|
276
|
+
let nativeReason = null;
|
|
277
|
+
if (!dry) {
|
|
278
|
+
const native = runClientCli("opencode2", ["plugin", "remove", "@lotargo/memory_plugin"]);
|
|
279
|
+
if (native.ok) {
|
|
280
|
+
config = await readJsonConfig(opencodeConfigPath);
|
|
281
|
+
method = fields.every((field) => !Array.isArray(config[field]) || !config[field].some(isMemoryPluginEntry))
|
|
282
|
+
? "native"
|
|
283
|
+
: "fallback";
|
|
284
|
+
if (method === "fallback") nativeReason = "native command did not remove every owned config entry";
|
|
285
|
+
} else {
|
|
286
|
+
method = "fallback";
|
|
287
|
+
nativeReason = cliFailureMessage(native);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (method === "fallback") {
|
|
291
|
+
for (const field of fields) {
|
|
292
|
+
if (!Array.isArray(config[field])) continue;
|
|
293
|
+
const filtered = config[field].filter((entry) => !isMemoryPluginEntry(entry));
|
|
294
|
+
if (filtered.length === 0) delete config[field];
|
|
295
|
+
else config[field] = filtered;
|
|
296
|
+
}
|
|
297
|
+
await mkdir(opencodeDir, { recursive: true });
|
|
298
|
+
await writeFile(opencodeConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
console.log(` ${tag} OpenCode: removed ${ownedCount} plugin entrie(s) via ${method === "native" ? "opencode2 plugin remove" : method === "preview" ? "native command or ownership-checked fallback" : "ownership-checked config fallback"}${dry ? " (would remove)" : ""}`);
|
|
302
|
+
if (nativeReason) console.log(` [INFO] OpenCode fallback: ${nativeReason}`);
|
|
303
|
+
removedCount++;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (opts.purgeCache) {
|
|
308
|
+
let cacheRemoved = 0;
|
|
309
|
+
for (const target of await discoverOpenCodeCacheTargets(clientPaths.opencodeCachePackages)) {
|
|
310
|
+
if (!existsSync(target)) continue;
|
|
311
|
+
if (!dry) await rm(target, { recursive: true, force: true });
|
|
312
|
+
console.log(` ${tag} OpenCode cache: removed ${target}${dry ? " (would remove)" : ""}`);
|
|
313
|
+
cacheRemoved++;
|
|
314
|
+
}
|
|
315
|
+
if (cacheRemoved > 0) removedCount += cacheRemoved;
|
|
316
|
+
else {
|
|
317
|
+
console.log(` [SKIP] OpenCode cache: no owned package-cache directories found`);
|
|
318
|
+
skippedCount++;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
} catch (err) {
|
|
322
|
+
console.log(` [FAIL] OpenCode: ${err.message}`);
|
|
323
|
+
failureCount++;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// 2. Claude Code
|
|
328
|
+
if (opts.doClaude) {
|
|
329
|
+
try {
|
|
330
|
+
const result = await removeJsonMcpClient({
|
|
331
|
+
label: "Claude Code",
|
|
332
|
+
configPath: clientPaths.claudeConfigPath,
|
|
333
|
+
cliName: "claude",
|
|
334
|
+
cliArgs: ["mcp", "remove", "--scope", "user", "memory-agent"],
|
|
335
|
+
dry,
|
|
336
|
+
});
|
|
337
|
+
if (result.status === "not_found") {
|
|
338
|
+
console.log(` [SKIP] Claude Code: owned mcpServers.memory-agent not found`);
|
|
339
|
+
skippedCount++;
|
|
340
|
+
} else if (result.status === "conflict") {
|
|
341
|
+
console.log(` [WARN] Claude Code: mcpServers.memory-agent is not owned by this plugin — skipped`);
|
|
342
|
+
skippedCount++;
|
|
343
|
+
} else {
|
|
344
|
+
console.log(` ${tag} Claude Code: removed MCP registration via ${result.method === "native" ? "claude mcp remove" : result.method === "preview" ? "native command or ownership-checked fallback" : "ownership-checked JSON fallback"}${dry ? " (would remove)" : ""}`);
|
|
345
|
+
if (result.nativeReason) console.log(` [INFO] Claude Code fallback: ${result.nativeReason}`);
|
|
346
|
+
removedCount++;
|
|
347
|
+
}
|
|
348
|
+
} catch (err) {
|
|
349
|
+
console.log(` [FAIL] Claude Code: ${err.message}`);
|
|
350
|
+
failureCount++;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// 3. Antigravity
|
|
355
|
+
if (opts.doAntigravity) {
|
|
356
|
+
try {
|
|
357
|
+
const geminiConfigFile = clientPaths.antigravityMcpConfigPath;
|
|
358
|
+
let didGlobal = false;
|
|
359
|
+
if (existsSync(geminiConfigFile)) {
|
|
360
|
+
const config = await readJsonConfig(geminiConfigFile);
|
|
361
|
+
const memoryEntry = config.mcpServers?.["memory-agent"];
|
|
362
|
+
if (memoryEntry && isMemoryMcpServerEntry(memoryEntry)) {
|
|
363
|
+
if (!dry) {
|
|
364
|
+
delete config.mcpServers["memory-agent"];
|
|
365
|
+
// keep empty mcpServers object (do not delete key) to preserve file structure
|
|
366
|
+
await mkdir(clientPaths.antigravityConfigDir, { recursive: true });
|
|
367
|
+
await writeFile(geminiConfigFile, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
368
|
+
}
|
|
369
|
+
console.log(` ${tag} Antigravity: removed mcpServers.memory-agent from ${geminiConfigFile}${dry ? " (would remove)" : ""}`);
|
|
370
|
+
didGlobal = true;
|
|
371
|
+
removedCount++;
|
|
372
|
+
} else if (memoryEntry) {
|
|
373
|
+
console.log(` [WARN] Antigravity: mcpServers.memory-agent is not owned by this plugin — skipped`);
|
|
374
|
+
didGlobal = true;
|
|
375
|
+
skippedCount++;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (!didGlobal) {
|
|
379
|
+
console.log(` [SKIP] Antigravity (global): mcpServers.memory-agent not found in ${geminiConfigFile}`);
|
|
380
|
+
skippedCount++;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// local .agents/mcp_config.json
|
|
384
|
+
const localMcpFile = clientPaths.localAgentsMcpConfigPath;
|
|
385
|
+
if (existsSync(localMcpFile)) {
|
|
386
|
+
try {
|
|
387
|
+
const localConfig = await readJsonConfig(localMcpFile);
|
|
388
|
+
const memoryEntry = localConfig.mcpServers?.["memory-agent"];
|
|
389
|
+
if (memoryEntry && isMemoryMcpServerEntry(memoryEntry)) {
|
|
390
|
+
if (!dry) {
|
|
391
|
+
delete localConfig.mcpServers["memory-agent"];
|
|
392
|
+
await writeFile(localMcpFile, JSON.stringify(localConfig, null, 2) + "\n", "utf-8");
|
|
393
|
+
}
|
|
394
|
+
console.log(` ${tag} Antigravity (local): removed mcpServers.memory-agent from ${localMcpFile}${dry ? " (would remove)" : ""}`);
|
|
395
|
+
removedCount++;
|
|
396
|
+
} else if (memoryEntry) {
|
|
397
|
+
console.log(` [WARN] Antigravity (local): mcpServers.memory-agent is not owned by this plugin — skipped`);
|
|
398
|
+
skippedCount++;
|
|
399
|
+
} else {
|
|
400
|
+
console.log(` [SKIP] Antigravity (local): mcpServers.memory-agent not found in ${localMcpFile}`);
|
|
401
|
+
skippedCount++;
|
|
402
|
+
}
|
|
403
|
+
} catch (e) {
|
|
404
|
+
console.log(` [FAIL] Antigravity (local): ${e.message}`);
|
|
405
|
+
failureCount++;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
} catch (err) {
|
|
409
|
+
console.log(` [FAIL] Antigravity: ${err.message}`);
|
|
410
|
+
failureCount++;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// 4. Gemini CLI
|
|
415
|
+
if (opts.doGemini) {
|
|
416
|
+
try {
|
|
417
|
+
const result = await removeJsonMcpClient({
|
|
418
|
+
label: "Gemini CLI",
|
|
419
|
+
configPath: clientPaths.geminiSettingsPath,
|
|
420
|
+
cliName: "gemini",
|
|
421
|
+
cliArgs: ["mcp", "remove", "--scope", "user", "memory-agent"],
|
|
422
|
+
dry,
|
|
423
|
+
});
|
|
424
|
+
if (result.status === "not_found") {
|
|
425
|
+
console.log(` [SKIP] Gemini CLI: owned mcpServers.memory-agent not found`);
|
|
426
|
+
skippedCount++;
|
|
427
|
+
} else if (result.status === "conflict") {
|
|
428
|
+
console.log(` [WARN] Gemini CLI: mcpServers.memory-agent is not owned by this plugin — skipped`);
|
|
429
|
+
skippedCount++;
|
|
430
|
+
} else {
|
|
431
|
+
console.log(` ${tag} Gemini CLI: removed MCP registration via ${result.method === "native" ? "gemini mcp remove" : result.method === "preview" ? "native command or ownership-checked fallback" : "ownership-checked settings.json fallback"}${dry ? " (would remove)" : ""}`);
|
|
432
|
+
if (result.nativeReason) console.log(` [INFO] Gemini CLI fallback: ${result.nativeReason}`);
|
|
433
|
+
removedCount++;
|
|
434
|
+
}
|
|
435
|
+
} catch (err) {
|
|
436
|
+
console.log(` [FAIL] Gemini CLI: ${err.message}`);
|
|
437
|
+
failureCount++;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// 5. Codex
|
|
442
|
+
if (opts.doCodex) {
|
|
443
|
+
try {
|
|
444
|
+
const codexConfig = clientPaths.codexConfigPath;
|
|
445
|
+
if (!existsSync(codexConfig)) {
|
|
446
|
+
console.log(` [SKIP] Codex: no config at ${codexConfig}`);
|
|
447
|
+
skippedCount++;
|
|
448
|
+
} else {
|
|
449
|
+
let content = await readFile(codexConfig, "utf-8");
|
|
450
|
+
const { removeCodexMemoryAgentConfig } = await import("./codex_config.js");
|
|
451
|
+
let result = removeCodexMemoryAgentConfig(content);
|
|
452
|
+
if (result.status === "not_found") {
|
|
453
|
+
console.log(` [SKIP] Codex: [mcp_servers.memory-agent] not found`);
|
|
454
|
+
skippedCount++;
|
|
455
|
+
} else if (result.status === "conflict") {
|
|
456
|
+
console.log(` [WARN] Codex: ${result.reason} — skipped to avoid touching foreign config`);
|
|
457
|
+
skippedCount++;
|
|
458
|
+
} else if (result.changed) {
|
|
459
|
+
let method = "preview";
|
|
460
|
+
let nativeReason = null;
|
|
461
|
+
if (!dry) {
|
|
462
|
+
const native = runClientCli("codex", ["mcp", "remove", "memory-agent"]);
|
|
463
|
+
if (native.ok) {
|
|
464
|
+
content = existsSync(codexConfig) ? await readFile(codexConfig, "utf-8") : "";
|
|
465
|
+
result = removeCodexMemoryAgentConfig(content);
|
|
466
|
+
if (result.status === "not_found") method = "native";
|
|
467
|
+
else if (result.status === "conflict") {
|
|
468
|
+
throw new Error("codex mcp remove left an unrecognized memory-agent section");
|
|
469
|
+
} else {
|
|
470
|
+
method = "fallback";
|
|
471
|
+
nativeReason = "native command did not remove the expected section";
|
|
472
|
+
}
|
|
473
|
+
} else {
|
|
474
|
+
method = "fallback";
|
|
475
|
+
nativeReason = cliFailureMessage(native);
|
|
476
|
+
}
|
|
477
|
+
if (method === "fallback" && result.changed) {
|
|
478
|
+
await writeFile(codexConfig, result.content, "utf-8");
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
console.log(` ${tag} Codex: removed memory-agent via ${method === "native" ? "codex mcp remove" : method === "preview" ? "native command or ownership-checked fallback" : "ownership-checked TOML fallback"}${dry ? " (would remove)" : ""}`);
|
|
482
|
+
if (nativeReason) console.log(` [INFO] Codex fallback: ${nativeReason}`);
|
|
483
|
+
removedCount++;
|
|
484
|
+
} else {
|
|
485
|
+
console.log(` [SKIP] Codex: already clean`);
|
|
486
|
+
skippedCount++;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
} catch (err) {
|
|
490
|
+
console.log(` [FAIL] Codex: ${err.message}`);
|
|
491
|
+
failureCount++;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// 6. Global Prompt blocks
|
|
496
|
+
try {
|
|
497
|
+
const { disableGlobalPrompt, getGlobalPromptStatus } = await import("./prompt_manager.js");
|
|
498
|
+
const promptTargets = [];
|
|
499
|
+
if (opts.doCodex) promptTargets.push("Codex");
|
|
500
|
+
if (opts.doClaude) promptTargets.push("Claude Code");
|
|
501
|
+
if (opts.doAntigravity) promptTargets.push("Antigravity");
|
|
502
|
+
if (opts.doGemini) promptTargets.push("Gemini CLI");
|
|
503
|
+
if (promptTargets.length > 0) {
|
|
504
|
+
if (dry) {
|
|
505
|
+
const status = await getGlobalPromptStatus(promptTargets);
|
|
506
|
+
for (const s of status) {
|
|
507
|
+
if (s.enabled) {
|
|
508
|
+
console.log(` ${tag} ${s.name}: would remove prompt block from ${s.filePath}`);
|
|
509
|
+
removedCount++;
|
|
510
|
+
} else {
|
|
511
|
+
console.log(` [SKIP] ${s.name}: prompt block not present`);
|
|
512
|
+
skippedCount++;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
} else {
|
|
516
|
+
const results = await disableGlobalPrompt(promptTargets);
|
|
517
|
+
for (const r of results) {
|
|
518
|
+
if (r.status === "disabled" || r.status === "removed_file") {
|
|
519
|
+
console.log(` [OK] ${r.name}: removed prompt block from ${r.filePath}`);
|
|
520
|
+
removedCount++;
|
|
521
|
+
} else if (r.status === "skipped") {
|
|
522
|
+
console.log(` [SKIP] ${r.name}: prompt block not found`);
|
|
523
|
+
skippedCount++;
|
|
524
|
+
} else if (r.status === "failed") {
|
|
525
|
+
console.log(` [FAIL] ${r.name}: ${r.error}`);
|
|
526
|
+
failureCount++;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
} else {
|
|
531
|
+
console.log(` [SKIP] Prompts: --opencode only, no AGENTS.md prompts to remove`);
|
|
532
|
+
skippedCount++;
|
|
533
|
+
}
|
|
534
|
+
} catch (err) {
|
|
535
|
+
console.log(` [FAIL] Prompts: ${err.message}`);
|
|
536
|
+
failureCount++;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// 7. Skills
|
|
540
|
+
try {
|
|
541
|
+
const rawSkills = [
|
|
542
|
+
opts.doOpenCode ? { name: "OpenCode", dir: join(clientPaths.opencodeDir, "skills", "using-memory") } : null,
|
|
543
|
+
opts.doAntigravity ? { name: "Antigravity", dir: join(clientPaths.antigravityConfigDir, "skills", "using-memory") } : null,
|
|
544
|
+
opts.doGemini ? { name: "Gemini CLI", dir: join(clientPaths.geminiSkillsDir, "using-memory") } : null,
|
|
545
|
+
opts.doCodex ? { name: "Codex", dir: join(home, ".codex", "skills", "using-memory") } : null,
|
|
546
|
+
opts.doCodex ? { name: "Codex shared agents", dir: join(home, ".agents", "skills", "using-memory") } : null,
|
|
547
|
+
opts.doClaude ? { name: "Claude Code", dir: join(home, ".claude", "skills", "using-memory") } : null,
|
|
548
|
+
].filter(Boolean);
|
|
549
|
+
|
|
550
|
+
// local Antigravity .agents/skills
|
|
551
|
+
const cwd = clientPaths.cwd;
|
|
552
|
+
if (opts.doAntigravity && existsSync(join(cwd, ".agents"))) {
|
|
553
|
+
rawSkills.push({ name: "Antigravity (local)", dir: join(cwd, ".agents", "skills", "using-memory") });
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Deduplicate by normalized path (e.g. Codex shared agents and Antigravity local may coincide)
|
|
557
|
+
const seen = new Set();
|
|
558
|
+
const homeSkills = [];
|
|
559
|
+
for (const target of rawSkills) {
|
|
560
|
+
const key = target.dir.replace(/\\/g, "/").toLowerCase();
|
|
561
|
+
if (seen.has(key)) continue;
|
|
562
|
+
seen.add(key);
|
|
563
|
+
homeSkills.push(target);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
for (const target of homeSkills) {
|
|
567
|
+
if (existsSync(target.dir)) {
|
|
568
|
+
if (!await isOwnedSkillDir(target.dir)) {
|
|
569
|
+
console.log(` [WARN] ${target.name}: using-memory skill is modified or not owned by this plugin — skipped`);
|
|
570
|
+
skippedCount++;
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
if (!dry) {
|
|
574
|
+
await rm(target.dir, { recursive: true, force: true });
|
|
575
|
+
}
|
|
576
|
+
console.log(` ${tag} ${target.name}: removed skill at ${target.dir}${dry ? " (would remove)" : ""}`);
|
|
577
|
+
removedCount++;
|
|
578
|
+
} else {
|
|
579
|
+
console.log(` [SKIP] ${target.name}: skill not found at ${target.dir}`);
|
|
580
|
+
skippedCount++;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
} catch (err) {
|
|
584
|
+
console.log(` [FAIL] Skills: ${err.message}`);
|
|
585
|
+
failureCount++;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// 8. Data purge
|
|
589
|
+
if (opts.purge) {
|
|
590
|
+
for (const d of purgeDirs) {
|
|
591
|
+
if (existsSync(d.path)) {
|
|
592
|
+
if (!dry) {
|
|
593
|
+
try {
|
|
594
|
+
await rm(d.path, { recursive: true, force: true });
|
|
595
|
+
} catch (e) {
|
|
596
|
+
console.log(` [FAIL] Purge ${d.label}: ${e.message}`);
|
|
597
|
+
failureCount++;
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
console.log(` ${tag} Purge: removed ${d.label} at ${d.path}${dry ? " (would remove)" : ""}`);
|
|
602
|
+
removedCount++;
|
|
603
|
+
} else {
|
|
604
|
+
console.log(` [SKIP] Purge: ${d.label} not found at ${d.path}`);
|
|
605
|
+
skippedCount++;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
console.log("");
|
|
611
|
+
if (dry) {
|
|
612
|
+
console.log(`Preview complete. ${removedCount} item(s) would be removed, ${skippedCount} skipped. Re-run without --dry-run to apply.`);
|
|
613
|
+
if (!opts.purge) console.log(`Tip: add --purge to also delete local Notebook / RAG / blobs (kept by default).`);
|
|
614
|
+
} else {
|
|
615
|
+
console.log(`Uninstall complete. Removed ${removedCount} item(s), ${skippedCount} skipped.`);
|
|
616
|
+
if (!opts.purge) {
|
|
617
|
+
console.log(`Local Notebook / RAG data was kept. To delete it, run: memory_plugin uninstall --purge`);
|
|
618
|
+
}
|
|
619
|
+
console.log(`To remove the npm package itself (if installed globally), run: npm uninstall -g @lotargo/memory_plugin`);
|
|
620
|
+
console.log(`Restart OpenCode / Codex / Claude Code / Gemini CLI / Antigravity to apply changes.\n`);
|
|
621
|
+
}
|
|
622
|
+
if (failureCount > 0) {
|
|
623
|
+
process.exitCode = 1;
|
|
624
|
+
console.log(`Completed with ${failureCount} failure(s).\n`);
|
|
625
|
+
}
|
|
626
|
+
return { ok: failureCount === 0, removedCount, skippedCount, failureCount };
|
|
627
|
+
}
|