memtrace 0.4.2 → 0.4.3
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.
|
@@ -23,18 +23,54 @@ const MARKETPLACE_SETTING_CONTAINERS = [
|
|
|
23
23
|
function isRecord(value) {
|
|
24
24
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
25
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Read the existing env block (if any) for the memtrace MCP entry in a Claude
|
|
28
|
+
* settings/mcp.json file. Returns an empty object if the file or entry is
|
|
29
|
+
* missing/malformed. Used to deep-merge user-set env keys
|
|
30
|
+
* (e.g. MEMTRACE_LICENSE_KEY) so they survive `memtrace install`
|
|
31
|
+
* re-registration. Mirrors the codex transformer's env-preservation contract.
|
|
32
|
+
*/
|
|
33
|
+
function readExistingMemtraceEnv(filePath, serverKey = 'mcpServers') {
|
|
34
|
+
if (!fs.existsSync(filePath))
|
|
35
|
+
return {};
|
|
36
|
+
const { value, corrupted } = safeReadJson(filePath);
|
|
37
|
+
if (corrupted || !value)
|
|
38
|
+
return {};
|
|
39
|
+
const servers = value[serverKey];
|
|
40
|
+
if (!isRecord(servers))
|
|
41
|
+
return {};
|
|
42
|
+
const entry = servers['memtrace'];
|
|
43
|
+
if (!isRecord(entry))
|
|
44
|
+
return {};
|
|
45
|
+
const env = entry.env;
|
|
46
|
+
if (!isRecord(env))
|
|
47
|
+
return {};
|
|
48
|
+
const out = {};
|
|
49
|
+
for (const [k, v] of Object.entries(env)) {
|
|
50
|
+
if (typeof v === 'string')
|
|
51
|
+
out[k] = v;
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
26
55
|
/**
|
|
27
56
|
* Try to register the memtrace MCP server via `claude mcp add-json`.
|
|
28
57
|
* Returns true on success, false on timeout/error/missing CLI.
|
|
29
58
|
* 5-second timeout — we fall back to direct JSON merge fast.
|
|
59
|
+
*
|
|
60
|
+
* The env block deep-merges any user-set env keys discovered in the existing
|
|
61
|
+
* settings.json on top of MEMTRACE_MCP_ENV defaults so that a re-install never
|
|
62
|
+
* stomps user-added keys like MEMTRACE_LICENSE_KEY.
|
|
30
63
|
*/
|
|
31
64
|
async function tryMcpAddJson(memtraceBinary) {
|
|
32
65
|
if (!(await commandExists('claude')))
|
|
33
66
|
return false;
|
|
67
|
+
const settingsFile = path.join(os.homedir(), '.claude', 'settings.json');
|
|
68
|
+
const existingEnv = readExistingMemtraceEnv(settingsFile);
|
|
69
|
+
const mergedEnv = { ...existingEnv, ...MEMTRACE_MCP_ENV };
|
|
34
70
|
const config = JSON.stringify({
|
|
35
71
|
command: memtraceBinary,
|
|
36
72
|
args: ['mcp'],
|
|
37
|
-
env:
|
|
73
|
+
env: mergedEnv,
|
|
38
74
|
});
|
|
39
75
|
// Shell-quote the JSON. Use single quotes; escape any embedded single quote.
|
|
40
76
|
const escaped = config.replace(/'/g, `'\\''`);
|
|
@@ -164,10 +200,18 @@ export function registerMcpInSettingsAt(settingsPath, memtraceBinary) {
|
|
|
164
200
|
}
|
|
165
201
|
const settings = (value ?? {});
|
|
166
202
|
settings.mcpServers = settings.mcpServers ?? {};
|
|
203
|
+
// Deep-merge env so user-set keys (e.g. MEMTRACE_LICENSE_KEY) survive
|
|
204
|
+
// re-registration. memtrace-owned keys (MEMTRACE_MCP_ENV) override on
|
|
205
|
+
// conflict so installer-managed values stay authoritative.
|
|
206
|
+
const existing = settings.mcpServers['memtrace'];
|
|
207
|
+
const existingEnv = (isRecord(existing) && isRecord(existing.env))
|
|
208
|
+
? existing.env
|
|
209
|
+
: {};
|
|
210
|
+
const mergedEnv = { ...existingEnv, ...MEMTRACE_MCP_ENV };
|
|
167
211
|
settings.mcpServers['memtrace'] = {
|
|
168
212
|
command: memtraceBinary,
|
|
169
213
|
args: ['mcp'],
|
|
170
|
-
env:
|
|
214
|
+
env: mergedEnv,
|
|
171
215
|
};
|
|
172
216
|
writeJsonAtomic(settingsPath, settings);
|
|
173
217
|
return { registered: true };
|
|
@@ -416,10 +460,16 @@ function writeClaudeLocalMcp(mcpPath, binary) {
|
|
|
416
460
|
}
|
|
417
461
|
const cfg = (value ?? {});
|
|
418
462
|
cfg.mcpServers = cfg.mcpServers ?? {};
|
|
463
|
+
// Deep-merge env: preserve user-set keys, memtrace defaults win on conflict.
|
|
464
|
+
const existing = cfg.mcpServers['memtrace'];
|
|
465
|
+
const existingEnv = (isRecord(existing) && isRecord(existing.env))
|
|
466
|
+
? existing.env
|
|
467
|
+
: {};
|
|
468
|
+
const mergedEnv = { ...existingEnv, ...MEMTRACE_MCP_ENV };
|
|
419
469
|
cfg.mcpServers['memtrace'] = {
|
|
420
470
|
command: binary,
|
|
421
471
|
args: ['mcp'],
|
|
422
|
-
env:
|
|
472
|
+
env: mergedEnv,
|
|
423
473
|
};
|
|
424
474
|
writeJsonAtomic(mcpPath, cfg);
|
|
425
475
|
return true;
|
|
@@ -38,6 +38,20 @@ function isMemtraceMcpTable(tableName) {
|
|
|
38
38
|
|| tableName.startsWith(`mcp_servers.${MCP_SERVER_NAME}.`)
|
|
39
39
|
|| tableName.startsWith(`mcp_servers."${MCP_SERVER_NAME}".`);
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Tables we want stripped on re-registration.
|
|
43
|
+
*
|
|
44
|
+
* PRESERVE [mcp_servers.memtrace.env] so user-set env vars
|
|
45
|
+
* (MEMTRACE_LICENSE_KEY, etc.) survive `memtrace install` re-registration.
|
|
46
|
+
* Only the top-level table + non-env sub-tables get stripped.
|
|
47
|
+
*/
|
|
48
|
+
function isMemtraceMcpTableToStrip(tableName) {
|
|
49
|
+
if (tableName === `mcp_servers.${MCP_SERVER_NAME}.env`)
|
|
50
|
+
return false;
|
|
51
|
+
if (tableName === `mcp_servers."${MCP_SERVER_NAME}".env`)
|
|
52
|
+
return false;
|
|
53
|
+
return isMemtraceMcpTable(tableName);
|
|
54
|
+
}
|
|
41
55
|
export function stripCodexMcpServer(raw) {
|
|
42
56
|
const lines = raw.split(/\r?\n/);
|
|
43
57
|
const kept = [];
|
|
@@ -45,7 +59,7 @@ export function stripCodexMcpServer(raw) {
|
|
|
45
59
|
for (const line of lines) {
|
|
46
60
|
const table = line.match(/^\s*\[([^\]]+)\]\s*$/);
|
|
47
61
|
if (table) {
|
|
48
|
-
skipping =
|
|
62
|
+
skipping = isMemtraceMcpTableToStrip(table[1].trim());
|
|
49
63
|
if (skipping)
|
|
50
64
|
continue;
|
|
51
65
|
}
|
|
@@ -54,16 +68,64 @@ export function stripCodexMcpServer(raw) {
|
|
|
54
68
|
}
|
|
55
69
|
return kept.join('\n').trimEnd();
|
|
56
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Extracts the preserved `[mcp_servers.memtrace.env]` block (if any) and
|
|
73
|
+
* returns:
|
|
74
|
+
* - rest: the input with the env block removed
|
|
75
|
+
* - envBlock: the env block as a string (empty if absent)
|
|
76
|
+
*
|
|
77
|
+
* Used by `registerCodexMcpAt` so the env block can be reattached AFTER the
|
|
78
|
+
* freshly-written `[mcp_servers.memtrace]` parent for readable ordering.
|
|
79
|
+
*/
|
|
80
|
+
function extractMemtraceEnvBlock(raw) {
|
|
81
|
+
const lines = raw.split(/\r?\n/);
|
|
82
|
+
const rest = [];
|
|
83
|
+
const envLines = [];
|
|
84
|
+
let inEnv = false;
|
|
85
|
+
for (const line of lines) {
|
|
86
|
+
const table = line.match(/^\s*\[([^\]]+)\]\s*$/);
|
|
87
|
+
if (table) {
|
|
88
|
+
const name = table[1].trim();
|
|
89
|
+
const isEnv = name === `mcp_servers.${MCP_SERVER_NAME}.env`
|
|
90
|
+
|| name === `mcp_servers."${MCP_SERVER_NAME}".env`;
|
|
91
|
+
if (isEnv) {
|
|
92
|
+
inEnv = true;
|
|
93
|
+
envLines.push(line);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
// Any other table header closes the env capture.
|
|
97
|
+
inEnv = false;
|
|
98
|
+
}
|
|
99
|
+
if (inEnv) {
|
|
100
|
+
envLines.push(line);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
rest.push(line);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
rest: rest.join('\n').replace(/\n+$/, ''),
|
|
108
|
+
envBlock: envLines.join('\n').replace(/\n+$/, ''),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
57
111
|
export function registerCodexMcpAt(configFile, binary) {
|
|
58
112
|
const existing = fs.existsSync(configFile) ? fs.readFileSync(configFile, 'utf-8') : '';
|
|
59
|
-
|
|
113
|
+
// Strip leaves the env sub-table intact; split it out so we can reorder it
|
|
114
|
+
// after the freshly written parent block for readability.
|
|
115
|
+
const stripped = stripCodexMcpServer(existing);
|
|
116
|
+
const { rest, envBlock } = extractMemtraceEnvBlock(stripped);
|
|
60
117
|
const block = [
|
|
61
118
|
`[mcp_servers.${MCP_SERVER_NAME}]`,
|
|
62
119
|
`command = ${tomlString(binary)}`,
|
|
63
120
|
`args = [${tomlString('mcp')}]`,
|
|
64
|
-
'',
|
|
65
121
|
].join('\n');
|
|
66
|
-
const
|
|
122
|
+
const parts = [];
|
|
123
|
+
if (rest)
|
|
124
|
+
parts.push(rest);
|
|
125
|
+
parts.push(block);
|
|
126
|
+
if (envBlock)
|
|
127
|
+
parts.push(envBlock);
|
|
128
|
+
const next = `${parts.join('\n\n')}\n`;
|
|
67
129
|
writeTextAtomic(configFile, next);
|
|
68
130
|
return { registered: true };
|
|
69
131
|
}
|
|
@@ -20,6 +20,9 @@ function writeSkill(skill, rootDir) {
|
|
|
20
20
|
const content = `---\nname: ${name}\ndescription: "${safeDesc}"\n---\n\n${skill.body}`;
|
|
21
21
|
fs.writeFileSync(path.join(outDir, 'SKILL.md'), content);
|
|
22
22
|
}
|
|
23
|
+
function isRecord(value) {
|
|
24
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
25
|
+
}
|
|
23
26
|
export function registerCursorMcpAt(mcpFile, binary) {
|
|
24
27
|
const { value, corrupted, backupPath } = safeReadJson(mcpFile);
|
|
25
28
|
if (corrupted) {
|
|
@@ -28,10 +31,17 @@ export function registerCursorMcpAt(mcpFile, binary) {
|
|
|
28
31
|
}
|
|
29
32
|
const cfg = (value ?? {});
|
|
30
33
|
cfg.mcpServers = cfg.mcpServers ?? {};
|
|
34
|
+
// Deep-merge env: preserve user-set keys (e.g. MEMTRACE_LICENSE_KEY) across
|
|
35
|
+
// re-registration; memtrace-owned defaults (MEMTRACE_MCP_ENV) win on conflict.
|
|
36
|
+
const existing = cfg.mcpServers['memtrace'];
|
|
37
|
+
const existingEnv = (isRecord(existing) && isRecord(existing.env))
|
|
38
|
+
? existing.env
|
|
39
|
+
: {};
|
|
40
|
+
const mergedEnv = { ...existingEnv, ...MEMTRACE_MCP_ENV };
|
|
31
41
|
cfg.mcpServers['memtrace'] = {
|
|
32
42
|
command: binary,
|
|
33
43
|
args: ['mcp'],
|
|
34
|
-
env:
|
|
44
|
+
env: mergedEnv,
|
|
35
45
|
};
|
|
36
46
|
writeJsonAtomic(mcpFile, cfg);
|
|
37
47
|
return { registered: true };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memtrace",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "Code intelligence graph — MCP server + AI agent skills + visualization UI",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -39,11 +39,11 @@
|
|
|
39
39
|
"fs-extra": "^11.0.0"
|
|
40
40
|
},
|
|
41
41
|
"optionalDependencies": {
|
|
42
|
-
"@memtrace/darwin-arm64": "0.4.
|
|
43
|
-
"@memtrace/linux-x64": "0.4.
|
|
44
|
-
"@memtrace/win32-x64": "0.4.
|
|
45
|
-
"@memtrace/linux-x64-noavx2": "0.4.
|
|
46
|
-
"@memtrace/win32-x64-noavx2": "0.4.
|
|
42
|
+
"@memtrace/darwin-arm64": "0.4.3",
|
|
43
|
+
"@memtrace/linux-x64": "0.4.3",
|
|
44
|
+
"@memtrace/win32-x64": "0.4.3",
|
|
45
|
+
"@memtrace/linux-x64-noavx2": "0.4.3",
|
|
46
|
+
"@memtrace/win32-x64-noavx2": "0.4.3"
|
|
47
47
|
},
|
|
48
48
|
"engines": {
|
|
49
49
|
"node": ">=18"
|