memtrace 0.3.34 → 0.3.35
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/bin/memtrace.js +7 -1
- package/hooks/posttool-mcp-telemetry.sh +56 -0
- package/hooks/userprompt-claude.sh +102 -0
- package/install.js +35 -1
- package/lib/claude-integration.js +447 -0
- package/lib/skill-metadata.js +303 -0
- package/lib/spawn-helper.js +63 -0
- package/lib/upgrade-skills.js +72 -0
- package/package.json +6 -4
- package/uninstall.js +107 -39
package/uninstall.js
CHANGED
|
@@ -6,6 +6,17 @@ const path = require("path");
|
|
|
6
6
|
const fs = require("fs");
|
|
7
7
|
const { execSync, spawnSync } = require("child_process");
|
|
8
8
|
|
|
9
|
+
// claude-integration may not be present in old installs running this
|
|
10
|
+
// uninstall script from ~/.memtrace/. Resolve it lazily so the absence
|
|
11
|
+
// of the module is a soft no-op.
|
|
12
|
+
function loadClaudeIntegration() {
|
|
13
|
+
try {
|
|
14
|
+
return require("./lib/claude-integration");
|
|
15
|
+
} catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
9
20
|
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
10
21
|
|
|
11
22
|
const PLUGIN_NAME = "memtrace-skills";
|
|
@@ -42,9 +53,13 @@ const SKILL_NAMES = [
|
|
|
42
53
|
];
|
|
43
54
|
|
|
44
55
|
// ── Remove skills ───────────────────────────────────────────────────────────
|
|
56
|
+
//
|
|
57
|
+
// Each cleanup helper takes an optional `home` parameter so we can
|
|
58
|
+
// drive it against a tmp dir in tests. Production callers pass
|
|
59
|
+
// nothing and the helper defaults to the real home directory.
|
|
45
60
|
|
|
46
|
-
function removeSkills() {
|
|
47
|
-
const claudeSkillsDir = path.join(
|
|
61
|
+
function removeSkills(home = os.homedir()) {
|
|
62
|
+
const claudeSkillsDir = path.join(home, ".claude", "skills");
|
|
48
63
|
let removed = 0;
|
|
49
64
|
|
|
50
65
|
for (const name of SKILL_NAMES) {
|
|
@@ -62,15 +77,20 @@ function removeSkills() {
|
|
|
62
77
|
if (removed > 0) {
|
|
63
78
|
console.log(`memtrace: removed ${removed} skills from ${claudeSkillsDir}`);
|
|
64
79
|
}
|
|
80
|
+
return removed;
|
|
65
81
|
}
|
|
66
82
|
|
|
67
83
|
// ── Settings.json helpers ────────────────────────────────────────────────────
|
|
68
84
|
|
|
69
|
-
function
|
|
70
|
-
|
|
71
|
-
|
|
85
|
+
function settingsPath(home = os.homedir()) {
|
|
86
|
+
return path.join(home, ".claude", "settings.json");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function readSettings(home = os.homedir()) {
|
|
90
|
+
const file = settingsPath(home);
|
|
91
|
+
if (fs.existsSync(file)) {
|
|
72
92
|
try {
|
|
73
|
-
return JSON.parse(fs.readFileSync(
|
|
93
|
+
return JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
74
94
|
} catch {
|
|
75
95
|
return {};
|
|
76
96
|
}
|
|
@@ -78,9 +98,8 @@ function readSettings() {
|
|
|
78
98
|
return {};
|
|
79
99
|
}
|
|
80
100
|
|
|
81
|
-
function writeSettings(settings) {
|
|
82
|
-
|
|
83
|
-
fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + "\n");
|
|
101
|
+
function writeSettings(settings, home = os.homedir()) {
|
|
102
|
+
fs.writeFileSync(settingsPath(home), JSON.stringify(settings, null, 2) + "\n");
|
|
84
103
|
}
|
|
85
104
|
|
|
86
105
|
function isRecord(value) {
|
|
@@ -89,8 +108,8 @@ function isRecord(value) {
|
|
|
89
108
|
|
|
90
109
|
// ── Remove MCP server ───────────────────────────────────────────────────────
|
|
91
110
|
|
|
92
|
-
function removeMcpServer() {
|
|
93
|
-
const settings = readSettings();
|
|
111
|
+
function removeMcpServer(home = os.homedir()) {
|
|
112
|
+
const settings = readSettings(home);
|
|
94
113
|
let changed = false;
|
|
95
114
|
|
|
96
115
|
if (isRecord(settings.mcpServers) && settings.mcpServers[MCP_SERVER_NAME]) {
|
|
@@ -102,13 +121,14 @@ function removeMcpServer() {
|
|
|
102
121
|
console.log("memtrace: removed MCP server from ~/.claude/settings.json");
|
|
103
122
|
}
|
|
104
123
|
|
|
105
|
-
if (changed) writeSettings(settings);
|
|
124
|
+
if (changed) writeSettings(settings, home);
|
|
125
|
+
return changed;
|
|
106
126
|
}
|
|
107
127
|
|
|
108
128
|
// ── Remove plugin + marketplace ─────────────────────────────────────────────
|
|
109
129
|
|
|
110
|
-
function removePlugin() {
|
|
111
|
-
const settings = readSettings();
|
|
130
|
+
function removePlugin(home = os.homedir()) {
|
|
131
|
+
const settings = readSettings(home);
|
|
112
132
|
let changed = false;
|
|
113
133
|
|
|
114
134
|
if (isRecord(settings.enabledPlugins)) {
|
|
@@ -144,34 +164,38 @@ function removePlugin() {
|
|
|
144
164
|
console.log(`memtrace: removed marketplace ${MARKETPLACE_NAME}`);
|
|
145
165
|
}
|
|
146
166
|
|
|
147
|
-
if (changed) writeSettings(settings);
|
|
167
|
+
if (changed) writeSettings(settings, home);
|
|
168
|
+
return changed;
|
|
148
169
|
}
|
|
149
170
|
|
|
150
|
-
function removeMarketplaceCaches() {
|
|
151
|
-
const marketplacesRoot = path.join(
|
|
171
|
+
function removeMarketplaceCaches(home = os.homedir()) {
|
|
172
|
+
const marketplacesRoot = path.join(home, ".claude", "plugins", "marketplaces");
|
|
173
|
+
let removed = 0;
|
|
152
174
|
for (const entry of MARKETPLACE_SETTING_KEYS) {
|
|
153
175
|
const dir = path.join(marketplacesRoot, entry);
|
|
154
176
|
if (fs.existsSync(dir)) {
|
|
155
177
|
try {
|
|
156
178
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
157
179
|
console.log(`memtrace: removed marketplace cache ${entry}`);
|
|
180
|
+
removed++;
|
|
158
181
|
} catch (e) {
|
|
159
182
|
console.warn(`memtrace: failed to remove marketplace cache ${entry}: ${e.message}`);
|
|
160
183
|
}
|
|
161
184
|
}
|
|
162
185
|
}
|
|
186
|
+
return removed;
|
|
163
187
|
}
|
|
164
188
|
|
|
165
|
-
function removeInstalledPluginMetadata() {
|
|
166
|
-
const installedPluginsFile = path.join(
|
|
167
|
-
if (!fs.existsSync(installedPluginsFile)) return;
|
|
189
|
+
function removeInstalledPluginMetadata(home = os.homedir()) {
|
|
190
|
+
const installedPluginsFile = path.join(home, ".claude", "plugins", "installed_plugins.json");
|
|
191
|
+
if (!fs.existsSync(installedPluginsFile)) return false;
|
|
168
192
|
let data;
|
|
169
193
|
try {
|
|
170
194
|
data = JSON.parse(fs.readFileSync(installedPluginsFile, "utf-8"));
|
|
171
195
|
} catch {
|
|
172
|
-
return;
|
|
196
|
+
return false;
|
|
173
197
|
}
|
|
174
|
-
if (!isRecord(data.plugins)) return;
|
|
198
|
+
if (!isRecord(data.plugins)) return false;
|
|
175
199
|
|
|
176
200
|
let changed = false;
|
|
177
201
|
for (const key of Object.keys(data.plugins)) {
|
|
@@ -184,6 +208,19 @@ function removeInstalledPluginMetadata() {
|
|
|
184
208
|
fs.writeFileSync(installedPluginsFile, JSON.stringify(data, null, 2) + "\n");
|
|
185
209
|
console.log("memtrace: removed plugin metadata");
|
|
186
210
|
}
|
|
211
|
+
return changed;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function removeMemtraceHomeDir(home = os.homedir()) {
|
|
215
|
+
const memtraceDir = path.join(home, ".memtrace");
|
|
216
|
+
if (!fs.existsSync(memtraceDir)) return false;
|
|
217
|
+
try {
|
|
218
|
+
fs.rmSync(memtraceDir, { recursive: true, force: true });
|
|
219
|
+
return true;
|
|
220
|
+
} catch (e) {
|
|
221
|
+
console.warn(`memtrace: failed to remove ${memtraceDir}: ${e.message}`);
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
187
224
|
}
|
|
188
225
|
|
|
189
226
|
// ── Try Claude CLI uninstall ────────────────────────────────────────────────
|
|
@@ -209,15 +246,30 @@ function tryClaudeCliUninstall() {
|
|
|
209
246
|
|
|
210
247
|
// ── Main uninstall logic ───────────────────────────────────────────────────
|
|
211
248
|
|
|
249
|
+
function removeClaudeIntegration(home = os.homedir()) {
|
|
250
|
+
const ci = loadClaudeIntegration();
|
|
251
|
+
if (!ci) return;
|
|
252
|
+
try {
|
|
253
|
+
const summary = ci.uninstallFromFs({ claudeDir: path.join(home, ".claude") });
|
|
254
|
+
if (summary.removed.length > 0) {
|
|
255
|
+
console.log(`memtrace: cleaned Claude Code integration (${summary.removed.length} item(s))`);
|
|
256
|
+
}
|
|
257
|
+
} catch (e) {
|
|
258
|
+
console.warn(`memtrace: Claude Code integration cleanup failed: ${e.message}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
212
262
|
function legacyCleanup(options = {}) {
|
|
213
|
-
|
|
263
|
+
const home = options.home || os.homedir();
|
|
264
|
+
try { removeSkills(home); } catch (e) { console.warn(`memtrace: skill removal failed: ${e.message}`); }
|
|
214
265
|
if (!options.skipClaudeCli) {
|
|
215
266
|
try { tryClaudeCliUninstall(); } catch { /* silent */ }
|
|
216
267
|
}
|
|
217
|
-
try { removePlugin(); } catch (e) { console.warn(`memtrace: plugin removal failed: ${e.message}`); }
|
|
218
|
-
try { removeMcpServer(); } catch (e) { console.warn(`memtrace: MCP server removal failed: ${e.message}`); }
|
|
219
|
-
try { removeMarketplaceCaches(); } catch (e) { console.warn(`memtrace: marketplace cache cleanup failed: ${e.message}`); }
|
|
220
|
-
try { removeInstalledPluginMetadata(); } catch (e) { console.warn(`memtrace: installed plugin metadata cleanup failed: ${e.message}`); }
|
|
268
|
+
try { removePlugin(home); } catch (e) { console.warn(`memtrace: plugin removal failed: ${e.message}`); }
|
|
269
|
+
try { removeMcpServer(home); } catch (e) { console.warn(`memtrace: MCP server removal failed: ${e.message}`); }
|
|
270
|
+
try { removeMarketplaceCaches(home); } catch (e) { console.warn(`memtrace: marketplace cache cleanup failed: ${e.message}`); }
|
|
271
|
+
try { removeInstalledPluginMetadata(home); } catch (e) { console.warn(`memtrace: installed plugin metadata cleanup failed: ${e.message}`); }
|
|
272
|
+
removeClaudeIntegration(home);
|
|
221
273
|
}
|
|
222
274
|
|
|
223
275
|
function run() {
|
|
@@ -242,23 +294,39 @@ function run() {
|
|
|
242
294
|
}
|
|
243
295
|
|
|
244
296
|
// Always clean up ~/.memtrace/
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
if (fs.existsSync(memtraceDir)) {
|
|
248
|
-
fs.rmSync(memtraceDir, { recursive: true, force: true });
|
|
249
|
-
console.log("memtrace: removed ~/.memtrace/");
|
|
250
|
-
}
|
|
251
|
-
} catch (e) {
|
|
252
|
-
console.warn(`memtrace: failed to remove ~/.memtrace/: ${e.message}`);
|
|
297
|
+
if (removeMemtraceHomeDir()) {
|
|
298
|
+
console.log("memtrace: removed ~/.memtrace/");
|
|
253
299
|
}
|
|
254
300
|
|
|
255
301
|
console.log("memtrace: uninstall complete");
|
|
256
302
|
}
|
|
257
303
|
|
|
258
|
-
//
|
|
304
|
+
// Exports for testability. legacyCleanup accepts `{ home }` so test
|
|
305
|
+
// suites can drive the full cleanup pipeline against a tmp dir.
|
|
306
|
+
module.exports = {
|
|
307
|
+
SKILL_NAMES,
|
|
308
|
+
PLUGIN_KEY,
|
|
309
|
+
PLUGIN_NAME,
|
|
310
|
+
MARKETPLACE_NAME,
|
|
311
|
+
MCP_SERVER_NAME,
|
|
312
|
+
MARKETPLACE_SETTING_KEYS,
|
|
313
|
+
MARKETPLACE_SETTING_CONTAINERS,
|
|
314
|
+
removeSkills,
|
|
315
|
+
readSettings,
|
|
316
|
+
writeSettings,
|
|
317
|
+
removeMcpServer,
|
|
318
|
+
removePlugin,
|
|
319
|
+
removeMarketplaceCaches,
|
|
320
|
+
removeInstalledPluginMetadata,
|
|
321
|
+
removeMemtraceHomeDir,
|
|
322
|
+
removeClaudeIntegration,
|
|
323
|
+
legacyCleanup,
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
// Run when called directly (npm preuninstall hook). When require()'d
|
|
327
|
+
// for tests, exports are available without running. The `memtrace
|
|
328
|
+
// uninstall` CLI invokes run() through bin/memtrace.js, not via this
|
|
329
|
+
// module's auto-execution.
|
|
259
330
|
if (require.main === module) {
|
|
260
331
|
run();
|
|
261
|
-
} else {
|
|
262
|
-
// Called via `memtrace uninstall` — run immediately
|
|
263
|
-
run();
|
|
264
332
|
}
|