local-agent-memory 0.0.0-stage → 1.0.1

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 (43) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +108 -2
  3. package/dist/cli/commands/hook.d.ts +1 -0
  4. package/dist/cli/commands/hook.js +66 -0
  5. package/dist/cli/commands/init.d.ts +5 -0
  6. package/dist/cli/commands/init.js +158 -0
  7. package/dist/cli/commands/record.d.ts +3 -0
  8. package/dist/cli/commands/record.js +58 -0
  9. package/dist/cli/commands/reindex.d.ts +1 -0
  10. package/dist/cli/commands/reindex.js +41 -0
  11. package/dist/cli/commands/status.d.ts +1 -0
  12. package/dist/cli/commands/status.js +88 -0
  13. package/dist/cli/commands/sync.d.ts +8 -0
  14. package/dist/cli/commands/sync.js +134 -0
  15. package/dist/cli/commands/template.d.ts +1 -0
  16. package/dist/cli/commands/template.js +116 -0
  17. package/dist/cli/index.d.ts +2 -0
  18. package/dist/cli/index.js +140 -0
  19. package/dist/cli/mcp.d.ts +2 -0
  20. package/dist/cli/mcp.js +13 -0
  21. package/dist/core/embeddings.d.ts +14 -0
  22. package/dist/core/embeddings.js +127 -0
  23. package/dist/core/parser.d.ts +25 -0
  24. package/dist/core/parser.js +151 -0
  25. package/dist/core/search.d.ts +19 -0
  26. package/dist/core/search.js +100 -0
  27. package/dist/core/sync.d.ts +22 -0
  28. package/dist/core/sync.js +159 -0
  29. package/dist/db/project.d.ts +12 -0
  30. package/dist/db/project.js +113 -0
  31. package/dist/db/registry.d.ts +9 -0
  32. package/dist/db/registry.js +92 -0
  33. package/dist/mcp/middleware.d.ts +1 -0
  34. package/dist/mcp/middleware.js +46 -0
  35. package/dist/mcp/server.d.ts +2 -0
  36. package/dist/mcp/server.js +102 -0
  37. package/dist/mcp/tools.d.ts +2 -0
  38. package/dist/mcp/tools.js +244 -0
  39. package/dist/mcp/utils.d.ts +5 -0
  40. package/dist/mcp/utils.js +82 -0
  41. package/dist/setup/initGlobal.d.ts +5 -0
  42. package/dist/setup/initGlobal.js +167 -0
  43. 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,2 @@
1
+ #!/usr/bin/env node
2
+ export declare function runCLI(): Promise<void>;
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export declare function runMCP(): Promise<void>;
@@ -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[]>;