local-agent-memory 0.0.0-stage → 1.0.0
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/LICENSE +15 -0
- package/README.md +108 -2
- package/dist/cli/commands/hook.d.ts +1 -0
- package/dist/cli/commands/hook.js +66 -0
- package/dist/cli/commands/init.d.ts +5 -0
- package/dist/cli/commands/init.js +158 -0
- package/dist/cli/commands/record.d.ts +3 -0
- package/dist/cli/commands/record.js +58 -0
- package/dist/cli/commands/reindex.d.ts +1 -0
- package/dist/cli/commands/reindex.js +41 -0
- package/dist/cli/commands/status.d.ts +1 -0
- package/dist/cli/commands/status.js +88 -0
- package/dist/cli/commands/sync.d.ts +8 -0
- package/dist/cli/commands/sync.js +134 -0
- package/dist/cli/commands/template.d.ts +1 -0
- package/dist/cli/commands/template.js +116 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +140 -0
- package/dist/cli/mcp.d.ts +2 -0
- package/dist/cli/mcp.js +13 -0
- package/dist/core/embeddings.d.ts +14 -0
- package/dist/core/embeddings.js +127 -0
- package/dist/core/parser.d.ts +25 -0
- package/dist/core/parser.js +151 -0
- package/dist/core/search.d.ts +19 -0
- package/dist/core/search.js +100 -0
- package/dist/core/sync.d.ts +22 -0
- package/dist/core/sync.js +159 -0
- package/dist/db/project.d.ts +12 -0
- package/dist/db/project.js +113 -0
- package/dist/db/registry.d.ts +9 -0
- package/dist/db/registry.js +92 -0
- package/dist/mcp/middleware.d.ts +1 -0
- package/dist/mcp/middleware.js +46 -0
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.js +102 -0
- package/dist/mcp/tools.d.ts +2 -0
- package/dist/mcp/tools.js +244 -0
- package/dist/mcp/utils.d.ts +5 -0
- package/dist/mcp/utils.js +82 -0
- package/dist/setup/initGlobal.d.ts +5 -0
- package/dist/setup/initGlobal.js +167 -0
- package/package.json +72 -6
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.doSyncProject = doSyncProject;
|
|
7
|
+
exports.syncProject = syncProject;
|
|
8
|
+
exports.computeSha256 = computeSha256;
|
|
9
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
10
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
const ora_1 = __importDefault(require("ora"));
|
|
13
|
+
const sync_1 = require("../../core/sync");
|
|
14
|
+
const project_1 = require("../../db/project");
|
|
15
|
+
async function doSyncProject(cwd) {
|
|
16
|
+
const memoryDir = node_path_1.default.join(cwd, ".memory");
|
|
17
|
+
if (!node_fs_1.default.existsSync(memoryDir)) {
|
|
18
|
+
throw new Error("Project not initialized. Run memory init first.");
|
|
19
|
+
}
|
|
20
|
+
const localDbPath = (0, project_1.getLocalDbPath)(cwd);
|
|
21
|
+
const db = (0, project_1.initLocalDb)(localDbPath);
|
|
22
|
+
const _asyncDb = new sync_1.AsyncDatabase(db);
|
|
23
|
+
const manifests = await (0, project_1.getAllManifests)(localDbPath);
|
|
24
|
+
const manifestMap = new Map(manifests.map((m) => [m.file_path, m]));
|
|
25
|
+
const configPath = node_path_1.default.join(memoryDir, "config.json");
|
|
26
|
+
let includePaths = [".memory"];
|
|
27
|
+
if (node_fs_1.default.existsSync(configPath)) {
|
|
28
|
+
try {
|
|
29
|
+
const configData = JSON.parse(await node_fs_1.default.promises.readFile(configPath, "utf8"));
|
|
30
|
+
if (configData.include_paths && Array.isArray(configData.include_paths)) {
|
|
31
|
+
includePaths = configData.include_paths;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch (_e) {
|
|
35
|
+
// Ignore parse errors, fallback to default
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
let currentFiles = [];
|
|
39
|
+
for (const inc of includePaths) {
|
|
40
|
+
const fullPath = node_path_1.default.join(cwd, inc);
|
|
41
|
+
if (node_fs_1.default.existsSync(fullPath)) {
|
|
42
|
+
const stat = await node_fs_1.default.promises.stat(fullPath);
|
|
43
|
+
if (stat.isDirectory()) {
|
|
44
|
+
const files = await getAllMarkdownFiles(fullPath);
|
|
45
|
+
currentFiles.push(...files);
|
|
46
|
+
}
|
|
47
|
+
else if (fullPath.endsWith(".md")) {
|
|
48
|
+
currentFiles.push(fullPath);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Deduplicate files
|
|
53
|
+
currentFiles = Array.from(new Set(currentFiles));
|
|
54
|
+
let newCount = 0;
|
|
55
|
+
let modCount = 0;
|
|
56
|
+
let delCount = 0;
|
|
57
|
+
try {
|
|
58
|
+
// Check for new and modified
|
|
59
|
+
for (const file of currentFiles) {
|
|
60
|
+
const stats = await node_fs_1.default.promises.stat(file);
|
|
61
|
+
const mtime = stats.mtimeMs;
|
|
62
|
+
const relativePath = node_path_1.default.relative(cwd, file);
|
|
63
|
+
const existing = manifestMap.get(relativePath);
|
|
64
|
+
if (!existing) {
|
|
65
|
+
// New file
|
|
66
|
+
const sha256 = await computeSha256(file);
|
|
67
|
+
await (0, project_1.upsertFileManifest)(localDbPath, relativePath, mtime, sha256);
|
|
68
|
+
const content = await node_fs_1.default.promises.readFile(file, "utf8");
|
|
69
|
+
await (0, sync_1.syncFile)(relativePath, content, db);
|
|
70
|
+
newCount++;
|
|
71
|
+
}
|
|
72
|
+
else if (existing.mtime !== mtime) {
|
|
73
|
+
// Potentially modified, check hash
|
|
74
|
+
const sha256 = await computeSha256(file);
|
|
75
|
+
if (sha256 !== existing.sha256) {
|
|
76
|
+
await (0, project_1.upsertFileManifest)(localDbPath, relativePath, mtime, sha256);
|
|
77
|
+
const content = await node_fs_1.default.promises.readFile(file, "utf8");
|
|
78
|
+
await (0, sync_1.syncFile)(relativePath, content, db);
|
|
79
|
+
modCount++;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Remove from map to track deletions
|
|
83
|
+
manifestMap.delete(relativePath);
|
|
84
|
+
}
|
|
85
|
+
// Anything left in map was deleted from disk
|
|
86
|
+
for (const [deletedPath] of manifestMap.entries()) {
|
|
87
|
+
await (0, sync_1.removeFile)(deletedPath, db);
|
|
88
|
+
delCount++;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
db.close();
|
|
93
|
+
}
|
|
94
|
+
return { newCount, modCount, delCount };
|
|
95
|
+
}
|
|
96
|
+
async function syncProject() {
|
|
97
|
+
const cwd = process.cwd();
|
|
98
|
+
const memoryDir = node_path_1.default.join(cwd, ".memory");
|
|
99
|
+
if (!node_fs_1.default.existsSync(memoryDir)) {
|
|
100
|
+
console.error("Project not initialized. Run memory init first.");
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
const spinner = (0, ora_1.default)("Syncing project memory...").start();
|
|
104
|
+
try {
|
|
105
|
+
const { newCount, modCount, delCount } = await doSyncProject(cwd);
|
|
106
|
+
spinner.succeed(`Sync complete: ${newCount} new, ${modCount} modified, ${delCount} deleted.`);
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
spinner.fail("Sync failed.");
|
|
110
|
+
console.error(err.message);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async function getAllMarkdownFiles(dirPath, arrayOfFiles = []) {
|
|
115
|
+
const files = await node_fs_1.default.promises.readdir(dirPath, { withFileTypes: true });
|
|
116
|
+
for (const file of files) {
|
|
117
|
+
if (file.isDirectory()) {
|
|
118
|
+
arrayOfFiles = await getAllMarkdownFiles(node_path_1.default.join(dirPath, file.name), arrayOfFiles);
|
|
119
|
+
}
|
|
120
|
+
else if (file.name.endsWith(".md")) {
|
|
121
|
+
arrayOfFiles.push(node_path_1.default.join(dirPath, file.name));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return arrayOfFiles;
|
|
125
|
+
}
|
|
126
|
+
function computeSha256(filePath) {
|
|
127
|
+
return new Promise((resolve, reject) => {
|
|
128
|
+
const hash = node_crypto_1.default.createHash("sha256");
|
|
129
|
+
const rs = node_fs_1.default.createReadStream(filePath);
|
|
130
|
+
rs.on("error", reject);
|
|
131
|
+
rs.on("data", (chunk) => hash.update(chunk));
|
|
132
|
+
rs.on("end", () => resolve(hash.digest("hex")));
|
|
133
|
+
});
|
|
134
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function manageTemplates(action: string, options?: any): Promise<void>;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.manageTemplates = manageTemplates;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
10
|
+
const ora_1 = __importDefault(require("ora"));
|
|
11
|
+
const initGlobal_1 = require("../../setup/initGlobal");
|
|
12
|
+
async function getTemplatesDir() {
|
|
13
|
+
const dir = node_path_1.default.join((0, initGlobal_1.getLocalMemoryDir)(), "templates");
|
|
14
|
+
if (!node_fs_1.default.existsSync(dir)) {
|
|
15
|
+
await node_fs_1.default.promises.mkdir(dir, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
return dir;
|
|
18
|
+
}
|
|
19
|
+
async function manageTemplates(action, options = {}) {
|
|
20
|
+
const templatesDir = await getTemplatesDir();
|
|
21
|
+
if (action === "list") {
|
|
22
|
+
const spinner = (0, ora_1.default)("Fetching templates...").start();
|
|
23
|
+
try {
|
|
24
|
+
const entries = await node_fs_1.default.promises.readdir(templatesDir, {
|
|
25
|
+
withFileTypes: true,
|
|
26
|
+
});
|
|
27
|
+
const templates = entries
|
|
28
|
+
.filter((e) => e.isDirectory())
|
|
29
|
+
.map((e) => e.name);
|
|
30
|
+
if (templates.length === 0) {
|
|
31
|
+
spinner.info("No templates found.");
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
spinner.succeed("Available templates:");
|
|
35
|
+
for (const t of templates) {
|
|
36
|
+
console.log(` - ${t}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
spinner.fail("Failed to list templates.");
|
|
42
|
+
console.error(err.message);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else if (action === "create") {
|
|
46
|
+
const { name, description } = await inquirer_1.default.prompt([
|
|
47
|
+
{ name: "name", message: "Template name:" },
|
|
48
|
+
{ name: "description", message: "Template description:" },
|
|
49
|
+
]);
|
|
50
|
+
const targetDir = node_path_1.default.join(templatesDir, name);
|
|
51
|
+
if (node_fs_1.default.existsSync(targetDir)) {
|
|
52
|
+
console.error("Template already exists.");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
await node_fs_1.default.promises.mkdir(targetDir, { recursive: true });
|
|
56
|
+
await node_fs_1.default.promises.writeFile(node_path_1.default.join(targetDir, "core.md"), `# ${name}\n\n${description}\n`);
|
|
57
|
+
console.log(`Template ${name} created successfully.`);
|
|
58
|
+
}
|
|
59
|
+
else if (action === "export-from-project") {
|
|
60
|
+
const cwd = process.cwd();
|
|
61
|
+
const memoryDir = node_path_1.default.join(cwd, ".memory");
|
|
62
|
+
if (!node_fs_1.default.existsSync(memoryDir)) {
|
|
63
|
+
console.error("No .memory directory found in current project.");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
let templateName = options.name;
|
|
67
|
+
if (!templateName) {
|
|
68
|
+
const answers = await inquirer_1.default.prompt([
|
|
69
|
+
{ name: "name", message: "Enter a name for the exported template:" },
|
|
70
|
+
]);
|
|
71
|
+
templateName = answers.name;
|
|
72
|
+
}
|
|
73
|
+
const targetDir = node_path_1.default.join(templatesDir, templateName);
|
|
74
|
+
if (node_fs_1.default.existsSync(targetDir)) {
|
|
75
|
+
console.error("A template with that name already exists.");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
await node_fs_1.default.promises.mkdir(targetDir, { recursive: true });
|
|
79
|
+
// Copy safe files recursively, sanitizing out sensitive or local paths
|
|
80
|
+
await copyAndSanitize(memoryDir, targetDir);
|
|
81
|
+
console.log(`Successfully exported project memory to template: ${templateName}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function copyAndSanitize(src, dest) {
|
|
85
|
+
const entries = await node_fs_1.default.promises.readdir(src, { withFileTypes: true });
|
|
86
|
+
for (const entry of entries) {
|
|
87
|
+
const srcPath = node_path_1.default.join(src, entry.name);
|
|
88
|
+
const destPath = node_path_1.default.join(dest, entry.name);
|
|
89
|
+
if (entry.isDirectory()) {
|
|
90
|
+
await node_fs_1.default.promises.mkdir(destPath, { recursive: true });
|
|
91
|
+
await copyAndSanitize(srcPath, destPath);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
// Ignore SQLite databases, index files, or full config.json with hardcoded uuid
|
|
95
|
+
if (entry.name.endsWith(".sqlite") ||
|
|
96
|
+
entry.name.endsWith(".sqlite-shm") ||
|
|
97
|
+
entry.name.endsWith(".sqlite-wal") ||
|
|
98
|
+
entry.name === "config.json") {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (entry.name.endsWith(".md")) {
|
|
102
|
+
const content = await node_fs_1.default.promises.readFile(srcPath, "utf8");
|
|
103
|
+
// Basic sanitization: strip absolute paths starting with /Users or C:\ or /home and common credentials
|
|
104
|
+
let sanitized = content.replace(/(?:\/Users|\/home|[C-Z]:\\)[^\s"']+/g, "[REDACTED_LOCAL_PATH]");
|
|
105
|
+
// Obfuscate potential API keys or tokens (very basic regex)
|
|
106
|
+
sanitized = sanitized.replace(/(sk-[a-zA-Z0-9]{20,})|(gh[po]_[a-zA-Z0-9]{36,})|([a-zA-Z0-9_-]{20,}:[a-zA-Z0-9_-]{20,})/g, "[REDACTED_CREDENTIAL]");
|
|
107
|
+
await node_fs_1.default.promises.writeFile(destPath, sanitized);
|
|
108
|
+
}
|
|
109
|
+
else if (entry.name.endsWith(".json") ||
|
|
110
|
+
entry.name.endsWith(".yaml") ||
|
|
111
|
+
entry.name.endsWith(".yml")) {
|
|
112
|
+
await node_fs_1.default.promises.copyFile(srcPath, destPath);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.runCLI = runCLI;
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const commander_1 = require("commander");
|
|
12
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
13
|
+
const ora_1 = __importDefault(require("ora"));
|
|
14
|
+
const initGlobal_1 = require("../setup/initGlobal");
|
|
15
|
+
const program = new commander_1.Command();
|
|
16
|
+
program.name("memory").description("Local Agent Memory CLI").version("1.0.0");
|
|
17
|
+
const hook_1 = require("./commands/hook");
|
|
18
|
+
const init_1 = require("./commands/init");
|
|
19
|
+
const record_1 = require("./commands/record");
|
|
20
|
+
const status_1 = require("./commands/status");
|
|
21
|
+
const sync_1 = require("./commands/sync");
|
|
22
|
+
const template_1 = require("./commands/template");
|
|
23
|
+
program
|
|
24
|
+
.command("init")
|
|
25
|
+
.description("Initialize a new memory project")
|
|
26
|
+
.option("-f, --force", "Force initialization even if already exists")
|
|
27
|
+
.option("-r, --rules <rules>", "Comma-separated IDE rules to inject (cursor,claude,copilot,antigravity-ide,antigravity-cli,all)")
|
|
28
|
+
.option("-d, --docs <path>", "Optional path to an existing docs directory to map/index automatically")
|
|
29
|
+
.action(init_1.initProject);
|
|
30
|
+
program
|
|
31
|
+
.command("template <action>")
|
|
32
|
+
.description("Manage memory templates (list, create, export-from-project)")
|
|
33
|
+
.option("-n, --name <name>", "Name of the template")
|
|
34
|
+
.action(template_1.manageTemplates);
|
|
35
|
+
program
|
|
36
|
+
.command("sync")
|
|
37
|
+
.description("Sync markdown files to the local memory index")
|
|
38
|
+
.action(sync_1.syncProject);
|
|
39
|
+
const reindex_1 = require("./commands/reindex");
|
|
40
|
+
program
|
|
41
|
+
.command("status")
|
|
42
|
+
.description("Check the sync status of the current memory project")
|
|
43
|
+
.action(status_1.projectStatus);
|
|
44
|
+
program
|
|
45
|
+
.command("reindex")
|
|
46
|
+
.alias("prune")
|
|
47
|
+
.description("Rebuild the vector and FTS indexes from scratch (in case of corruption)")
|
|
48
|
+
.action(reindex_1.reindexProject);
|
|
49
|
+
program
|
|
50
|
+
.command("record-session")
|
|
51
|
+
.description("Record a summary of the current session into the memory ledger")
|
|
52
|
+
.option("-s, --summary <summary>", "Summary of the work done in this session")
|
|
53
|
+
.action(record_1.recordSession);
|
|
54
|
+
program
|
|
55
|
+
.command("install-hook")
|
|
56
|
+
.description("Install Git hooks to automatically record sessions or remind developers on commit")
|
|
57
|
+
.action(hook_1.installGitHook);
|
|
58
|
+
program
|
|
59
|
+
.command("mcp-setup")
|
|
60
|
+
.description("Configure the MCP server for Cursor or Claude")
|
|
61
|
+
.action(async () => {
|
|
62
|
+
const spinner = (0, ora_1.default)("Setting up global environment...").start();
|
|
63
|
+
try {
|
|
64
|
+
await (0, initGlobal_1.initGlobalEnv)();
|
|
65
|
+
spinner.succeed("Global environment configured (ONNX model, sqlite-vec downloaded).");
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
spinner.fail("Failed to set up global environment.");
|
|
69
|
+
console.error("\x1b[31m%s\x1b[0m", error);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
const { client } = await inquirer_1.default.prompt([
|
|
73
|
+
{
|
|
74
|
+
type: "list",
|
|
75
|
+
name: "client",
|
|
76
|
+
message: "Which client do you want to configure?",
|
|
77
|
+
choices: [
|
|
78
|
+
"Claude Desktop",
|
|
79
|
+
"Cursor",
|
|
80
|
+
"Antigravity IDE",
|
|
81
|
+
"Antigravity CLI",
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
]);
|
|
85
|
+
let configPath = "";
|
|
86
|
+
if (client === "Claude Desktop") {
|
|
87
|
+
if (node_os_1.default.platform() === "win32") {
|
|
88
|
+
configPath = node_path_1.default.join(node_os_1.default.homedir(), "AppData", "Roaming", "Claude", "claude_desktop_config.json");
|
|
89
|
+
}
|
|
90
|
+
else if (node_os_1.default.platform() === "darwin") {
|
|
91
|
+
configPath = node_path_1.default.join(node_os_1.default.homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
// Fallback for Linux or others if needed
|
|
95
|
+
configPath = node_path_1.default.join(node_os_1.default.homedir(), ".config", "Claude", "claude_desktop_config.json");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else if (client === "Antigravity IDE" || client === "Antigravity CLI") {
|
|
99
|
+
configPath = node_path_1.default.join(node_os_1.default.homedir(), ".gemini", "config", "mcp_config.json");
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
// Cursor logic
|
|
103
|
+
configPath = node_path_1.default.join(node_os_1.default.homedir(), ".cursor", "mcp.json"); // Assumed path for cursor
|
|
104
|
+
}
|
|
105
|
+
// Ensure directory exists
|
|
106
|
+
const configDir = node_path_1.default.dirname(configPath);
|
|
107
|
+
if (!node_fs_1.default.existsSync(configDir)) {
|
|
108
|
+
await node_fs_1.default.promises.mkdir(configDir, { recursive: true });
|
|
109
|
+
}
|
|
110
|
+
// Backup config if it exists
|
|
111
|
+
let configObj = {};
|
|
112
|
+
if (node_fs_1.default.existsSync(configPath)) {
|
|
113
|
+
const backupPath = `${configPath}.backup-${Date.now()}`;
|
|
114
|
+
await node_fs_1.default.promises.copyFile(configPath, backupPath);
|
|
115
|
+
console.log(`Created backup at: ${backupPath}`);
|
|
116
|
+
try {
|
|
117
|
+
const fileContent = await node_fs_1.default.promises.readFile(configPath, "utf8");
|
|
118
|
+
configObj = JSON.parse(fileContent);
|
|
119
|
+
}
|
|
120
|
+
catch (_e) {
|
|
121
|
+
console.error("Failed to parse existing config, starting fresh.");
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Modify config (same format for Claude Desktop, Cursor, and Antigravity)
|
|
125
|
+
configObj.mcpServers = configObj.mcpServers || {};
|
|
126
|
+
configObj.mcpServers["local-agent-memory"] = {
|
|
127
|
+
command: "memory-mcp",
|
|
128
|
+
args: [],
|
|
129
|
+
};
|
|
130
|
+
// Write modified config
|
|
131
|
+
await node_fs_1.default.promises.writeFile(configPath, JSON.stringify(configObj, null, 2));
|
|
132
|
+
console.log(`Successfully configured MCP for ${client} at ${configPath}`);
|
|
133
|
+
});
|
|
134
|
+
async function runCLI() {
|
|
135
|
+
await program.parseAsync(process.argv);
|
|
136
|
+
}
|
|
137
|
+
/* istanbul ignore if */
|
|
138
|
+
if (require.main === module) {
|
|
139
|
+
runCLI().catch(console.error);
|
|
140
|
+
}
|
package/dist/cli/mcp.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.runMCP = runMCP;
|
|
5
|
+
const server_1 = require("../mcp/server");
|
|
6
|
+
async function runMCP() {
|
|
7
|
+
await (0, server_1.setupMCPServer)();
|
|
8
|
+
}
|
|
9
|
+
// Start MCP server if run directly
|
|
10
|
+
/* istanbul ignore if */
|
|
11
|
+
if (require.main === module) {
|
|
12
|
+
runMCP().catch(console.error);
|
|
13
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { FeatureExtractionPipeline } from "@xenova/transformers";
|
|
2
|
+
/**
|
|
3
|
+
* Initializes and returns the feature extraction pipeline.
|
|
4
|
+
* It uses a singleton pattern to only load the model once.
|
|
5
|
+
*/
|
|
6
|
+
export declare function getExtractor(): Promise<FeatureExtractionPipeline>;
|
|
7
|
+
/**
|
|
8
|
+
* Generates embeddings for a given text.
|
|
9
|
+
* Uses `@xenova/transformers` with `Xenova/all-MiniLM-L6-v2`
|
|
10
|
+
*
|
|
11
|
+
* @param text The input text to vectorise.
|
|
12
|
+
* @returns An array of floats representing the embedding vector.
|
|
13
|
+
*/
|
|
14
|
+
export declare function generateEmbedding(text: string): Promise<number[]>;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.getExtractor = getExtractor;
|
|
37
|
+
exports.generateEmbedding = generateEmbedding;
|
|
38
|
+
let extractorPromise = null;
|
|
39
|
+
let inactivityTimeout = null;
|
|
40
|
+
const INACTIVITY_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutos
|
|
41
|
+
function resetInactivityTimeout() {
|
|
42
|
+
if (inactivityTimeout) {
|
|
43
|
+
clearTimeout(inactivityTimeout);
|
|
44
|
+
}
|
|
45
|
+
inactivityTimeout = setTimeout(async () => {
|
|
46
|
+
if (extractorPromise) {
|
|
47
|
+
try {
|
|
48
|
+
const extractor = await extractorPromise;
|
|
49
|
+
// Si el pipeline soporta dispose, lo llamamos para liberar memoria del runtime ONNX
|
|
50
|
+
if (typeof extractor.dispose === "function") {
|
|
51
|
+
await extractor.dispose();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (_err) {
|
|
55
|
+
// Ignorar posibles errores en el dispose
|
|
56
|
+
}
|
|
57
|
+
extractorPromise = null;
|
|
58
|
+
}
|
|
59
|
+
}, INACTIVITY_TIMEOUT_MS);
|
|
60
|
+
// Evitamos que este timeout mantenga vivo el proceso de Node.js si no hay nada más corriendo
|
|
61
|
+
if (inactivityTimeout.unref) {
|
|
62
|
+
inactivityTimeout.unref();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Initializes and returns the feature extraction pipeline.
|
|
67
|
+
* It uses a singleton pattern to only load the model once.
|
|
68
|
+
*/
|
|
69
|
+
async function getExtractor() {
|
|
70
|
+
resetInactivityTimeout();
|
|
71
|
+
if (!extractorPromise) {
|
|
72
|
+
extractorPromise = (async () => {
|
|
73
|
+
const { pipeline, env } = await Promise.resolve().then(() => __importStar(require("@xenova/transformers")));
|
|
74
|
+
const os = await Promise.resolve().then(() => __importStar(require("node:os")));
|
|
75
|
+
const path = await Promise.resolve().then(() => __importStar(require("node:path")));
|
|
76
|
+
const fs = await Promise.resolve().then(() => __importStar(require("node:fs")));
|
|
77
|
+
// Ensure we use the correct models directory
|
|
78
|
+
const modelsDir = path.join(os.homedir(), ".local_memory", "models");
|
|
79
|
+
env.cacheDir = modelsDir;
|
|
80
|
+
// Check if the model is corrupted (size < ~22MB) before loading
|
|
81
|
+
const modelPath = path.join(modelsDir, "Xenova", "all-MiniLM-L6-v2", "onnx", "model_quantized.onnx");
|
|
82
|
+
if (fs.existsSync(modelPath)) {
|
|
83
|
+
const stats = fs.statSync(modelPath);
|
|
84
|
+
if (stats.size < 22000000) {
|
|
85
|
+
console.warn("⚠️ Corrupted or incomplete ONNX model found. Removing to trigger re-download...");
|
|
86
|
+
fs.rmSync(path.join(modelsDir, "Xenova", "all-MiniLM-L6-v2"), {
|
|
87
|
+
recursive: true,
|
|
88
|
+
force: true,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
return (await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2", {
|
|
94
|
+
quantized: true,
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
// If it fails to load, it might be corrupted. Clean it up.
|
|
99
|
+
console.warn("⚠️ Error loading ONNX model, it may be corrupted. Cleaning up cache...", err);
|
|
100
|
+
if (fs.existsSync(path.join(modelsDir, "Xenova", "all-MiniLM-L6-v2"))) {
|
|
101
|
+
fs.rmSync(path.join(modelsDir, "Xenova", "all-MiniLM-L6-v2"), {
|
|
102
|
+
recursive: true,
|
|
103
|
+
force: true,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
throw err;
|
|
107
|
+
}
|
|
108
|
+
})();
|
|
109
|
+
}
|
|
110
|
+
return extractorPromise;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Generates embeddings for a given text.
|
|
114
|
+
* Uses `@xenova/transformers` with `Xenova/all-MiniLM-L6-v2`
|
|
115
|
+
*
|
|
116
|
+
* @param text The input text to vectorise.
|
|
117
|
+
* @returns An array of floats representing the embedding vector.
|
|
118
|
+
*/
|
|
119
|
+
async function generateEmbedding(text) {
|
|
120
|
+
const extractor = await getExtractor();
|
|
121
|
+
// Await the extraction
|
|
122
|
+
const output = await extractor(text, { pooling: "mean", normalize: true });
|
|
123
|
+
// Extend timeout after work is done as well
|
|
124
|
+
resetInactivityTimeout();
|
|
125
|
+
// Return the raw float array data
|
|
126
|
+
return Array.from(output.data);
|
|
127
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface Chunk {
|
|
2
|
+
path: string;
|
|
3
|
+
text: string;
|
|
4
|
+
breadcrumbs: string;
|
|
5
|
+
startLine: number;
|
|
6
|
+
endLine: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* A very simple token count heuristic (4 chars ~ 1 token)
|
|
10
|
+
* to avoid depending on tiktoken or similar heavy libraries,
|
|
11
|
+
* suitable for MiniLM approximation.
|
|
12
|
+
*/
|
|
13
|
+
export declare function countTokens(text: string): number;
|
|
14
|
+
/**
|
|
15
|
+
* Parses markdown content and breaks it down into chunks of text
|
|
16
|
+
* bounded by maxTokens, aiming to preserve logical code blocks and headings.
|
|
17
|
+
* Includes simple breadcrumbs based on headings.
|
|
18
|
+
*
|
|
19
|
+
* @param content The markdown text content.
|
|
20
|
+
* @param path The file path (for metadata).
|
|
21
|
+
* @param maxTokens Maximum number of tokens per chunk.
|
|
22
|
+
* @param overlapTokens Target token overlap between chunks.
|
|
23
|
+
* @returns Array of chunk objects.
|
|
24
|
+
*/
|
|
25
|
+
export declare function parseMarkdown(content: string, path: string, maxTokens?: number, overlapTokens?: number): Promise<Chunk[]>;
|