memtrace-skills 0.7.10

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 (97) hide show
  1. package/dist/commands/doctor.d.ts +16 -0
  2. package/dist/commands/doctor.js +199 -0
  3. package/dist/commands/enterprise-install.d.ts +7 -0
  4. package/dist/commands/enterprise-install.js +129 -0
  5. package/dist/commands/install.d.ts +9 -0
  6. package/dist/commands/install.js +104 -0
  7. package/dist/commands/picker.d.ts +6 -0
  8. package/dist/commands/picker.js +22 -0
  9. package/dist/commands/rail-install.d.ts +7 -0
  10. package/dist/commands/rail-install.js +37 -0
  11. package/dist/fs-safe.d.ts +21 -0
  12. package/dist/fs-safe.js +35 -0
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.js +87 -0
  15. package/dist/rail-install.d.ts +20 -0
  16. package/dist/rail-install.js +183 -0
  17. package/dist/skills.d.ts +17 -0
  18. package/dist/skills.js +64 -0
  19. package/dist/transformers/claude.d.ts +71 -0
  20. package/dist/transformers/claude.js +702 -0
  21. package/dist/transformers/codex.d.ts +8 -0
  22. package/dist/transformers/codex.js +294 -0
  23. package/dist/transformers/cursor.d.ts +7 -0
  24. package/dist/transformers/cursor.js +124 -0
  25. package/dist/transformers/gemini.d.ts +3 -0
  26. package/dist/transformers/gemini.js +78 -0
  27. package/dist/transformers/hermes.d.ts +5 -0
  28. package/dist/transformers/hermes.js +136 -0
  29. package/dist/transformers/index.d.ts +14 -0
  30. package/dist/transformers/index.js +24 -0
  31. package/dist/transformers/kiro.d.ts +2 -0
  32. package/dist/transformers/kiro.js +69 -0
  33. package/dist/transformers/opencode.d.ts +2 -0
  34. package/dist/transformers/opencode.js +77 -0
  35. package/dist/transformers/rail-hooks.d.ts +56 -0
  36. package/dist/transformers/rail-hooks.js +303 -0
  37. package/dist/transformers/shared.d.ts +18 -0
  38. package/dist/transformers/shared.js +129 -0
  39. package/dist/transformers/types.d.ts +40 -0
  40. package/dist/transformers/types.js +1 -0
  41. package/dist/transformers/vscode.d.ts +3 -0
  42. package/dist/transformers/vscode.js +53 -0
  43. package/dist/transformers/windsurf.d.ts +3 -0
  44. package/dist/transformers/windsurf.js +43 -0
  45. package/dist/utils.d.ts +5 -0
  46. package/dist/utils.js +22 -0
  47. package/package.json +50 -0
  48. package/plugins/memtrace-skills/.claude-plugin/plugin.json +23 -0
  49. package/plugins/memtrace-skills/references/mcp-parameters.md +302 -0
  50. package/plugins/memtrace-skills/skills/memtrace-api-topology/SKILL.md +58 -0
  51. package/plugins/memtrace-skills/skills/memtrace-change-impact-analysis/SKILL.md +75 -0
  52. package/plugins/memtrace-skills/skills/memtrace-cochange/SKILL.md +71 -0
  53. package/plugins/memtrace-skills/skills/memtrace-code-review/SKILL.md +41 -0
  54. package/plugins/memtrace-skills/skills/memtrace-codebase-exploration/SKILL.md +94 -0
  55. package/plugins/memtrace-skills/skills/memtrace-continuous-memory/SKILL.md +96 -0
  56. package/plugins/memtrace-skills/skills/memtrace-episode-replay/SKILL.md +94 -0
  57. package/plugins/memtrace-skills/skills/memtrace-evolution/SKILL.md +128 -0
  58. package/plugins/memtrace-skills/skills/memtrace-first/SKILL.md +194 -0
  59. package/plugins/memtrace-skills/skills/memtrace-fleet-coordination/SKILL.md +80 -0
  60. package/plugins/memtrace-skills/skills/memtrace-fleet-first/SKILL.md +125 -0
  61. package/plugins/memtrace-skills/skills/memtrace-fleet-publish-intent/SKILL.md +48 -0
  62. package/plugins/memtrace-skills/skills/memtrace-fleet-record-episode/SKILL.md +44 -0
  63. package/plugins/memtrace-skills/skills/memtrace-fleet-resolve/SKILL.md +54 -0
  64. package/plugins/memtrace-skills/skills/memtrace-graph/SKILL.md +67 -0
  65. package/plugins/memtrace-skills/skills/memtrace-impact/SKILL.md +58 -0
  66. package/plugins/memtrace-skills/skills/memtrace-incident-investigation/SKILL.md +112 -0
  67. package/plugins/memtrace-skills/skills/memtrace-index/SKILL.md +65 -0
  68. package/plugins/memtrace-skills/skills/memtrace-quality/SKILL.md +63 -0
  69. package/plugins/memtrace-skills/skills/memtrace-refactoring-guide/SKILL.md +103 -0
  70. package/plugins/memtrace-skills/skills/memtrace-relationships/SKILL.md +67 -0
  71. package/plugins/memtrace-skills/skills/memtrace-search/SKILL.md +93 -0
  72. package/plugins/memtrace-skills/skills/memtrace-session-continuity/SKILL.md +93 -0
  73. package/plugins/memtrace-skills/skills/memtrace-style-fingerprint/SKILL.md +105 -0
  74. package/skills/commands/memtrace-api-topology.md +65 -0
  75. package/skills/commands/memtrace-cochange.md +76 -0
  76. package/skills/commands/memtrace-evolution.md +135 -0
  77. package/skills/commands/memtrace-fleet-publish-intent.md +51 -0
  78. package/skills/commands/memtrace-fleet-record-episode.md +48 -0
  79. package/skills/commands/memtrace-fleet-resolve.md +59 -0
  80. package/skills/commands/memtrace-graph.md +75 -0
  81. package/skills/commands/memtrace-impact.md +64 -0
  82. package/skills/commands/memtrace-index.md +71 -0
  83. package/skills/commands/memtrace-quality.md +69 -0
  84. package/skills/commands/memtrace-relationships.md +73 -0
  85. package/skills/commands/memtrace-search.md +93 -0
  86. package/skills/workflows/memtrace-change-impact-analysis.md +85 -0
  87. package/skills/workflows/memtrace-code-review.md +48 -0
  88. package/skills/workflows/memtrace-codebase-exploration.md +108 -0
  89. package/skills/workflows/memtrace-continuous-memory.md +104 -0
  90. package/skills/workflows/memtrace-episode-replay.md +100 -0
  91. package/skills/workflows/memtrace-first.md +194 -0
  92. package/skills/workflows/memtrace-fleet-coordination.md +87 -0
  93. package/skills/workflows/memtrace-fleet-first.md +132 -0
  94. package/skills/workflows/memtrace-incident-investigation.md +125 -0
  95. package/skills/workflows/memtrace-refactoring-guide.md +116 -0
  96. package/skills/workflows/memtrace-session-continuity.md +98 -0
  97. package/skills/workflows/memtrace-style-fingerprint.md +111 -0
@@ -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,199 @@
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
+ import { ALL_TRANSFORMERS } from '../transformers/index.js';
7
+ async function checkBinary() {
8
+ if (!(await commandExists('memtrace')))
9
+ return { binaryOk: false };
10
+ try {
11
+ const which = process.platform === 'win32' ? 'where memtrace' : 'which memtrace';
12
+ const binaryPath = (await execCommand(which, { timeoutMs: 3_000 })).split('\n')[0].trim();
13
+ let binaryVersion;
14
+ try {
15
+ binaryVersion = await execCommand('memtrace --version', { timeoutMs: 3_000 });
16
+ }
17
+ catch { /* optional */ }
18
+ return { binaryOk: true, binaryPath, binaryVersion };
19
+ }
20
+ catch {
21
+ return { binaryOk: false };
22
+ }
23
+ }
24
+ function countMemtraceSkills(dir) {
25
+ if (!fs.existsSync(dir))
26
+ return 0;
27
+ return fs.readdirSync(dir)
28
+ .filter(e => e.startsWith('memtrace-'))
29
+ .filter(e => fs.existsSync(path.join(dir, e, 'SKILL.md')))
30
+ .length;
31
+ }
32
+ function countMemtraceMarkdown(dir) {
33
+ if (!fs.existsSync(dir))
34
+ return 0;
35
+ return fs.readdirSync(dir)
36
+ .filter(e => e.startsWith('memtrace-') && e.endsWith('.md'))
37
+ .length;
38
+ }
39
+ function mcpHasMemtrace(file) {
40
+ const { value } = safeReadJson(file);
41
+ if (!value)
42
+ return false;
43
+ return Boolean(value.mcpServers && value.mcpServers['memtrace']);
44
+ }
45
+ function vscodeMcpHasMemtrace(file) {
46
+ const { value } = safeReadJson(file);
47
+ if (!value)
48
+ return false;
49
+ return Boolean(value.servers && value.servers['memtrace']);
50
+ }
51
+ function opencodeMcpHasMemtrace(file) {
52
+ const { value } = safeReadJson(file);
53
+ if (!value)
54
+ return false;
55
+ return Boolean(value.mcp && value.mcp['memtrace']);
56
+ }
57
+ function codexMcpHasMemtrace(file) {
58
+ if (!fs.existsSync(file))
59
+ return false;
60
+ const raw = fs.readFileSync(file, 'utf-8');
61
+ return /^\s*\[mcp_servers\.(?:"memtrace"|memtrace)\]\s*$/m.test(raw);
62
+ }
63
+ function hermesMcpHasMemtrace(file) {
64
+ if (!fs.existsSync(file))
65
+ return false;
66
+ const raw = fs.readFileSync(file, 'utf-8');
67
+ return /^mcp_servers:\s*$(?:[\s\S]*?)^\s{2}memtrace:\s*$/m.test(raw);
68
+ }
69
+ function opencodeConfigDir() {
70
+ const xdg = process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), '.config');
71
+ return path.join(xdg, 'opencode');
72
+ }
73
+ function vscodeUserMcpPath() {
74
+ if (process.platform === 'win32') {
75
+ const appData = process.env.APPDATA ?? path.join(os.homedir(), 'AppData', 'Roaming');
76
+ return path.join(appData, 'Code', 'User', 'mcp.json');
77
+ }
78
+ if (process.platform === 'darwin') {
79
+ return path.join(os.homedir(), 'Library', 'Application Support', 'Code', 'User', 'mcp.json');
80
+ }
81
+ const xdg = process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), '.config');
82
+ return path.join(xdg, 'Code', 'User', 'mcp.json');
83
+ }
84
+ function checkAgent(agent) {
85
+ if (agent === 'claude') {
86
+ const skillsDir = path.join(os.homedir(), '.claude', 'skills');
87
+ const mcpConfigPath = path.join(os.homedir(), '.claude', 'settings.json');
88
+ return {
89
+ agent,
90
+ skillsFound: countMemtraceSkills(skillsDir),
91
+ skillsDir,
92
+ mcpRegistered: mcpHasMemtrace(mcpConfigPath),
93
+ mcpConfigPath,
94
+ };
95
+ }
96
+ if (agent === 'codex') {
97
+ const skillsDir = path.join(os.homedir(), '.agents', 'skills');
98
+ const mcpConfigPath = path.join(os.homedir(), '.codex', 'config.toml');
99
+ return {
100
+ agent,
101
+ skillsFound: countMemtraceSkills(skillsDir),
102
+ skillsDir,
103
+ mcpRegistered: codexMcpHasMemtrace(mcpConfigPath),
104
+ mcpConfigPath,
105
+ };
106
+ }
107
+ if (agent === 'windsurf') {
108
+ const skillsDir = path.join(os.homedir(), '.codeium', 'windsurf', 'skills');
109
+ const mcpConfigPath = path.join(os.homedir(), '.codeium', 'windsurf', 'mcp_config.json');
110
+ return {
111
+ agent,
112
+ skillsFound: countMemtraceSkills(skillsDir),
113
+ skillsDir,
114
+ mcpRegistered: mcpHasMemtrace(mcpConfigPath),
115
+ mcpConfigPath,
116
+ };
117
+ }
118
+ if (agent === 'vscode') {
119
+ const skillsDir = path.join(os.homedir(), '.copilot', 'skills');
120
+ const mcpConfigPath = vscodeUserMcpPath();
121
+ return {
122
+ agent,
123
+ skillsFound: countMemtraceSkills(skillsDir),
124
+ skillsDir,
125
+ mcpRegistered: vscodeMcpHasMemtrace(mcpConfigPath),
126
+ mcpConfigPath,
127
+ };
128
+ }
129
+ if (agent === 'hermes') {
130
+ const skillsDir = path.join(os.homedir(), '.hermes', 'skills');
131
+ const mcpConfigPath = path.join(os.homedir(), '.hermes', 'config.yaml');
132
+ return {
133
+ agent,
134
+ skillsFound: countMemtraceSkills(skillsDir),
135
+ skillsDir,
136
+ mcpRegistered: hermesMcpHasMemtrace(mcpConfigPath),
137
+ mcpConfigPath,
138
+ };
139
+ }
140
+ if (agent === 'opencode') {
141
+ const skillsDir = path.join(opencodeConfigDir(), 'skills');
142
+ const mcpConfigPath = path.join(opencodeConfigDir(), 'opencode.json');
143
+ return {
144
+ agent,
145
+ skillsFound: countMemtraceSkills(skillsDir),
146
+ skillsDir,
147
+ mcpRegistered: opencodeMcpHasMemtrace(mcpConfigPath),
148
+ mcpConfigPath,
149
+ };
150
+ }
151
+ if (agent === 'kiro') {
152
+ const skillsDir = path.join(os.homedir(), '.kiro', 'steering');
153
+ const mcpConfigPath = path.join(os.homedir(), '.kiro', 'settings', 'mcp.json');
154
+ return {
155
+ agent,
156
+ skillsFound: countMemtraceMarkdown(skillsDir),
157
+ skillsDir,
158
+ mcpRegistered: mcpHasMemtrace(mcpConfigPath),
159
+ mcpConfigPath,
160
+ };
161
+ }
162
+ const skillsDir = path.join(os.homedir(), '.cursor', 'skills');
163
+ const mcpConfigPath = path.join(os.homedir(), '.cursor', 'mcp.json');
164
+ return {
165
+ agent,
166
+ skillsFound: countMemtraceSkills(skillsDir),
167
+ skillsDir,
168
+ mcpRegistered: mcpHasMemtrace(mcpConfigPath),
169
+ mcpConfigPath,
170
+ };
171
+ }
172
+ export async function runDoctorChecks() {
173
+ const binary = await checkBinary();
174
+ return {
175
+ ...binary,
176
+ agents: ALL_TRANSFORMERS.map(t => checkAgent(t.name)),
177
+ };
178
+ }
179
+ export function formatReport(r) {
180
+ const lines = [];
181
+ const check = (ok) => (ok ? '✓' : '✗');
182
+ lines.push('memtrace doctor');
183
+ lines.push('───────────────');
184
+ lines.push(`${check(r.binaryOk)} memtrace binary${r.binaryPath ? ' ' + r.binaryPath : ''}`);
185
+ if (r.binaryVersion)
186
+ lines.push(` version: ${r.binaryVersion}`);
187
+ for (const a of r.agents) {
188
+ lines.push(`${check(a.skillsFound > 0)} ${a.agent} skills installed ${a.skillsFound} in ${a.skillsDir}`);
189
+ lines.push(`${check(a.mcpRegistered)} ${a.agent} MCP registered ${a.mcpConfigPath}`);
190
+ }
191
+ return lines.join('\n');
192
+ }
193
+ export async function runDoctor() {
194
+ const report = await runDoctorChecks();
195
+ console.log(formatReport(report));
196
+ const healthy = report.binaryOk
197
+ && report.agents.every(a => a.skillsFound > 0 && a.mcpRegistered);
198
+ return healthy ? 0 : 1;
199
+ }
@@ -0,0 +1,7 @@
1
+ export interface EnterpriseInstallOptions {
2
+ seedPath: string;
3
+ scope?: 'global' | 'local';
4
+ binary?: string;
5
+ yes?: boolean;
6
+ }
7
+ export declare function runEnterpriseInstall(opts: EnterpriseInstallOptions): Promise<void>;
@@ -0,0 +1,129 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import { safeReadJson, writeJsonAtomic } from '../fs-safe.js';
5
+ import { resolveMemtraceBinary } from './install.js';
6
+ import { registerCursorMcpAt } from '../transformers/cursor.js';
7
+ function loadSeed(seedPath) {
8
+ const raw = fs.readFileSync(seedPath, 'utf8');
9
+ const parsed = JSON.parse(raw);
10
+ if (!parsed.memdb?.database) {
11
+ throw new Error('seed JSON must include memdb.database');
12
+ }
13
+ return parsed;
14
+ }
15
+ function loadWriterToken(seedDir) {
16
+ const tokPath = path.join(seedDir, 'tokens.plaintext.json');
17
+ if (!fs.existsSync(tokPath)) {
18
+ throw new Error(`Missing ${tokPath} — generate bundle via enterprise admin first`);
19
+ }
20
+ const tokens = JSON.parse(fs.readFileSync(tokPath, 'utf8'));
21
+ if (!tokens.writer)
22
+ throw new Error('tokens.plaintext.json must include writer token');
23
+ return tokens.writer;
24
+ }
25
+ function resolveLicenseKey(seed, seedDir) {
26
+ if (seed.license?.key)
27
+ return seed.license.key;
28
+ const seedJson = path.join(seedDir, 'seed.json');
29
+ if (fs.existsSync(seedJson)) {
30
+ const full = JSON.parse(fs.readFileSync(seedJson, 'utf8'));
31
+ if (full.license?.key)
32
+ return full.license.key;
33
+ }
34
+ const env = process.env.MEMTRACE_LICENSE_KEY;
35
+ if (env)
36
+ return env;
37
+ throw new Error('License key not found in seed; set MEMTRACE_LICENSE_KEY or include license.key');
38
+ }
39
+ const DEFAULT_AUTH_URL = 'https://www.memtrace.io';
40
+ async function verifySubscriptionWithApi(organizationId, licenseKey) {
41
+ const base = (process.env.MEMTRACE_AUTH_URL ?? DEFAULT_AUTH_URL).replace(/\/$/, '');
42
+ const resp = await fetch(`${base}/api/enterprise/install/verify`, {
43
+ method: 'POST',
44
+ headers: { 'Content-Type': 'application/json' },
45
+ body: JSON.stringify({ organizationId, licenseKey }),
46
+ signal: AbortSignal.timeout(10_000),
47
+ });
48
+ if (!resp.ok) {
49
+ const payload = await resp.json().catch(() => ({}));
50
+ throw new Error(payload.error ?? `Enterprise install blocked (${resp.status})`);
51
+ }
52
+ const payload = await resp.json();
53
+ if (!payload.ok) {
54
+ throw new Error('Enterprise subscription verification failed');
55
+ }
56
+ }
57
+ async function validateRemoteEndpoint(endpoint, token) {
58
+ const url = endpoint.replace(/\/$/, '');
59
+ // gRPC health is not available from Node without deps; ping via HTTP metrics if exposed.
60
+ const metricsUrl = url.replace(/:50051$/, ':9090');
61
+ try {
62
+ const resp = await fetch(`${metricsUrl}/metrics`, {
63
+ headers: token ? { Authorization: `Bearer ${token}` } : {},
64
+ signal: AbortSignal.timeout(5000),
65
+ });
66
+ if (!resp.ok) {
67
+ console.warn(` ⚠ MemDB metrics at ${metricsUrl} returned ${resp.status} (gRPC may still work)`);
68
+ }
69
+ else {
70
+ console.log(` ✓ MemDB metrics reachable at ${metricsUrl}`);
71
+ }
72
+ }
73
+ catch (e) {
74
+ console.warn(` ⚠ Could not reach MemDB at ${endpoint}: ${e.message}`);
75
+ console.warn(' Continuing — verify VPN/firewall if remote mode fails at runtime.');
76
+ }
77
+ }
78
+ function mcpPath(scope) {
79
+ const base = scope === 'global' ? os.homedir() : process.cwd();
80
+ return path.join(base, '.cursor', 'mcp.json');
81
+ }
82
+ export async function runEnterpriseInstall(opts) {
83
+ const seedPath = path.resolve(opts.seedPath);
84
+ const seedDir = path.dirname(seedPath);
85
+ const seed = loadSeed(seedPath);
86
+ const scope = opts.scope ?? 'global';
87
+ const endpoint = seed.memdb.endpoint ?? process.env.MEMTRACE_MEMDB_ENDPOINT;
88
+ if (!endpoint) {
89
+ throw new Error('memdb.endpoint required in seed or MEMTRACE_MEMDB_ENDPOINT env');
90
+ }
91
+ const writerToken = loadWriterToken(seedDir);
92
+ const licenseKey = resolveLicenseKey(seed, seedDir);
93
+ const binary = opts.binary ?? (await resolveMemtraceBinary()) ?? 'memtrace';
94
+ console.log('\n Memtrace Enterprise Installer\n');
95
+ console.log(` Org: ${seed.orgId}`);
96
+ console.log(` MemDB: ${endpoint}`);
97
+ console.log(` Database: ${seed.memdb.database}`);
98
+ console.log(` Scope: ${scope}\n`);
99
+ console.log(' Verifying enterprise subscription…');
100
+ await verifySubscriptionWithApi(seed.orgId, licenseKey);
101
+ console.log(' ✓ Subscription active');
102
+ await validateRemoteEndpoint(endpoint, writerToken);
103
+ const mcpFile = mcpPath(scope);
104
+ const { value, corrupted, backupPath } = safeReadJson(mcpFile);
105
+ if (corrupted) {
106
+ console.warn(` ⚠ ${mcpFile} malformed; backed up to ${backupPath}`);
107
+ }
108
+ const cfg = (value ?? {});
109
+ cfg.mcpServers = cfg.mcpServers ?? {};
110
+ cfg.mcpServers.memtrace = {
111
+ command: binary,
112
+ args: ['mcp'],
113
+ env: {
114
+ MEMTRACE_LICENSE_KEY: licenseKey,
115
+ MEMTRACE_MEMDB_MODE: 'external',
116
+ MEMTRACE_MEMDB_ENDPOINT: endpoint,
117
+ MEMTRACE_MEMDB_AUTH_TOKEN: writerToken,
118
+ MEMTRACE_MEMDB_DB: seed.memdb.database,
119
+ MEMTRACE_WORKSPACE_ROOT: '${workspaceFolder}',
120
+ },
121
+ };
122
+ writeJsonAtomic(mcpFile, cfg);
123
+ console.log(` ✓ Wrote enterprise MCP config → ${mcpFile}`);
124
+ // Also register via cursor helper when file is the standard cursor path
125
+ if (mcpFile.endsWith('.cursor/mcp.json')) {
126
+ registerCursorMcpAt(mcpFile, binary);
127
+ }
128
+ console.log('\n Done. Restart Cursor and run `memtrace auth login` if needed.\n');
129
+ }
@@ -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
+ }
@@ -0,0 +1,7 @@
1
+ export interface RailInstallOptions {
2
+ /** Explicit memtrace binary (from `memtrace rail enable`); wins over PATH lookup. */
3
+ binary?: string;
4
+ scope?: 'global' | 'local';
5
+ }
6
+ export declare function runInstallRail(options?: RailInstallOptions): Promise<void>;
7
+ export declare function runUninstallRail(options?: RailInstallOptions): Promise<void>;
@@ -0,0 +1,37 @@
1
+ import { resolveMemtraceBinary } from './install.js';
2
+ import { installRailHooks, printRailInstallSummary, uninstallRailHooks, } from '../rail-install.js';
3
+ export async function runInstallRail(options = {}) {
4
+ const memtraceBin = options.binary?.trim() ||
5
+ (await resolveMemtraceBinary()) ||
6
+ '';
7
+ if (!memtraceBin) {
8
+ console.error(' Error: memtrace binary not found. Pass --binary or ensure memtrace is on PATH.\n');
9
+ process.exit(1);
10
+ }
11
+ const ctx = {
12
+ scope: options.scope ?? 'global',
13
+ cwd: process.cwd(),
14
+ memtraceBinary: memtraceBin,
15
+ skipMcp: true,
16
+ };
17
+ const results = installRailHooks(ctx);
18
+ printRailInstallSummary(results, 'installed');
19
+ const wired = results.filter((r) => r.registered).map((r) => r.host);
20
+ if (wired.length > 0) {
21
+ console.log(' Next steps:');
22
+ console.log(' 1. memtrace start (daemon for find_code)');
23
+ console.log(' 2. Reload your IDE (Cursor: reload window; Claude: new session)');
24
+ console.log(' 3. Shell-based search (Cursor native Grep is not hooked)\n');
25
+ }
26
+ }
27
+ export async function runUninstallRail(options = {}) {
28
+ const ctx = {
29
+ scope: options.scope ?? 'global',
30
+ cwd: process.cwd(),
31
+ memtraceBinary: options.binary?.trim() ?? '',
32
+ skipMcp: true,
33
+ };
34
+ const results = uninstallRailHooks(ctx);
35
+ printRailInstallSummary(results, 'removed');
36
+ console.log('');
37
+ }
@@ -0,0 +1,21 @@
1
+ export interface SafeReadResult<T> {
2
+ /** Parsed value, or null if file missing or corrupt. */
3
+ value: T | null;
4
+ /** True if the file existed but failed to parse. */
5
+ corrupted: boolean;
6
+ /** Path to the backup copy, if we made one. */
7
+ backupPath?: string;
8
+ }
9
+ /**
10
+ * Read and JSON-parse a file without ever silently losing data.
11
+ * On parse failure, copies the file to <path>.corrupt-<ISO-timestamp>
12
+ * and returns { value: null, corrupted: true, backupPath }.
13
+ * Caller is responsible for deciding whether to proceed with writes.
14
+ */
15
+ export declare function safeReadJson<T = unknown>(filePath: string): SafeReadResult<T>;
16
+ /**
17
+ * Write JSON atomically: write to a sibling .tmp file, then rename.
18
+ * Rename is atomic on POSIX and good enough on Windows for config files.
19
+ * Creates parent directories as needed.
20
+ */
21
+ export declare function writeJsonAtomic(filePath: string, data: unknown): void;
@@ -0,0 +1,35 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ /**
4
+ * Read and JSON-parse a file without ever silently losing data.
5
+ * On parse failure, copies the file to <path>.corrupt-<ISO-timestamp>
6
+ * and returns { value: null, corrupted: true, backupPath }.
7
+ * Caller is responsible for deciding whether to proceed with writes.
8
+ */
9
+ export function safeReadJson(filePath) {
10
+ if (!fs.existsSync(filePath)) {
11
+ return { value: null, corrupted: false };
12
+ }
13
+ const raw = fs.readFileSync(filePath, 'utf-8');
14
+ try {
15
+ return { value: JSON.parse(raw), corrupted: false };
16
+ }
17
+ catch {
18
+ const iso = new Date().toISOString().replace(/[:.]/g, '-');
19
+ const backupPath = `${filePath}.corrupt-${iso}`;
20
+ fs.copyFileSync(filePath, backupPath);
21
+ return { value: null, corrupted: true, backupPath };
22
+ }
23
+ }
24
+ /**
25
+ * Write JSON atomically: write to a sibling .tmp file, then rename.
26
+ * Rename is atomic on POSIX and good enough on Windows for config files.
27
+ * Creates parent directories as needed.
28
+ */
29
+ export function writeJsonAtomic(filePath, data) {
30
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
31
+ const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
32
+ const payload = JSON.stringify(data, null, 2) + '\n';
33
+ fs.writeFileSync(tmpPath, payload);
34
+ fs.renameSync(tmpPath, filePath);
35
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};