persyst-mcp 2.2.4 → 2.2.6

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 CHANGED
@@ -1,8 +1,23 @@
1
1
  # Persyst
2
2
 
3
- **Local-first MCP memory server for coding agents.**
3
+ **Local-first, compliance-grade MCP memory layer for regulated enterprise coding teams using AI assistants.**
4
4
 
5
- Persyst gives AI coding agents (Claude Code, Cursor, VS Code, Aider, Windsurf, Antigravity) persistent memory across sessions. It stores memories in a local SQLite database with hybrid keyword + semantic search — no cloud, no API keys, works offline.
5
+ Persyst gives AI coding agents (Claude Code, Cursor, VS Code, Aider, Continue.dev, Antigravity) persistent memory across sessions. It stores memories in a local SQLite database with hybrid keyword + semantic search — operating 100% offline with zero cloud egress.
6
+
7
+ ## Compliance-Grade Security Features
8
+
9
+ Persyst is built from the ground up for highly regulated enterprise environments (finance, healthcare, defense) subject to **SOC 2**, **HIPAA**, and the **EU AI Act**:
10
+
11
+ * **100% Data Residency (Zero-Egress)**: All vector calculations, full-text searches, and model inferences run locally on the developer's workstation. No database records or context data ever leave the local machine. Bypasses Business Associate Agreement (BAA) complexity for HIPAA.
12
+ * **Cryptographic Chain of Custody**: Every context retrieval generates an Ed25519 cryptographic signature sealing the query and retrieved memory hashes. Each attestation is chained to the previous one via SHA-256 hash chains, creating a tamper-evident audit ledger verifiable by security teams.
13
+ * **Automatic Secret Redaction**: Scans incoming log files and text writes to redact high-entropy secrets (API keys, JWTs, database strings, private keys) before they reach the persistent database.
14
+ * **Reactive File Watching**: Integrates `chokidar` for instant event-driven scanning of agent transcript folders, guaranteeing that your memories are synchronized immediately after each agent interaction.
15
+
16
+ *Read more in our compliance mapping guides:*
17
+ - [SOC 2 Type II Controls](file:///c:/Users/Super/Desktop/Peryst/compliance/SOC2-controls.md)
18
+ - [HIPAA Mapping & PHI Boundaries](file:///c:/Users/Super/Desktop/Peryst/compliance/HIPAA-mapping.md)
19
+ - [EU AI Act Article 13 Transparency](file:///c:/Users/Super/Desktop/Peryst/compliance/EU-AI-Act-Article13.md)
20
+ - [Compliance Audit Trail Sample](file:///c:/Users/Super/Desktop/Peryst/compliance/audit-trail-sample.md)
6
21
 
7
22
  ## How It Works
8
23
 
@@ -16,6 +31,14 @@ Your AI Agent ←→ MCP (stdio) ←→ Persyst ←→ SQLite (local)
16
31
 
17
32
  > 🚨 **First-Run Note**: On the first start, Persyst will automatically download the local embedding model (`all-MiniLM-L6-v2` ~50MB). This can take 30-60 seconds depending on your connection. The server will log `Loading embedding model...` and then proceed normally.
18
33
 
34
+ ### Passive Recording vs. Active Retrieval
35
+
36
+ > ⚠️ **Honest Note on Agent Integration**: Persyst operates in two complementary modes:
37
+ > 1. **Passive Recording**: The file watcher automatically extracts and saves memories from your agent conversation transcripts in the background.
38
+ > 2. **Active Retrieval**: The AI agent calls `search_memories` or `get_optimized_context` to fetch relevant context.
39
+ >
40
+ > The IDE itself does not automatically inject retrieved memories into prompt inputs unless configured to do so via workspace rules (e.g. `.cursorrules`, `.windsurfrules`, `.agents/AGENTS.md`) or custom system prompt builders. To ensure the agent utilizes its memory, make sure your agent instructions direct it to query the database.
41
+
19
42
  ---
20
43
 
21
44
  ## Quick Start
@@ -116,6 +139,13 @@ Add Persyst to your Antigravity agent configuration file at `~/.gemini/antigravi
116
139
  | `delete_memory` | Delete a memory and clean up edges | `id` (number) |
117
140
  | `get_recent_memories` | Get latest memories | `limit` (number) |
118
141
  | `get_important_memories` | Get by importance score | `limit` (number) |
142
+ | `get_optimized_context` | Get compressed, ranked context block | `query` (string), `max_tokens` (number) |
143
+ | `ingest_git_commits` | Import recent git commits as memories | `repo_path` (string), `count` (number) |
144
+ | `consolidate_memories` | Merge highly similar duplicate memories | — |
145
+ | `get_memory_history` | Retrieve all versions of a memory | `query` (string) |
146
+ | `get_agent_stats` | Agent reputation stats | — |
147
+ | `export_audit_log` | Export attestation audit log | `start_date`, `end_date` (ISO8601) |
148
+ | `verify_attestation` | Verify Ed25519 signature chain | `attestation_id` (string) |
119
149
 
120
150
  ---
121
151
 
@@ -159,6 +189,38 @@ Do not run `npx` with `sudo`. If you run into permission issues, ensure your npm
159
189
 
160
190
  ---
161
191
 
192
+ ## Backup & Migration
193
+
194
+ Persyst includes built-in JSONL export/import commands for portable memory backup and cross-machine migration.
195
+
196
+ ```bash
197
+ # Export all memories to a file
198
+ npx persyst-mcp export
199
+ # → persyst-export-<timestamp>.jsonl
200
+
201
+ # Export to a specific file
202
+ npx persyst-mcp export my-backup.jsonl
203
+
204
+ # Preview what would be imported (dry run)
205
+ npx persyst-mcp import my-backup.jsonl --dry-run
206
+
207
+ # Import memories (skips exact & semantic duplicates automatically)
208
+ npx persyst-mcp import my-backup.jsonl
209
+ ```
210
+
211
+ ---
212
+
213
+ ## Roadmap & Future Directions
214
+
215
+ Persyst is built for the privacy-focused solo developer. We are actively hardening the local-first experience before introducing network dependencies.
216
+
217
+ * **File-Based Sync** ✅ **Done**: `persyst-export` / `persyst-import` JSONL commands for backup and migration.
218
+ * **IDE Integrations**: First-class extensions for Cursor, VS Code, and Aider configuration helper commands.
219
+ * **True P2P Sync (Roadmap)**: Peer-to-peer secure sync between developer devices without relying on central cloud servers.
220
+
221
+ ---
222
+
162
223
  ## License
163
224
 
164
225
  MIT License. See [LICENSE](LICENSE) for details.
226
+
package/bin/export.js ADDED
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * export.js — Persyst Memory JSONL Export CLI
5
+ *
6
+ * Exports all active memories to a portable JSONL file for backup or migration.
7
+ * Each line is a JSON object representing one memory with its full metadata.
8
+ *
9
+ * Usage:
10
+ * persyst-export → exports to persyst-export-<timestamp>.jsonl
11
+ * persyst-export memories.jsonl → exports to memories.jsonl
12
+ * persyst-export --namespace=shared → exports only the shared namespace
13
+ * persyst-export --all → includes archived (valid_until IS NOT NULL) memories
14
+ *
15
+ * The output format is designed to be imported back via `persyst-import`.
16
+ */
17
+
18
+ import { createWriteStream } from 'fs';
19
+ import db, { closeDatabase } from '../src/database.js';
20
+
21
+ // ============================================================
22
+ // ARG PARSING
23
+ // ============================================================
24
+
25
+ const args = process.argv.slice(2);
26
+ const outputFile = args.find(a => !a.startsWith('--')) || `persyst-export-${Date.now()}.jsonl`;
27
+ const namespace = (args.find(a => a.startsWith('--namespace=')) || '').replace('--namespace=', '') || null;
28
+ const includeArchived = args.includes('--all');
29
+
30
+ // ============================================================
31
+ // EXPORT
32
+ // ============================================================
33
+
34
+ try {
35
+ const conditions = [];
36
+ const params = [];
37
+
38
+ if (!includeArchived) {
39
+ conditions.push('m.valid_until IS NULL');
40
+ }
41
+ if (namespace) {
42
+ conditions.push("(m.namespace = ? OR m.namespace = 'shared')");
43
+ params.push(namespace);
44
+ }
45
+
46
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
47
+
48
+ const query = `
49
+ SELECT
50
+ m.id,
51
+ m.content,
52
+ m.importance_score,
53
+ m.namespace,
54
+ m.created_at,
55
+ m.last_accessed,
56
+ m.access_count,
57
+ m.parent_id,
58
+ m.valid_until,
59
+ p.source_type,
60
+ p.source_id,
61
+ p.confidence
62
+ FROM memories m
63
+ LEFT JOIN provenance p ON p.memory_id = m.id
64
+ ${whereClause}
65
+ ORDER BY m.id ASC
66
+ `;
67
+
68
+ const rows = params.length > 0 ? db.prepare(query).all(...params) : db.prepare(query).all();
69
+
70
+ const out = createWriteStream(outputFile, { encoding: 'utf8' });
71
+
72
+ let count = 0;
73
+ for (const row of rows) {
74
+ const record = {
75
+ id: row.id,
76
+ content: row.content,
77
+ importance_score: row.importance_score,
78
+ namespace: row.namespace || 'shared',
79
+ created_at: row.created_at,
80
+ last_accessed: row.last_accessed,
81
+ access_count: row.access_count,
82
+ parent_id: row.parent_id ?? null,
83
+ valid_until: row.valid_until ?? null,
84
+ provenance: row.source_type
85
+ ? {
86
+ source_type: row.source_type,
87
+ source_id: row.source_id ?? null,
88
+ confidence: row.confidence ?? 1.0
89
+ }
90
+ : null
91
+ };
92
+ out.write(JSON.stringify(record) + '\n');
93
+ count++;
94
+ }
95
+
96
+ await new Promise((resolve, reject) => {
97
+ out.end((err) => {
98
+ if (err) reject(err);
99
+ else resolve();
100
+ });
101
+ });
102
+
103
+ console.log(`✅ Exported ${count} memories to: ${outputFile}`);
104
+ if (namespace) {
105
+ console.log(` Namespace filter: "${namespace}" + shared`);
106
+ }
107
+ if (includeArchived) {
108
+ console.log(' Includes archived (superseded) memories.');
109
+ }
110
+
111
+ } catch (err) {
112
+ console.error(`❌ Export failed: ${err.message}`);
113
+ process.exit(1);
114
+ } finally {
115
+ closeDatabase();
116
+ }
package/bin/import.js ADDED
@@ -0,0 +1,160 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * import.js — Persyst Memory JSONL Import CLI
5
+ *
6
+ * Imports memories from a JSONL file created by `persyst-export`.
7
+ * Regenerates vector embeddings for each imported memory.
8
+ * Skips duplicates using both exact-content and semantic similarity checks.
9
+ *
10
+ * Usage:
11
+ * persyst-import memories.jsonl
12
+ * persyst-import memories.jsonl --dry-run → preview without writing
13
+ * persyst-import memories.jsonl --namespace=shared → force all into shared namespace
14
+ * persyst-import memories.jsonl --skip-embeddings → skip re-generating embeddings (fast, no semantic search for these)
15
+ *
16
+ * Compatible with the JSONL format produced by `persyst-export`.
17
+ */
18
+
19
+ import { createReadStream } from 'fs';
20
+ import { createInterface } from 'readline';
21
+ import db, {
22
+ insertMemory,
23
+ insertVector,
24
+ memoryExists,
25
+ closeDatabase
26
+ } from '../src/database.js';
27
+ import { generateEmbedding } from '../src/embeddings.js';
28
+ import { searchHybrid } from '../src/search.js';
29
+
30
+ // ============================================================
31
+ // ARG PARSING
32
+ // ============================================================
33
+
34
+ const args = process.argv.slice(2);
35
+ const inputFile = args.find(a => !a.startsWith('--'));
36
+ const isDryRun = args.includes('--dry-run');
37
+ const forceNamespace = (args.find(a => a.startsWith('--namespace=')) || '').replace('--namespace=', '') || null;
38
+ const skipEmbeddings = args.includes('--skip-embeddings');
39
+
40
+ const DEDUP_THRESHOLD = 0.85;
41
+
42
+ if (!inputFile) {
43
+ console.error('❌ Usage: persyst-import <file.jsonl> [--dry-run] [--namespace=<ns>] [--skip-embeddings]');
44
+ process.exit(1);
45
+ }
46
+
47
+ // ============================================================
48
+ // MAIN
49
+ // ============================================================
50
+
51
+ async function main() {
52
+ console.log(`📥 Persyst Import${isDryRun ? ' (DRY RUN — nothing will be written)' : ''}`);
53
+ console.log(` Source: ${inputFile}`);
54
+ if (forceNamespace) console.log(` Forcing namespace: "${forceNamespace}"`);
55
+ if (skipEmbeddings) console.log(' Skipping embedding regeneration.');
56
+ console.log('');
57
+
58
+ const rl = createInterface({
59
+ input: createReadStream(inputFile, { encoding: 'utf8' }),
60
+ crlfDelay: Infinity
61
+ });
62
+
63
+ let lineNum = 0;
64
+ let imported = 0;
65
+ let skipped = 0;
66
+ let errors = 0;
67
+
68
+ for await (const line of rl) {
69
+ lineNum++;
70
+ const trimmed = line.trim();
71
+ if (!trimmed) continue;
72
+
73
+ let record;
74
+ try {
75
+ record = JSON.parse(trimmed);
76
+ } catch (err) {
77
+ console.error(` ⚠️ Line ${lineNum}: Invalid JSON — skipping`);
78
+ errors++;
79
+ continue;
80
+ }
81
+
82
+ const { content, importance_score = 1.0, namespace, provenance, valid_until } = record;
83
+
84
+ if (!content || typeof content !== 'string' || content.trim().length === 0) {
85
+ console.error(` ⚠️ Line ${lineNum}: Empty content — skipping`);
86
+ errors++;
87
+ continue;
88
+ }
89
+
90
+ // Skip archived memories (unless they have a parent_id, we skip them anyway)
91
+ if (valid_until !== null && valid_until !== undefined) {
92
+ skipped++;
93
+ continue;
94
+ }
95
+
96
+ const targetNamespace = forceNamespace || namespace || 'shared';
97
+
98
+ // --- Dedup: exact content match ---
99
+ if (memoryExists(content, targetNamespace)) {
100
+ console.log(` ⏭️ Line ${lineNum}: Already exists — skipping "${content.slice(0, 60)}..."`);
101
+ skipped++;
102
+ continue;
103
+ }
104
+
105
+ // --- Dedup: semantic similarity ---
106
+ if (!skipEmbeddings) {
107
+ try {
108
+ const similar = await searchHybrid(content, 1, null, null, targetNamespace);
109
+ if (similar.length > 0 && parseFloat(similar[0].similarity) >= DEDUP_THRESHOLD) {
110
+ console.log(` ⏭️ Line ${lineNum}: Semantically similar to #${similar[0].id} (sim=${similar[0].similarity}) — skipping`);
111
+ skipped++;
112
+ continue;
113
+ }
114
+ } catch (_) {
115
+ // Non-critical: proceed with import if semantic check fails
116
+ }
117
+ }
118
+
119
+ if (isDryRun) {
120
+ console.log(` ✅ Would import: "${content.slice(0, 80)}${content.length > 80 ? '...' : ''}" → ns="${targetNamespace}"`);
121
+ imported++;
122
+ continue;
123
+ }
124
+
125
+ // --- Write to DB ---
126
+ try {
127
+ const prov = provenance || { source_type: 'import', source_id: 'persyst-import', confidence: 1.0 };
128
+ const id = insertMemory(content, importance_score, prov, targetNamespace);
129
+
130
+ if (!skipEmbeddings) {
131
+ const embedding = await generateEmbedding(content);
132
+ insertVector(id, embedding);
133
+ }
134
+
135
+ console.log(` ✅ Imported #${id}: "${content.slice(0, 70)}${content.length > 70 ? '...' : ''}"`);
136
+ imported++;
137
+ } catch (err) {
138
+ console.error(` ❌ Line ${lineNum}: Failed to insert — ${err.message}`);
139
+ errors++;
140
+ }
141
+ }
142
+
143
+ console.log('');
144
+ console.log('═'.repeat(50));
145
+ if (isDryRun) {
146
+ console.log(`📊 Dry run complete: ${imported} would import, ${skipped} skipped, ${errors} errors`);
147
+ } else {
148
+ console.log(`📊 Import complete: ${imported} imported, ${skipped} skipped, ${errors} errors`);
149
+ }
150
+ console.log('═'.repeat(50));
151
+ }
152
+
153
+ main()
154
+ .catch(err => {
155
+ console.error(`❌ Import crashed: ${err.message}`);
156
+ process.exit(1);
157
+ })
158
+ .finally(() => {
159
+ closeDatabase();
160
+ });
package/bin/init.js CHANGED
@@ -1,30 +1,33 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * persyst-init — Workspace rules generator for VS Code-based IDEs (Cursor, Windsurf, Antigravity)
4
+ * persyst-init — Workspace rules generator and global IDE configuration builder
5
5
  *
6
6
  * Usage:
7
7
  * npx persyst-mcp init
8
+ * npx persyst-mcp init --mcp cursor,aider
8
9
  *
9
10
  * 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)
11
+ * 1. Safely creates or appends system instructions to `.cursorrules` and `.windsurfrules`
12
+ * 2. Creates a general `.persystrules.md` workspace guide
13
+ * 3. Configures Git post-commit hook for auto-ingestion
14
+ * 4. Generates cryptographic Ed25519 keys inside ~/.persyst
15
+ * 5. Automatically detects and configures global settings for Cursor, Aider, Claude Code, and Continue
19
16
  */
20
17
 
21
18
  import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from 'fs';
22
19
  import { join, resolve, dirname } from 'path';
20
+ import { homedir } from 'os';
23
21
  import { fileURLToPath } from 'url';
22
+ import { execSync } from 'child_process';
23
+ import { initializeKeys } from '../src/attestation.js';
24
24
 
25
25
  const __filename = fileURLToPath(import.meta.url);
26
26
  const __dirname = dirname(__filename);
27
27
 
28
+ const CONFIG_DIR = join(homedir(), '.persyst');
29
+ const PERSYST_DB = join(CONFIG_DIR, 'persyst.db');
30
+
28
31
  // ============================================================
29
32
  // SYSTEM INSTRUCTION CONTENT
30
33
  // ============================================================
@@ -46,6 +49,10 @@ You are integrated with Persyst, a local-first MCP memory server that stores use
46
49
  - Handle Contradictions: Persyst handles contradiction detection automatically. If a new fact contradicts an old memory, Persyst will flag it.
47
50
  - 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
51
 
52
+ ## Explicit User Save Requests
53
+ - If the user explicitly asks you to remember, save, or keep a note of a fact (e.g., "Remember that John handles deployment", "remind me that staging is flaky"), call the \`add_memory\` tool immediately with that content.
54
+ - Bypassing Tech Filters: Explicit user requests bypass the programming keyword filters. Ensure they are captured verbatim.
55
+
49
56
  ## Mandatory Completion Checklist (HARD CONSTRAINT)
50
57
  Before writing your final response declaring a task, feature, or bug fix complete:
51
58
  1. Ask yourself: "Did I implement a feature, fix a bug, configure a tool, or discover a project rule?"
@@ -82,7 +89,7 @@ ${RULE_CONTENT.trim()}
82
89
  `;
83
90
 
84
91
  // ============================================================
85
- // HELPERS
92
+ // WORKSPACE HELPERS
86
93
  // ============================================================
87
94
 
88
95
  function setupRuleFile(filePath, fileName) {
@@ -104,33 +111,153 @@ function setupRuleFile(filePath, fileName) {
104
111
  }
105
112
 
106
113
  // ============================================================
107
- // MAIN
114
+ // GLOBAL CONFIG WRITERS
115
+ // ============================================================
116
+
117
+ function detectEditors() {
118
+ const editors = [];
119
+ const home = homedir();
120
+
121
+ // Cursor
122
+ const cursorDir = join(home, '.cursor');
123
+ const winCursorDir = join(home, 'AppData', 'Roaming', 'Cursor');
124
+ if (existsSync(cursorDir) || existsSync(winCursorDir) || existsSync('/Applications/Cursor.app') || existsSync(join(home, 'AppData', 'Local', 'Programs', 'cursor'))) {
125
+ editors.push('cursor');
126
+ }
127
+
128
+ // Aider
129
+ try {
130
+ execSync('aider --version', { stdio: 'ignore' });
131
+ editors.push('aider');
132
+ } catch (_) {}
133
+
134
+ // Claude Code
135
+ const claudeDir = join(home, '.claude');
136
+ if (existsSync(claudeDir) || existsSync('/Applications/Claude Code.app')) {
137
+ editors.push('claude-code');
138
+ }
139
+
140
+ // Continue.dev
141
+ const continueConfig = join(home, '.continue', 'config.json');
142
+ if (existsSync(continueConfig)) {
143
+ editors.push('continue');
144
+ }
145
+
146
+ return editors;
147
+ }
148
+
149
+ function writeCursorConfig() {
150
+ const cursorMcp = join(homedir(), '.cursor', 'mcp.json');
151
+ try {
152
+ const config = existsSync(cursorMcp) ? JSON.parse(readFileSync(cursorMcp, 'utf8')) : {};
153
+ config.mcpServers = config.mcpServers || {};
154
+ config.mcpServers.persyst = {
155
+ "command": "npx",
156
+ "args": ["-y", "persyst-mcp"],
157
+ "env": { "PERSYST_DB": PERSYST_DB }
158
+ };
159
+ mkdirSync(dirname(cursorMcp), { recursive: true });
160
+ writeFileSync(cursorMcp, JSON.stringify(config, null, 2));
161
+ console.log(' ✅ Cursor MCP config written to ~/.cursor/mcp.json');
162
+ } catch (err) {
163
+ console.error(` ✗ Failed to configure Cursor: ${err.message}`);
164
+ }
165
+ }
166
+
167
+ function writeAiderConfig() {
168
+ const aiderYml = join(homedir(), '.aider.conf.yml');
169
+ try {
170
+ let content = '';
171
+ if (existsSync(aiderYml)) {
172
+ content = readFileSync(aiderYml, 'utf8');
173
+ }
174
+ if (!content.includes('persyst')) {
175
+ content += `\n# Persyst MCP integration\nmcp:\n - name: persyst\n cmd: npx\n args: ["-y", "persyst-mcp"]\n env:\n PERSYST_DB: ${PERSYST_DB}\n`;
176
+ writeFileSync(aiderYml, content);
177
+ console.log(' ✅ Aider MCP config appended to ~/.aider.conf.yml');
178
+ } else {
179
+ console.log(' ℹ️ Aider already has Persyst configured (skipped).');
180
+ }
181
+ } catch (err) {
182
+ console.error(` ✗ Failed to configure Aider: ${err.message}`);
183
+ }
184
+ }
185
+
186
+ function writeClaudeCodeConfig() {
187
+ const claudeJson = join(homedir(), '.claude.json');
188
+ try {
189
+ const config = existsSync(claudeJson) ? JSON.parse(readFileSync(claudeJson, 'utf8')) : {};
190
+ config.mcpServers = config.mcpServers || {};
191
+ config.mcpServers.persyst = {
192
+ "command": "npx",
193
+ "args": ["-y", "persyst-mcp"],
194
+ "env": { "PERSYST_DB": PERSYST_DB }
195
+ };
196
+ writeFileSync(claudeJson, JSON.stringify(config, null, 2));
197
+ console.log(' ✅ Claude Code MCP config written to ~/.claude.json');
198
+ } catch (err) {
199
+ console.error(` ✗ Failed to configure Claude Code: ${err.message}`);
200
+ }
201
+ }
202
+
203
+ function writeContinueConfig() {
204
+ const continueConfig = join(homedir(), '.continue', 'config.json');
205
+ try {
206
+ const config = existsSync(continueConfig) ? JSON.parse(readFileSync(continueConfig, 'utf8')) : {};
207
+ config.mcpServers = config.mcpServers || [];
208
+ // Remove existing persyst entry
209
+ config.mcpServers = config.mcpServers.filter(s => s.name !== 'persyst');
210
+ config.mcpServers.push({
211
+ "name": "persyst",
212
+ "command": "npx",
213
+ "args": ["-y", "persyst-mcp"],
214
+ "env": { "PERSYST_DB": PERSYST_DB }
215
+ });
216
+ mkdirSync(dirname(continueConfig), { recursive: true });
217
+ writeFileSync(continueConfig, JSON.stringify(config, null, 2));
218
+ console.log(' ✅ Continue.dev MCP config written to ~/.continue/config.json');
219
+ } catch (err) {
220
+ console.error(` ✗ Failed to configure Continue.dev: ${err.message}`);
221
+ }
222
+ }
223
+
224
+ // ============================================================
225
+ // MAIN RUNNER
108
226
  // ============================================================
109
227
 
110
228
  function run() {
111
229
  console.log('');
112
- console.log(' 🧠 Persyst — Workspace Rules Setup');
113
- console.log(' ════════════════════════════════════');
230
+ console.log(' 🧠 Persyst — Workspace & Editor Setup');
231
+ console.log(' ══════════════════════════════════════');
114
232
  console.log('');
115
233
 
116
234
  const cwd = process.cwd();
117
235
  console.log(` 📁 Target workspace: ${cwd}`);
118
- console.log('');
119
236
 
120
- // 1. Create/Append Cursor Rules
237
+ // 1. Initialize local configuration folder and attestations
238
+ console.log(' ⚙️ Initializing keypairs & DB folders...');
239
+ mkdirSync(CONFIG_DIR, { recursive: true });
240
+ initializeKeys();
241
+ console.log(' ✅ Cryptographic keypairs generated');
242
+
243
+ // 2. Local workspace configurations
244
+ console.log('');
245
+ console.log(' 📄 Initializing workspace rule files...');
246
+
121
247
  const cursorRulesPath = join(cwd, '.cursorrules');
122
248
  setupRuleFile(cursorRulesPath, '.cursorrules');
123
249
 
124
- // 2. Create/Append Windsurf Rules
125
250
  const windsurfRulesPath = join(cwd, '.windsurfrules');
126
251
  setupRuleFile(windsurfRulesPath, '.windsurfrules');
127
252
 
128
- // 3. Create General Guide File
253
+ const clineRulesPath = join(cwd, '.clinerules');
254
+ setupRuleFile(clineRulesPath, '.clinerules');
255
+
129
256
  const generalGuidePath = join(cwd, '.persystrules.md');
130
257
  writeFileSync(generalGuidePath, GENERAL_GUIDE.trim() + '\n', 'utf8');
131
258
  console.log(' ✅ Created .persystrules.md (General Guide)');
132
259
 
133
- // 4. Configure Git post-commit hook for automatic commit ingestion
260
+ // 3. Git post-commit hook
134
261
  const gitDir = join(cwd, '.git');
135
262
  if (existsSync(gitDir)) {
136
263
  const hooksDir = join(gitDir, 'hooks');
@@ -159,22 +286,31 @@ fi
159
286
  console.log(' ✅ Configured Git post-commit hook for auto-ingestion');
160
287
  }
161
288
 
162
- // 5. Print Success & Configuration Help
289
+ // 4. Global editor configurations
163
290
  console.log('');
164
- console.log(' ════════════════════════════════════');
165
- console.log(' ✅ Rules and Git hooks initialization complete!');
291
+ console.log(' 💻 Initializing global IDE configurations...');
292
+
293
+ const args = process.argv.slice(2);
294
+ const mcpFlag = args.find(a => a.startsWith('--mcp='));
295
+ const requestedEditors = mcpFlag ? mcpFlag.split('=')[1].split(',') : [];
296
+
297
+ const editors = requestedEditors.length > 0 ? requestedEditors : detectEditors();
298
+ console.log(` Detected editors/environments: ${editors.join(', ') || 'none'}`);
299
+
300
+ if (editors.includes('cursor')) writeCursorConfig();
301
+ if (editors.includes('aider')) writeAiderConfig();
302
+ if (editors.includes('claude-code')) writeClaudeCodeConfig();
303
+ if (editors.includes('continue')) writeContinueConfig();
304
+
305
+ // 5. Final self-test and notes
166
306
  console.log('');
167
- console.log(' To connect the memory server to Cursor, Antigravity, or VS Code:');
168
- console.log(' 1. Open your IDE Settings -> MCP (Model Context Protocol).');
169
- console.log(' 2. Add a new command server:');
170
- console.log(' • Name: persyst');
171
- console.log(' • Command: npx');
172
- console.log(' • Arguments: -y persyst-mcp');
307
+ console.log(' ══════════════════════════════════════');
308
+ console.log(' 🎉 Setup complete! Persyst is fully configured.');
173
309
  console.log('');
174
- console.log(' The rules we generated will guide the AI agents in this workspace to:');
175
- console.log(' Proactively search memory before answering prompts.');
176
- console.log(' Log milestone achievements and user preferences.');
177
- console.log(' Keep the memory clean ("no bad data").');
310
+ console.log(' Next steps:');
311
+ console.log(' 1. Restart your editor to load the new MCP configurations.');
312
+ console.log(' 2. Test gateway connection:');
313
+ console.log(' curl http://127.0.0.1:4321/health');
178
314
  console.log('');
179
315
  }
180
316
 
package/bin/mcp.js ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { startServer } from '../src/server.js';
3
+
4
+ startServer().catch(err => {
5
+ console.error('❌ Persyst failed to start:', err.message);
6
+ process.exit(1);
7
+ });