memtrace 0.8.9 → 0.8.11
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/installer/dist/commands/doctor.js +24 -1
- package/installer/dist/commands/install.js +10 -4
- package/installer/dist/index.js +1 -1
- package/installer/dist/transformers/index.d.ts +2 -1
- package/installer/dist/transformers/index.js +3 -1
- package/installer/dist/transformers/pi.d.ts +5 -0
- package/installer/dist/transformers/pi.js +152 -0
- package/installer/dist/transformers/types.d.ts +1 -1
- package/installer/package.json +1 -1
- package/lib/install-consent.js +1 -1
- package/package.json +9 -8
- package/pi-package/LICENSE +21 -0
- package/pi-package/README.md +51 -0
- package/pi-package/extension/config.ts +96 -0
- package/pi-package/extension/index.ts +764 -0
- package/pi-package/extension/mcp-client.ts +259 -0
- package/pi-package/extension/schema.ts +293 -0
- package/pi-package/package.json +25 -0
- package/pi-package/skills/memtrace-codebase-exploration/SKILL.md +132 -0
- package/pi-package/skills/memtrace-evolution/SKILL.md +158 -0
- package/pi-package/skills/memtrace-first/SKILL.md +213 -0
- package/pi-package/skills/memtrace-impact/SKILL.md +108 -0
- package/pi-package/skills/references/mcp-parameters.md +358 -0
- package/pi-package/tsconfig.json +17 -0
|
@@ -66,6 +66,17 @@ function hermesMcpHasMemtrace(file) {
|
|
|
66
66
|
const raw = fs.readFileSync(file, 'utf-8');
|
|
67
67
|
return /^mcp_servers:\s*$(?:[\s\S]*?)^\s{2}memtrace:\s*$/m.test(raw);
|
|
68
68
|
}
|
|
69
|
+
function piPackageRegistered(file) {
|
|
70
|
+
const { value } = safeReadJson(file);
|
|
71
|
+
if (!value?.packages?.length)
|
|
72
|
+
return false;
|
|
73
|
+
return value.packages.some(entry => {
|
|
74
|
+
const source = typeof entry === 'string' ? entry : entry.source;
|
|
75
|
+
if (!source)
|
|
76
|
+
return false;
|
|
77
|
+
return source.endsWith(`${path.sep}pi-package`) || source.endsWith('/pi-package');
|
|
78
|
+
});
|
|
79
|
+
}
|
|
69
80
|
function opencodeConfigDir() {
|
|
70
81
|
const xdg = process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), '.config');
|
|
71
82
|
return path.join(xdg, 'opencode');
|
|
@@ -170,6 +181,17 @@ function checkAgent(agent) {
|
|
|
170
181
|
mcpConfigPath,
|
|
171
182
|
};
|
|
172
183
|
}
|
|
184
|
+
if (agent === 'pi') {
|
|
185
|
+
const skillsDir = path.join(os.homedir(), '.pi', 'agent', 'skills');
|
|
186
|
+
const mcpConfigPath = path.join(os.homedir(), '.pi', 'agent', 'settings.json');
|
|
187
|
+
return {
|
|
188
|
+
agent,
|
|
189
|
+
skillsFound: countMemtraceSkills(skillsDir),
|
|
190
|
+
skillsDir,
|
|
191
|
+
mcpRegistered: piPackageRegistered(mcpConfigPath),
|
|
192
|
+
mcpConfigPath,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
173
195
|
const skillsDir = path.join(os.homedir(), '.cursor', 'skills');
|
|
174
196
|
const mcpConfigPath = path.join(os.homedir(), '.cursor', 'mcp.json');
|
|
175
197
|
return {
|
|
@@ -197,7 +219,8 @@ export function formatReport(r) {
|
|
|
197
219
|
lines.push(` version: ${r.binaryVersion}`);
|
|
198
220
|
for (const a of r.agents) {
|
|
199
221
|
lines.push(`${check(a.skillsFound > 0)} ${a.agent} skills installed ${a.skillsFound} in ${a.skillsDir}`);
|
|
200
|
-
|
|
222
|
+
const integrationLabel = a.agent === 'pi' ? 'Pi package registered' : 'MCP registered';
|
|
223
|
+
lines.push(`${check(a.mcpRegistered)} ${a.agent} ${integrationLabel} ${a.mcpConfigPath}`);
|
|
201
224
|
}
|
|
202
225
|
return lines.join('\n');
|
|
203
226
|
}
|
|
@@ -61,12 +61,18 @@ export async function runInstall(options) {
|
|
|
61
61
|
try {
|
|
62
62
|
const r = await t.install(skills, ctx);
|
|
63
63
|
console.log(` [${t.name}] ✓ ${r.skillsWritten} skills → ${r.skillsDir}`);
|
|
64
|
-
if (r.mcpRegistered)
|
|
65
|
-
console.log(` [${t.name}] ✓ MCP registered`);
|
|
66
|
-
|
|
64
|
+
if (r.mcpRegistered) {
|
|
65
|
+
console.log(` [${t.name}] ✓ ${t.name === 'pi' ? 'Pi package registered' : 'MCP registered'}`);
|
|
66
|
+
}
|
|
67
|
+
else if (t.name === 'pi') {
|
|
68
|
+
console.log(` [${t.name}] ⚠ Pi package registration failed`);
|
|
69
|
+
}
|
|
70
|
+
else if (ctx.skipMcp) {
|
|
67
71
|
console.log(` [${t.name}] (MCP skipped)`);
|
|
68
|
-
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
69
74
|
console.log(` [${t.name}] ⚠ MCP registration failed`);
|
|
75
|
+
}
|
|
70
76
|
for (const w of r.warnings)
|
|
71
77
|
console.warn(` [${t.name}] warn: ${w}`);
|
|
72
78
|
}
|
package/installer/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ function parseOnly(val) {
|
|
|
15
15
|
program
|
|
16
16
|
.command('install')
|
|
17
17
|
.description('Install memtrace skills and register MCP for selected agents')
|
|
18
|
-
.option('--only <agents>', 'comma-separated agent names (claude,cursor,codex,gemini,windsurf,vscode,hermes,opencode,kiro,warp)', parseOnly)
|
|
18
|
+
.option('--only <agents>', 'comma-separated agent names (claude,cursor,codex,gemini,windsurf,vscode,hermes,opencode,kiro,warp,pi)', parseOnly)
|
|
19
19
|
.option('--local', 'install into the current project where the selected agent supports project scope', false)
|
|
20
20
|
.option('--global', 'install globally (~/.claude/, ~/.cursor/, ~/.agents/) [default]', false)
|
|
21
21
|
.option('--skip-mcp', 'write skills only, skip MCP server registration', false)
|
|
@@ -9,7 +9,8 @@ import { hermesTransformer } from './hermes.js';
|
|
|
9
9
|
import { opencodeTransformer } from './opencode.js';
|
|
10
10
|
import { kiroTransformer } from './kiro.js';
|
|
11
11
|
import { warpTransformer } from './warp.js';
|
|
12
|
+
import { piTransformer } from './pi.js';
|
|
12
13
|
export declare const ALL_TRANSFORMERS: Transformer[];
|
|
13
14
|
export declare function findTransformer(name: string): Transformer | undefined;
|
|
14
|
-
export { claudeTransformer, cursorTransformer, codexTransformer, geminiTransformer, windsurfTransformer, vscodeTransformer, hermesTransformer, opencodeTransformer, kiroTransformer, warpTransformer, };
|
|
15
|
+
export { claudeTransformer, cursorTransformer, codexTransformer, geminiTransformer, windsurfTransformer, vscodeTransformer, hermesTransformer, opencodeTransformer, kiroTransformer, warpTransformer, piTransformer, };
|
|
15
16
|
export type { Transformer, InstallContext, InstallResult, TransformResult } from './types.js';
|
|
@@ -8,6 +8,7 @@ import { hermesTransformer } from './hermes.js';
|
|
|
8
8
|
import { opencodeTransformer } from './opencode.js';
|
|
9
9
|
import { kiroTransformer } from './kiro.js';
|
|
10
10
|
import { warpTransformer } from './warp.js';
|
|
11
|
+
import { piTransformer } from './pi.js';
|
|
11
12
|
export const ALL_TRANSFORMERS = [
|
|
12
13
|
claudeTransformer,
|
|
13
14
|
cursorTransformer,
|
|
@@ -19,8 +20,9 @@ export const ALL_TRANSFORMERS = [
|
|
|
19
20
|
opencodeTransformer,
|
|
20
21
|
kiroTransformer,
|
|
21
22
|
warpTransformer,
|
|
23
|
+
piTransformer,
|
|
22
24
|
];
|
|
23
25
|
export function findTransformer(name) {
|
|
24
26
|
return ALL_TRANSFORMERS.find(t => t.name === name);
|
|
25
27
|
}
|
|
26
|
-
export { claudeTransformer, cursorTransformer, codexTransformer, geminiTransformer, windsurfTransformer, vscodeTransformer, hermesTransformer, opencodeTransformer, kiroTransformer, warpTransformer, };
|
|
28
|
+
export { claudeTransformer, cursorTransformer, codexTransformer, geminiTransformer, windsurfTransformer, vscodeTransformer, hermesTransformer, opencodeTransformer, kiroTransformer, warpTransformer, piTransformer, };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Transformer, InstallContext } from './types.js';
|
|
2
|
+
export declare function piSettingsPath(ctx: InstallContext): string;
|
|
3
|
+
/** Resolve the pi-package directory shipped inside the memtrace npm bundle. */
|
|
4
|
+
export declare function resolveBundledPiPackage(fromDir?: string): string;
|
|
5
|
+
export declare const piTransformer: Transformer;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { removeMemtraceSkills, skillName } from './shared.js';
|
|
6
|
+
import { safeReadJson, writeJsonAtomic } from '../fs-safe.js';
|
|
7
|
+
import { commandExists } from '../utils.js';
|
|
8
|
+
// Pi (https://pi.dev/docs/latest/packages) uses native pi packages — not MCP JSON
|
|
9
|
+
// config like Cursor. The bundled `pi-package/` extension spawns `memtrace mcp`
|
|
10
|
+
// itself and registers every Memtrace tool as a native Pi tool (`memtrace_*`).
|
|
11
|
+
//
|
|
12
|
+
// `memtrace install --only pi` writes skills + registers this bundled package.
|
|
13
|
+
function piAgentRoot(ctx) {
|
|
14
|
+
return ctx.scope === 'global'
|
|
15
|
+
? path.join(os.homedir(), '.pi', 'agent')
|
|
16
|
+
: path.join(ctx.cwd, '.pi');
|
|
17
|
+
}
|
|
18
|
+
function skillsRoot(ctx) {
|
|
19
|
+
return path.join(piAgentRoot(ctx), 'skills');
|
|
20
|
+
}
|
|
21
|
+
export function piSettingsPath(ctx) {
|
|
22
|
+
return path.join(piAgentRoot(ctx), 'settings.json');
|
|
23
|
+
}
|
|
24
|
+
/** Resolve the pi-package directory shipped inside the memtrace npm bundle. */
|
|
25
|
+
export function resolveBundledPiPackage(fromDir = path.dirname(fileURLToPath(import.meta.url))) {
|
|
26
|
+
for (const rel of ['../../../pi-package', '../../pi-package']) {
|
|
27
|
+
const candidate = path.resolve(fromDir, rel);
|
|
28
|
+
if (fs.existsSync(path.join(candidate, 'package.json')))
|
|
29
|
+
return candidate;
|
|
30
|
+
}
|
|
31
|
+
throw new Error('bundled pi-package not found (reinstall memtrace from npm)');
|
|
32
|
+
}
|
|
33
|
+
function transformForPi(text) {
|
|
34
|
+
return text.replace(/mcp__memtrace__/g, 'memtrace_');
|
|
35
|
+
}
|
|
36
|
+
function transformAllowedTool(tool) {
|
|
37
|
+
return tool
|
|
38
|
+
.replace(/^mcp__memtrace__/, 'memtrace_')
|
|
39
|
+
.replace(/^Read$/, 'read')
|
|
40
|
+
.replace(/^Grep$/, 'grep')
|
|
41
|
+
.replace(/^Glob$/, 'find');
|
|
42
|
+
}
|
|
43
|
+
function skillMarkdownForPi(skill) {
|
|
44
|
+
const name = skillName(skill);
|
|
45
|
+
const safeDesc = skill.frontmatter.description.replace(/"/g, '\\"').trim();
|
|
46
|
+
const tools = skill.frontmatter['allowed-tools'];
|
|
47
|
+
const toolsLine = tools?.length
|
|
48
|
+
? `\nallowed-tools: ${tools.map(transformAllowedTool).join(' ')}`
|
|
49
|
+
: '';
|
|
50
|
+
const body = transformForPi(skill.body);
|
|
51
|
+
return `---\nname: ${name}\ndescription: "${safeDesc}"${toolsLine}\n---\n\n${body}`;
|
|
52
|
+
}
|
|
53
|
+
function writeSkillsForPi(skills, rootDir) {
|
|
54
|
+
removeMemtraceSkills(rootDir);
|
|
55
|
+
for (const skill of skills) {
|
|
56
|
+
const name = skillName(skill);
|
|
57
|
+
const outDir = path.join(rootDir, name);
|
|
58
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
59
|
+
fs.writeFileSync(path.join(outDir, 'SKILL.md'), skillMarkdownForPi(skill));
|
|
60
|
+
}
|
|
61
|
+
return skills.length;
|
|
62
|
+
}
|
|
63
|
+
function packageEntrySource(entry) {
|
|
64
|
+
if (typeof entry === 'string')
|
|
65
|
+
return entry;
|
|
66
|
+
return typeof entry.source === 'string' ? entry.source : undefined;
|
|
67
|
+
}
|
|
68
|
+
function isMemtracePiPackageEntry(entry, packagePath) {
|
|
69
|
+
const source = packageEntrySource(entry);
|
|
70
|
+
if (!source)
|
|
71
|
+
return false;
|
|
72
|
+
if (source === packagePath)
|
|
73
|
+
return true;
|
|
74
|
+
const base = `${path.sep}pi-package`;
|
|
75
|
+
return source.endsWith(base) || source.endsWith('/pi-package');
|
|
76
|
+
}
|
|
77
|
+
function registerPiPackageAt(settingsFile, packagePath) {
|
|
78
|
+
const { value, corrupted, backupPath } = safeReadJson(settingsFile);
|
|
79
|
+
if (corrupted)
|
|
80
|
+
return { registered: false, backupPath, packagePath };
|
|
81
|
+
const cfg = (value ?? {});
|
|
82
|
+
cfg.packages = (cfg.packages ?? []).filter(entry => !isMemtracePiPackageEntry(entry, packagePath));
|
|
83
|
+
cfg.packages.push(packagePath);
|
|
84
|
+
writeJsonAtomic(settingsFile, cfg);
|
|
85
|
+
return { registered: true, packagePath };
|
|
86
|
+
}
|
|
87
|
+
function removePiPackageAt(settingsFile, packagePath) {
|
|
88
|
+
const { value, corrupted } = safeReadJson(settingsFile);
|
|
89
|
+
if (corrupted || !value?.packages?.length)
|
|
90
|
+
return;
|
|
91
|
+
const kept = value.packages.filter(entry => !isMemtracePiPackageEntry(entry, packagePath));
|
|
92
|
+
if (kept.length === value.packages.length)
|
|
93
|
+
return;
|
|
94
|
+
if (kept.length === 0) {
|
|
95
|
+
delete value.packages;
|
|
96
|
+
if (Object.keys(value).length === 0)
|
|
97
|
+
fs.unlinkSync(settingsFile);
|
|
98
|
+
else
|
|
99
|
+
writeJsonAtomic(settingsFile, value);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
value.packages = kept;
|
|
103
|
+
writeJsonAtomic(settingsFile, value);
|
|
104
|
+
}
|
|
105
|
+
export const piTransformer = {
|
|
106
|
+
name: 'pi',
|
|
107
|
+
async install(skills, ctx) {
|
|
108
|
+
const rootDir = skillsRoot(ctx);
|
|
109
|
+
const count = writeSkillsForPi(skills, rootDir);
|
|
110
|
+
const warnings = [];
|
|
111
|
+
const settingsFile = piSettingsPath(ctx);
|
|
112
|
+
let packagePath;
|
|
113
|
+
try {
|
|
114
|
+
packagePath = resolveBundledPiPackage();
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
return {
|
|
118
|
+
agent: 'pi',
|
|
119
|
+
skillsWritten: count,
|
|
120
|
+
skillsDir: rootDir,
|
|
121
|
+
mcpConfigPath: settingsFile,
|
|
122
|
+
mcpRegistered: false,
|
|
123
|
+
warnings: [`${e.message}`],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const result = registerPiPackageAt(settingsFile, packagePath);
|
|
127
|
+
if (!result.registered && result.backupPath) {
|
|
128
|
+
warnings.push(`Pi settings.json was malformed; backed up to ${result.backupPath}.`);
|
|
129
|
+
}
|
|
130
|
+
if (!(await commandExists('pi'))) {
|
|
131
|
+
warnings.push('Pi CLI not found on PATH — skills and package path are configured; install Pi from https://pi.dev to use them.');
|
|
132
|
+
}
|
|
133
|
+
if (ctx.skipMcp) {
|
|
134
|
+
warnings.push('memtrace binary not found — the Pi extension will connect once `memtrace` is on PATH.');
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
agent: 'pi',
|
|
138
|
+
skillsWritten: count,
|
|
139
|
+
skillsDir: rootDir,
|
|
140
|
+
mcpConfigPath: settingsFile,
|
|
141
|
+
mcpRegistered: result.registered,
|
|
142
|
+
warnings,
|
|
143
|
+
};
|
|
144
|
+
},
|
|
145
|
+
async uninstall(ctx) {
|
|
146
|
+
removeMemtraceSkills(skillsRoot(ctx));
|
|
147
|
+
try {
|
|
148
|
+
removePiPackageAt(piSettingsPath(ctx), resolveBundledPiPackage());
|
|
149
|
+
}
|
|
150
|
+
catch { /* bundled pi-package path unavailable */ }
|
|
151
|
+
},
|
|
152
|
+
};
|
|
@@ -29,7 +29,7 @@ export interface InstallResult {
|
|
|
29
29
|
mcpRegistered: boolean;
|
|
30
30
|
warnings: string[];
|
|
31
31
|
}
|
|
32
|
-
export type AgentName = 'claude' | 'cursor' | 'codex' | 'gemini' | 'windsurf' | 'vscode' | 'hermes' | 'opencode' | 'kiro' | 'warp';
|
|
32
|
+
export type AgentName = 'claude' | 'cursor' | 'codex' | 'gemini' | 'windsurf' | 'vscode' | 'hermes' | 'opencode' | 'kiro' | 'warp' | 'pi';
|
|
33
33
|
/**
|
|
34
34
|
* One transformer per supported AI coding agent.
|
|
35
35
|
*/
|
package/installer/package.json
CHANGED
package/lib/install-consent.js
CHANGED
|
@@ -85,7 +85,7 @@ function buildMutationList({ home, cwd, projectOnly = false, noHooks = false })
|
|
|
85
85
|
{ path: path.join(claude, "plugins", "installed_plugins.json"), change: "plugin registry entry (written by the `claude` CLI when available)" },
|
|
86
86
|
{ path: path.join(home, ".claude.json"), change: "possible memtrace MCP entry (written by `claude mcp add-json --scope user` when the `claude` CLI is available)" },
|
|
87
87
|
{ path: path.join(home, ".memtrace") + path.sep, change: "create memtrace data dir: persisted uninstall.js + install-backup/ (backups + manifest)" },
|
|
88
|
-
{ path: path.join(home, "{.cursor,.codex,.gemini,.codeium,.hermes,.kiro,.copilot,.warp,.config/opencode,VS Code user config}"), change: "skills + memtrace MCP registration for other detected agents (same merge-add pattern)" },
|
|
88
|
+
{ path: path.join(home, "{.cursor,.codex,.gemini,.codeium,.hermes,.kiro,.copilot,.warp,.pi,.config/opencode,VS Code user config}"), change: "skills + memtrace MCP registration (or Pi native package) for other detected agents (same merge-add pattern)" },
|
|
89
89
|
);
|
|
90
90
|
return list;
|
|
91
91
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memtrace",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.11",
|
|
4
4
|
"description": "Code intelligence graph — MCP server + AI agent skills + visualization UI",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"skills/",
|
|
28
28
|
"installer/",
|
|
29
29
|
"install.js",
|
|
30
|
-
"uninstall.js"
|
|
30
|
+
"uninstall.js",
|
|
31
|
+
"pi-package/"
|
|
31
32
|
],
|
|
32
33
|
"scripts": {
|
|
33
34
|
"prepack": "node ../../installer/scripts/prepare-npm-package.js",
|
|
@@ -39,12 +40,12 @@
|
|
|
39
40
|
"fs-extra": "^11.0.0"
|
|
40
41
|
},
|
|
41
42
|
"optionalDependencies": {
|
|
42
|
-
"@memtrace/darwin-arm64": "0.8.
|
|
43
|
-
"@memtrace/linux-x64": "0.8.
|
|
44
|
-
"@memtrace/linux-arm64": "0.8.
|
|
45
|
-
"@memtrace/win32-x64": "0.8.
|
|
46
|
-
"@memtrace/linux-x64-noavx2": "0.8.
|
|
47
|
-
"@memtrace/win32-x64-noavx2": "0.8.
|
|
43
|
+
"@memtrace/darwin-arm64": "0.8.11",
|
|
44
|
+
"@memtrace/linux-x64": "0.8.11",
|
|
45
|
+
"@memtrace/linux-arm64": "0.8.11",
|
|
46
|
+
"@memtrace/win32-x64": "0.8.11",
|
|
47
|
+
"@memtrace/linux-x64-noavx2": "0.8.11",
|
|
48
|
+
"@memtrace/win32-x64-noavx2": "0.8.11"
|
|
48
49
|
},
|
|
49
50
|
"engines": {
|
|
50
51
|
"node": ">=18"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Memtrace
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Memtrace for Pi
|
|
2
|
+
|
|
3
|
+
Bundled Pi extension for [Memtrace](https://memtrace.io). Installed automatically by `memtrace install --only pi` — not published or installed separately.
|
|
4
|
+
|
|
5
|
+
This package speaks the Memtrace MCP protocol over stdio and registers every Memtrace tool as a **native Pi tool** (`memtrace_*`), so Pi's model sees real Typebox schemas and tool names instead of a generic MCP passthrough.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g memtrace
|
|
11
|
+
memtrace install --only pi -y
|
|
12
|
+
# or pick pi from the interactive harness list:
|
|
13
|
+
memtrace install
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
This writes all Memtrace skills to `~/.pi/agent/skills/` and registers this package in `~/.pi/agent/settings.json`.
|
|
17
|
+
|
|
18
|
+
Use `--local` for project scope (`.pi/skills/` + `.pi/settings.json`).
|
|
19
|
+
|
|
20
|
+
## Requirements
|
|
21
|
+
|
|
22
|
+
- [Pi](https://pi.dev) on PATH
|
|
23
|
+
- `memtrace` CLI on PATH (`npm install -g memtrace`)
|
|
24
|
+
|
|
25
|
+
The extension spawns `memtrace mcp` itself. Run `memtrace start` separately only if you want the daemon/HTTP API to stay up outside Pi sessions.
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
| Command | What it does |
|
|
30
|
+
|---|---|
|
|
31
|
+
| `/memtrace` | Connection state, indexed repos, cwd coverage |
|
|
32
|
+
| `/memtrace:connect` | Reconnect and refresh tools |
|
|
33
|
+
| `/memtrace:index [path]` | Index a directory (defaults to cwd) |
|
|
34
|
+
| `/memtrace:reindex` | Incremental re-index for cwd repo |
|
|
35
|
+
| `/memtrace:tools` | List registered `memtrace_*` tools |
|
|
36
|
+
| `/memtrace:setup` | Check binary install, print next steps |
|
|
37
|
+
|
|
38
|
+
## Configuration
|
|
39
|
+
|
|
40
|
+
Precedence (highest first):
|
|
41
|
+
|
|
42
|
+
1. `MEMTRACE_PI_COMMAND`, `MEMTRACE_PI_ARGS` env vars
|
|
43
|
+
2. `.pi/memtrace.json` (project)
|
|
44
|
+
3. `~/.pi/agent/memtrace.json` (user)
|
|
45
|
+
4. Defaults: `memtrace` + `["mcp"]`
|
|
46
|
+
|
|
47
|
+
Set `MEMTRACE_PI_DEBUG=1` to log connection lifecycle to stderr.
|
|
48
|
+
|
|
49
|
+
## License
|
|
50
|
+
|
|
51
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// config.ts — resolve the Memtrace server connection config.
|
|
2
|
+
//
|
|
3
|
+
// Precedence (highest first):
|
|
4
|
+
// 1. MEMTRACE_*_PI env overrides (MEMTRACE_PI_COMMAND, MEMTRACE_PI_ARGS)
|
|
5
|
+
// 2. project file .pi/memtrace.json
|
|
6
|
+
// 3. user file ~/.pi/agent/memtrace.json
|
|
7
|
+
// 4. base env process.env (MEMTRACE_EMBED_KEY, MEMTRACE_MEMDB_ENDPOINT, …)
|
|
8
|
+
// 5. defaults command "memtrace", args ["mcp"]
|
|
9
|
+
//
|
|
10
|
+
// `memtrace mcp` is the real stdio MCP subcommand (verified against memtrace
|
|
11
|
+
// 0.8.7's CLI); it attaches to an already-running `memtrace start` workspace
|
|
12
|
+
// daemon or boots its own embedded MemDB sidecar if none is running. Older
|
|
13
|
+
// docs/configs may still reference a `serve` subcommand that never shipped —
|
|
14
|
+
// index.ts retries with ["mcp"] if a configured `serve` is rejected.
|
|
15
|
+
|
|
16
|
+
import { readFileSync } from "node:fs";
|
|
17
|
+
import { homedir } from "node:os";
|
|
18
|
+
import { resolve } from "node:path";
|
|
19
|
+
|
|
20
|
+
export interface MemtraceConfig {
|
|
21
|
+
/** Executable used to launch the Memtrace MCP server over stdio. */
|
|
22
|
+
command: string;
|
|
23
|
+
/** Args passed to the command. Default ["mcp"]; a configured legacy ["serve"] falls back to ["mcp"] on failure. */
|
|
24
|
+
args: string[];
|
|
25
|
+
/** Extra env merged on top of process.env for the child process. */
|
|
26
|
+
env: Record<string, string>;
|
|
27
|
+
/** Working directory for the child process. */
|
|
28
|
+
cwd?: string;
|
|
29
|
+
/** Idle disconnect timeout in minutes (0 = never). */
|
|
30
|
+
idleTimeoutMin: number;
|
|
31
|
+
/** Per-request timeout in ms. */
|
|
32
|
+
requestTimeoutMs: number;
|
|
33
|
+
/** Startup connect timeout in ms (eager connect at session start). */
|
|
34
|
+
startupTimeoutMs: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const DEFAULTS: MemtraceConfig = {
|
|
38
|
+
command: "memtrace",
|
|
39
|
+
args: ["mcp"],
|
|
40
|
+
env: {},
|
|
41
|
+
idleTimeoutMin: 10,
|
|
42
|
+
requestTimeoutMs: 60_000,
|
|
43
|
+
startupTimeoutMs: 4000,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
function tryRead(path: string): Record<string, unknown> | null {
|
|
47
|
+
try {
|
|
48
|
+
const raw = readFileSync(path, "utf8");
|
|
49
|
+
return JSON.parse(raw) as Record<string, unknown>;
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function shallowMerge(base: MemtraceConfig, extra: Record<string, unknown>): MemtraceConfig {
|
|
56
|
+
const out = { ...base };
|
|
57
|
+
if (typeof extra.command === "string" && extra.command.trim()) out.command = extra.command;
|
|
58
|
+
if (Array.isArray(extra.args) && extra.args.every((a) => typeof a === "string")) {
|
|
59
|
+
out.args = extra.args as string[];
|
|
60
|
+
}
|
|
61
|
+
if (extra.env && typeof extra.env === "object") {
|
|
62
|
+
out.env = { ...out.env, ...(extra.env as Record<string, string>) };
|
|
63
|
+
}
|
|
64
|
+
if (typeof extra.cwd === "string") out.cwd = extra.cwd;
|
|
65
|
+
if (typeof extra.idleTimeoutMin === "number") out.idleTimeoutMin = extra.idleTimeoutMin;
|
|
66
|
+
if (typeof extra.requestTimeoutMs === "number") out.requestTimeoutMs = extra.requestTimeoutMs;
|
|
67
|
+
if (typeof extra.startupTimeoutMs === "number") out.startupTimeoutMs = extra.startupTimeoutMs;
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function loadConfig(projectDir: string): MemtraceConfig {
|
|
72
|
+
let cfg: MemtraceConfig = { ...DEFAULTS, env: {} };
|
|
73
|
+
|
|
74
|
+
const userFile = resolve(homedir(), ".pi/agent/memtrace.json");
|
|
75
|
+
const userJson = tryRead(userFile);
|
|
76
|
+
if (userJson) cfg = shallowMerge(cfg, userJson);
|
|
77
|
+
|
|
78
|
+
const projectFile = resolve(projectDir, ".pi/memtrace.json");
|
|
79
|
+
const projectJson = tryRead(projectFile);
|
|
80
|
+
if (projectJson) cfg = shallowMerge(cfg, projectJson);
|
|
81
|
+
|
|
82
|
+
// MEMTRACE_EMBED_KEY / MEMTRACE_MEMDB_ENDPOINT etc. propagate automatically
|
|
83
|
+
// because the child inherits process.env; surface a couple of common ones into
|
|
84
|
+
// the explicit env map only if the user already set them in config.
|
|
85
|
+
if (process.env.MEMTRACE_EMBED_KEY && !cfg.env.MEMTRACE_EMBED_KEY) {
|
|
86
|
+
cfg.env.MEMTRACE_EMBED_KEY = process.env.MEMTRACE_EMBED_KEY;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Highest-precedence env overrides for the launcher itself.
|
|
90
|
+
if (process.env.MEMTRACE_PI_COMMAND) cfg.command = process.env.MEMTRACE_PI_COMMAND;
|
|
91
|
+
if (process.env.MEMTRACE_PI_ARGS) {
|
|
92
|
+
cfg.args = process.env.MEMTRACE_PI_ARGS.split(/\s+/).filter(Boolean);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return cfg;
|
|
96
|
+
}
|