polydev-ai 1.2.3 → 1.2.4

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,6 +1,6 @@
1
1
  # Polydev AI Website
2
2
 
3
- Advanced Model Context Protocol platform with comprehensive multi-LLM integration, subscription-based CLI access, OAuth bridges, and advanced tooling for AI development.
3
+ Advanced Model Context Protocol platform with comprehensive multi-LLM integrations, subscription-based CLI access, OAuth bridges, and advanced tooling for AI development.
4
4
 
5
5
  ## Features
6
6
 
@@ -0,0 +1,607 @@
1
+ "use strict";
2
+ /**
3
+ * Universal Memory Extraction System
4
+ * Extracts memory from ALL CLI tools with zero-knowledge encryption
5
+ * Preserves existing functionality while adding comprehensive memory support
6
+ */
7
+ var __importDefault = (this && this.__importDefault) || function (mod) {
8
+ return (mod && mod.__esModule) ? mod : { "default": mod };
9
+ };
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.universalMemoryExtractor = exports.UniversalMemoryExtractor = void 0;
12
+ const promises_1 = __importDefault(require("fs/promises"));
13
+ const path_1 = __importDefault(require("path"));
14
+ const os_1 = __importDefault(require("os"));
15
+ const server_1 = require("@/app/utils/supabase/server");
16
+ const zeroKnowledgeEncryption_1 = require("./zeroKnowledgeEncryption");
17
+ class UniversalMemoryExtractor {
18
+ constructor() {
19
+ // Memory location patterns for all supported CLI tools
20
+ this.MEMORY_PATTERNS = {
21
+ claude_code: {
22
+ global: [
23
+ '~/.claude/CLAUDE.md',
24
+ '~/.claude/global_memory.md',
25
+ '~/.claude/config.json'
26
+ ],
27
+ project: [
28
+ 'CLAUDE.md',
29
+ '.claude/CLAUDE.md',
30
+ '.claude/project_memory.md'
31
+ ],
32
+ conversations: [
33
+ '~/.claude/projects/*/conversations/*.jsonl',
34
+ '~/.claude/conversations/*.jsonl'
35
+ ],
36
+ config: ['~/.claude/config.json']
37
+ },
38
+ cline: {
39
+ global: [
40
+ '~/.cline/global_memory.md',
41
+ '~/.cline/settings.json',
42
+ '~/.cline/config.json'
43
+ ],
44
+ project: [
45
+ '.cline/project_memory.md',
46
+ '.cline/cline_project.json',
47
+ '.cline/memory.md'
48
+ ],
49
+ conversations: [
50
+ '.cline/conversations/*.json',
51
+ '.cline/chat_history.json'
52
+ ],
53
+ config: ['.cline/cline_config.json', '.cline/settings.json']
54
+ },
55
+ codex_cli: {
56
+ global: [
57
+ '~/.codex/memory.json',
58
+ '~/.codex/config.yaml',
59
+ '~/.codex/global_context.md'
60
+ ],
61
+ project: [
62
+ '.codex/project_context.json',
63
+ '.codex/memory.md',
64
+ '.codex/context.json'
65
+ ],
66
+ conversations: [
67
+ '~/.codex/conversations/*.json',
68
+ '.codex/history.json'
69
+ ],
70
+ config: ['~/.codex/config.json', '~/.codex/config.yaml']
71
+ },
72
+ cursor: {
73
+ global: [
74
+ '~/.cursor/memory.json',
75
+ '~/.cursor/settings.json',
76
+ '~/.cursor/global_context.md'
77
+ ],
78
+ project: [
79
+ '.cursor/project.json',
80
+ '.cursor/workspace_context.json',
81
+ '.cursor/memory.md'
82
+ ],
83
+ conversations: [
84
+ '.cursor/chat_history.json',
85
+ '.cursor/conversations/*.json'
86
+ ],
87
+ config: ['.cursor/settings.json']
88
+ },
89
+ continue: {
90
+ global: [
91
+ '~/.continue/config.json',
92
+ '~/.continue/memory.json',
93
+ '~/.continue/global_context.md'
94
+ ],
95
+ project: [
96
+ '.continue/config.json',
97
+ '.continue/memory.md',
98
+ '.continue/project_context.json'
99
+ ],
100
+ conversations: [
101
+ '.continue/sessions/*.json',
102
+ '.continue/chat_history.json'
103
+ ],
104
+ config: ['.continue/config.json']
105
+ },
106
+ aider: {
107
+ global: [
108
+ '~/.aider.conf.yml',
109
+ '~/.aider/memory.json',
110
+ '~/.aider/global_context.md'
111
+ ],
112
+ project: [
113
+ '.aider.conf.yml',
114
+ '.aider/project_memory.md',
115
+ '.aider/context.md'
116
+ ],
117
+ conversations: [
118
+ '.aider/chat_history.json',
119
+ '.aider/sessions/*.json'
120
+ ],
121
+ config: ['.aider.conf.yml']
122
+ },
123
+ generic: {
124
+ global: [
125
+ '~/.ai/global_memory.md',
126
+ '~/.ai/config.json'
127
+ ],
128
+ project: [
129
+ 'ai_memory.md',
130
+ 'project_context.md',
131
+ '.ai/memory.json',
132
+ '.ai/context.md'
133
+ ],
134
+ conversations: [
135
+ 'ai_conversations.json',
136
+ '.ai/history.json',
137
+ 'chat_history.json'
138
+ ],
139
+ config: ['~/.ai/config.json']
140
+ }
141
+ };
142
+ this.homeDir = os_1.default.homedir();
143
+ this.encryption = new zeroKnowledgeEncryption_1.ZeroKnowledgeEncryption();
144
+ }
145
+ /**
146
+ * Initialize the memory extractor
147
+ */
148
+ async initialize() {
149
+ await this.encryption.initialize();
150
+ }
151
+ /**
152
+ * Detect all available memory sources for specified CLI tools
153
+ */
154
+ async detectMemorySources(projectPath = process.cwd(), cliTools = ['all']) {
155
+ const sources = [];
156
+ const toolsToCheck = cliTools.includes('all')
157
+ ? Object.keys(this.MEMORY_PATTERNS)
158
+ : cliTools;
159
+ for (const tool of toolsToCheck) {
160
+ const patterns = this.MEMORY_PATTERNS[tool];
161
+ if (!patterns)
162
+ continue;
163
+ // Check global memory sources
164
+ if (patterns.global) {
165
+ for (const pattern of patterns.global) {
166
+ const resolved = this.resolvePath(pattern);
167
+ const exists = await this.fileExists(resolved);
168
+ sources.push({
169
+ cli_tool: tool,
170
+ memory_type: 'global',
171
+ file_path: resolved,
172
+ exists,
173
+ ...(exists && await this.getFileStats(resolved))
174
+ });
175
+ }
176
+ }
177
+ // Check project memory sources
178
+ if (patterns.project) {
179
+ for (const pattern of patterns.project) {
180
+ const resolved = path_1.default.resolve(projectPath, pattern);
181
+ const exists = await this.fileExists(resolved);
182
+ sources.push({
183
+ cli_tool: tool,
184
+ memory_type: 'project',
185
+ file_path: resolved,
186
+ exists,
187
+ ...(exists && await this.getFileStats(resolved))
188
+ });
189
+ }
190
+ }
191
+ // Check conversation sources (simplified glob pattern matching)
192
+ if (patterns.conversations) {
193
+ for (const pattern of patterns.conversations) {
194
+ const conversationSources = await this.findConversationFiles(pattern, projectPath);
195
+ sources.push(...conversationSources.map(filePath => ({
196
+ cli_tool: tool,
197
+ memory_type: 'conversation',
198
+ file_path: filePath,
199
+ exists: true
200
+ })));
201
+ }
202
+ }
203
+ }
204
+ return sources.filter(source => source.exists);
205
+ }
206
+ /**
207
+ * Extract memory from detected sources
208
+ */
209
+ async extractMemory(sources, options = {}) {
210
+ const { encryptContent = true, maxFileSizeKB = 500, relevanceThreshold = 0.3 } = options;
211
+ const extractedMemories = [];
212
+ for (const source of sources) {
213
+ if (!source.exists)
214
+ continue;
215
+ try {
216
+ // Skip large files
217
+ if (source.size_bytes && source.size_bytes > maxFileSizeKB * 1024) {
218
+ console.warn(`[Memory Extractor] Skipping large file: ${source.file_path} (${source.size_bytes} bytes)`);
219
+ continue;
220
+ }
221
+ const content = await promises_1.default.readFile(source.file_path, 'utf-8');
222
+ const relevanceScore = this.calculateRelevanceScore(content, source);
223
+ // Skip low-relevance content
224
+ if (relevanceScore < relevanceThreshold) {
225
+ continue;
226
+ }
227
+ const extractedMemory = {
228
+ source,
229
+ content,
230
+ relevance_score: relevanceScore,
231
+ extraction_method: this.getExtractionMethod(source),
232
+ project_identifier: this.extractProjectIdentifier(source.file_path)
233
+ };
234
+ extractedMemories.push(extractedMemory);
235
+ }
236
+ catch (error) {
237
+ console.warn(`[Memory Extractor] Failed to read ${source.file_path}:`, error);
238
+ continue;
239
+ }
240
+ }
241
+ return extractedMemories.sort((a, b) => b.relevance_score - a.relevance_score);
242
+ }
243
+ /**
244
+ * Extract recent conversations from CLI tools
245
+ */
246
+ async extractRecentConversations(sources, options = {}) {
247
+ const { limit = 10, timeRangeHours = 24, queryContext = '' } = options;
248
+ const conversations = [];
249
+ const cutoffTime = new Date(Date.now() - timeRangeHours * 60 * 60 * 1000);
250
+ const conversationSources = sources.filter(s => s.memory_type === 'conversation');
251
+ for (const source of conversationSources) {
252
+ try {
253
+ const content = await promises_1.default.readFile(source.file_path, 'utf-8');
254
+ const extracted = this.parseConversations(content, source.cli_tool, cutoffTime);
255
+ conversations.push(...extracted);
256
+ }
257
+ catch (error) {
258
+ console.warn(`[Memory Extractor] Failed to parse conversations from ${source.file_path}:`, error);
259
+ }
260
+ }
261
+ // Sort by relevance if query context provided, otherwise by timestamp
262
+ if (queryContext) {
263
+ conversations.forEach(conv => {
264
+ conv.relevance_score = this.calculateConversationRelevance(conv, queryContext);
265
+ });
266
+ conversations.sort((a, b) => (b.relevance_score || 0) - (a.relevance_score || 0));
267
+ }
268
+ else {
269
+ conversations.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
270
+ }
271
+ return conversations.slice(0, limit);
272
+ }
273
+ /**
274
+ * Store extracted memory in database with encryption
275
+ */
276
+ async storeMemory(userId, extractedMemories, useEncryption = true) {
277
+ const supabase = await (0, server_1.createClient)();
278
+ for (const memory of extractedMemories) {
279
+ try {
280
+ let storedContent = memory.content;
281
+ let encryptedContent = null;
282
+ let contentHash;
283
+ if (useEncryption) {
284
+ const encrypted = await this.encryption.encrypt(memory.content);
285
+ encryptedContent = JSON.stringify(encrypted);
286
+ contentHash = await this.encryption.createContentHash(memory.content);
287
+ }
288
+ else {
289
+ contentHash = await this.encryption.createContentHash(memory.content);
290
+ }
291
+ const sourcePathHash = await this.encryption.createContentHash(memory.source.file_path);
292
+ // Insert into user_cli_memory_sources
293
+ await supabase
294
+ .from('user_cli_memory_sources')
295
+ .upsert({
296
+ user_id: userId,
297
+ cli_tool: memory.source.cli_tool,
298
+ memory_type: memory.source.memory_type,
299
+ source_path_hash: sourcePathHash,
300
+ encrypted_content: encryptedContent,
301
+ content_hash: contentHash,
302
+ file_count: 1,
303
+ last_modified: memory.source.last_modified?.toISOString(),
304
+ extraction_method: memory.extraction_method,
305
+ relevance_score: memory.relevance_score,
306
+ project_identifier: memory.project_identifier,
307
+ encryption_version: useEncryption ? 1 : 0
308
+ }, {
309
+ onConflict: 'user_id,cli_tool,memory_type,source_path_hash'
310
+ });
311
+ // Log the extraction for audit
312
+ await this.logMemoryAccess(userId, {
313
+ action_type: 'extract',
314
+ cli_tool: memory.source.cli_tool,
315
+ memory_type: memory.source.memory_type,
316
+ sources_processed: 1,
317
+ bytes_processed: memory.content.length,
318
+ privacy_mode: useEncryption
319
+ });
320
+ }
321
+ catch (error) {
322
+ console.error(`[Memory Extractor] Failed to store memory for ${memory.source.file_path}:`, error);
323
+ }
324
+ }
325
+ }
326
+ /**
327
+ * Store conversations with encryption
328
+ */
329
+ async storeConversations(userId, conversations, useEncryption = true) {
330
+ const supabase = await (0, server_1.createClient)();
331
+ for (const conv of conversations) {
332
+ try {
333
+ let encryptedUserMessage = null;
334
+ let encryptedAssistantResponse = null;
335
+ let conversationHash;
336
+ const conversationText = `${conv.user_message}\n\n${conv.assistant_response}`;
337
+ if (useEncryption) {
338
+ const encryptedUser = await this.encryption.encrypt(conv.user_message);
339
+ const encryptedAssistant = await this.encryption.encrypt(conv.assistant_response);
340
+ encryptedUserMessage = JSON.stringify(encryptedUser);
341
+ encryptedAssistantResponse = JSON.stringify(encryptedAssistant);
342
+ conversationHash = await this.encryption.createContentHash(conversationText);
343
+ }
344
+ else {
345
+ conversationHash = await this.encryption.createContentHash(conversationText);
346
+ }
347
+ // Insert into mcp_conversation_memory (extending existing table)
348
+ await supabase
349
+ .from('mcp_conversation_memory')
350
+ .upsert({
351
+ user_id: userId,
352
+ user_message: useEncryption ? '' : conv.user_message,
353
+ assistant_response: useEncryption ? '' : conv.assistant_response,
354
+ encrypted_user_message: encryptedUserMessage,
355
+ encrypted_assistant_response: encryptedAssistantResponse,
356
+ model_used: conv.model_used || 'unknown',
357
+ tokens_used: conv.tokens_used || 0,
358
+ conversation_hash: conversationHash,
359
+ encryption_version: useEncryption ? 1 : 0,
360
+ privacy_mode: useEncryption,
361
+ created_at: conv.timestamp.toISOString()
362
+ }, {
363
+ onConflict: 'conversation_hash'
364
+ });
365
+ }
366
+ catch (error) {
367
+ console.error('[Memory Extractor] Failed to store conversation:', error);
368
+ }
369
+ }
370
+ }
371
+ /**
372
+ * Get comprehensive memory context for injection
373
+ */
374
+ async getContextForInjection(userId, queryContext = '', options = {}) {
375
+ const supabase = await (0, server_1.createClient)();
376
+ const { maxMemoryKB = 50, maxConversations = 6 } = options;
377
+ let memory = '';
378
+ let conversations = '';
379
+ let totalSize = 0;
380
+ const sourcesUsed = [];
381
+ try {
382
+ // Get relevant memory sources
383
+ const { data: memorySources } = await supabase
384
+ .from('user_cli_memory_sources')
385
+ .select('*')
386
+ .eq('user_id', userId)
387
+ .gte('relevance_score', 0.5)
388
+ .order('relevance_score', { ascending: false })
389
+ .limit(20);
390
+ if (memorySources) {
391
+ for (const source of memorySources) {
392
+ if (totalSize > maxMemoryKB * 1024)
393
+ break;
394
+ let content = '';
395
+ if (source.encrypted_content) {
396
+ try {
397
+ const encryptedData = JSON.parse(source.encrypted_content);
398
+ content = await this.encryption.decrypt(encryptedData);
399
+ }
400
+ catch (error) {
401
+ console.warn('[Memory Context] Failed to decrypt memory source:', error);
402
+ continue;
403
+ }
404
+ }
405
+ if (content) {
406
+ const contextEntry = `[${source.cli_tool}:${source.memory_type}]\n${content}\n\n`;
407
+ if (totalSize + contextEntry.length <= maxMemoryKB * 1024) {
408
+ memory += contextEntry;
409
+ totalSize += contextEntry.length;
410
+ sourcesUsed.push(`${source.cli_tool}:${source.memory_type}`);
411
+ }
412
+ }
413
+ }
414
+ }
415
+ // Get relevant conversations
416
+ const { data: recentConversations } = await supabase
417
+ .from('mcp_conversation_memory')
418
+ .select('*')
419
+ .eq('user_id', userId)
420
+ .order('created_at', { ascending: false })
421
+ .limit(maxConversations);
422
+ if (recentConversations) {
423
+ for (const conv of recentConversations.slice(0, maxConversations)) {
424
+ let userMsg = conv.user_message;
425
+ let assistantResp = conv.assistant_response;
426
+ if (conv.encrypted_user_message && conv.encrypted_assistant_response) {
427
+ try {
428
+ const encryptedUser = JSON.parse(conv.encrypted_user_message);
429
+ const encryptedAssistant = JSON.parse(conv.encrypted_assistant_response);
430
+ userMsg = await this.encryption.decrypt(encryptedUser);
431
+ assistantResp = await this.encryption.decrypt(encryptedAssistant);
432
+ }
433
+ catch (error) {
434
+ console.warn('[Memory Context] Failed to decrypt conversation:', error);
435
+ continue;
436
+ }
437
+ }
438
+ const convEntry = `[Conversation ${conv.created_at}]\nUser: ${userMsg}\nAssistant: ${assistantResp}\n\n`;
439
+ conversations += convEntry;
440
+ }
441
+ }
442
+ }
443
+ catch (error) {
444
+ console.error('[Memory Context] Failed to get context:', error);
445
+ }
446
+ return {
447
+ memory: memory.trim(),
448
+ conversations: conversations.trim(),
449
+ totalSizeKB: Math.round(totalSize / 1024),
450
+ sourcesUsed
451
+ };
452
+ }
453
+ // Private helper methods
454
+ resolvePath(pathPattern) {
455
+ return pathPattern.replace('~', this.homeDir);
456
+ }
457
+ async fileExists(filePath) {
458
+ try {
459
+ await promises_1.default.access(filePath);
460
+ return true;
461
+ }
462
+ catch {
463
+ return false;
464
+ }
465
+ }
466
+ async getFileStats(filePath) {
467
+ const stats = await promises_1.default.stat(filePath);
468
+ return {
469
+ size_bytes: stats.size,
470
+ last_modified: stats.mtime
471
+ };
472
+ }
473
+ async findConversationFiles(pattern, projectPath) {
474
+ const files = [];
475
+ try {
476
+ const resolved = pattern.includes('~') ? this.resolvePath(pattern) : path_1.default.resolve(projectPath, pattern);
477
+ const dir = path_1.default.dirname(resolved.replace('*', ''));
478
+ if (await this.fileExists(dir)) {
479
+ const entries = await promises_1.default.readdir(dir);
480
+ for (const entry of entries) {
481
+ const fullPath = path_1.default.join(dir, entry);
482
+ const stats = await promises_1.default.stat(fullPath);
483
+ if (stats.isFile() && (entry.endsWith('.json') || entry.endsWith('.jsonl'))) {
484
+ files.push(fullPath);
485
+ }
486
+ }
487
+ }
488
+ }
489
+ catch (error) {
490
+ // Silently handle directory access errors
491
+ }
492
+ return files;
493
+ }
494
+ calculateRelevanceScore(content, source) {
495
+ let score = 0.5; // Base score
496
+ // Boost for project-level memory
497
+ if (source.memory_type === 'project')
498
+ score += 0.2;
499
+ // Boost for recent conversations
500
+ if (source.memory_type === 'conversation')
501
+ score += 0.1;
502
+ // Boost for certain CLI tools
503
+ if (['claude_code', 'cursor', 'cline'].includes(source.cli_tool))
504
+ score += 0.1;
505
+ // Content quality indicators
506
+ if (content.includes('TODO') || content.includes('FIXME'))
507
+ score += 0.1;
508
+ if (content.includes('pattern') || content.includes('decision'))
509
+ score += 0.1;
510
+ if (content.length > 1000 && content.length < 10000)
511
+ score += 0.1;
512
+ return Math.min(score, 1.0);
513
+ }
514
+ getExtractionMethod(source) {
515
+ const ext = path_1.default.extname(source.file_path).toLowerCase();
516
+ switch (ext) {
517
+ case '.md': return 'markdown_parse';
518
+ case '.json': return 'json_parse';
519
+ case '.jsonl': return 'jsonl_parse';
520
+ case '.yml':
521
+ case '.yaml': return 'yaml_parse';
522
+ default: return 'text_parse';
523
+ }
524
+ }
525
+ extractProjectIdentifier(filePath) {
526
+ const parts = filePath.split(path_1.default.sep);
527
+ const projectIndex = parts.findIndex(part => !part.startsWith('.') && part !== 'home' && part !== 'Users');
528
+ return projectIndex >= 0 ? parts.slice(projectIndex, projectIndex + 2).join('/') : 'unknown';
529
+ }
530
+ parseConversations(content, cliTool, cutoffTime) {
531
+ const conversations = [];
532
+ try {
533
+ if (cliTool === 'claude_code' && content.includes('"type":"message"')) {
534
+ // Parse Claude Code JSONL format
535
+ const lines = content.split('\n').filter(line => line.trim());
536
+ for (const line of lines) {
537
+ try {
538
+ const entry = JSON.parse(line);
539
+ if (entry.type === 'message' && entry.timestamp) {
540
+ const timestamp = new Date(entry.timestamp);
541
+ if (timestamp > cutoffTime) {
542
+ conversations.push({
543
+ timestamp,
544
+ user_message: entry.content || '',
545
+ assistant_response: entry.response || '',
546
+ model_used: entry.model,
547
+ tokens_used: entry.tokens
548
+ });
549
+ }
550
+ }
551
+ }
552
+ catch { }
553
+ }
554
+ }
555
+ else if (content.startsWith('[') || content.startsWith('{')) {
556
+ // Parse JSON format conversations
557
+ const data = JSON.parse(content);
558
+ const messages = Array.isArray(data) ? data : data.messages || [];
559
+ for (let i = 0; i < messages.length - 1; i += 2) {
560
+ const userMsg = messages[i];
561
+ const assistantMsg = messages[i + 1];
562
+ if (userMsg && assistantMsg) {
563
+ const timestamp = new Date(userMsg.timestamp || assistantMsg.timestamp || Date.now());
564
+ if (timestamp > cutoffTime) {
565
+ conversations.push({
566
+ timestamp,
567
+ user_message: userMsg.content || userMsg.text || '',
568
+ assistant_response: assistantMsg.content || assistantMsg.text || '',
569
+ model_used: assistantMsg.model,
570
+ tokens_used: assistantMsg.tokens
571
+ });
572
+ }
573
+ }
574
+ }
575
+ }
576
+ }
577
+ catch (error) {
578
+ console.warn(`[Memory Extractor] Failed to parse conversations for ${cliTool}:`, error);
579
+ }
580
+ return conversations;
581
+ }
582
+ calculateConversationRelevance(conversation, queryContext) {
583
+ if (!queryContext)
584
+ return 0.5;
585
+ const text = `${conversation.user_message} ${conversation.assistant_response}`.toLowerCase();
586
+ const queryWords = queryContext.toLowerCase().split(/\s+/);
587
+ const matches = queryWords.filter(word => text.includes(word)).length;
588
+ return Math.min(matches / queryWords.length, 1.0);
589
+ }
590
+ async logMemoryAccess(userId, logData) {
591
+ const supabase = await (0, server_1.createClient)();
592
+ try {
593
+ await supabase
594
+ .from('user_memory_audit_log')
595
+ .insert({
596
+ user_id: userId,
597
+ ...logData
598
+ });
599
+ }
600
+ catch (error) {
601
+ console.error('[Memory Extractor] Failed to log memory access:', error);
602
+ }
603
+ }
604
+ }
605
+ exports.UniversalMemoryExtractor = UniversalMemoryExtractor;
606
+ // Export singleton instance
607
+ exports.universalMemoryExtractor = new UniversalMemoryExtractor();
@@ -0,0 +1,289 @@
1
+ "use strict";
2
+ /**
3
+ * Zero-Knowledge Client-Side Encryption System
4
+ * All encryption keys never leave the user's browser
5
+ * Enterprise-grade AES-256-GCM encryption with automatic key rotation
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.zkEncryption = exports.ZeroKnowledgeEncryption = void 0;
9
+ class ZeroKnowledgeEncryption {
10
+ constructor() {
11
+ this.db = null;
12
+ this.activeKey = null;
13
+ }
14
+ /**
15
+ * Initialize the encryption system
16
+ */
17
+ async initialize() {
18
+ await this.initializeIndexedDB();
19
+ await this.loadOrCreateActiveKey();
20
+ }
21
+ /**
22
+ * Initialize IndexedDB for local key storage
23
+ */
24
+ async initializeIndexedDB() {
25
+ return new Promise((resolve, reject) => {
26
+ const request = indexedDB.open(ZeroKnowledgeEncryption.DB_NAME, ZeroKnowledgeEncryption.DB_VERSION);
27
+ request.onerror = () => reject(request.error);
28
+ request.onsuccess = () => {
29
+ this.db = request.result;
30
+ resolve();
31
+ };
32
+ request.onupgradeneeded = (event) => {
33
+ const db = event.target.result;
34
+ // Store for encryption keys (never synchronized)
35
+ if (!db.objectStoreNames.contains('keys')) {
36
+ const keyStore = db.createObjectStore('keys', { keyPath: 'keyId' });
37
+ keyStore.createIndex('created', 'created', { unique: false });
38
+ }
39
+ // Cache for encrypted data
40
+ if (!db.objectStoreNames.contains('cache')) {
41
+ const cacheStore = db.createObjectStore('cache', { keyPath: 'id' });
42
+ cacheStore.createIndex('expires', 'expires', { unique: false });
43
+ }
44
+ };
45
+ });
46
+ }
47
+ /**
48
+ * Generate new AES-256-GCM encryption key
49
+ */
50
+ async generateKey() {
51
+ const keyId = this.generateKeyId();
52
+ const key = await window.crypto.subtle.generateKey({
53
+ name: ZeroKnowledgeEncryption.ALGORITHM,
54
+ length: ZeroKnowledgeEncryption.KEY_LENGTH,
55
+ }, true, // extractable for storage
56
+ ['encrypt', 'decrypt']);
57
+ const now = new Date();
58
+ const rotationDue = new Date(now.getTime() + (ZeroKnowledgeEncryption.KEY_ROTATION_DAYS * 24 * 60 * 60 * 1000));
59
+ return {
60
+ keyId,
61
+ key,
62
+ created: now,
63
+ rotationDue
64
+ };
65
+ }
66
+ /**
67
+ * Load active key or create new one
68
+ */
69
+ async loadOrCreateActiveKey() {
70
+ if (!this.db)
71
+ throw new Error('Database not initialized');
72
+ const transaction = this.db.transaction(['keys'], 'readonly');
73
+ const store = transaction.objectStore('keys');
74
+ // Get the most recent key
75
+ const index = store.index('created');
76
+ const request = index.openCursor(null, 'prev');
77
+ return new Promise((resolve) => {
78
+ request.onsuccess = async (event) => {
79
+ const cursor = event.target.result;
80
+ if (cursor) {
81
+ const keyData = cursor.value;
82
+ // Check if key needs rotation
83
+ if (new Date() > new Date(keyData.rotationDue)) {
84
+ console.log('[ZK Encryption] Key rotation needed, generating new key');
85
+ await this.createAndStoreKey();
86
+ }
87
+ else {
88
+ // Import existing key
89
+ const keyBuffer = new Uint8Array(keyData.keyBuffer);
90
+ this.activeKey = {
91
+ keyId: keyData.keyId,
92
+ key: await window.crypto.subtle.importKey('raw', keyBuffer, { name: ZeroKnowledgeEncryption.ALGORITHM }, true, ['encrypt', 'decrypt']),
93
+ created: new Date(keyData.created),
94
+ rotationDue: new Date(keyData.rotationDue)
95
+ };
96
+ }
97
+ }
98
+ else {
99
+ // No keys exist, create first one
100
+ await this.createAndStoreKey();
101
+ }
102
+ resolve();
103
+ };
104
+ });
105
+ }
106
+ /**
107
+ * Create and store new key
108
+ */
109
+ async createAndStoreKey() {
110
+ this.activeKey = await this.generateKey();
111
+ await this.storeKey(this.activeKey);
112
+ }
113
+ /**
114
+ * Store key in IndexedDB
115
+ */
116
+ async storeKey(userKey) {
117
+ if (!this.db)
118
+ throw new Error('Database not initialized');
119
+ const keyBuffer = await window.crypto.subtle.exportKey('raw', userKey.key);
120
+ const transaction = this.db.transaction(['keys'], 'readwrite');
121
+ const store = transaction.objectStore('keys');
122
+ await store.put({
123
+ keyId: userKey.keyId,
124
+ keyBuffer: Array.from(new Uint8Array(keyBuffer)),
125
+ created: userKey.created.toISOString(),
126
+ rotationDue: userKey.rotationDue.toISOString()
127
+ });
128
+ }
129
+ /**
130
+ * Encrypt sensitive data
131
+ */
132
+ async encrypt(plaintext) {
133
+ if (!this.activeKey)
134
+ throw new Error('Encryption key not available');
135
+ const encoder = new TextEncoder();
136
+ const data = encoder.encode(plaintext);
137
+ const iv = window.crypto.getRandomValues(new Uint8Array(ZeroKnowledgeEncryption.IV_LENGTH));
138
+ const encrypted = await window.crypto.subtle.encrypt({
139
+ name: ZeroKnowledgeEncryption.ALGORITHM,
140
+ iv: iv,
141
+ }, this.activeKey.key, data);
142
+ const encryptedArray = new Uint8Array(encrypted);
143
+ const ciphertext = encryptedArray.slice(0, -ZeroKnowledgeEncryption.AUTH_TAG_LENGTH);
144
+ const authTag = encryptedArray.slice(-ZeroKnowledgeEncryption.AUTH_TAG_LENGTH);
145
+ return {
146
+ ciphertext: this.arrayBufferToBase64(ciphertext.buffer),
147
+ iv: this.arrayBufferToBase64(iv.buffer),
148
+ authTag: this.arrayBufferToBase64(authTag.buffer),
149
+ keyId: this.activeKey.keyId,
150
+ version: 1
151
+ };
152
+ }
153
+ /**
154
+ * Decrypt sensitive data
155
+ */
156
+ async decrypt(encryptedData) {
157
+ const key = await this.getKeyById(encryptedData.keyId);
158
+ if (!key)
159
+ throw new Error(`Encryption key ${encryptedData.keyId} not found`);
160
+ const ciphertext = this.base64ToArrayBuffer(encryptedData.ciphertext);
161
+ const iv = this.base64ToArrayBuffer(encryptedData.iv);
162
+ const authTag = this.base64ToArrayBuffer(encryptedData.authTag);
163
+ // Combine ciphertext and auth tag for Web Crypto API
164
+ const combined = new Uint8Array(ciphertext.byteLength + authTag.byteLength);
165
+ combined.set(new Uint8Array(ciphertext));
166
+ combined.set(new Uint8Array(authTag), ciphertext.byteLength);
167
+ const decrypted = await window.crypto.subtle.decrypt({
168
+ name: ZeroKnowledgeEncryption.ALGORITHM,
169
+ iv: new Uint8Array(iv),
170
+ }, key, combined);
171
+ const decoder = new TextDecoder();
172
+ return decoder.decode(decrypted);
173
+ }
174
+ /**
175
+ * Get encryption key by ID
176
+ */
177
+ async getKeyById(keyId) {
178
+ if (!this.db)
179
+ return null;
180
+ const transaction = this.db.transaction(['keys'], 'readonly');
181
+ const store = transaction.objectStore('keys');
182
+ const request = store.get(keyId);
183
+ return new Promise((resolve) => {
184
+ request.onsuccess = async () => {
185
+ if (request.result) {
186
+ const keyBuffer = new Uint8Array(request.result.keyBuffer);
187
+ const key = await window.crypto.subtle.importKey('raw', keyBuffer, { name: ZeroKnowledgeEncryption.ALGORITHM }, true, ['encrypt', 'decrypt']);
188
+ resolve(key);
189
+ }
190
+ else {
191
+ resolve(null);
192
+ }
193
+ };
194
+ request.onerror = () => resolve(null);
195
+ });
196
+ }
197
+ /**
198
+ * Create SHA-256 hash for content deduplication
199
+ */
200
+ async createContentHash(content) {
201
+ const encoder = new TextEncoder();
202
+ const data = encoder.encode(content);
203
+ const hashBuffer = await window.crypto.subtle.digest('SHA-256', data);
204
+ return this.arrayBufferToHex(hashBuffer);
205
+ }
206
+ /**
207
+ * Check if key rotation is needed
208
+ */
209
+ isRotationDue() {
210
+ return this.activeKey ? new Date() > this.activeKey.rotationDue : true;
211
+ }
212
+ /**
213
+ * Force key rotation
214
+ */
215
+ async rotateKeys() {
216
+ await this.createAndStoreKey();
217
+ console.log('[ZK Encryption] Key rotation completed');
218
+ }
219
+ /**
220
+ * Generate unique key ID
221
+ */
222
+ generateKeyId() {
223
+ const timestamp = Date.now().toString(36);
224
+ const random = Math.random().toString(36).substr(2, 9);
225
+ return `zk_${timestamp}_${random}`;
226
+ }
227
+ /**
228
+ * Utility: ArrayBuffer to Base64
229
+ */
230
+ arrayBufferToBase64(buffer) {
231
+ const bytes = new Uint8Array(buffer);
232
+ let binary = '';
233
+ for (let i = 0; i < bytes.byteLength; i++) {
234
+ binary += String.fromCharCode(bytes[i]);
235
+ }
236
+ return btoa(binary);
237
+ }
238
+ /**
239
+ * Utility: Base64 to ArrayBuffer
240
+ */
241
+ base64ToArrayBuffer(base64) {
242
+ const binary = atob(base64);
243
+ const bytes = new Uint8Array(binary.length);
244
+ for (let i = 0; i < binary.length; i++) {
245
+ bytes[i] = binary.charCodeAt(i);
246
+ }
247
+ return bytes.buffer;
248
+ }
249
+ /**
250
+ * Utility: ArrayBuffer to Hex
251
+ */
252
+ arrayBufferToHex(buffer) {
253
+ const bytes = new Uint8Array(buffer);
254
+ return Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');
255
+ }
256
+ /**
257
+ * Get encryption status for dashboard
258
+ */
259
+ getEncryptionStatus() {
260
+ return {
261
+ keyId: this.activeKey?.keyId || null,
262
+ created: this.activeKey?.created || null,
263
+ rotationDue: this.activeKey?.rotationDue || null,
264
+ rotationNeeded: this.isRotationDue()
265
+ };
266
+ }
267
+ /**
268
+ * Clear all keys (for user logout/reset)
269
+ */
270
+ async clearAllKeys() {
271
+ if (!this.db)
272
+ return;
273
+ const transaction = this.db.transaction(['keys', 'cache'], 'readwrite');
274
+ await transaction.objectStore('keys').clear();
275
+ await transaction.objectStore('cache').clear();
276
+ this.activeKey = null;
277
+ console.log('[ZK Encryption] All keys cleared');
278
+ }
279
+ }
280
+ exports.ZeroKnowledgeEncryption = ZeroKnowledgeEncryption;
281
+ ZeroKnowledgeEncryption.ALGORITHM = 'AES-GCM';
282
+ ZeroKnowledgeEncryption.KEY_LENGTH = 256;
283
+ ZeroKnowledgeEncryption.IV_LENGTH = 12;
284
+ ZeroKnowledgeEncryption.AUTH_TAG_LENGTH = 16;
285
+ ZeroKnowledgeEncryption.KEY_ROTATION_DAYS = 30;
286
+ ZeroKnowledgeEncryption.DB_NAME = 'PolydevZKMemory';
287
+ ZeroKnowledgeEncryption.DB_VERSION = 1;
288
+ // Export singleton instance
289
+ exports.zkEncryption = new ZeroKnowledgeEncryption();
package/mcp/manifest.json CHANGED
@@ -198,7 +198,7 @@
198
198
  ]
199
199
  },
200
200
  {
201
- "name": "polydev.force_cli_detection",
201
+ "name": "force_cli_detection",
202
202
  "description": "Force detection and status update for CLI tools (Claude Code, Codex CLI, Gemini CLI). Updates local cache and reports status to MCP server via Supabase.",
203
203
  "inputSchema": {
204
204
  "type": "object",
@@ -290,7 +290,7 @@
290
290
  ]
291
291
  },
292
292
  {
293
- "name": "polydev.get_cli_status",
293
+ "name": "get_cli_status",
294
294
  "description": "Get current CLI status with caching support. Returns cached results if available and fresh, otherwise performs new detection.",
295
295
  "inputSchema": {
296
296
  "type": "object",
@@ -347,7 +347,7 @@
347
347
  }
348
348
  },
349
349
  {
350
- "name": "polydev.send_cli_prompt",
350
+ "name": "send_cli_prompt",
351
351
  "description": "Send a prompt to a CLI provider and get response. Supports both stdin and argument modes for maximum compatibility.",
352
352
  "inputSchema": {
353
353
  "type": "object",
@@ -443,7 +443,7 @@
443
443
  ]
444
444
  },
445
445
  {
446
- "name": "polydev.detect_memory_sources",
446
+ "name": "detect_memory_sources",
447
447
  "description": "Detect all available memory sources across CLI tools (Claude Code, Cline, Codex, Cursor, Continue, Aider). Returns file paths for global memory, project memory, and recent conversations.",
448
448
  "inputSchema": {
449
449
  "type": "object",
@@ -503,7 +503,7 @@
503
503
  }
504
504
  },
505
505
  {
506
- "name": "polydev.extract_memory",
506
+ "name": "extract_memory",
507
507
  "description": "Extract and optionally encrypt memory content from detected CLI tool sources. Provides TF-IDF relevance scoring and content analysis.",
508
508
  "inputSchema": {
509
509
  "type": "object",
@@ -550,7 +550,7 @@
550
550
  }
551
551
  },
552
552
  {
553
- "name": "polydev.get_recent_conversations",
553
+ "name": "get_recent_conversations",
554
554
  "description": "Get recent conversations from CLI tools with TF-IDF relevance scoring against query context. Supports encrypted storage and retrieval.",
555
555
  "inputSchema": {
556
556
  "type": "object",
@@ -590,7 +590,7 @@
590
590
  }
591
591
  },
592
592
  {
593
- "name": "polydev.get_memory_context",
593
+ "name": "get_memory_context",
594
594
  "description": "Get formatted memory and conversation context for injection into prompts. Automatically handles decryption and relevance scoring.",
595
595
  "inputSchema": {
596
596
  "type": "object",
@@ -631,7 +631,7 @@
631
631
  }
632
632
  },
633
633
  {
634
- "name": "polydev.manage_memory_preferences",
634
+ "name": "manage_memory_preferences",
635
635
  "description": "Configure memory extraction and privacy preferences. Control which CLI tools and memory types are enabled, encryption settings, and context injection behavior.",
636
636
  "inputSchema": {
637
637
  "type": "object",
package/mcp/server.js CHANGED
@@ -1,9 +1,32 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // Register ts-node for TypeScript support
4
+ try {
5
+ require('ts-node/register');
6
+ } catch (e) {
7
+ // ts-node not available, proceed without it
8
+ }
9
+
3
10
  const fs = require('fs');
4
11
  const path = require('path');
5
12
  const CLIManager = require('../lib/cliManager').default;
6
- const UniversalMemoryExtractor = require('../src/lib/universalMemoryExtractor').UniversalMemoryExtractor;
13
+
14
+ let UniversalMemoryExtractor;
15
+ try {
16
+ UniversalMemoryExtractor = require('../src/lib/universalMemoryExtractor').UniversalMemoryExtractor;
17
+ } catch (e) {
18
+ // Fallback if TypeScript module is not available
19
+ console.warn('UniversalMemoryExtractor not available, memory features will be disabled');
20
+ UniversalMemoryExtractor = class {
21
+ async detectMemorySources() { return []; }
22
+ async extractMemory() { return {}; }
23
+ async getRecentConversations() { return []; }
24
+ async getRelevantContext() { return ''; }
25
+ async getPreferences() { return {}; }
26
+ async updatePreferences() { return {}; }
27
+ async resetPreferences() { return {}; }
28
+ };
29
+ }
7
30
 
8
31
  class MCPServer {
9
32
  constructor() {
@@ -126,35 +149,35 @@ class MCPServer {
126
149
  result = await this.callPerspectivesAPI(args);
127
150
  break;
128
151
 
129
- case 'polydev.force_cli_detection':
152
+ case 'force_cli_detection':
130
153
  result = await this.forceCliDetection(args);
131
154
  break;
132
155
 
133
- case 'polydev.get_cli_status':
156
+ case 'get_cli_status':
134
157
  result = await this.getCliStatus(args);
135
158
  break;
136
159
 
137
- case 'polydev.send_cli_prompt':
160
+ case 'send_cli_prompt':
138
161
  result = await this.sendCliPrompt(args);
139
162
  break;
140
163
 
141
- case 'polydev.detect_memory_sources':
164
+ case 'detect_memory_sources':
142
165
  result = await this.detectMemorySources(args);
143
166
  break;
144
167
 
145
- case 'polydev.extract_memory':
168
+ case 'extract_memory':
146
169
  result = await this.extractMemory(args);
147
170
  break;
148
171
 
149
- case 'polydev.get_recent_conversations':
172
+ case 'get_recent_conversations':
150
173
  result = await this.getRecentConversations(args);
151
174
  break;
152
175
 
153
- case 'polydev.get_memory_context':
176
+ case 'get_memory_context':
154
177
  result = await this.getMemoryContext(args);
155
178
  break;
156
179
 
157
- case 'polydev.manage_memory_preferences':
180
+ case 'manage_memory_preferences':
158
181
  result = await this.manageMemoryPreferences(args);
159
182
  break;
160
183
 
@@ -298,20 +321,20 @@ class MCPServer {
298
321
  case 'get_perspectives':
299
322
  return this.formatPerspectivesResponse(result);
300
323
 
301
- case 'polydev.force_cli_detection':
324
+ case 'force_cli_detection':
302
325
  return this.formatCliDetectionResponse(result);
303
-
304
- case 'polydev.get_cli_status':
326
+
327
+ case 'get_cli_status':
305
328
  return this.formatCliStatusResponse(result);
306
-
307
- case 'polydev.send_cli_prompt':
329
+
330
+ case 'send_cli_prompt':
308
331
  return this.formatCliPromptResponse(result);
309
-
310
- case 'polydev.detect_memory_sources':
311
- case 'polydev.extract_memory':
312
- case 'polydev.get_recent_conversations':
313
- case 'polydev.get_memory_context':
314
- case 'polydev.manage_memory_preferences':
332
+
333
+ case 'detect_memory_sources':
334
+ case 'extract_memory':
335
+ case 'get_recent_conversations':
336
+ case 'get_memory_context':
337
+ case 'manage_memory_preferences':
315
338
  return this.formatMemoryResponse(result);
316
339
 
317
340
  default:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polydev-ai",
3
- "version": "1.2.3",
3
+ "version": "1.2.4",
4
4
  "description": "Agentic workflow assistant with CLI integration - get diverse perspectives from multiple LLMs when stuck or need enhanced reasoning",
5
5
  "keywords": [
6
6
  "mcp",
@@ -69,6 +69,7 @@
69
69
  "sonner": "^2.0.7",
70
70
  "stripe": "^18.5.0",
71
71
  "tailwind-merge": "^3.3.1",
72
+ "ts-node": "^10.9.2",
72
73
  "which": "^5.0.0"
73
74
  },
74
75
  "devDependencies": {