polydev-ai 1.0.0 → 1.0.1

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/mcp/manifest.json CHANGED
@@ -441,6 +441,232 @@
441
441
  }
442
442
  }
443
443
  ]
444
+ },
445
+ {
446
+ "name": "polydev.detect_memory_sources",
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
+ "inputSchema": {
449
+ "type": "object",
450
+ "properties": {
451
+ "project_path": {
452
+ "type": "string",
453
+ "description": "Project directory path to scan for memory sources",
454
+ "default": "."
455
+ },
456
+ "cli_tools": {
457
+ "type": "array",
458
+ "items": {
459
+ "type": "string",
460
+ "enum": ["claude_code", "cline", "codex_cli", "cursor", "continue", "aider", "generic", "all"]
461
+ },
462
+ "description": "CLI tools to detect memory for",
463
+ "default": ["all"]
464
+ },
465
+ "memory_types": {
466
+ "type": "array",
467
+ "items": {
468
+ "type": "string",
469
+ "enum": ["global", "project", "conversation", "config"]
470
+ },
471
+ "description": "Types of memory to detect",
472
+ "default": ["global", "project", "conversation"]
473
+ }
474
+ }
475
+ },
476
+ "outputSchema": {
477
+ "type": "object",
478
+ "properties": {
479
+ "success": { "type": "boolean" },
480
+ "sources": {
481
+ "type": "array",
482
+ "items": {
483
+ "type": "object",
484
+ "properties": {
485
+ "cli_tool": { "type": "string" },
486
+ "memory_type": { "type": "string" },
487
+ "file_path": { "type": "string" },
488
+ "exists": { "type": "boolean" },
489
+ "size_bytes": { "type": "number" },
490
+ "last_modified": { "type": "string" }
491
+ }
492
+ }
493
+ },
494
+ "summary": {
495
+ "type": "object",
496
+ "properties": {
497
+ "total_sources": { "type": "number" },
498
+ "sources_by_tool": { "type": "object" },
499
+ "sources_by_type": { "type": "object" }
500
+ }
501
+ }
502
+ }
503
+ }
504
+ },
505
+ {
506
+ "name": "polydev.extract_memory",
507
+ "description": "Extract and optionally encrypt memory content from detected CLI tool sources. Provides TF-IDF relevance scoring and content analysis.",
508
+ "inputSchema": {
509
+ "type": "object",
510
+ "properties": {
511
+ "user_token": {
512
+ "type": "string",
513
+ "description": "Polydev user authentication token for server storage",
514
+ "minLength": 1
515
+ },
516
+ "project_path": {
517
+ "type": "string",
518
+ "description": "Project directory path",
519
+ "default": "."
520
+ },
521
+ "cli_tools": {
522
+ "type": "array",
523
+ "items": { "type": "string" },
524
+ "default": ["all"]
525
+ },
526
+ "memory_types": {
527
+ "type": "array",
528
+ "items": { "type": "string" },
529
+ "default": ["global", "project", "conversation"]
530
+ },
531
+ "encryption_enabled": {
532
+ "type": "boolean",
533
+ "description": "Enable zero-knowledge encryption for extracted content",
534
+ "default": true
535
+ },
536
+ "max_file_size_kb": {
537
+ "type": "integer",
538
+ "minimum": 1,
539
+ "maximum": 1000,
540
+ "default": 500
541
+ },
542
+ "relevance_threshold": {
543
+ "type": "number",
544
+ "minimum": 0,
545
+ "maximum": 1,
546
+ "default": 0.3
547
+ }
548
+ },
549
+ "required": ["user_token"]
550
+ }
551
+ },
552
+ {
553
+ "name": "polydev.get_recent_conversations",
554
+ "description": "Get recent conversations from CLI tools with TF-IDF relevance scoring against query context. Supports encrypted storage and retrieval.",
555
+ "inputSchema": {
556
+ "type": "object",
557
+ "properties": {
558
+ "user_token": {
559
+ "type": "string",
560
+ "description": "Polydev user authentication token",
561
+ "minLength": 1
562
+ },
563
+ "query_context": {
564
+ "type": "string",
565
+ "description": "Current query for relevance scoring and context matching"
566
+ },
567
+ "limit": {
568
+ "type": "integer",
569
+ "minimum": 1,
570
+ "maximum": 50,
571
+ "default": 6
572
+ },
573
+ "cli_tools": {
574
+ "type": "array",
575
+ "items": { "type": "string" },
576
+ "default": ["all"]
577
+ },
578
+ "time_range_hours": {
579
+ "type": "integer",
580
+ "minimum": 1,
581
+ "maximum": 168,
582
+ "default": 24
583
+ },
584
+ "project_path": {
585
+ "type": "string",
586
+ "default": "."
587
+ }
588
+ },
589
+ "required": ["user_token"]
590
+ }
591
+ },
592
+ {
593
+ "name": "polydev.get_memory_context",
594
+ "description": "Get formatted memory and conversation context for injection into prompts. Automatically handles decryption and relevance scoring.",
595
+ "inputSchema": {
596
+ "type": "object",
597
+ "properties": {
598
+ "user_token": {
599
+ "type": "string",
600
+ "description": "Polydev user authentication token",
601
+ "minLength": 1
602
+ },
603
+ "query_context": {
604
+ "type": "string",
605
+ "description": "Query to find relevant memory and conversations for"
606
+ },
607
+ "max_memory_kb": {
608
+ "type": "integer",
609
+ "minimum": 1,
610
+ "maximum": 200,
611
+ "default": 50
612
+ },
613
+ "max_conversations": {
614
+ "type": "integer",
615
+ "minimum": 1,
616
+ "maximum": 20,
617
+ "default": 6
618
+ },
619
+ "cli_tools": {
620
+ "type": "array",
621
+ "items": { "type": "string" },
622
+ "default": ["all"]
623
+ },
624
+ "memory_types": {
625
+ "type": "array",
626
+ "items": { "type": "string" },
627
+ "default": ["project", "conversation"]
628
+ }
629
+ },
630
+ "required": ["user_token"]
631
+ }
632
+ },
633
+ {
634
+ "name": "polydev.manage_memory_preferences",
635
+ "description": "Configure memory extraction and privacy preferences. Control which CLI tools and memory types are enabled, encryption settings, and context injection behavior.",
636
+ "inputSchema": {
637
+ "type": "object",
638
+ "properties": {
639
+ "user_token": {
640
+ "type": "string",
641
+ "description": "Polydev user authentication token",
642
+ "minLength": 1
643
+ },
644
+ "action": {
645
+ "type": "string",
646
+ "enum": ["get", "update"],
647
+ "description": "Action to perform",
648
+ "default": "get"
649
+ },
650
+ "preferences": {
651
+ "type": "object",
652
+ "description": "Memory preferences to update",
653
+ "properties": {
654
+ "memory_enabled": { "type": "boolean" },
655
+ "privacy_mode": { "type": "boolean" },
656
+ "auto_extraction": { "type": "boolean" },
657
+ "cli_tools_enabled": { "type": "object" },
658
+ "memory_types_enabled": { "type": "object" },
659
+ "auto_inject_context": { "type": "boolean" },
660
+ "context_relevance_threshold": { "type": "number", "minimum": 0, "maximum": 1 },
661
+ "max_conversations_inject": { "type": "integer" },
662
+ "max_memory_inject_kb": { "type": "integer" },
663
+ "key_rotation_days": { "type": "integer" },
664
+ "cache_ttl_minutes": { "type": "integer" }
665
+ }
666
+ }
667
+ },
668
+ "required": ["user_token"]
669
+ }
444
670
  }
445
671
  ],
446
672
  "configuration": {
package/mcp/server.js CHANGED
@@ -3,6 +3,7 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const CLIManager = require('../lib/cliManager').default;
6
+ const UniversalMemoryExtractor = require('../src/lib/universalMemoryExtractor').UniversalMemoryExtractor;
6
7
 
7
8
  class MCPServer {
8
9
  constructor() {
@@ -14,6 +15,7 @@ class MCPServer {
14
15
 
15
16
  this.tools = new Map();
16
17
  this.cliManager = new CLIManager();
18
+ this.memoryExtractor = new UniversalMemoryExtractor();
17
19
  this.loadManifest();
18
20
  }
19
21
 
@@ -136,6 +138,26 @@ class MCPServer {
136
138
  result = await this.sendCliPrompt(args);
137
139
  break;
138
140
 
141
+ case 'polydev.detect_memory_sources':
142
+ result = await this.detectMemorySources(args);
143
+ break;
144
+
145
+ case 'polydev.extract_memory':
146
+ result = await this.extractMemory(args);
147
+ break;
148
+
149
+ case 'polydev.get_recent_conversations':
150
+ result = await this.getRecentConversations(args);
151
+ break;
152
+
153
+ case 'polydev.get_memory_context':
154
+ result = await this.getMemoryContext(args);
155
+ break;
156
+
157
+ case 'polydev.manage_memory_preferences':
158
+ result = await this.manageMemoryPreferences(args);
159
+ break;
160
+
139
161
  default:
140
162
  throw new Error(`Tool ${name} not implemented`);
141
163
  }
@@ -172,6 +194,30 @@ class MCPServer {
172
194
  throw new Error('prompt is required and must be a string');
173
195
  }
174
196
 
197
+ // Auto-inject memory context if enabled and not already provided
198
+ let enhancedPrompt = args.prompt;
199
+ if (!args.project_memory && args.auto_inject_memory !== false) {
200
+ try {
201
+ console.error('[Polydev MCP] Attempting to inject memory context...');
202
+ const memoryContext = await this.getMemoryContext({
203
+ prompt: args.prompt,
204
+ cli_tools: ['all'],
205
+ max_entries: 3
206
+ });
207
+
208
+ if (memoryContext.success && memoryContext.context.entries.length > 0) {
209
+ const contextText = memoryContext.context.entries
210
+ .map(entry => `[${entry.source}] ${entry.content.substring(0, 500)}`)
211
+ .join('\n\n');
212
+
213
+ enhancedPrompt = `Context from previous work:\n${contextText}\n\nCurrent request:\n${args.prompt}`;
214
+ console.error(`[Polydev MCP] Injected ${memoryContext.context.entries.length} memory entries`);
215
+ }
216
+ } catch (error) {
217
+ console.error('[Polydev MCP] Memory injection failed, continuing without:', error.message);
218
+ }
219
+ }
220
+
175
221
  // Support both parameter token (Claude Code) and environment token (Cline)
176
222
  const userToken = args.user_token || process.env.POLYDEV_USER_TOKEN;
177
223
 
@@ -204,7 +250,11 @@ class MCPServer {
204
250
  method: 'tools/call',
205
251
  params: {
206
252
  name: 'get_perspectives',
207
- arguments: args
253
+ arguments: {
254
+ ...args,
255
+ prompt: enhancedPrompt,
256
+ project_memory: enhancedPrompt !== args.prompt ? 'auto-injected' : args.project_memory
257
+ }
208
258
  },
209
259
  id: 1
210
260
  })
@@ -257,6 +307,13 @@ class MCPServer {
257
307
  case 'polydev.send_cli_prompt':
258
308
  return this.formatCliPromptResponse(result);
259
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':
315
+ return this.formatMemoryResponse(result);
316
+
260
317
  default:
261
318
  return JSON.stringify(result, null, 2);
262
319
  }
@@ -536,6 +593,281 @@ class MCPServer {
536
593
  }
537
594
  }
538
595
 
596
+ /**
597
+ * Detect available memory sources across CLI tools
598
+ */
599
+ async detectMemorySources(args) {
600
+ console.log('[MCP Server] Detect memory sources requested');
601
+
602
+ try {
603
+ const { cli_tools = ['all'] } = args;
604
+
605
+ const sources = await this.memoryExtractor.detectMemorySources(cli_tools);
606
+
607
+ return {
608
+ success: true,
609
+ sources,
610
+ message: `Detected ${Object.keys(sources).length} memory sources`,
611
+ timestamp: new Date().toISOString()
612
+ };
613
+ } catch (error) {
614
+ console.error('[MCP Server] Memory detection error:', error);
615
+ return {
616
+ success: false,
617
+ error: error.message,
618
+ timestamp: new Date().toISOString()
619
+ };
620
+ }
621
+ }
622
+
623
+ /**
624
+ * Extract memory from detected sources
625
+ */
626
+ async extractMemory(args) {
627
+ console.log('[MCP Server] Extract memory requested');
628
+
629
+ try {
630
+ const { cli_tools = ['all'], memory_types = ['all'], project_path } = args;
631
+
632
+ const extractedMemory = await this.memoryExtractor.extractMemory({
633
+ cliTools: cli_tools,
634
+ memoryTypes: memory_types,
635
+ projectPath: project_path
636
+ });
637
+
638
+ return {
639
+ success: true,
640
+ memory: extractedMemory,
641
+ message: `Extracted ${extractedMemory.totalEntries} memory entries`,
642
+ timestamp: new Date().toISOString()
643
+ };
644
+ } catch (error) {
645
+ console.error('[MCP Server] Memory extraction error:', error);
646
+ return {
647
+ success: false,
648
+ error: error.message,
649
+ timestamp: new Date().toISOString()
650
+ };
651
+ }
652
+ }
653
+
654
+ /**
655
+ * Get recent conversations from CLI tools
656
+ */
657
+ async getRecentConversations(args) {
658
+ console.log('[MCP Server] Get recent conversations requested');
659
+
660
+ try {
661
+ const { cli_tools = ['all'], limit = 10, project_path } = args;
662
+
663
+ const conversations = await this.memoryExtractor.getRecentConversations({
664
+ cliTools: cli_tools,
665
+ limit,
666
+ projectPath: project_path
667
+ });
668
+
669
+ return {
670
+ success: true,
671
+ conversations,
672
+ message: `Retrieved ${conversations.length} recent conversations`,
673
+ timestamp: new Date().toISOString()
674
+ };
675
+ } catch (error) {
676
+ console.error('[MCP Server] Conversation retrieval error:', error);
677
+ return {
678
+ success: false,
679
+ error: error.message,
680
+ timestamp: new Date().toISOString()
681
+ };
682
+ }
683
+ }
684
+
685
+ /**
686
+ * Get relevant memory context for current prompt
687
+ */
688
+ async getMemoryContext(args) {
689
+ console.log('[MCP Server] Get memory context requested');
690
+
691
+ try {
692
+ const { prompt, cli_tools = ['all'], max_entries = 5, project_path } = args;
693
+
694
+ if (!prompt) {
695
+ throw new Error('prompt is required for context retrieval');
696
+ }
697
+
698
+ const context = await this.memoryExtractor.getRelevantContext({
699
+ prompt,
700
+ cliTools: cli_tools,
701
+ maxEntries: max_entries,
702
+ projectPath: project_path
703
+ });
704
+
705
+ return {
706
+ success: true,
707
+ context,
708
+ message: `Found ${context.entries.length} relevant context entries`,
709
+ relevanceScore: context.averageRelevance,
710
+ timestamp: new Date().toISOString()
711
+ };
712
+ } catch (error) {
713
+ console.error('[MCP Server] Context retrieval error:', error);
714
+ return {
715
+ success: false,
716
+ error: error.message,
717
+ timestamp: new Date().toISOString()
718
+ };
719
+ }
720
+ }
721
+
722
+ /**
723
+ * Manage memory preferences and settings
724
+ */
725
+ async manageMemoryPreferences(args) {
726
+ console.log('[MCP Server] Manage memory preferences requested');
727
+
728
+ try {
729
+ const { action = 'get', preferences = {} } = args;
730
+
731
+ let result;
732
+
733
+ switch (action) {
734
+ case 'get':
735
+ result = await this.memoryExtractor.getPreferences();
736
+ break;
737
+
738
+ case 'set':
739
+ result = await this.memoryExtractor.updatePreferences(preferences);
740
+ break;
741
+
742
+ case 'reset':
743
+ result = await this.memoryExtractor.resetPreferences();
744
+ break;
745
+
746
+ default:
747
+ throw new Error(`Unknown action: ${action}`);
748
+ }
749
+
750
+ return {
751
+ success: true,
752
+ preferences: result,
753
+ message: `Memory preferences ${action} completed`,
754
+ timestamp: new Date().toISOString()
755
+ };
756
+ } catch (error) {
757
+ console.error('[MCP Server] Preferences management error:', error);
758
+ return {
759
+ success: false,
760
+ error: error.message,
761
+ timestamp: new Date().toISOString()
762
+ };
763
+ }
764
+ }
765
+
766
+ /**
767
+ * Format memory tool responses
768
+ */
769
+ formatMemoryResponse(result) {
770
+ if (!result.success) {
771
+ return `❌ Memory Operation Failed: ${result.error}`;
772
+ }
773
+
774
+ let formatted = `# Memory Operation Results\n\n`;
775
+ formatted += `✅ ${result.message}\n`;
776
+ formatted += `⏰ ${result.timestamp}\n\n`;
777
+
778
+ // Format specific results based on operation type
779
+ if (result.sources) {
780
+ formatted += this.formatMemorySourcesResult(result.sources);
781
+ } else if (result.memory) {
782
+ formatted += this.formatExtractedMemoryResult(result.memory);
783
+ } else if (result.conversations) {
784
+ formatted += this.formatConversationsResult(result.conversations);
785
+ } else if (result.context) {
786
+ formatted += this.formatContextResult(result.context, result.relevanceScore);
787
+ } else if (result.preferences) {
788
+ formatted += this.formatPreferencesResult(result.preferences);
789
+ }
790
+
791
+ return formatted;
792
+ }
793
+
794
+ formatMemorySourcesResult(sources) {
795
+ let formatted = `## 📁 Memory Sources Detected\n\n`;
796
+
797
+ Object.entries(sources).forEach(([cliTool, toolSources]) => {
798
+ formatted += `### ${cliTool.toUpperCase().replace('_', ' ')}\n`;
799
+
800
+ Object.entries(toolSources).forEach(([memoryType, files]) => {
801
+ const icon = memoryType === 'global' ? '🌍' : memoryType === 'project' ? '📂' : '💬';
802
+ formatted += `${icon} **${memoryType}**: ${files.length} sources\n`;
803
+
804
+ files.forEach(file => {
805
+ const status = file.exists ? '✅' : '❌';
806
+ formatted += ` ${status} \`${file.path}\`\n`;
807
+ });
808
+ });
809
+
810
+ formatted += `\n`;
811
+ });
812
+
813
+ return formatted;
814
+ }
815
+
816
+ formatExtractedMemoryResult(memory) {
817
+ let formatted = `## 🧠 Extracted Memory\n\n`;
818
+ formatted += `📊 **Total Entries**: ${memory.totalEntries}\n`;
819
+ formatted += `💾 **Total Size**: ${(memory.totalSize / 1024).toFixed(1)}KB\n\n`;
820
+
821
+ Object.entries(memory.byCliTool).forEach(([cliTool, toolMemory]) => {
822
+ formatted += `### ${cliTool.toUpperCase().replace('_', ' ')}\n`;
823
+ formatted += `- Global: ${toolMemory.global.length} entries\n`;
824
+ formatted += `- Project: ${toolMemory.project.length} entries\n`;
825
+ formatted += `- Conversations: ${toolMemory.conversations.length} entries\n\n`;
826
+ });
827
+
828
+ return formatted;
829
+ }
830
+
831
+ formatConversationsResult(conversations) {
832
+ let formatted = `## 💬 Recent Conversations\n\n`;
833
+
834
+ conversations.forEach((conv, index) => {
835
+ formatted += `### ${index + 1}. ${conv.cliTool.toUpperCase().replace('_', ' ')}\n`;
836
+ formatted += `📅 ${new Date(conv.timestamp).toLocaleString()}\n`;
837
+ formatted += `💭 ${conv.messageCount} messages, ${(conv.size / 1024).toFixed(1)}KB\n`;
838
+
839
+ if (conv.topics && conv.topics.length > 0) {
840
+ formatted += `🏷️ Topics: ${conv.topics.join(', ')}\n`;
841
+ }
842
+
843
+ formatted += `\n`;
844
+ });
845
+
846
+ return formatted;
847
+ }
848
+
849
+ formatContextResult(context, relevanceScore) {
850
+ let formatted = `## 🎯 Relevant Context\n\n`;
851
+ formatted += `📊 **Average Relevance**: ${(relevanceScore * 100).toFixed(1)}%\n\n`;
852
+
853
+ context.entries.forEach((entry, index) => {
854
+ formatted += `### ${index + 1}. ${entry.source} (${(entry.relevance * 100).toFixed(1)}%)\n`;
855
+ formatted += `${entry.content.substring(0, 200)}...\n\n`;
856
+ });
857
+
858
+ return formatted;
859
+ }
860
+
861
+ formatPreferencesResult(preferences) {
862
+ let formatted = `## ⚙️ Memory Preferences\n\n`;
863
+
864
+ Object.entries(preferences).forEach(([key, value]) => {
865
+ formatted += `- **${key}**: ${typeof value === 'object' ? JSON.stringify(value) : value}\n`;
866
+ });
867
+
868
+ return formatted;
869
+ }
870
+
539
871
  // CLI status and usage tracking is handled locally by CLIManager
540
872
  // No database integration needed - this MCP server runs independently
541
873
 
@@ -3,7 +3,7 @@
3
3
  // Lightweight stdio wrapper with local CLI functionality and remote Polydev MCP server fallback
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
- const CLIManager = require('../lib/cliManager').default;
6
+ const { CLIManager } = require('../lib/cliManager');
7
7
 
8
8
  class StdioMCPWrapper {
9
9
  constructor() {
@@ -204,7 +204,7 @@ class StdioMCPWrapper {
204
204
  const providerId = args.provider_id; // Optional - detect specific provider
205
205
 
206
206
  // Force detection using CLI Manager (no remote API calls)
207
- const results = await this.cliManager.forceCliDetection(null, providerId);
207
+ const results = await this.cliManager.forceCliDetection(providerId);
208
208
 
209
209
  // Save status locally to file-based cache
210
210
  await this.saveLocalCliStatus(results);
@@ -241,13 +241,13 @@ class StdioMCPWrapper {
241
241
  if (providerId) {
242
242
  // Get specific provider status
243
243
  const status = await this.cliManager.getCliStatus(providerId);
244
- results[providerId] = status;
244
+ results = status;
245
245
  } else {
246
246
  // Get all providers status
247
- const providers = this.cliManager.getProviders();
247
+ const providers = this.cliManager.getAvailableProviders();
248
248
  for (const provider of providers) {
249
249
  const status = await this.cliManager.getCliStatus(provider.id);
250
- results[provider.id] = status;
250
+ results[provider.id] = status[provider.id];
251
251
  }
252
252
  }
253
253
 
@@ -271,10 +271,10 @@ class StdioMCPWrapper {
271
271
  }
272
272
 
273
273
  /**
274
- * Local CLI prompt sending
274
+ * Local CLI prompt sending with remote perspectives fallback/supplement
275
275
  */
276
276
  async localSendCliPrompt(args) {
277
- console.error(`[Stdio Wrapper] Local CLI prompt sending`);
277
+ console.error(`[Stdio Wrapper] Local CLI prompt sending with perspectives`);
278
278
 
279
279
  try {
280
280
  const { provider_id, prompt, mode = 'args', timeout_ms = 30000 } = args;
@@ -283,29 +283,37 @@ class StdioMCPWrapper {
283
283
  throw new Error('provider_id and prompt are required');
284
284
  }
285
285
 
286
- // Send prompt using CLI Manager (local execution)
287
- const response = await this.cliManager.sendCliPrompt(
288
- provider_id,
289
- prompt,
290
- mode,
291
- timeout_ms
292
- );
293
-
294
- // Record usage locally (file-based analytics)
295
- await this.recordLocalUsage(provider_id, prompt, response);
286
+ // Use shorter timeout for faster fallback (5 seconds instead of 30)
287
+ const gracefulTimeout = Math.min(timeout_ms, 5000);
288
+
289
+ // Start both operations concurrently for better performance
290
+ const [localResult, perspectivesResult] = await Promise.allSettled([
291
+ this.cliManager.sendCliPrompt(provider_id, prompt, mode, gracefulTimeout),
292
+ this.callPerspectivesForCli(args, null)
293
+ ]);
294
+
295
+ // Process results
296
+ const localResponse = localResult.status === 'fulfilled' ? localResult.value : {
297
+ success: false,
298
+ error: `CLI check failed: ${localResult.reason?.message || 'Unknown error'}`,
299
+ latency_ms: gracefulTimeout,
300
+ timestamp: new Date().toISOString()
301
+ };
296
302
 
297
- return {
298
- success: response.success,
299
- content: response.content,
300
- error: response.error,
301
- tokens_used: response.tokensUsed,
302
- latency_ms: response.latencyMs,
303
- provider: provider_id,
304
- mode,
305
- timestamp: new Date().toISOString(),
306
- local_only: true
303
+ const perspectivesResponse = perspectivesResult.status === 'fulfilled' ? perspectivesResult.value : {
304
+ success: false,
305
+ error: `Perspectives failed: ${perspectivesResult.reason?.message || 'Unknown error'}`,
306
+ timestamp: new Date().toISOString()
307
307
  };
308
308
 
309
+ // Record usage locally (file-based analytics) - non-blocking
310
+ this.recordLocalUsage(provider_id, prompt, localResponse).catch(err => {
311
+ console.error('[Stdio Wrapper] Usage recording failed (non-critical):', err.message);
312
+ });
313
+
314
+ // Combine results
315
+ return this.combineCliAndPerspectives(localResponse, perspectivesResponse, args);
316
+
309
317
  } catch (error) {
310
318
  console.error('[Stdio Wrapper] Local CLI prompt error:', error);
311
319
  return {
@@ -317,6 +325,132 @@ class StdioMCPWrapper {
317
325
  }
318
326
  }
319
327
 
328
+ /**
329
+ * Call remote perspectives for CLI prompts
330
+ */
331
+ async callPerspectivesForCli(args, localResult) {
332
+ console.error(`[Stdio Wrapper] Calling remote perspectives for CLI prompt`);
333
+
334
+ try {
335
+ const perspectivesRequest = {
336
+ jsonrpc: '2.0',
337
+ method: 'tools/call',
338
+ params: {
339
+ name: 'get_perspectives',
340
+ arguments: {
341
+ prompt: args.prompt,
342
+ user_token: this.userToken,
343
+ // Let the remote server use user's configured preferences for models
344
+ // Don't specify models to use dashboard defaults
345
+ project_memory: 'none',
346
+ temperature: 0.7,
347
+ max_tokens: 2000
348
+ }
349
+ },
350
+ id: `perspectives-${Date.now()}`
351
+ };
352
+
353
+ const remoteResponse = await this.forwardToRemoteServer(perspectivesRequest);
354
+
355
+ if (remoteResponse.result && remoteResponse.result.content && remoteResponse.result.content[0]) {
356
+ return {
357
+ success: true,
358
+ content: remoteResponse.result.content[0].text,
359
+ timestamp: new Date().toISOString()
360
+ };
361
+ } else if (remoteResponse.error) {
362
+ return {
363
+ success: false,
364
+ error: remoteResponse.error.message || 'Remote perspectives failed',
365
+ timestamp: new Date().toISOString()
366
+ };
367
+ } else {
368
+ return {
369
+ success: false,
370
+ error: 'Unexpected remote response format',
371
+ timestamp: new Date().toISOString()
372
+ };
373
+ }
374
+ } catch (error) {
375
+ console.error('[Stdio Wrapper] Perspectives call error:', error);
376
+ return {
377
+ success: false,
378
+ error: `Perspectives request failed: ${error.message}`,
379
+ timestamp: new Date().toISOString()
380
+ };
381
+ }
382
+ }
383
+
384
+ /**
385
+ * Combine local CLI and remote perspectives results
386
+ */
387
+ combineCliAndPerspectives(localResult, perspectivesResult, args) {
388
+ const combinedResult = {
389
+ success: true,
390
+ timestamp: new Date().toISOString(),
391
+ provider: args.provider_id,
392
+ mode: args.mode,
393
+ sections: {
394
+ local: localResult,
395
+ remote: perspectivesResult
396
+ }
397
+ };
398
+
399
+ // Determine overall success and fallback status
400
+ if (localResult.success && perspectivesResult.success) {
401
+ combinedResult.content = this.formatCombinedResponse(localResult, perspectivesResult, false);
402
+ combinedResult.tokens_used = localResult.tokens_used || 0;
403
+ combinedResult.latency_ms = localResult.latency_ms || 0;
404
+ } else if (!localResult.success && perspectivesResult.success) {
405
+ // Fallback case
406
+ combinedResult.content = this.formatCombinedResponse(localResult, perspectivesResult, true);
407
+ combinedResult.fallback_used = true;
408
+ combinedResult.tokens_used = 0; // No local tokens used
409
+ } else if (localResult.success && !perspectivesResult.success) {
410
+ // Local succeeded, remote failed
411
+ combinedResult.content = this.formatCombinedResponse(localResult, perspectivesResult, false);
412
+ combinedResult.tokens_used = localResult.tokens_used || 0;
413
+ combinedResult.latency_ms = localResult.latency_ms || 0;
414
+ } else {
415
+ // Both failed
416
+ combinedResult.success = false;
417
+ combinedResult.error = `Local CLI failed: ${localResult.error}; Perspectives also failed: ${perspectivesResult.error}`;
418
+ }
419
+
420
+ return combinedResult;
421
+ }
422
+
423
+ /**
424
+ * Format combined response text
425
+ */
426
+ formatCombinedResponse(localResult, perspectivesResult, isFallback) {
427
+ let formatted = '';
428
+
429
+ if (localResult.success) {
430
+ // Local CLI succeeded
431
+ formatted += `🟢 **Local CLI Response** (${localResult.provider} - ${localResult.mode} mode)\n\n`;
432
+ formatted += `${localResult.content}\n\n`;
433
+ formatted += `*Latency: ${localResult.latency_ms || 0}ms | Tokens: ${localResult.tokens_used || 0}*\n\n`;
434
+ formatted += `---\n\n`;
435
+ } else if (isFallback) {
436
+ // Local CLI failed, using fallback
437
+ formatted += `⚠️ **Local CLI unavailable**: ${localResult.error}\n`;
438
+ formatted += `Using perspectives fallback.\n\n`;
439
+ formatted += `---\n\n`;
440
+ }
441
+
442
+ if (perspectivesResult.success) {
443
+ const title = isFallback ? '🧠 **Perspectives Fallback**' : '🧠 **Supplemental Multi-Model Perspectives**';
444
+ formatted += `${title}\n\n`;
445
+ formatted += `${perspectivesResult.content}\n\n`;
446
+ } else if (!isFallback) {
447
+ // Show remote error only if not in fallback mode
448
+ formatted += `❌ **Perspectives request failed**: ${perspectivesResult.error}\n\n`;
449
+ }
450
+
451
+ return formatted.trim();
452
+ }
453
+
320
454
  /**
321
455
  * Save CLI status to local file cache
322
456
  */
@@ -354,10 +488,20 @@ class StdioMCPWrapper {
354
488
  const polydevevDir = path.join(homeDir, '.polydev');
355
489
  const usageFile = path.join(polydevevDir, 'cli-usage.json');
356
490
 
491
+ // Ensure directory exists
492
+ if (!fs.existsSync(polydevevDir)) {
493
+ fs.mkdirSync(polydevevDir, { recursive: true });
494
+ }
495
+
357
496
  // Load existing usage data
358
497
  let usageData = [];
359
498
  if (fs.existsSync(usageFile)) {
360
- usageData = JSON.parse(fs.readFileSync(usageFile, 'utf8'));
499
+ try {
500
+ usageData = JSON.parse(fs.readFileSync(usageFile, 'utf8'));
501
+ } catch (parseError) {
502
+ console.error('[Stdio Wrapper] Failed to parse existing usage file, starting fresh:', parseError);
503
+ usageData = [];
504
+ }
361
505
  }
362
506
 
363
507
  // Add new usage record
@@ -366,8 +510,8 @@ class StdioMCPWrapper {
366
510
  provider: providerId,
367
511
  prompt_length: prompt.length,
368
512
  success: response.success,
369
- latency_ms: response.latencyMs,
370
- tokens_used: response.tokensUsed || 0
513
+ latency_ms: response.latency_ms || response.latencyMs || 0,
514
+ tokens_used: response.tokens_used || response.tokensUsed || 0
371
515
  });
372
516
 
373
517
  // Keep only last 1000 records
@@ -379,7 +523,8 @@ class StdioMCPWrapper {
379
523
  console.error(`[Stdio Wrapper] Usage recorded locally`);
380
524
 
381
525
  } catch (error) {
382
- console.error('[Stdio Wrapper] Failed to record local usage:', error);
526
+ console.error('[Stdio Wrapper] Failed to record local usage (non-critical):', error.message);
527
+ // Don't throw - this is non-critical functionality
383
528
  }
384
529
  }
385
530
 
@@ -391,8 +536,13 @@ class StdioMCPWrapper {
391
536
  return `❌ **CLI Error**\n\n${result.error}\n\n*Timestamp: ${result.timestamp}*`;
392
537
  }
393
538
 
539
+ // Handle combined CLI + perspectives response
540
+ if (result.sections) {
541
+ return result.content;
542
+ }
543
+
394
544
  if (result.content) {
395
- // Prompt response
545
+ // Standard prompt response
396
546
  return `✅ **CLI Response** (${result.provider || 'Unknown'} - ${result.mode || 'unknown'} mode)\n\n${result.content}\n\n*Latency: ${result.latency_ms || 0}ms | Tokens: ${result.tokens_used || 0} | ${result.timestamp}*`;
397
547
  } else {
398
548
  // Status/detection response
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polydev-ai",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
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",
@@ -59,6 +59,7 @@
59
59
  "lucide-react": "^0.542.0",
60
60
  "marked": "^16.2.1",
61
61
  "next": "15.0.0",
62
+ "polydev-ai": "^1.0.0",
62
63
  "posthog-js": "^1.157.2",
63
64
  "react": "^18.3.1",
64
65
  "react-dom": "^18.3.1",