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.
Files changed (54) hide show
  1. package/README.md +2 -2
  2. package/bin/memtrace.js +102 -14
  3. package/install.js +21 -179
  4. package/installer/dist/commands/doctor.d.ts +16 -0
  5. package/installer/dist/commands/doctor.js +86 -0
  6. package/installer/dist/commands/install.d.ts +9 -0
  7. package/installer/dist/commands/install.js +104 -0
  8. package/installer/dist/commands/picker.d.ts +6 -0
  9. package/installer/dist/commands/picker.js +22 -0
  10. package/installer/dist/fs-safe.d.ts +21 -0
  11. package/installer/dist/fs-safe.js +35 -0
  12. package/installer/dist/index.d.ts +2 -0
  13. package/installer/dist/index.js +52 -0
  14. package/installer/dist/skills.d.ts +17 -0
  15. package/installer/dist/skills.js +64 -0
  16. package/installer/dist/transformers/claude.d.ts +41 -0
  17. package/installer/dist/transformers/claude.js +400 -0
  18. package/installer/dist/transformers/cursor.d.ts +7 -0
  19. package/installer/dist/transformers/cursor.js +84 -0
  20. package/installer/dist/transformers/index.d.ts +7 -0
  21. package/installer/dist/transformers/index.js +7 -0
  22. package/installer/dist/transformers/types.d.ts +39 -0
  23. package/installer/dist/transformers/types.js +1 -0
  24. package/installer/dist/utils.d.ts +5 -0
  25. package/installer/dist/utils.js +22 -0
  26. package/installer/package.json +49 -0
  27. package/installer/skills/commands/memtrace-api-topology.md +65 -0
  28. package/installer/skills/commands/memtrace-cochange.md +76 -0
  29. package/installer/skills/commands/memtrace-evolution.md +135 -0
  30. package/installer/skills/commands/memtrace-graph.md +117 -0
  31. package/installer/skills/commands/memtrace-impact.md +64 -0
  32. package/installer/skills/commands/memtrace-index.md +66 -0
  33. package/installer/skills/commands/memtrace-quality.md +69 -0
  34. package/installer/skills/commands/memtrace-relationships.md +73 -0
  35. package/installer/skills/commands/memtrace-search.md +67 -0
  36. package/installer/skills/workflows/memtrace-change-impact-analysis.md +85 -0
  37. package/installer/skills/workflows/memtrace-codebase-exploration.md +108 -0
  38. package/installer/skills/workflows/memtrace-episode-replay.md +100 -0
  39. package/installer/skills/workflows/memtrace-first.md +120 -0
  40. package/installer/skills/workflows/memtrace-incident-investigation.md +125 -0
  41. package/installer/skills/workflows/memtrace-refactoring-guide.md +116 -0
  42. package/installer/skills/workflows/memtrace-session-continuity.md +98 -0
  43. package/package.json +10 -5
  44. package/skills/commands/memtrace-api-topology.md +3 -0
  45. package/skills/commands/memtrace-cochange.md +3 -0
  46. package/skills/commands/memtrace-evolution.md +3 -0
  47. package/skills/commands/memtrace-graph.md +54 -4
  48. package/skills/commands/memtrace-impact.md +3 -0
  49. package/skills/commands/memtrace-index.md +3 -0
  50. package/skills/commands/memtrace-quality.md +3 -0
  51. package/skills/commands/memtrace-relationships.md +3 -0
  52. package/skills/commands/memtrace-search.md +18 -13
  53. package/skills/workflows/memtrace-first.md +12 -0
  54. 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.dev"><img src="https://img.shields.io/badge/docs-memtrace.dev-00D4B8?style=flat-square" alt="docs" /></a>
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.dev">Documentation</a> · <a href="https://www.npmjs.com/package/memtrace">npm</a> · <a href="https://github.com/syncable-dev/memtrace-public/issues">Issues</a>
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 { spawnSync } = require("child_process");
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
- // ── Delegate everything else to the Rust binary ─────────────────────────────
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
- const result = spawnSync(binaryPath, args, {
55
- stdio: "inherit",
56
- env: process.env,
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
- if (result.error) {
60
- console.error(`Failed to run memtrace: ${result.error.message}`);
61
- process.exit(1);
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
- process.exit(result.status ?? 0);
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 { execSync } = require("child_process");
7
+ const { spawnSync } = require("child_process");
8
8
 
9
- // ── Constants ─────────────────────────────────────────────────────────────────
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. Install skills to ~/.claude/skills/
211
- try {
212
- installSkills();
213
- } catch (e) {
214
- console.warn(`memtrace: skill installation failed: ${e.message}`);
215
- }
216
-
217
- // 3. Try Claude CLI first, fall back to manual settings
218
- try {
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
- // 5. Persist uninstall script to ~/.memtrace/ so it survives `npm uninstall -g`
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
- const src = path.join(__dirname, "uninstall.js");
241
- const dest = path.join(memtraceDir, "uninstall.js");
242
- fs.copyFileSync(src, dest);
243
- console.log(`memtrace: persisted uninstall script to ${dest}`);
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,6 @@
1
+ export interface PickerResult {
2
+ agents: string[];
3
+ scope: 'global' | 'local';
4
+ }
5
+ export declare function canPrompt(): boolean;
6
+ export declare function promptForChoices(defaults: PickerResult): Promise<PickerResult>;
@@ -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
+ }