Add standalone executable packaging with esbuild + pkg

- Add npm scripts for building Windows/Linux/macOS executables
- Replace uuid package with crypto.randomUUID() for ESM compatibility
- Use esbuild to pre-bundle code before pkg (fixes MCP SDK subpath exports)
- Update CLI name from 'memory' to 'cortex'
- Update USAGE.md with build instructions and standalone setup
- Add bundle/ and build/ to .gitignore
This commit is contained in:
2026-02-03 09:14:26 +01:00
parent 661325a235
commit 3d5a979a1b
8 changed files with 1735 additions and 80 deletions

View File

@@ -16,8 +16,8 @@ import { closeDb } from '../core/db';
const program = new Command();
program
.name('memory')
.description('Cortex — AI project memory & knowledge graph')
.name('cortex')
.description('Cortex — AI project memory & knowledge graph\n\nStore, link, and search project knowledge as a graph of typed nodes.')
.version('1.0.0');
program.addCommand(addCommand);

View File

@@ -1,4 +1,4 @@
import { v4 as uuid } from 'uuid';
import { randomUUID as uuid } from 'crypto';
import { getDb } from './db';
import { Node, Edge, AddNodeInput, UpdateNodeInput, ListOptions, QueryOptions, SearchResult, EdgeType } from '../types';
import { hybridSearch, deserializeEmbedding } from './search/index';

View File

@@ -367,6 +367,24 @@ server.tool(
}
);
// --- memory_summary ---
import { getCachedSummary, generateSummary } from '../core/summary';
server.tool(
'memory_summary',
'Get a pre-computed hierarchical summary of the memory graph. Use this instead of memory_list to reduce context usage.',
{
refresh: z.boolean().optional().describe('Force regenerate summary (default: use cached)'),
},
async ({ refresh }) => {
let summary = refresh ? null : getCachedSummary();
if (!summary) {
summary = await generateSummary();
}
return { content: [{ type: 'text' as const, text: serialize(summary) }] };
}
);
// --- memory_prompt ---
import { interpretAndExecute } from '../core/prompt/interpreter';

View File

@@ -2,6 +2,7 @@ import { getDb } from '../core/db';
import { deserializeEmbedding } from '../core/search/index';
import { cosineSimilarity } from '../core/search/vector';
import { isGenAvailable, generate } from '../core/search/ollamaGen';
import { generateSummary } from '../core/summary';
let dirty = false;
@@ -194,7 +195,7 @@ Existing tags: ${tagVocab.join(', ')}`;
if (orphans.length > 0 && nonOrphans.length > 0) {
const insertEdge = db.prepare('INSERT INTO edges (id, from_id, to_id, type, metadata, created_at) VALUES (?, ?, ?, ?, ?, ?)');
const { v4: uuidv4 } = require('uuid');
const { randomUUID: uuidv4 } = require('crypto');
if (aiAvailable) {
for (const orphan of orphans) {
@@ -265,7 +266,7 @@ Content: ${node.content.slice(0, 2000)}`;
// Auto-split: content > 2000 chars
const hugeNodes = nodes.filter(n => n.content.length > 2000 && !staleIds.has(n.id));
const { v4: uuidv4 } = require('uuid');
const { randomUUID: uuidv4Split } = require('crypto');
const insertNode = db.prepare(`INSERT INTO nodes (id, kind, title, content, status, tags, metadata, embedding, created_at, updated_at, last_accessed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
const insertEdge2 = db.prepare('INSERT INTO edges (id, from_id, to_id, type, metadata, created_at) VALUES (?, ?, ?, ?, ?, ?)');
@@ -289,10 +290,10 @@ Content: ${node.content.slice(0, 3000)}`;
const titleMatch = section.match(/Title:\s*(.+)/);
const contentMatch = section.match(/Content:\s*([\s\S]+)/);
if (titleMatch && contentMatch) {
const childId = uuidv4();
const childId = uuidv4Split();
insertNode.run(childId, node.kind, titleMatch[1].trim(), contentMatch[1].trim(),
null, JSON.stringify(node.tags), JSON.stringify({}), null, now, now, now);
insertEdge2.run(uuidv4(), node.id, childId, 'contains', '{}', now);
insertEdge2.run(uuidv4Split(), node.id, childId, 'contains', '{}', now);
childIds.push(childId);
}
}
@@ -334,6 +335,14 @@ Content: ${node.content.slice(0, 300)}`;
const pruneResult = db.prepare('DELETE FROM nodes WHERE is_stale = 1 AND updated_at < ?').run(thirtyDaysAgo);
pruned = pruneResult.changes;
// Regenerate summary cache
try {
await generateSummary();
console.log('[Heartbeat] Summary cache regenerated');
} catch (err) {
console.error('[Heartbeat] Failed to regenerate summary:', err);
}
const report: HeartbeatReport = {
ranAt: now, deduped, autoTagged, autoOrganized, pruned,
summarized, merged, split: splitCount, archived,