memtrace 0.1.37 → 0.1.45
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 +2 -2
- package/bin/memtrace.js +102 -14
- package/install.js +21 -179
- package/installer/dist/commands/doctor.d.ts +16 -0
- package/installer/dist/commands/doctor.js +86 -0
- package/installer/dist/commands/install.d.ts +9 -0
- package/installer/dist/commands/install.js +104 -0
- package/installer/dist/commands/picker.d.ts +6 -0
- package/installer/dist/commands/picker.js +22 -0
- package/installer/dist/fs-safe.d.ts +21 -0
- package/installer/dist/fs-safe.js +35 -0
- package/installer/dist/index.d.ts +2 -0
- package/installer/dist/index.js +52 -0
- package/installer/dist/skills.d.ts +17 -0
- package/installer/dist/skills.js +64 -0
- package/installer/dist/transformers/claude.d.ts +41 -0
- package/installer/dist/transformers/claude.js +400 -0
- package/installer/dist/transformers/cursor.d.ts +7 -0
- package/installer/dist/transformers/cursor.js +84 -0
- package/installer/dist/transformers/index.d.ts +7 -0
- package/installer/dist/transformers/index.js +7 -0
- package/installer/dist/transformers/types.d.ts +39 -0
- package/installer/dist/transformers/types.js +1 -0
- package/installer/dist/utils.d.ts +5 -0
- package/installer/dist/utils.js +22 -0
- package/installer/package.json +49 -0
- package/installer/skills/commands/memtrace-api-topology.md +65 -0
- package/installer/skills/commands/memtrace-cochange.md +76 -0
- package/installer/skills/commands/memtrace-evolution.md +135 -0
- package/installer/skills/commands/memtrace-graph.md +117 -0
- package/installer/skills/commands/memtrace-impact.md +64 -0
- package/installer/skills/commands/memtrace-index.md +66 -0
- package/installer/skills/commands/memtrace-quality.md +69 -0
- package/installer/skills/commands/memtrace-relationships.md +73 -0
- package/installer/skills/commands/memtrace-search.md +67 -0
- package/installer/skills/workflows/memtrace-change-impact-analysis.md +85 -0
- package/installer/skills/workflows/memtrace-codebase-exploration.md +108 -0
- package/installer/skills/workflows/memtrace-episode-replay.md +100 -0
- package/installer/skills/workflows/memtrace-first.md +120 -0
- package/installer/skills/workflows/memtrace-incident-investigation.md +125 -0
- package/installer/skills/workflows/memtrace-refactoring-guide.md +116 -0
- package/installer/skills/workflows/memtrace-session-continuity.md +98 -0
- package/package.json +10 -5
- package/skills/commands/memtrace-api-topology.md +3 -0
- package/skills/commands/memtrace-cochange.md +3 -0
- package/skills/commands/memtrace-evolution.md +3 -0
- package/skills/commands/memtrace-graph.md +54 -4
- package/skills/commands/memtrace-impact.md +3 -0
- package/skills/commands/memtrace-index.md +3 -0
- package/skills/commands/memtrace-quality.md +3 -0
- package/skills/commands/memtrace-relationships.md +3 -0
- package/skills/commands/memtrace-search.md +18 -13
- package/skills/workflows/memtrace-first.md +12 -0
- package/uninstall.js +22 -28
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<p align="center">
|
|
13
13
|
<a href="https://www.npmjs.com/package/memtrace"><img src="https://img.shields.io/npm/v/memtrace?style=flat-square&color=00D4B8&label=npm" alt="npm version" /></a>
|
|
14
14
|
<a href="https://github.com/syncable-dev/memtrace-public/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Proprietary%20EULA-0A1628?style=flat-square" alt="license" /></a>
|
|
15
|
-
<a href="https://memtrace.
|
|
15
|
+
<a href="https://memtrace.io"><img src="https://img.shields.io/badge/docs-memtrace.io-00D4B8?style=flat-square" alt="docs" /></a>
|
|
16
16
|
</p>
|
|
17
17
|
|
|
18
18
|
> **Early Access** — Memtrace is under active development. Core indexing and structural search are stable. Temporal features (evolution scoring, timeline replay) are functional but may have rough edges. [Report issues here.](https://github.com/syncable-dev/memtrace-public/issues)
|
|
@@ -294,7 +294,7 @@ Rust · Go · TypeScript · JavaScript · Python · Java · C · C++ · C# · Sw
|
|
|
294
294
|
<br/>
|
|
295
295
|
|
|
296
296
|
<p align="center">
|
|
297
|
-
<a href="https://memtrace.
|
|
297
|
+
<a href="https://memtrace.io">Documentation</a> · <a href="https://www.npmjs.com/package/memtrace">npm</a> · <a href="https://github.com/syncable-dev/memtrace-public/issues">Issues</a>
|
|
298
298
|
</p>
|
|
299
299
|
|
|
300
300
|
<p align="center">
|
package/bin/memtrace.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
|
-
const
|
|
4
|
+
const os = require("os");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
const { spawnSync, spawn } = require("child_process");
|
|
5
8
|
const { getBinaryPath } = require("../install.js");
|
|
6
9
|
|
|
7
10
|
// ── Handle `memtrace uninstall` before delegating to the Rust binary ────────
|
|
@@ -11,10 +14,6 @@ const { getBinaryPath } = require("../install.js");
|
|
|
11
14
|
const args = process.argv.slice(2);
|
|
12
15
|
|
|
13
16
|
if (args[0] === "uninstall" || args[0] === "cleanup") {
|
|
14
|
-
const path = require("path");
|
|
15
|
-
const os = require("os");
|
|
16
|
-
|
|
17
|
-
// Try the bundled script first, then the persisted copy in ~/.memtrace/
|
|
18
17
|
const candidates = [
|
|
19
18
|
path.join(__dirname, "..", "uninstall.js"),
|
|
20
19
|
path.join(os.homedir(), ".memtrace", "uninstall.js"),
|
|
@@ -41,7 +40,7 @@ if (args[0] === "uninstall" || args[0] === "cleanup") {
|
|
|
41
40
|
process.exit(0);
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
// ──
|
|
43
|
+
// ── Resolve native binary ─────────────────────────────────────────────────────
|
|
45
44
|
|
|
46
45
|
let binaryPath;
|
|
47
46
|
try {
|
|
@@ -51,14 +50,103 @@ try {
|
|
|
51
50
|
process.exit(1);
|
|
52
51
|
}
|
|
53
52
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
});
|
|
53
|
+
// ── Update checker ────────────────────────────────────────────────────────────
|
|
54
|
+
// Checks registry.npmjs.org at most once every 24 h (cached in ~/.memtrace/).
|
|
55
|
+
// Completely non-blocking: errors are silently swallowed.
|
|
58
56
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
const CACHE_FILE = path.join(os.homedir(), ".memtrace", "update-check.json");
|
|
58
|
+
const CHECK_INTERVAL = 24 * 60 * 60 * 1000; // 24 h in ms
|
|
59
|
+
|
|
60
|
+
function readUpdateCache() {
|
|
61
|
+
try {
|
|
62
|
+
return JSON.parse(fs.readFileSync(CACHE_FILE, "utf-8"));
|
|
63
|
+
} catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function writeUpdateCache(data) {
|
|
69
|
+
try {
|
|
70
|
+
fs.mkdirSync(path.dirname(CACHE_FILE), { recursive: true });
|
|
71
|
+
fs.writeFileSync(CACHE_FILE, JSON.stringify(data), "utf-8");
|
|
72
|
+
} catch {
|
|
73
|
+
// non-fatal
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function checkForUpdate(currentVersion) {
|
|
78
|
+
return new Promise((resolve) => {
|
|
79
|
+
// Return cached result if it's fresh enough
|
|
80
|
+
const cache = readUpdateCache();
|
|
81
|
+
if (cache && Date.now() - cache.checkedAt < CHECK_INTERVAL) {
|
|
82
|
+
resolve(cache.latestVersion !== currentVersion ? cache.latestVersion : null);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const https = require("https");
|
|
87
|
+
const req = https.get(
|
|
88
|
+
"https://registry.npmjs.org/memtrace/latest",
|
|
89
|
+
{ headers: { Accept: "application/json" }, timeout: 3000 },
|
|
90
|
+
(res) => {
|
|
91
|
+
let body = "";
|
|
92
|
+
res.on("data", (chunk) => (body += chunk));
|
|
93
|
+
res.on("end", () => {
|
|
94
|
+
try {
|
|
95
|
+
const latest = JSON.parse(body).version;
|
|
96
|
+
writeUpdateCache({ checkedAt: Date.now(), latestVersion: latest });
|
|
97
|
+
resolve(latest !== currentVersion ? latest : null);
|
|
98
|
+
} catch {
|
|
99
|
+
resolve(null);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
req.on("error", () => resolve(null));
|
|
105
|
+
req.on("timeout", () => { req.destroy(); resolve(null); });
|
|
106
|
+
});
|
|
62
107
|
}
|
|
63
108
|
|
|
64
|
-
|
|
109
|
+
// ── MCP mode: async spawn so the event loop stays alive for the update check ─
|
|
110
|
+
|
|
111
|
+
if (args[0] === "mcp") {
|
|
112
|
+
const { version: currentVersion } = require("../package.json");
|
|
113
|
+
|
|
114
|
+
// Fire the update check in the background — result prints to stderr if newer
|
|
115
|
+
checkForUpdate(currentVersion).then((latest) => {
|
|
116
|
+
if (latest) {
|
|
117
|
+
process.stderr.write(
|
|
118
|
+
`\n[memtrace] Update available: ${currentVersion} → ${latest}\n` +
|
|
119
|
+
` Run: npm install -g memtrace\n\n`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Use async spawn (not spawnSync) so Node's event loop keeps running
|
|
125
|
+
const child = spawn(binaryPath, args, {
|
|
126
|
+
stdio: "inherit",
|
|
127
|
+
env: process.env,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
child.on("error", (e) => {
|
|
131
|
+
process.stderr.write(`memtrace: failed to start: ${e.message}\n`);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
});
|
|
134
|
+
child.on("exit", (code, signal) => {
|
|
135
|
+
process.exit(signal ? 1 : (code ?? 0));
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
} else {
|
|
139
|
+
// ── All other commands: synchronous pass-through ────────────────────────────
|
|
140
|
+
|
|
141
|
+
const result = spawnSync(binaryPath, args, {
|
|
142
|
+
stdio: "inherit",
|
|
143
|
+
env: process.env,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (result.error) {
|
|
147
|
+
console.error(`Failed to run memtrace: ${result.error.message}`);
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
process.exit(result.status ?? 0);
|
|
152
|
+
}
|
package/install.js
CHANGED
|
@@ -4,13 +4,9 @@
|
|
|
4
4
|
const os = require("os");
|
|
5
5
|
const path = require("path");
|
|
6
6
|
const fs = require("fs");
|
|
7
|
-
const {
|
|
7
|
+
const { spawnSync } = require("child_process");
|
|
8
8
|
|
|
9
|
-
// ──
|
|
10
|
-
|
|
11
|
-
const PLUGIN_NAME = "memtrace-skills";
|
|
12
|
-
const MARKETPLACE_NAME = "memtrace";
|
|
13
|
-
const MARKETPLACE_REPO = "syncable-dev/memtrace-public";
|
|
9
|
+
// ── Platform binary resolution (preserved from legacy) ───────────────────────
|
|
14
10
|
|
|
15
11
|
const PLATFORM_MAP = {
|
|
16
12
|
"darwin-arm64": "@memtrace/darwin-arm64",
|
|
@@ -20,8 +16,6 @@ const PLATFORM_MAP = {
|
|
|
20
16
|
"win32-x64": "@memtrace/win32-x64",
|
|
21
17
|
};
|
|
22
18
|
|
|
23
|
-
// ── Binary helpers ────────────────────────────────────────────────────────────
|
|
24
|
-
|
|
25
19
|
function getPlatformKey() {
|
|
26
20
|
return `${os.platform()}-${os.arch()}`;
|
|
27
21
|
}
|
|
@@ -29,17 +23,14 @@ function getPlatformKey() {
|
|
|
29
23
|
function getBinaryPath() {
|
|
30
24
|
const key = getPlatformKey();
|
|
31
25
|
const pkg = PLATFORM_MAP[key];
|
|
32
|
-
|
|
33
26
|
if (!pkg) {
|
|
34
27
|
throw new Error(
|
|
35
28
|
`Memtrace does not support platform: ${key}\n` +
|
|
36
29
|
`Supported: ${Object.keys(PLATFORM_MAP).join(", ")}`
|
|
37
30
|
);
|
|
38
31
|
}
|
|
39
|
-
|
|
40
32
|
const ext = os.platform() === "win32" ? ".exe" : "";
|
|
41
33
|
const binaryName = `memtrace${ext}`;
|
|
42
|
-
|
|
43
34
|
try {
|
|
44
35
|
return require.resolve(`${pkg}/bin/${binaryName}`);
|
|
45
36
|
} catch {
|
|
@@ -50,149 +41,10 @@ function getBinaryPath() {
|
|
|
50
41
|
}
|
|
51
42
|
}
|
|
52
43
|
|
|
53
|
-
// ── Skill installation ───────────────────────────────────────────────────────
|
|
54
|
-
|
|
55
|
-
function installSkills() {
|
|
56
|
-
const skillsSrc = path.join(__dirname, "skills");
|
|
57
|
-
if (!fs.existsSync(skillsSrc)) {
|
|
58
|
-
console.warn("memtrace: skills directory not found, skipping skill installation");
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const claudeSkillsDir = path.join(os.homedir(), ".claude", "skills");
|
|
63
|
-
|
|
64
|
-
// Collect all .md skill files from commands/ and workflows/
|
|
65
|
-
const skillDirs = ["commands", "workflows"];
|
|
66
|
-
let installed = 0;
|
|
67
|
-
|
|
68
|
-
for (const dir of skillDirs) {
|
|
69
|
-
const srcDir = path.join(skillsSrc, dir);
|
|
70
|
-
if (!fs.existsSync(srcDir)) continue;
|
|
71
|
-
|
|
72
|
-
const files = fs.readdirSync(srcDir).filter(f => f.endsWith(".md"));
|
|
73
|
-
for (const file of files) {
|
|
74
|
-
const skillName = file.replace(/\.md$/, "");
|
|
75
|
-
const destDir = path.join(claudeSkillsDir, skillName);
|
|
76
|
-
const srcFile = path.join(srcDir, file);
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
80
|
-
fs.copyFileSync(srcFile, path.join(destDir, "SKILL.md"));
|
|
81
|
-
installed++;
|
|
82
|
-
} catch (e) {
|
|
83
|
-
console.warn(`memtrace: failed to install skill ${skillName}: ${e.message}`);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (installed > 0) {
|
|
89
|
-
console.log(`memtrace: installed ${installed} skills to ${claudeSkillsDir}`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// ── Settings.json helpers ────────────────────────────────────────────────────
|
|
94
|
-
|
|
95
|
-
function readSettings() {
|
|
96
|
-
const settingsFile = path.join(os.homedir(), ".claude", "settings.json");
|
|
97
|
-
if (fs.existsSync(settingsFile)) {
|
|
98
|
-
try {
|
|
99
|
-
return JSON.parse(fs.readFileSync(settingsFile, "utf-8"));
|
|
100
|
-
} catch {
|
|
101
|
-
return {};
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return {};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function writeSettings(settings) {
|
|
108
|
-
const settingsFile = path.join(os.homedir(), ".claude", "settings.json");
|
|
109
|
-
fs.mkdirSync(path.dirname(settingsFile), { recursive: true });
|
|
110
|
-
fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + "\n");
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// ── MCP server registration ─────────────────────────────────────────────────
|
|
114
|
-
|
|
115
|
-
function registerMcpServer(binaryPath) {
|
|
116
|
-
const settings = readSettings();
|
|
117
|
-
|
|
118
|
-
if (!settings.mcpServers || typeof settings.mcpServers !== "object") {
|
|
119
|
-
settings.mcpServers = {};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
settings.mcpServers.memtrace = {
|
|
123
|
-
command: binaryPath,
|
|
124
|
-
args: ["mcp"],
|
|
125
|
-
env: {
|
|
126
|
-
MEMGRAPH_URL: "bolt://localhost:7687",
|
|
127
|
-
},
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
writeSettings(settings);
|
|
131
|
-
console.log("memtrace: registered MCP server in ~/.claude/settings.json");
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// ── Plugin + marketplace registration ────────────────────────────────────────
|
|
135
|
-
|
|
136
|
-
function enablePlugin() {
|
|
137
|
-
const settings = readSettings();
|
|
138
|
-
|
|
139
|
-
// Enable plugin
|
|
140
|
-
if (!settings.enabledPlugins || typeof settings.enabledPlugins !== "object") {
|
|
141
|
-
settings.enabledPlugins = {};
|
|
142
|
-
}
|
|
143
|
-
const pluginKey = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
|
|
144
|
-
settings.enabledPlugins[pluginKey] = true;
|
|
145
|
-
|
|
146
|
-
// Register marketplace
|
|
147
|
-
if (!settings.extraKnownMarketplaces || typeof settings.extraKnownMarketplaces !== "object") {
|
|
148
|
-
settings.extraKnownMarketplaces = {};
|
|
149
|
-
}
|
|
150
|
-
settings.extraKnownMarketplaces[MARKETPLACE_NAME] = {
|
|
151
|
-
source: {
|
|
152
|
-
source: "github",
|
|
153
|
-
repo: MARKETPLACE_REPO,
|
|
154
|
-
},
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
writeSettings(settings);
|
|
158
|
-
console.log(`memtrace: enabled plugin ${pluginKey}`);
|
|
159
|
-
console.log(`memtrace: registered marketplace ${MARKETPLACE_NAME}`);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// ── Try Claude CLI install first ─────────────────────────────────────────────
|
|
163
|
-
|
|
164
|
-
function commandExists(cmd) {
|
|
165
|
-
try {
|
|
166
|
-
execSync(`which ${cmd}`, { stdio: "ignore" });
|
|
167
|
-
return true;
|
|
168
|
-
} catch {
|
|
169
|
-
return false;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function tryClaudeCliInstall() {
|
|
174
|
-
if (!commandExists("claude")) return false;
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
execSync(`claude plugin marketplace add ${MARKETPLACE_REPO}`, {
|
|
178
|
-
stdio: "ignore",
|
|
179
|
-
timeout: 15000,
|
|
180
|
-
});
|
|
181
|
-
execSync(`claude plugin install ${PLUGIN_NAME}@${MARKETPLACE_NAME} --scope user`, {
|
|
182
|
-
stdio: "ignore",
|
|
183
|
-
timeout: 15000,
|
|
184
|
-
});
|
|
185
|
-
console.log("memtrace: installed skills via Claude CLI");
|
|
186
|
-
return true;
|
|
187
|
-
} catch {
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
44
|
// ── Main postinstall ─────────────────────────────────────────────────────────
|
|
193
45
|
|
|
194
46
|
if (require.main === module) {
|
|
195
|
-
// 1. Verify binary
|
|
47
|
+
// 1. Verify binary exists + is executable
|
|
196
48
|
let binaryPath = null;
|
|
197
49
|
try {
|
|
198
50
|
binaryPath = getBinaryPath();
|
|
@@ -207,40 +59,30 @@ if (require.main === module) {
|
|
|
207
59
|
console.warn(`memtrace: ${e.message}`);
|
|
208
60
|
}
|
|
209
61
|
|
|
210
|
-
// 2.
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
if (!tryClaudeCliInstall()) {
|
|
220
|
-
enablePlugin();
|
|
221
|
-
}
|
|
222
|
-
} catch (e) {
|
|
223
|
-
console.warn(`memtrace: plugin registration failed: ${e.message}`);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// 4. Register MCP server
|
|
227
|
-
if (binaryPath) {
|
|
228
|
-
try {
|
|
229
|
-
registerMcpServer(binaryPath);
|
|
230
|
-
} catch (e) {
|
|
231
|
-
console.warn(`memtrace: MCP server registration failed: ${e.message}`);
|
|
62
|
+
// 2. Delegate to compiled installer (global, all agents, non-interactive)
|
|
63
|
+
const bundled = path.join(__dirname, "installer", "dist", "index.js");
|
|
64
|
+
if (fs.existsSync(bundled)) {
|
|
65
|
+
const result = spawnSync(process.execPath, [bundled, "install", "--yes"], {
|
|
66
|
+
stdio: "inherit",
|
|
67
|
+
env: { ...process.env, MEMTRACE_POSTINSTALL: "1" },
|
|
68
|
+
});
|
|
69
|
+
if (result.status !== 0) {
|
|
70
|
+
console.warn(`memtrace: installer exited with code ${result.status}; continuing.`);
|
|
232
71
|
}
|
|
72
|
+
} else {
|
|
73
|
+
console.warn("memtrace: bundled installer not found at installer/dist/index.js");
|
|
74
|
+
console.warn("memtrace: run 'memtrace install' manually after the package is built.");
|
|
233
75
|
}
|
|
234
76
|
|
|
235
|
-
//
|
|
236
|
-
// (npm v7+ does NOT fire preuninstall hooks for global packages — npm/cli#3042)
|
|
77
|
+
// 3. Persist uninstall script to ~/.memtrace/ (survives `npm uninstall -g`)
|
|
237
78
|
try {
|
|
238
79
|
const memtraceDir = path.join(os.homedir(), ".memtrace");
|
|
239
80
|
fs.mkdirSync(memtraceDir, { recursive: true });
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
81
|
+
fs.copyFileSync(
|
|
82
|
+
path.join(__dirname, "uninstall.js"),
|
|
83
|
+
path.join(memtraceDir, "uninstall.js"),
|
|
84
|
+
);
|
|
85
|
+
console.log(`memtrace: persisted uninstall script to ${path.join(memtraceDir, "uninstall.js")}`);
|
|
244
86
|
} catch (e) {
|
|
245
87
|
console.warn(`memtrace: failed to persist uninstall script: ${e.message}`);
|
|
246
88
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface AgentReport {
|
|
2
|
+
agent: string;
|
|
3
|
+
skillsFound: number;
|
|
4
|
+
skillsDir: string;
|
|
5
|
+
mcpRegistered: boolean;
|
|
6
|
+
mcpConfigPath: string;
|
|
7
|
+
}
|
|
8
|
+
export interface DoctorReport {
|
|
9
|
+
binaryOk: boolean;
|
|
10
|
+
binaryPath?: string;
|
|
11
|
+
binaryVersion?: string;
|
|
12
|
+
agents: AgentReport[];
|
|
13
|
+
}
|
|
14
|
+
export declare function runDoctorChecks(): Promise<DoctorReport>;
|
|
15
|
+
export declare function formatReport(r: DoctorReport): string;
|
|
16
|
+
export declare function runDoctor(): Promise<number>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { commandExists, execCommand } from '../utils.js';
|
|
5
|
+
import { safeReadJson } from '../fs-safe.js';
|
|
6
|
+
async function checkBinary() {
|
|
7
|
+
if (!(await commandExists('memtrace')))
|
|
8
|
+
return { binaryOk: false };
|
|
9
|
+
try {
|
|
10
|
+
const which = process.platform === 'win32' ? 'where memtrace' : 'which memtrace';
|
|
11
|
+
const binaryPath = (await execCommand(which, { timeoutMs: 3_000 })).split('\n')[0].trim();
|
|
12
|
+
let binaryVersion;
|
|
13
|
+
try {
|
|
14
|
+
binaryVersion = await execCommand('memtrace --version', { timeoutMs: 3_000 });
|
|
15
|
+
}
|
|
16
|
+
catch { /* optional */ }
|
|
17
|
+
return { binaryOk: true, binaryPath, binaryVersion };
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return { binaryOk: false };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function countMemtraceSkills(dir) {
|
|
24
|
+
if (!fs.existsSync(dir))
|
|
25
|
+
return 0;
|
|
26
|
+
return fs.readdirSync(dir)
|
|
27
|
+
.filter(e => e.startsWith('memtrace-'))
|
|
28
|
+
.filter(e => fs.existsSync(path.join(dir, e, 'SKILL.md')))
|
|
29
|
+
.length;
|
|
30
|
+
}
|
|
31
|
+
function mcpHasMemtrace(file) {
|
|
32
|
+
const { value } = safeReadJson(file);
|
|
33
|
+
if (!value)
|
|
34
|
+
return false;
|
|
35
|
+
return Boolean(value.mcpServers && value.mcpServers['memtrace']);
|
|
36
|
+
}
|
|
37
|
+
function checkAgent(agent) {
|
|
38
|
+
if (agent === 'claude') {
|
|
39
|
+
const skillsDir = path.join(os.homedir(), '.claude', 'skills');
|
|
40
|
+
const mcpConfigPath = path.join(os.homedir(), '.claude', 'settings.json');
|
|
41
|
+
return {
|
|
42
|
+
agent,
|
|
43
|
+
skillsFound: countMemtraceSkills(skillsDir),
|
|
44
|
+
skillsDir,
|
|
45
|
+
mcpRegistered: mcpHasMemtrace(mcpConfigPath),
|
|
46
|
+
mcpConfigPath,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const skillsDir = path.join(os.homedir(), '.cursor', 'skills');
|
|
50
|
+
const mcpConfigPath = path.join(os.homedir(), '.cursor', 'mcp.json');
|
|
51
|
+
return {
|
|
52
|
+
agent,
|
|
53
|
+
skillsFound: countMemtraceSkills(skillsDir),
|
|
54
|
+
skillsDir,
|
|
55
|
+
mcpRegistered: mcpHasMemtrace(mcpConfigPath),
|
|
56
|
+
mcpConfigPath,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export async function runDoctorChecks() {
|
|
60
|
+
const binary = await checkBinary();
|
|
61
|
+
return {
|
|
62
|
+
...binary,
|
|
63
|
+
agents: [checkAgent('claude'), checkAgent('cursor')],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export function formatReport(r) {
|
|
67
|
+
const lines = [];
|
|
68
|
+
const check = (ok) => (ok ? '✓' : '✗');
|
|
69
|
+
lines.push('memtrace doctor');
|
|
70
|
+
lines.push('───────────────');
|
|
71
|
+
lines.push(`${check(r.binaryOk)} memtrace binary${r.binaryPath ? ' ' + r.binaryPath : ''}`);
|
|
72
|
+
if (r.binaryVersion)
|
|
73
|
+
lines.push(` version: ${r.binaryVersion}`);
|
|
74
|
+
for (const a of r.agents) {
|
|
75
|
+
lines.push(`${check(a.skillsFound > 0)} ${a.agent} skills installed ${a.skillsFound} in ${a.skillsDir}`);
|
|
76
|
+
lines.push(`${check(a.mcpRegistered)} ${a.agent} MCP registered ${a.mcpConfigPath}`);
|
|
77
|
+
}
|
|
78
|
+
return lines.join('\n');
|
|
79
|
+
}
|
|
80
|
+
export async function runDoctor() {
|
|
81
|
+
const report = await runDoctorChecks();
|
|
82
|
+
console.log(formatReport(report));
|
|
83
|
+
const healthy = report.binaryOk
|
|
84
|
+
&& report.agents.every(a => a.skillsFound > 0 && a.mcpRegistered);
|
|
85
|
+
return healthy ? 0 : 1;
|
|
86
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface InstallOptions {
|
|
2
|
+
scope: 'global' | 'local';
|
|
3
|
+
only?: string[];
|
|
4
|
+
skipMcp?: boolean;
|
|
5
|
+
yes?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function resolveMemtraceBinary(): Promise<string | null>;
|
|
8
|
+
export declare function runInstall(options: InstallOptions): Promise<void>;
|
|
9
|
+
export declare function runUninstall(options: InstallOptions): Promise<void>;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import { loadSkills } from '../skills.js';
|
|
4
|
+
import { ALL_TRANSFORMERS, findTransformer } from '../transformers/index.js';
|
|
5
|
+
import { commandExists, execCommand } from '../utils.js';
|
|
6
|
+
import { canPrompt, promptForChoices } from './picker.js';
|
|
7
|
+
export async function resolveMemtraceBinary() {
|
|
8
|
+
if (await commandExists('memtrace'))
|
|
9
|
+
return 'memtrace';
|
|
10
|
+
try {
|
|
11
|
+
const npmBin = await execCommand('npm bin -g', { timeoutMs: 5_000 });
|
|
12
|
+
const ext = process.platform === 'win32' ? '.exe' : '';
|
|
13
|
+
const abs = path.join(npmBin.trim(), `memtrace${ext}`);
|
|
14
|
+
if (fs.existsSync(abs))
|
|
15
|
+
return abs;
|
|
16
|
+
}
|
|
17
|
+
catch { /* fall through */ }
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
function selectTransformers(options) {
|
|
21
|
+
if (!options.only || options.only.length === 0)
|
|
22
|
+
return ALL_TRANSFORMERS;
|
|
23
|
+
const picks = [];
|
|
24
|
+
for (const name of options.only) {
|
|
25
|
+
const t = findTransformer(name);
|
|
26
|
+
if (!t) {
|
|
27
|
+
throw new Error(`Unknown agent: ${name}. Known: ${ALL_TRANSFORMERS.map(x => x.name).join(', ')}`);
|
|
28
|
+
}
|
|
29
|
+
picks.push(t);
|
|
30
|
+
}
|
|
31
|
+
return picks;
|
|
32
|
+
}
|
|
33
|
+
export async function runInstall(options) {
|
|
34
|
+
console.log('\n Memtrace Skills Installer\n');
|
|
35
|
+
// Interactive prompt only when TTY is available and user didn't pre-specify
|
|
36
|
+
if (!options.yes && (!options.only || options.only.length === 0) && canPrompt()) {
|
|
37
|
+
const choice = await promptForChoices({ agents: [], scope: options.scope });
|
|
38
|
+
options.only = choice.agents;
|
|
39
|
+
options.scope = choice.scope;
|
|
40
|
+
}
|
|
41
|
+
const skillsDir = path.resolve(import.meta.dirname, '..', '..', 'skills');
|
|
42
|
+
const skills = loadSkills(skillsDir);
|
|
43
|
+
if (skills.length === 0) {
|
|
44
|
+
console.error(' Error: No skills found. Ensure skills/ directory exists.\n');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
const memtraceBin = await resolveMemtraceBinary();
|
|
48
|
+
if (memtraceBin === null) {
|
|
49
|
+
console.warn(' ⚠ memtrace binary not found on PATH.');
|
|
50
|
+
console.warn(' MCP registration will be skipped for every agent.\n');
|
|
51
|
+
}
|
|
52
|
+
const transformers = selectTransformers(options);
|
|
53
|
+
const ctx = {
|
|
54
|
+
scope: options.scope,
|
|
55
|
+
cwd: process.cwd(),
|
|
56
|
+
memtraceBinary: memtraceBin ?? '',
|
|
57
|
+
skipMcp: options.skipMcp || memtraceBin === null,
|
|
58
|
+
};
|
|
59
|
+
for (const t of transformers) {
|
|
60
|
+
console.log(` [${t.name}] installing...`);
|
|
61
|
+
try {
|
|
62
|
+
const r = await t.install(skills, ctx);
|
|
63
|
+
console.log(` [${t.name}] ✓ ${r.skillsWritten} skills → ${r.skillsDir}`);
|
|
64
|
+
if (r.mcpRegistered)
|
|
65
|
+
console.log(` [${t.name}] ✓ MCP registered`);
|
|
66
|
+
else if (ctx.skipMcp)
|
|
67
|
+
console.log(` [${t.name}] (MCP skipped)`);
|
|
68
|
+
else
|
|
69
|
+
console.log(` [${t.name}] ⚠ MCP registration failed`);
|
|
70
|
+
for (const w of r.warnings)
|
|
71
|
+
console.warn(` [${t.name}] warn: ${w}`);
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
console.error(` [${t.name}] ✗ install failed: ${e.message}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Auto health check — never affects install exit code
|
|
78
|
+
try {
|
|
79
|
+
const { runDoctorChecks, formatReport } = await import('./doctor.js');
|
|
80
|
+
const report = await runDoctorChecks();
|
|
81
|
+
console.log('\n' + formatReport(report));
|
|
82
|
+
}
|
|
83
|
+
catch { /* doctor failures never break install */ }
|
|
84
|
+
console.log('\n Done.\n');
|
|
85
|
+
}
|
|
86
|
+
export async function runUninstall(options) {
|
|
87
|
+
console.log('\n Removing Memtrace skills...\n');
|
|
88
|
+
const transformers = selectTransformers(options);
|
|
89
|
+
const ctx = {
|
|
90
|
+
scope: options.scope,
|
|
91
|
+
cwd: process.cwd(),
|
|
92
|
+
memtraceBinary: '',
|
|
93
|
+
};
|
|
94
|
+
for (const t of transformers) {
|
|
95
|
+
try {
|
|
96
|
+
await t.uninstall(ctx);
|
|
97
|
+
console.log(` [${t.name}] removed`);
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
console.warn(` [${t.name}] uninstall failed: ${e.message}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
console.log('\n Done.\n');
|
|
104
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import readline from 'readline';
|
|
2
|
+
import { ALL_TRANSFORMERS } from '../transformers/index.js';
|
|
3
|
+
export function canPrompt() {
|
|
4
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
5
|
+
}
|
|
6
|
+
export async function promptForChoices(defaults) {
|
|
7
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
8
|
+
const ask = (q) => new Promise(resolve => rl.question(q, resolve));
|
|
9
|
+
console.log('\n Agents available:');
|
|
10
|
+
for (const t of ALL_TRANSFORMERS)
|
|
11
|
+
console.log(` - ${t.name}`);
|
|
12
|
+
const agentsInput = await ask(`\n Install for which agents? (comma-separated, default: all) `);
|
|
13
|
+
const agents = agentsInput.trim()
|
|
14
|
+
? agentsInput.split(',').map(s => s.trim()).filter(Boolean)
|
|
15
|
+
: ALL_TRANSFORMERS.map(t => t.name);
|
|
16
|
+
const defaultScope = defaults.scope;
|
|
17
|
+
const scopeInput = await ask(` Scope? [global/local] (default: ${defaultScope}) `);
|
|
18
|
+
const typed = scopeInput.trim().toLowerCase();
|
|
19
|
+
const scope = typed === 'local' ? 'local' : typed === 'global' ? 'global' : defaultScope;
|
|
20
|
+
rl.close();
|
|
21
|
+
return { agents, scope };
|
|
22
|
+
}
|