persyst-mcp 2.1.0 → 2.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/bin/init.js ADDED
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * persyst-init — Workspace rules generator for VS Code-based IDEs (Cursor, Windsurf, Antigravity)
5
+ *
6
+ * Usage:
7
+ * npx persyst-mcp init
8
+ *
9
+ * What it does:
10
+ * 1. Safely creates or appends system instructions to `.cursorrules`
11
+ * 2. Safely creates or appends system instructions to `.windsurfrules`
12
+ * 3. Creates a general `.persystrules.md` copy-pasteable guide
13
+ * 4. Prints instructions on configuring MCP servers in Cursor/VS Code/Antigravity
14
+ *
15
+ * Design:
16
+ * - Non-destructive: checks for existing content before appending to avoid duplication
17
+ * - Idempotent: safe to run multiple times
18
+ * - Localized: targets the current working directory (project root)
19
+ */
20
+
21
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from 'fs';
22
+ import { join, resolve, dirname } from 'path';
23
+ import { fileURLToPath } from 'url';
24
+
25
+ const __filename = fileURLToPath(import.meta.url);
26
+ const __dirname = dirname(__filename);
27
+
28
+ // ============================================================
29
+ // SYSTEM INSTRUCTION CONTENT
30
+ // ============================================================
31
+
32
+ const INSTRUCTION_HEADER = '# Persyst Memory Integration';
33
+
34
+ const RULE_CONTENT = `
35
+ ${INSTRUCTION_HEADER}
36
+ You are integrated with Persyst, a local-first MCP memory server that stores user preferences, project guidelines, context, and decisions.
37
+
38
+ ## Proactive Memory Retrieval (CRITICAL)
39
+ - Before responding to the user's prompt or executing any task, you MUST invoke the \`persyst\` MCP server's \`search_memories\` or \`get_optimized_context\` tool.
40
+ - Extract relevant search terms from the user's prompt (e.g. if the user says "update the database schema", query "database", "schema", "sqlite", "table").
41
+ - Provide your agent name (e.g. \`cursor-agent\`, \`roo-code\`) as the \`agent_id\` parameter when searching to query your private namespace + shared project context.
42
+
43
+ ## Proactive Memory Storage (CRITICAL)
44
+ - Record Milestones: When you successfully implement a feature, fix a bug, or make an architectural decision, call the \`add_memory\` tool to store a summary of the change.
45
+ - Agentic Swarms & Namespaces: If you are part of a multi-agent swarm or need private partition, pass your agent name as \`agent_id\` and set \`shared: false\` to store private memories. For general project guidelines and files, leave \`shared: true\` (default) so other agents can access them.
46
+ - Handle Contradictions: Persyst handles contradiction detection automatically. If a new fact contradicts an old memory, Persyst will flag it.
47
+ - Quality Over Quantity: Do NOT store trivial facts, temporary conversation noise, or duplicate data. "Bad data is worse than no data". Only store long-term architecture decisions, project details, and explicit user preferences.
48
+ `;
49
+
50
+ const GENERAL_GUIDE = `# Persyst General Agent Integration Guide
51
+
52
+ This workspace is configured with the Persyst local-first memory server.
53
+
54
+ ## How to Configure the MCP Server in VS Code / Cursor / Antigravity
55
+
56
+ Add the following configuration to your IDE's MCP Server settings:
57
+
58
+ - **Server Name:** \`persyst\`
59
+ - **Type:** \`command\`
60
+ - **Command:** \`npx\`
61
+ - **Arguments:** \`["-y", "persyst-mcp"]\`
62
+
63
+ Alternatively, if you have installed the package globally (\`npm install -g persyst-mcp\`), you can configure:
64
+ - **Command:** \`persyst-mcp\`
65
+ - **Arguments:** \`[]\`
66
+
67
+ ---
68
+
69
+ ## Copy-Paste System Prompt Instructions
70
+ If your agent does not read \`.cursorrules\` or \`.windsurfrules\` natively, copy and paste the following prompt into the agent's Custom Instructions, System Prompt, or System Rules:
71
+
72
+ \`\`\`markdown
73
+ ${RULE_CONTENT.trim()}
74
+ \`\`\`
75
+ `;
76
+
77
+ // ============================================================
78
+ // HELPERS
79
+ // ============================================================
80
+
81
+ function setupRuleFile(filePath, fileName) {
82
+ let content = RULE_CONTENT;
83
+ let action = 'Created';
84
+
85
+ if (existsSync(filePath)) {
86
+ const existing = readFileSync(filePath, 'utf8');
87
+ if (existing.includes(INSTRUCTION_HEADER)) {
88
+ console.log(` ℹ️ ${fileName} already has Persyst rules configured (skipped).`);
89
+ return;
90
+ }
91
+ content = existing + '\n' + RULE_CONTENT;
92
+ action = 'Appended to';
93
+ }
94
+
95
+ writeFileSync(filePath, content.trim() + '\n', 'utf8');
96
+ console.log(` ✅ ${action} ${fileName}`);
97
+ }
98
+
99
+ // ============================================================
100
+ // MAIN
101
+ // ============================================================
102
+
103
+ function run() {
104
+ console.log('');
105
+ console.log(' 🧠 Persyst — Workspace Rules Setup');
106
+ console.log(' ════════════════════════════════════');
107
+ console.log('');
108
+
109
+ const cwd = process.cwd();
110
+ console.log(` 📁 Target workspace: ${cwd}`);
111
+ console.log('');
112
+
113
+ // 1. Create/Append Cursor Rules
114
+ const cursorRulesPath = join(cwd, '.cursorrules');
115
+ setupRuleFile(cursorRulesPath, '.cursorrules');
116
+
117
+ // 2. Create/Append Windsurf Rules
118
+ const windsurfRulesPath = join(cwd, '.windsurfrules');
119
+ setupRuleFile(windsurfRulesPath, '.windsurfrules');
120
+
121
+ // 3. Create General Guide File
122
+ const generalGuidePath = join(cwd, '.persystrules.md');
123
+ writeFileSync(generalGuidePath, GENERAL_GUIDE.trim() + '\n', 'utf8');
124
+ console.log(' ✅ Created .persystrules.md (General Guide)');
125
+
126
+ // 4. Configure Git post-commit hook for automatic commit ingestion
127
+ const gitDir = join(cwd, '.git');
128
+ if (existsSync(gitDir)) {
129
+ const hooksDir = join(gitDir, 'hooks');
130
+ mkdirSync(hooksDir, { recursive: true });
131
+ const postCommitPath = join(hooksDir, 'post-commit');
132
+ const localPersystPath = resolve(__dirname, '..', 'index.js').replace(/\\/g, '/');
133
+
134
+ const hookContent = `#!/bin/sh
135
+ # Persyst Git Commit Ingestion Hook
136
+ # Automatically ingests recent commits into Persyst memory on every commit.
137
+
138
+ # Local project path fallback for development
139
+ LOCAL_PERSYST="${localPersystPath}"
140
+
141
+ if [ -f "$LOCAL_PERSYST" ]; then
142
+ node "$LOCAL_PERSYST" ingest "$PWD" 5 >/dev/null 2>&1 || true
143
+ else
144
+ npx persyst-mcp ingest "$PWD" 5 >/dev/null 2>&1 || true
145
+ fi
146
+ `;
147
+
148
+ writeFileSync(postCommitPath, hookContent, { mode: 0o755 });
149
+ try {
150
+ chmodSync(postCommitPath, 0o755);
151
+ } catch (_) {}
152
+ console.log(' ✅ Configured Git post-commit hook for auto-ingestion');
153
+ }
154
+
155
+ // 5. Print Success & Configuration Help
156
+ console.log('');
157
+ console.log(' ════════════════════════════════════');
158
+ console.log(' ✅ Rules and Git hooks initialization complete!');
159
+ console.log('');
160
+ console.log(' To connect the memory server to Cursor, Antigravity, or VS Code:');
161
+ console.log(' 1. Open your IDE Settings -> MCP (Model Context Protocol).');
162
+ console.log(' 2. Add a new command server:');
163
+ console.log(' • Name: persyst');
164
+ console.log(' • Command: npx');
165
+ console.log(' • Arguments: -y persyst-mcp');
166
+ console.log('');
167
+ console.log(' The rules we generated will guide the AI agents in this workspace to:');
168
+ console.log(' • Proactively search memory before answering prompts.');
169
+ console.log(' • Log milestone achievements and user preferences.');
170
+ console.log(' • Keep the memory clean ("no bad data").');
171
+ console.log('');
172
+ }
173
+
174
+ run();
package/bin/setup.js CHANGED
@@ -130,11 +130,16 @@ function run() {
130
130
  process.exit(1);
131
131
  }
132
132
 
133
- // Step 2: Copy hook file to ~/.persyst/hooks/
134
- console.log(' 📁 Installing hook script...');
133
+ // Step 2: Copy and template hook file to ~/.persyst/hooks/
134
+ console.log(' 📁 Installing and templating hook script...');
135
135
  ensureDir(PERSYST_HOOKS_DIR);
136
- copyFileSync(HOOK_SOURCE, HOOK_DEST);
137
- console.log(` ✅ Copied to ${HOOK_DEST}`);
136
+ const INDEX_PATH = resolve(__dirname, '..', 'index.js');
137
+ const WORKER_PATH = resolve(__dirname, '..', 'bin', 'extract-worker.js');
138
+ let hookContent = readFileSync(HOOK_SOURCE, 'utf8');
139
+ hookContent = hookContent.replace('{{PERSYST_INDEX_PATH}}', INDEX_PATH.replace(/\\/g, '/'));
140
+ hookContent = hookContent.replace('{{PERSYST_WORKER_PATH}}', WORKER_PATH.replace(/\\/g, '/'));
141
+ writeFileSync(HOOK_DEST, hookContent, 'utf8');
142
+ console.log(` ✅ Copied & templated to ${HOOK_DEST}`);
138
143
 
139
144
  // Step 3: Merge into ~/.claude/settings.json
140
145
  console.log('');
@@ -1,38 +1,66 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * persyst-hook.js — Claude Code Hook for Persyst Memory
4
+ * persyst-hook.js — Claude Code Hook for Persyst Memory (PAMP-Enhanced)
5
5
  *
6
6
  * Automatically injects relevant memories into Claude Code's context
7
- * on SessionStart and UserPromptSubmit events.
7
+ * on SessionStart and UserPromptSubmit events, and queues conversation
8
+ * turns for async background extraction on Stop events.
9
+ *
10
+ * PAMP Integration (Persyst Auto-Memory Pipeline):
11
+ * - Tier 1: Agent-explicit add_memory calls (existing, unchanged)
12
+ * - Tier 2: Heuristic regex extraction on UserPromptSubmit (sync, zero-cost)
13
+ * - Tier 3: Async LLM extraction via background worker (spawned on Stop)
8
14
  *
9
15
  * How it works:
10
- * 1. Claude Code sends a JSON payload on stdin with hook_event_name, session_id, cwd, etc.
16
+ * 1. Claude Code sends a JSON payload on stdin with hook_event_name, session_id, etc.
11
17
  * 2. This script connects to the Persyst MCP server via StdioClientTransport.
12
18
  * 3. It calls get_optimized_context or search_memories to retrieve relevant memories.
13
19
  * 4. It returns a JSON response on stdout with additionalContext for Claude Code to inject.
20
+ * 5. On Stop: queues the conversation text for background LLM extraction.
14
21
  *
15
22
  * Installation:
16
23
  * npx persyst-mcp setup
17
24
  *
18
25
  * Manual registration in ~/.claude/settings.json:
19
- * { "hooks": { "SessionStart": [...], "UserPromptSubmit": [...] } }
26
+ * { "hooks": { "SessionStart": [...], "UserPromptSubmit": [...], "Stop": [...] } }
20
27
  */
21
28
 
22
29
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
23
30
  import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
24
31
  import { fileURLToPath } from 'url';
25
- import { dirname, resolve } from 'path';
32
+ import { dirname, resolve, join } from 'path';
33
+ import { spawn } from 'child_process';
34
+ import { writeFileSync, readdirSync, mkdirSync, existsSync } from 'fs';
35
+ import { homedir } from 'os';
26
36
 
27
37
  const __filename = fileURLToPath(import.meta.url);
28
38
  const __dirname = dirname(__filename);
29
39
 
40
+ // ============================================================
41
+ // CONFIGURATION
42
+ // ============================================================
43
+
30
44
  // Minimum prompt length to trigger memory search (skip "y", "ok", "/run", etc.)
31
45
  const MIN_PROMPT_LENGTH = 15;
32
46
 
33
47
  // Maximum time to wait for Persyst MCP connection (ms)
34
48
  const CONNECTION_TIMEOUT = 8000;
35
49
 
50
+ // Hard timeout for the entire hook execution (ms)
51
+ // Claude Code will kill the hook if it exceeds this
52
+ const MAX_HOOK_LATENCY_MS = 500;
53
+
54
+ // Maximum active queue jobs before skipping worker spawn
55
+ const MAX_QUEUE_JOBS = 20;
56
+
57
+ // Queue directory for background extraction jobs
58
+ const QUEUE_DIR = join(homedir(), '.persyst', 'queue');
59
+
60
+ // ============================================================
61
+ // STDIN READER
62
+ // ============================================================
63
+
36
64
  /**
37
65
  * Read the full JSON payload from stdin.
38
66
  * Claude Code sends the hook context as a single JSON object.
@@ -53,13 +81,20 @@ function readStdin() {
53
81
  });
54
82
  }
55
83
 
84
+ // ============================================================
85
+ // MCP CLIENT CONNECTION
86
+ // ============================================================
87
+
56
88
  /**
57
89
  * Connect to the Persyst MCP server as a client.
58
90
  * Uses StdioClientTransport to spawn and communicate with the server.
59
91
  */
60
92
  async function connectToPersyst() {
61
- // Resolve the path to Persyst's index.js relative to this hook file
62
- const persystPath = resolve(__dirname, '..', 'index.js');
93
+ // Resolve the path to Persyst's index.js
94
+ let persystPath = '{{PERSYST_INDEX_PATH}}';
95
+ if (persystPath.startsWith('{{')) {
96
+ persystPath = resolve(__dirname, '..', 'index.js');
97
+ }
63
98
 
64
99
  const transport = new StdioClientTransport({
65
100
  command: 'node',
@@ -93,6 +128,89 @@ async function callTool(client, toolName, args) {
93
128
  return null;
94
129
  }
95
130
 
131
+ // ============================================================
132
+ // PAMP: QUEUE MANAGEMENT
133
+ // ============================================================
134
+
135
+ /**
136
+ * Count active job files in the queue directory.
137
+ * Used for worker pool protection — don't spawn if overloaded.
138
+ * @returns {number}
139
+ */
140
+ function countQueueJobs() {
141
+ try {
142
+ if (!existsSync(QUEUE_DIR)) return 0;
143
+ return readdirSync(QUEUE_DIR).filter(f => f.endsWith('.json')).length;
144
+ } catch (_) {
145
+ return 0;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Write a conversation turn to the extraction queue.
151
+ * @param {string} text - The conversation text to extract from
152
+ * @param {Object} meta - Metadata (session_id, agent_id, etc.)
153
+ */
154
+ function enqueueJob(text, meta = {}) {
155
+ try {
156
+ mkdirSync(QUEUE_DIR, { recursive: true });
157
+
158
+ const jobId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
159
+ const jobFile = join(QUEUE_DIR, `${jobId}.json`);
160
+
161
+ writeFileSync(jobFile, JSON.stringify({
162
+ text,
163
+ session_id: meta.session_id || null,
164
+ agent_id: meta.agent_id || 'claude-code',
165
+ namespace: meta.namespace || 'shared',
166
+ cwd: meta.cwd || null,
167
+ queued_at: new Date().toISOString(),
168
+ _retries: 0
169
+ }, null, 2));
170
+
171
+ return jobId;
172
+ } catch (err) {
173
+ // Non-critical — log and continue
174
+ process.stderr.write(`[persyst-hook] Queue write failed: ${err.message}\n`);
175
+ return null;
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Spawn the background extraction worker as a detached process.
181
+ * The worker runs independently — hook doesn't wait for it.
182
+ */
183
+ function spawnWorker() {
184
+ // Check queue depth first
185
+ const queueDepth = countQueueJobs();
186
+ if (queueDepth > MAX_QUEUE_JOBS) {
187
+ process.stderr.write(`[persyst-hook] Queue overloaded (${queueDepth} jobs), skipping worker spawn.\n`);
188
+ return;
189
+ }
190
+
191
+ try {
192
+ let workerPath = '{{PERSYST_WORKER_PATH}}';
193
+ if (workerPath.startsWith('{{')) {
194
+ workerPath = resolve(__dirname, '..', 'bin', 'extract-worker.js');
195
+ }
196
+
197
+ const child = spawn('node', [workerPath], {
198
+ detached: true,
199
+ stdio: 'ignore',
200
+ env: { ...process.env }
201
+ });
202
+
203
+ // Unref so the hook can exit without waiting for the worker
204
+ child.unref();
205
+ } catch (err) {
206
+ process.stderr.write(`[persyst-hook] Worker spawn failed: ${err.message}\n`);
207
+ }
208
+ }
209
+
210
+ // ============================================================
211
+ // EVENT HANDLERS
212
+ // ============================================================
213
+
96
214
  /**
97
215
  * Handle SessionStart: load project-wide context and ingest git history.
98
216
  */
@@ -152,6 +270,7 @@ async function handleSessionStart(client, input) {
152
270
 
153
271
  /**
154
272
  * Handle UserPromptSubmit: search for memories relevant to the user's prompt.
273
+ * Also runs Tier 2 heuristic extraction inline (zero-cost).
155
274
  */
156
275
  async function handleUserPromptSubmit(client, input) {
157
276
  const prompt = input.prompt || '';
@@ -161,6 +280,25 @@ async function handleUserPromptSubmit(client, input) {
161
280
  return {};
162
281
  }
163
282
 
283
+ // --- Tier 2: Run heuristic extraction inline (sync, zero-cost) ---
284
+ // We don't store results here — we queue them alongside the LLM job.
285
+ // This just detects if there's extractable signal in the prompt.
286
+ let heuristicFacts = [];
287
+ try {
288
+ const { extractHeuristic } = await import('../src/extractor-heuristic.js');
289
+ heuristicFacts = extractHeuristic(prompt);
290
+ } catch (_) {
291
+ // Heuristic module not available — Tier 3 will handle it
292
+ }
293
+
294
+ // Queue the prompt for Tier 3 background extraction (non-blocking)
295
+ enqueueJob(prompt, {
296
+ session_id: input.session_id,
297
+ agent_id: 'claude-code',
298
+ cwd: input.cwd
299
+ });
300
+
301
+ // --- Memory Retrieval (existing behavior) ---
164
302
  // Use search_memories for speed on per-prompt lookups (faster than get_optimized_context)
165
303
  const searchResult = await callTool(client, 'search_memories', {
166
304
  query: prompt.slice(0, 200), // Truncate very long prompts for search efficiency
@@ -178,6 +316,13 @@ async function handleUserPromptSubmit(client, input) {
178
316
  for (const mem of searchResult.results) {
179
317
  contextLines.push(`• [Memory #${mem.id}] ${mem.content}`);
180
318
  }
319
+
320
+ // Add heuristic extraction notice if any facts were detected
321
+ if (heuristicFacts.length > 0) {
322
+ contextLines.push('');
323
+ contextLines.push(`[PAMP: ${heuristicFacts.length} fact signal(s) detected, queued for extraction]`);
324
+ }
325
+
181
326
  contextLines.push('=== END MEMORY ===');
182
327
 
183
328
  return {
@@ -189,8 +334,31 @@ async function handleUserPromptSubmit(client, input) {
189
334
  }
190
335
 
191
336
  /**
192
- * Main entry point.
337
+ * Handle Stop: queue the final conversation turn for background extraction
338
+ * and spawn the worker to process the queue.
193
339
  */
340
+ async function handleStop(input) {
341
+ // The Stop event may include conversation_turns or transcript data
342
+ const transcript = input.transcript || input.conversation || '';
343
+
344
+ if (transcript && typeof transcript === 'string' && transcript.length > MIN_PROMPT_LENGTH) {
345
+ enqueueJob(transcript, {
346
+ session_id: input.session_id,
347
+ agent_id: 'claude-code',
348
+ cwd: input.cwd
349
+ });
350
+ }
351
+
352
+ // Spawn background worker to process all queued jobs
353
+ spawnWorker();
354
+
355
+ return {};
356
+ }
357
+
358
+ // ============================================================
359
+ // MAIN ENTRY POINT
360
+ // ============================================================
361
+
194
362
  async function main() {
195
363
  let client = null;
196
364
 
@@ -198,20 +366,37 @@ async function main() {
198
366
  const input = await readStdin();
199
367
  const eventName = input.hook_event_name;
200
368
 
369
+ // Handle Stop event without MCP connection (just queue + spawn)
370
+ if (eventName === 'Stop') {
371
+ const response = await handleStop(input);
372
+ console.log(JSON.stringify(response));
373
+ return;
374
+ }
375
+
201
376
  // Only handle events we care about
202
377
  if (eventName !== 'SessionStart' && eventName !== 'UserPromptSubmit') {
203
378
  console.log(JSON.stringify({}));
204
379
  return;
205
380
  }
206
381
 
207
- // Connect to Persyst
382
+ // Connect to Persyst with hard timeout
383
+ const hookStart = Date.now();
208
384
  client = await connectToPersyst();
209
385
 
210
386
  let response;
211
387
  if (eventName === 'SessionStart') {
212
388
  response = await handleSessionStart(client, input);
213
389
  } else if (eventName === 'UserPromptSubmit') {
214
- response = await handleUserPromptSubmit(client, input);
390
+ // Apply hard timeout for prompt-time hook execution
391
+ response = await Promise.race([
392
+ handleUserPromptSubmit(client, input),
393
+ new Promise((resolve) =>
394
+ setTimeout(() => {
395
+ process.stderr.write(`[persyst-hook] UserPromptSubmit hit ${MAX_HOOK_LATENCY_MS}ms timeout, returning partial.\n`);
396
+ resolve({});
397
+ }, MAX_HOOK_LATENCY_MS - (Date.now() - hookStart))
398
+ )
399
+ ]);
215
400
  } else {
216
401
  response = {};
217
402
  }
package/index.js CHANGED
@@ -10,6 +10,8 @@
10
10
  * node index.js (direct — starts MCP server)
11
11
  * npx persyst-mcp (via npm — starts MCP server)
12
12
  * npx persyst-mcp setup (install Claude Code hooks)
13
+ * npx persyst-mcp init (initialize workspace rules & git hooks)
14
+ * npx persyst-mcp ingest (manually ingest git commits)
13
15
  * persyst-mcp (if installed globally)
14
16
  */
15
17
 
@@ -19,6 +21,24 @@ const subcommand = process.argv[2];
19
21
  if (subcommand === 'setup') {
20
22
  // Delegate to the setup CLI
21
23
  await import('./bin/setup.js');
24
+ } else if (subcommand === 'aider') {
25
+ // Shift 'aider' from process.argv so aider.js gets the correct arguments
26
+ process.argv.splice(2, 1);
27
+ await import('./bin/aider.js');
28
+ } else if (subcommand === 'init') {
29
+ // Delegate to the rules init CLI
30
+ await import('./bin/init.js');
31
+ } else if (subcommand === 'ingest') {
32
+ // Shift 'ingest' from process.argv so ingest.js gets the correct arguments
33
+ process.argv.splice(2, 1);
34
+ await import('./bin/ingest.js');
35
+ } else if (subcommand === 'extract') {
36
+ // Shift 'extract' from process.argv so extract.js gets the correct arguments
37
+ process.argv.splice(2, 1);
38
+ await import('./bin/extract.js');
39
+ } else if (subcommand === 'worker') {
40
+ // Run the background extraction worker directly
41
+ await import('./bin/extract-worker.js');
22
42
  } else {
23
43
  // Default: start the MCP server
24
44
  const { startServer } = await import('./src/server.js');
package/package.json CHANGED
@@ -1,13 +1,17 @@
1
1
  {
2
2
  "name": "persyst-mcp",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "description": "Local-first MCP memory server with hybrid keyword + semantic search for coding agents",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "bin": {
8
8
  "persyst-mcp": "index.js",
9
9
  "persyst-setup": "bin/setup.js",
10
- "persyst-aider": "bin/aider.js"
10
+ "persyst-aider": "bin/aider.js",
11
+ "persyst-init": "bin/init.js",
12
+ "persyst-ingest": "bin/ingest.js",
13
+ "persyst-extract": "bin/extract.js",
14
+ "persyst-worker": "bin/extract-worker.js"
11
15
  },
12
16
  "engines": {
13
17
  "node": ">=18.0.0"
@@ -23,7 +27,9 @@
23
27
  "scripts": {
24
28
  "start": "node index.js",
25
29
  "test": "node test/smoke.js",
26
- "test:heavy": "cross-env NODE_ENV=test node --test test/test_*.js"
30
+ "test:heavy": "cross-env NODE_ENV=test node --test test/test_*.js",
31
+ "worker": "node bin/extract-worker.js",
32
+ "extract": "node bin/extract.js"
27
33
  },
28
34
  "keywords": [
29
35
  "mcp",