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:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,5 @@
|
||||
node_modules/
|
||||
dist/
|
||||
bundle/
|
||||
build/
|
||||
.memory/
|
||||
|
||||
192
USAGE.md
192
USAGE.md
@@ -8,11 +8,65 @@ Data is stored in `.memory/cortex.db` (SQLite) in the current working directory.
|
||||
|
||||
---
|
||||
|
||||
## Installation & Building
|
||||
|
||||
### Development Mode
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Build TypeScript
|
||||
npm run build
|
||||
|
||||
# Run CLI (development)
|
||||
node dist/cli/index.js <command>
|
||||
|
||||
# Run MCP server (development)
|
||||
node dist/mcp/index.js
|
||||
```
|
||||
|
||||
### Standalone Executables
|
||||
|
||||
Build self-contained executables that don't require Node.js:
|
||||
|
||||
```bash
|
||||
# Build for Windows (creates cortex.exe and cortex-mcp.exe)
|
||||
npm run package:win
|
||||
|
||||
# Build for Linux
|
||||
npm run package:linux
|
||||
|
||||
# Build for macOS
|
||||
npm run package:mac
|
||||
|
||||
# Build all (CLI + MCP for current platform)
|
||||
npm run package
|
||||
```
|
||||
|
||||
**Output files** (in `build/` directory):
|
||||
- `cortex.exe` / `cortex` — CLI tool
|
||||
- `cortex-mcp.exe` / `cortex-mcp` — MCP server for Claude Code integration
|
||||
|
||||
### Running Standalone
|
||||
|
||||
```bash
|
||||
# CLI
|
||||
./build/cortex.exe list
|
||||
./build/cortex.exe query "authentication"
|
||||
./build/cortex.exe serve --port 3100
|
||||
|
||||
# MCP server (for Claude Code)
|
||||
./build/cortex-mcp.exe
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CLI Reference
|
||||
|
||||
All commands are invoked as `memory <command>`.
|
||||
All commands are invoked as `cortex <command>` (standalone) or `node dist/cli/index.js <command>` (dev).
|
||||
|
||||
### `memory add <kind>`
|
||||
### `cortex add <kind>`
|
||||
|
||||
Add a node to the knowledge graph.
|
||||
|
||||
@@ -26,14 +80,14 @@ Add a node to the knowledge graph.
|
||||
| `--section <section>` | no | Structured section as `"Label: body"` (repeatable) |
|
||||
|
||||
```bash
|
||||
memory add memory -t "Auth flow design" -c "OAuth2 PKCE flow for SPA" --tags auth,security --status active
|
||||
memory add memory -t "add command" --section "Arguments: <kind> — memory, component, task, decision" --section "Options: -t title, -c content, --tags, --status"
|
||||
memory add task -t "Implement login page" --status todo
|
||||
memory add decision -t "Use PostgreSQL" -c "Chose Postgres over MySQL for JSON support"
|
||||
memory add component -t "UserService" -c "Handles user CRUD operations" --tags backend
|
||||
cortex add memory -t "Auth flow design" -c "OAuth2 PKCE flow for SPA" --tags auth,security --status active
|
||||
cortex add memory -t "add command" --section "Arguments: <kind> — memory, component, task, decision" --section "Options: -t title, -c content, --tags, --status"
|
||||
cortex add task -t "Implement login page" --status todo
|
||||
cortex add decision -t "Use PostgreSQL" -c "Chose Postgres over MySQL for JSON support"
|
||||
cortex add component -t "UserService" -c "Handles user CRUD operations" --tags backend
|
||||
```
|
||||
|
||||
### `memory query <text>`
|
||||
### `cortex query <text>`
|
||||
|
||||
Search the knowledge graph using natural language. Uses hybrid BM25 + vector + freshness scoring.
|
||||
|
||||
@@ -45,12 +99,12 @@ Search the knowledge graph using natural language. Uses hybrid BM25 + vector + f
|
||||
| `--format <fmt>` | no | Output format: `text` or `json` (default: `text`) |
|
||||
|
||||
```bash
|
||||
memory query "authentication"
|
||||
memory query "database decisions" --kind decision
|
||||
memory query "user service" --limit 5 --format json
|
||||
cortex query "authentication"
|
||||
cortex query "database decisions" --kind decision
|
||||
cortex query "user service" --limit 5 --format json
|
||||
```
|
||||
|
||||
### `memory show <id>`
|
||||
### `cortex show <id>`
|
||||
|
||||
Show a node's full details, structured sections, inline children, and connections. The `<id>` can be a full UUID or a unique prefix.
|
||||
|
||||
@@ -64,11 +118,11 @@ If the node has `metadata.sections`, they render as labeled blocks. If the node
|
||||
| `--format <fmt>` | no | Output format: `text` or `json` (default: `text`) |
|
||||
|
||||
```bash
|
||||
memory show abc123
|
||||
memory show abc123 --format json
|
||||
cortex show abc123
|
||||
cortex show abc123 --format json
|
||||
```
|
||||
|
||||
### `memory list`
|
||||
### `cortex list`
|
||||
|
||||
List nodes in the knowledge graph.
|
||||
|
||||
@@ -82,14 +136,14 @@ List nodes in the knowledge graph.
|
||||
| `--format <fmt>` | no | Output format: `text` or `json` (default: `text`) |
|
||||
|
||||
```bash
|
||||
memory list
|
||||
memory list --kind task --status todo
|
||||
memory list --tags auth,security
|
||||
memory list --stale
|
||||
memory list --format json
|
||||
cortex list
|
||||
cortex list --kind task --status todo
|
||||
cortex list --tags auth,security
|
||||
cortex list --stale
|
||||
cortex list --format json
|
||||
```
|
||||
|
||||
### `memory update <id>`
|
||||
### `cortex update <id>`
|
||||
|
||||
Update an existing node's fields.
|
||||
|
||||
@@ -104,13 +158,13 @@ Update an existing node's fields.
|
||||
| `--section <section>` | no | Structured section as `"Label: body"` (repeatable, replaces by label) |
|
||||
|
||||
```bash
|
||||
memory update abc123 --status done
|
||||
memory update abc123 -t "Updated title" -c "New content"
|
||||
memory update abc123 --tags newtag1,newtag2
|
||||
memory update abc123 --section "Notes: Updated implementation notes"
|
||||
cortex update abc123 --status done
|
||||
cortex update abc123 -t "Updated title" -c "New content"
|
||||
cortex update abc123 --tags newtag1,newtag2
|
||||
cortex update abc123 --section "Notes: Updated implementation notes"
|
||||
```
|
||||
|
||||
### `memory remove <id>`
|
||||
### `cortex remove <id>`
|
||||
|
||||
Remove a node. Default is soft delete (marks as stale). Use `--hard` for permanent deletion.
|
||||
|
||||
@@ -120,11 +174,11 @@ Remove a node. Default is soft delete (marks as stale). Use `--hard` for permane
|
||||
| `--hard` | no | Permanently delete instead of marking stale |
|
||||
|
||||
```bash
|
||||
memory remove abc123
|
||||
memory remove abc123 --hard
|
||||
cortex remove abc123
|
||||
cortex remove abc123 --hard
|
||||
```
|
||||
|
||||
### `memory link <fromId> <toId>`
|
||||
### `cortex link <fromId> <toId>`
|
||||
|
||||
Create a directed edge between two nodes.
|
||||
|
||||
@@ -135,11 +189,11 @@ Create a directed edge between two nodes.
|
||||
| `--type <type>` | yes | Edge type: `depends_on`, `contains`, `implements`, `blocked_by`, `subtask_of`, `relates_to`, `supersedes`, `about` |
|
||||
|
||||
```bash
|
||||
memory link abc123 def456 --type depends_on
|
||||
memory link abc123 def456 --type contains
|
||||
cortex link abc123 def456 --type depends_on
|
||||
cortex link abc123 def456 --type contains
|
||||
```
|
||||
|
||||
### `memory graph [id]`
|
||||
### `cortex graph [id]`
|
||||
|
||||
Visualize the knowledge graph as an ASCII tree. Optionally root at a specific node.
|
||||
|
||||
@@ -148,11 +202,11 @@ Visualize the knowledge graph as an ASCII tree. Optionally root at a specific no
|
||||
| `[id]` | no | Root node ID or prefix. Omit for full graph. |
|
||||
|
||||
```bash
|
||||
memory graph
|
||||
memory graph abc123
|
||||
cortex graph
|
||||
cortex graph abc123
|
||||
```
|
||||
|
||||
### `memory children <id>`
|
||||
### `cortex children <id>`
|
||||
|
||||
List child nodes connected via outgoing `contains` edges from the given node.
|
||||
|
||||
@@ -163,12 +217,12 @@ List child nodes connected via outgoing `contains` edges from the given node.
|
||||
| `--format <fmt>` | no | Output format: `text` or `json` (default: `text`) |
|
||||
|
||||
```bash
|
||||
memory children abc123
|
||||
memory children abc123 --kind task
|
||||
memory children abc123 --format json
|
||||
cortex children abc123
|
||||
cortex children abc123 --kind task
|
||||
cortex children abc123 --format json
|
||||
```
|
||||
|
||||
### `memory decay`
|
||||
### `cortex decay`
|
||||
|
||||
Run auto-decay to mark old untouched nodes as stale. Nodes whose `lastAccessedAt` exceeds the threshold are marked stale and hidden from default listings and search.
|
||||
|
||||
@@ -177,12 +231,12 @@ Run auto-decay to mark old untouched nodes as stale. Nodes whose `lastAccessedAt
|
||||
| `--days <number>` | no | Max age in days before decay (default: 180) |
|
||||
|
||||
```bash
|
||||
memory decay
|
||||
memory decay --days 90
|
||||
memory decay --days 0 # decay all nodes not accessed today
|
||||
cortex decay
|
||||
cortex decay --days 90
|
||||
cortex decay --days 0 # decay all nodes not accessed today
|
||||
```
|
||||
|
||||
### `memory serve`
|
||||
### `cortex serve`
|
||||
|
||||
Start the Cortex Portal web server. Auto-decay runs on startup and every 24 hours.
|
||||
|
||||
@@ -191,8 +245,8 @@ Start the Cortex Portal web server. Auto-decay runs on startup and every 24 hour
|
||||
| `-p, --port <number>` | no | Port number (default: 3100) |
|
||||
|
||||
```bash
|
||||
memory serve
|
||||
memory serve --port 8080
|
||||
cortex serve
|
||||
cortex serve --port 8080
|
||||
```
|
||||
|
||||
---
|
||||
@@ -223,7 +277,7 @@ memory serve --port 8080
|
||||
|
||||
Nodes can have structured content via `metadata.sections`. Each section has a `label` and `body`. Use the `--section "Label: body"` flag on `add` or `update` to create sections.
|
||||
|
||||
When displayed with `memory show`, sections render as:
|
||||
When displayed with `cortex show`, sections render as:
|
||||
|
||||
```
|
||||
── Arguments ──
|
||||
@@ -233,7 +287,7 @@ When displayed with `memory show`, sections render as:
|
||||
-t title, -c content, --tags, --status
|
||||
```
|
||||
|
||||
The `show` command also displays inline children (nodes linked via `contains` edges). Use `memory children <id>` to list only children.
|
||||
The `show` command also displays inline children (nodes linked via `contains` edges). Use `cortex children <id>` to list only children.
|
||||
|
||||
## Search Scoring
|
||||
|
||||
@@ -267,7 +321,7 @@ The web server exposes these endpoints under `/api`:
|
||||
|
||||
Cortex includes an MCP (Model Context Protocol) server that exposes memory tools directly to Claude Code.
|
||||
|
||||
### Setup
|
||||
### Setup (Development)
|
||||
|
||||
1. Build the project: `npm run build`
|
||||
2. Ensure `.mcp.json` exists in the project root:
|
||||
@@ -285,6 +339,35 @@ Cortex includes an MCP (Model Context Protocol) server that exposes memory tools
|
||||
|
||||
3. Restart Claude Code — the memory tools will appear automatically.
|
||||
|
||||
### Setup (Standalone Executable)
|
||||
|
||||
1. Build the MCP server: `npm run package:win` (or `package:linux`/`package:mac`)
|
||||
2. Configure `.mcp.json` to use the standalone executable:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"memory": {
|
||||
"command": "./build/cortex-mcp.exe"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Or with an absolute path for use from any directory:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"memory": {
|
||||
"command": "C:/path/to/cortex-mcp.exe"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Restart Claude Code — the memory tools will appear automatically.
|
||||
|
||||
### MCP Tools
|
||||
|
||||
| Tool | Description | Parameters |
|
||||
@@ -295,9 +378,22 @@ Cortex includes an MCP (Model Context Protocol) server that exposes memory tools
|
||||
| `memory_children` | List children of a node | `id`, `kind?` |
|
||||
| `memory_add` | Add a new node | `kind`, `title`, `content?`, `tags?`, `status?`, `sections?` |
|
||||
| `memory_link` | Create an edge between nodes | `fromId`, `toId`, `type` |
|
||||
| `memory_split` | Break a large node into smaller children | `id`, `pieces`, `summary?` |
|
||||
| `memory_merge` | Merge multiple nodes into one | `nodeIds`, `title`, `content`, `kind?` |
|
||||
| `memory_dedupe` | Find similar/duplicate nodes | `threshold?`, `kind?`, `limit?` |
|
||||
| `memory_prune` | Clean up stale nodes or orphans | `mode`, `maxAgeDays?` |
|
||||
| `memory_reorganize` | Move a node under a new parent | `nodeId`, `newParentId` |
|
||||
| `memory_bulk_tag` | Add/remove tags on multiple nodes | `action`, `tags`, `nodeIds?`, `filter?` |
|
||||
| `memory_stats` | Get graph statistics | — |
|
||||
| `memory_summary` | Get hierarchical summary of the graph | `refresh?` |
|
||||
| `memory_prompt` | Execute natural language instruction | `prompt` |
|
||||
|
||||
### Manual Testing
|
||||
|
||||
```bash
|
||||
# Development
|
||||
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"0.1"}}}' | node dist/mcp/index.js
|
||||
|
||||
# Standalone
|
||||
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"0.1"}}}' | ./build/cortex-mcp.exe
|
||||
```
|
||||
|
||||
1548
package-lock.json
generated
1548
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
32
package.json
32
package.json
@@ -5,15 +5,37 @@
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch",
|
||||
"serve": "node dist/server/index.js"
|
||||
"serve": "node dist/server/index.js",
|
||||
"bundle": "npm run build && npm run bundle:cli && npm run bundle:mcp",
|
||||
"bundle:cli": "esbuild dist/cli/index.js --bundle --platform=node --outfile=bundle/cli.js --external:better-sqlite3",
|
||||
"bundle:mcp": "esbuild dist/mcp/index.js --bundle --platform=node --outfile=bundle/mcp.js --external:better-sqlite3",
|
||||
"package": "npm run bundle && npm run package:cli && npm run package:mcp",
|
||||
"package:cli": "pkg ./bundle/cli.js --targets node22-win-x64 --output build/cortex.exe --config package.json",
|
||||
"package:mcp": "pkg ./bundle/mcp.js --targets node22-win-x64 --output build/cortex-mcp.exe --config package.json",
|
||||
"package:win": "npm run bundle && npm run package:cli && npm run package:mcp",
|
||||
"package:linux": "npm run bundle && pkg ./bundle/cli.js --targets node22-linux-x64 --output build/cortex --config package.json && pkg ./bundle/mcp.js --targets node22-linux-x64 --output build/cortex-mcp --config package.json",
|
||||
"package:mac": "npm run bundle && pkg ./bundle/cli.js --targets node22-macos-x64 --output build/cortex --config package.json && pkg ./bundle/mcp.js --targets node22-macos-x64 --output build/cortex-mcp --config package.json"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "AI project memory - knowledge graph backed by SQLite + Ollama embeddings",
|
||||
"bin": {
|
||||
"memory": "./dist/cli/index.js",
|
||||
"memory-mcp": "./dist/mcp/index.js"
|
||||
"cortex": "./dist/cli/index.js",
|
||||
"cortex-mcp": "./dist/mcp/index.js"
|
||||
},
|
||||
"pkg": {
|
||||
"targets": [
|
||||
"node22-win-x64"
|
||||
],
|
||||
"outputPath": "build",
|
||||
"assets": [
|
||||
"node_modules/better-sqlite3/build/Release/*.node",
|
||||
"node_modules/better-sqlite3/prebuilds/**/*"
|
||||
],
|
||||
"scripts": [
|
||||
"dist/**/*.js"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.25.3",
|
||||
@@ -22,7 +44,6 @@
|
||||
"commander": "^14.0.3",
|
||||
"cors": "^2.8.6",
|
||||
"express": "^5.2.1",
|
||||
"uuid": "^13.0.0",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -30,7 +51,8 @@
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/node": "^25.2.0",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@yao-pkg/pkg": "^6.3.0",
|
||||
"esbuild": "^0.27.2",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user