@postnesia/cli 0.1.2
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/README.md +117 -0
- package/dist/index.js +214 -0
- package/package.json +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Postnesia Memory MCP Server
|
|
2
|
+
|
|
3
|
+
Model Context Protocol server for main agent memory system. Exposes memory operations as standardized tools.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
cd postnesia
|
|
9
|
+
npm install
|
|
10
|
+
npm run db:generate
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Configuration
|
|
14
|
+
|
|
15
|
+
Add to your MCP settings (e.g., Claude Desktop config):
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"mcpServers": {
|
|
20
|
+
"postnesia": {
|
|
21
|
+
"command": "/absolute/path/to/tsx",
|
|
22
|
+
"env": {
|
|
23
|
+
"DATABASE_URL": "file:/absolute/path/to/memory.db",
|
|
24
|
+
"GEMINI_API_KEY": "token-for-gemini-embedding-model-api"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or use npm script:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"mcpServers": {
|
|
36
|
+
"openmind": {
|
|
37
|
+
"command": "/absolute/path/to/tsx",
|
|
38
|
+
"env": {
|
|
39
|
+
"DATABASE_URL": "/absolute/path/to/memory.db",
|
|
40
|
+
"GEMINI_API_KEY": "token-for-gemini-embedding-model-api"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Available Tools
|
|
48
|
+
|
|
49
|
+
### memory_search
|
|
50
|
+
Semantic search across memories.
|
|
51
|
+
- `query` (required): Search text
|
|
52
|
+
- `maxResults` (optional): Max results (default: 10)
|
|
53
|
+
- `minScore` (optional): Min similarity 0-1 (default: 0.3)
|
|
54
|
+
|
|
55
|
+
### memory_add
|
|
56
|
+
Create a new memory.
|
|
57
|
+
- `content` (required): Full memory text
|
|
58
|
+
- `contentL1` (optional): Compressed form
|
|
59
|
+
- `type` (required): event|decision|lesson|preference|person|technical
|
|
60
|
+
- `importance` (required): 1-5
|
|
61
|
+
- `tags` (required): Array of tags
|
|
62
|
+
- `context` (optional): Creation context
|
|
63
|
+
|
|
64
|
+
### memory_recent
|
|
65
|
+
Get recent memories.
|
|
66
|
+
- `hours` (optional): Hours to look back (default: 24)
|
|
67
|
+
- `limit` (optional): Max results (default: 20)
|
|
68
|
+
|
|
69
|
+
### memory_context
|
|
70
|
+
Get contextually related memories.
|
|
71
|
+
- `query` (required): Context query
|
|
72
|
+
- `maxResults` (optional): Max results (default: 5)
|
|
73
|
+
|
|
74
|
+
### memory_stats
|
|
75
|
+
Get database statistics. No parameters.
|
|
76
|
+
|
|
77
|
+
### memory_consolidate
|
|
78
|
+
Run consolidation cycle (decay + boost). No parameters - always applies changes.
|
|
79
|
+
|
|
80
|
+
### journal_add
|
|
81
|
+
Add daily journal entry.
|
|
82
|
+
- `date` (required): YYYY-MM-DD
|
|
83
|
+
- `content` (required): Full narrative
|
|
84
|
+
- `learned` (optional): What I learned
|
|
85
|
+
- `learnedAboutRye` (optional): What I learned about Rye
|
|
86
|
+
- `keyMoments` (optional): Key moments
|
|
87
|
+
- `mood` (optional): Mood/feeling
|
|
88
|
+
|
|
89
|
+
### journal_recent
|
|
90
|
+
Get recent journal entries.
|
|
91
|
+
- `days` (optional): Days to look back (default: 7)
|
|
92
|
+
|
|
93
|
+
### memory_relationships
|
|
94
|
+
View relationship graph for a memory.
|
|
95
|
+
- `memoryId` (required): Memory ID to explore
|
|
96
|
+
|
|
97
|
+
## Access Tracking
|
|
98
|
+
|
|
99
|
+
All read operations (search, recent, context) automatically log accesses, which feed into the importance dynamics system.
|
|
100
|
+
|
|
101
|
+
## Testing
|
|
102
|
+
|
|
103
|
+
Test the server manually:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
npm run mcp
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Then send MCP requests via stdin (see MCP protocol docs).
|
|
110
|
+
|
|
111
|
+
## Architecture
|
|
112
|
+
|
|
113
|
+
- Built on Prisma + SQLite
|
|
114
|
+
- Automatic access tracking
|
|
115
|
+
- Dynamic importance scoring (decay + boost)
|
|
116
|
+
- Relationship graph support
|
|
117
|
+
- Journal integration
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Postnesia CLI
|
|
4
|
+
* Like-for-like Commander interface over the MCP tools.
|
|
5
|
+
* Each MCP tool becomes a subcommand; output mirrors MCP text responses.
|
|
6
|
+
*/
|
|
7
|
+
import 'dotenv/config';
|
|
8
|
+
import { Command } from 'commander';
|
|
9
|
+
import { getDb, queries, createMemory } from '@postnesia/db';
|
|
10
|
+
import { embed } from '@postnesia/db/embeddings';
|
|
11
|
+
import { logAccess } from '@postnesia/mcp/access';
|
|
12
|
+
import { runConsolidation } from '@postnesia/mcp/importance';
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Bootstrap
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
const db = getDb();
|
|
17
|
+
const program = new Command();
|
|
18
|
+
program
|
|
19
|
+
.name('postnesia')
|
|
20
|
+
.description('Postnesia memory & task CLI')
|
|
21
|
+
.version('0.1.0');
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// memory
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
const memory = program.command('memory').description('Memory operations');
|
|
26
|
+
memory
|
|
27
|
+
.command('search <query>')
|
|
28
|
+
.description('Search memories by content')
|
|
29
|
+
.option('-l, --limit <n>', 'Max results', '10')
|
|
30
|
+
.action((query, opts) => {
|
|
31
|
+
const limit = parseInt(opts.limit, 10);
|
|
32
|
+
const pattern = `%${query.toLowerCase()}%`;
|
|
33
|
+
const results = queries.searchMemories(db).all(pattern, pattern, limit);
|
|
34
|
+
for (const r of results)
|
|
35
|
+
logAccess(r.id, 'search');
|
|
36
|
+
process.stdout.write(JSON.stringify(results, null, 2) + '\n');
|
|
37
|
+
});
|
|
38
|
+
memory
|
|
39
|
+
.command('add <content>')
|
|
40
|
+
.description('Add a new memory')
|
|
41
|
+
.requiredOption('-t, --type <type>', 'Memory type: event|decision|lesson|preference|person|technical')
|
|
42
|
+
.requiredOption('-i, --importance <n>', 'Importance 1-5')
|
|
43
|
+
.requiredOption('--tags <tags>', 'Comma-separated tags')
|
|
44
|
+
.option('--content-l1 <summary>', 'Compressed L1 summary (auto-generated if omitted)')
|
|
45
|
+
.option('--context <ctx>', 'Optional context')
|
|
46
|
+
.option('--core', 'Mark as core memory')
|
|
47
|
+
.action(async (content, opts) => {
|
|
48
|
+
const importance = parseInt(opts.importance, 10);
|
|
49
|
+
const tags = opts.tags.split(',').map((t) => t.trim());
|
|
50
|
+
const content_l1 = opts.contentL1 ?? content.slice(0, 200);
|
|
51
|
+
const core = opts.core ? 1 : 0;
|
|
52
|
+
const embedding = await embed(content);
|
|
53
|
+
const id = createMemory(db, {
|
|
54
|
+
timestamp: new Date().toISOString(),
|
|
55
|
+
content,
|
|
56
|
+
content_l1,
|
|
57
|
+
type: opts.type,
|
|
58
|
+
core,
|
|
59
|
+
importance,
|
|
60
|
+
context: opts.context,
|
|
61
|
+
tags,
|
|
62
|
+
embedding,
|
|
63
|
+
});
|
|
64
|
+
process.stdout.write(`✓ Created memory #${id}\n Type: ${opts.type}\n Core: ${core ? 'yes' : 'no'}\n Importance: ${'⭐'.repeat(importance)}\n Tags: ${tags.join(', ')}\n`);
|
|
65
|
+
});
|
|
66
|
+
memory
|
|
67
|
+
.command('update-core <memoryId>')
|
|
68
|
+
.description('Update content of an existing core memory in place')
|
|
69
|
+
.requiredOption('--content <content>', 'New full content')
|
|
70
|
+
.requiredOption('--content-l1 <summary>', 'New compressed L1 summary')
|
|
71
|
+
.action(async (memoryId, opts) => {
|
|
72
|
+
const id = parseInt(memoryId, 10);
|
|
73
|
+
const existing = db.prepare('SELECT id, core FROM memory WHERE id = ?').get(id);
|
|
74
|
+
if (!existing)
|
|
75
|
+
throw new Error(`Memory #${id} not found`);
|
|
76
|
+
if (!existing.core)
|
|
77
|
+
throw new Error(`Memory #${id} is not a core memory.`);
|
|
78
|
+
const embedding = await embed(opts.content);
|
|
79
|
+
db.prepare(`UPDATE memory SET content = ?, content_l1 = ?, updated_at = datetime('now') WHERE id = ?`)
|
|
80
|
+
.run(opts.content, opts.contentL1, id);
|
|
81
|
+
db.prepare('DELETE FROM vec_memories WHERE memory_id = ?').run(BigInt(id));
|
|
82
|
+
db.prepare('INSERT INTO vec_memories(memory_id, embedding) VALUES (?, ?)').run(BigInt(id), Buffer.from(embedding.buffer));
|
|
83
|
+
process.stdout.write(`✓ Updated core memory #${id}\n`);
|
|
84
|
+
});
|
|
85
|
+
memory
|
|
86
|
+
.command('recent')
|
|
87
|
+
.description('Get recent memories')
|
|
88
|
+
.option('--hours <n>', 'Hours to look back', '24')
|
|
89
|
+
.option('-l, --limit <n>', 'Max results', '20')
|
|
90
|
+
.action((opts) => {
|
|
91
|
+
const hours = parseInt(opts.hours, 10);
|
|
92
|
+
const limit = parseInt(opts.limit, 10);
|
|
93
|
+
const results = queries.getRecentMemories(db).all(`-${hours} hours`, limit);
|
|
94
|
+
for (const r of results)
|
|
95
|
+
logAccess(r.id, 'recent');
|
|
96
|
+
process.stdout.write(JSON.stringify(results, null, 2) + '\n');
|
|
97
|
+
});
|
|
98
|
+
memory
|
|
99
|
+
.command('stats')
|
|
100
|
+
.description('Database statistics')
|
|
101
|
+
.action(() => {
|
|
102
|
+
const stats = queries.getStats(db).all();
|
|
103
|
+
const total = db.prepare('SELECT COUNT(*) as count FROM memory').get().count;
|
|
104
|
+
const tagCount = db.prepare('SELECT COUNT(*) as count FROM tag').get().count;
|
|
105
|
+
const relCount = db.prepare('SELECT COUNT(*) as count FROM relationship').get().count;
|
|
106
|
+
const logCount = db.prepare('SELECT COUNT(*) as count FROM access_log').get().count;
|
|
107
|
+
const rows = stats
|
|
108
|
+
.map((s) => ` ${s.type.padEnd(15)} ${s.count.toString().padStart(3)} memories (avg importance: ${s.avg_importance?.toFixed(1)})`)
|
|
109
|
+
.join('\n');
|
|
110
|
+
process.stdout.write(`Memory Statistics:\n\n${rows}\n\n Total: ${total} memories\n Tags: ${tagCount}\n Relationships: ${relCount}\n Access Logs: ${logCount}\n`);
|
|
111
|
+
});
|
|
112
|
+
memory
|
|
113
|
+
.command('consolidate')
|
|
114
|
+
.description('Run memory consolidation cycle')
|
|
115
|
+
.action(() => {
|
|
116
|
+
const r = runConsolidation();
|
|
117
|
+
process.stdout.write(`Consolidation Complete\n\nReviewed: ${r.reviewed}\nBoosted: ${r.boosted}\nDecayed: ${r.decayed}\nUnchanged: ${r.unchanged}\n`);
|
|
118
|
+
});
|
|
119
|
+
memory
|
|
120
|
+
.command('relationships <memoryId>')
|
|
121
|
+
.description('View relationship graph for a memory')
|
|
122
|
+
.action((memoryId) => {
|
|
123
|
+
const id = parseInt(memoryId, 10);
|
|
124
|
+
const relationships = queries.getMemoryRelationships(db).all(id, id);
|
|
125
|
+
process.stdout.write(JSON.stringify(relationships, null, 2) + '\n');
|
|
126
|
+
});
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
// journal
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
const journal = program.command('journal').description('Journal operations');
|
|
131
|
+
journal
|
|
132
|
+
.command('add <date> <content>')
|
|
133
|
+
.description('Add a daily journal entry (date: YYYY-MM-DD)')
|
|
134
|
+
.option('--learned <text>', 'What was learned')
|
|
135
|
+
.option('--learned-about-rye <text>', 'What was learned about Rye')
|
|
136
|
+
.option('--key-moments <text>', 'Key moments')
|
|
137
|
+
.option('--mood <text>', 'Mood/feeling')
|
|
138
|
+
.action((date, content, opts) => {
|
|
139
|
+
const combinedLearned = [opts.learned, opts.learnedAboutRye].filter(Boolean).join('\n\n') || null;
|
|
140
|
+
queries.insertJournal(db).run(date, content, combinedLearned, opts.keyMoments ?? null, opts.mood ?? null);
|
|
141
|
+
process.stdout.write(`✓ Journal entry created for ${date}\n`);
|
|
142
|
+
});
|
|
143
|
+
journal
|
|
144
|
+
.command('recent')
|
|
145
|
+
.description('Get recent journal entries')
|
|
146
|
+
.option('--days <n>', 'Days to look back', '7')
|
|
147
|
+
.action((opts) => {
|
|
148
|
+
const days = parseInt(opts.days, 10);
|
|
149
|
+
const entries = queries.getRecentJournals(db).all(`-${days} days`);
|
|
150
|
+
process.stdout.write(JSON.stringify(entries, null, 2) + '\n');
|
|
151
|
+
});
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
// task
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
const task = program.command('task').description('Task operations');
|
|
156
|
+
task
|
|
157
|
+
.command('create <title>')
|
|
158
|
+
.description('Create a new task')
|
|
159
|
+
.option('-d, --description <text>', 'Task description')
|
|
160
|
+
.option('-s, --session-id <id>', 'Project/feature label')
|
|
161
|
+
.option('-m, --memory-id <n>', 'Related memory ID')
|
|
162
|
+
.action((title, opts) => {
|
|
163
|
+
const result = queries.insertTask(db).run(title, opts.description ?? null, opts.sessionId ?? null, opts.memoryId != null ? parseInt(opts.memoryId, 10) : null);
|
|
164
|
+
const id = Number(result.lastInsertRowid);
|
|
165
|
+
process.stdout.write(`✓ Created task #${id}: ${title}${opts.sessionId ? `\n Session: ${opts.sessionId}` : ''}\n`);
|
|
166
|
+
});
|
|
167
|
+
task
|
|
168
|
+
.command('update <taskId>')
|
|
169
|
+
.description('Update a task')
|
|
170
|
+
.option('-s, --status <status>', 'pending|in_progress|completed|cancelled')
|
|
171
|
+
.option('-t, --title <title>', 'New title')
|
|
172
|
+
.option('-d, --description <text>', 'New description')
|
|
173
|
+
.action((taskId, opts) => {
|
|
174
|
+
const id = parseInt(taskId, 10);
|
|
175
|
+
const existing = queries.getTaskById(db).get(id);
|
|
176
|
+
if (!existing)
|
|
177
|
+
throw new Error(`Task #${id} not found`);
|
|
178
|
+
queries.updateTask(db).run(opts.status ?? null, opts.title ?? null, opts.description ?? null, id);
|
|
179
|
+
const updated = queries.getTaskById(db).get(id);
|
|
180
|
+
process.stdout.write(`✓ Task #${id} updated\n Status: ${updated.status}\n Title: ${updated.title}\n`);
|
|
181
|
+
});
|
|
182
|
+
task
|
|
183
|
+
.command('list')
|
|
184
|
+
.description('List tasks')
|
|
185
|
+
.option('-s, --status <status>', 'Filter by status')
|
|
186
|
+
.option('--session-id <id>', 'Filter by session/project label')
|
|
187
|
+
.option('-l, --limit <n>', 'Max results', '50')
|
|
188
|
+
.action((opts) => {
|
|
189
|
+
const conditions = [];
|
|
190
|
+
const params = [];
|
|
191
|
+
if (opts.status) {
|
|
192
|
+
conditions.push('status = ?');
|
|
193
|
+
params.push(opts.status);
|
|
194
|
+
}
|
|
195
|
+
if (opts.sessionId) {
|
|
196
|
+
conditions.push('session_id = ?');
|
|
197
|
+
params.push(opts.sessionId);
|
|
198
|
+
}
|
|
199
|
+
const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
200
|
+
params.push(parseInt(opts.limit, 10));
|
|
201
|
+
const tasks = db.prepare(`SELECT * FROM task ${where} ORDER BY created_at ASC LIMIT ?`).all(...params);
|
|
202
|
+
process.stdout.write(JSON.stringify(tasks, null, 2) + '\n');
|
|
203
|
+
});
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
// Run
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
try {
|
|
208
|
+
await program.parseAsync(process.argv);
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
const e = error;
|
|
212
|
+
process.stderr.write(`Error: ${e.message}\n`);
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@postnesia/cli",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Commander CLI for Postnesia — direct tool access for agents",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist"
|
|
8
|
+
],
|
|
9
|
+
"bin": {
|
|
10
|
+
"postnesia": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@postnesia/db": "0.1.2",
|
|
14
|
+
"@postnesia/mcp": "0.1.2",
|
|
15
|
+
"commander": "^14.0.3",
|
|
16
|
+
"dotenv": "^17.3.1"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/node": "^22.10.5",
|
|
20
|
+
"tsx": "^4.19.2",
|
|
21
|
+
"typescript": "^5.7.3"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc -p tsconfig.json",
|
|
25
|
+
"start": "tsx src/index.ts"
|
|
26
|
+
}
|
|
27
|
+
}
|