@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.
Files changed (3) hide show
  1. package/README.md +117 -0
  2. package/dist/index.js +214 -0
  3. 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
+ }