polydev-ai 1.2.0 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/mcp/manifest.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polydev-perspectives",
3
- "version": "1.2.0",
3
+ "version": "1.0.0",
4
4
  "description": "Agentic workflow assistant - get diverse perspectives from multiple LLMs when stuck or need enhanced reasoning",
5
5
  "author": "Polydev AI",
6
6
  "license": "MIT",
@@ -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,41 +271,55 @@ 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
- const { provider_id, prompt, mode = 'args', timeout_ms = 30000 } = args;
280
+ let { provider_id, prompt, mode = 'args', timeout_ms = 30000 } = args;
281
281
 
282
- if (!provider_id || !prompt) {
283
- throw new Error('provider_id and prompt are required');
282
+ if (!prompt) {
283
+ throw new Error('prompt is 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
- );
286
+ // Auto-select best available provider if none specified
287
+ if (!provider_id) {
288
+ provider_id = await this.selectBestProvider();
289
+ console.error(`[Stdio Wrapper] Auto-selected provider: ${provider_id}`);
290
+ }
293
291
 
294
- // Record usage locally (file-based analytics)
295
- await this.recordLocalUsage(provider_id, prompt, response);
292
+ // Use shorter timeout for faster fallback (5 seconds instead of 30)
293
+ const gracefulTimeout = Math.min(timeout_ms, 5000);
294
+
295
+ // Start both operations concurrently for better performance
296
+ const [localResult, perspectivesResult] = await Promise.allSettled([
297
+ this.cliManager.sendCliPrompt(provider_id, prompt, mode, gracefulTimeout),
298
+ this.callPerspectivesForCli(args, null)
299
+ ]);
300
+
301
+ // Process results
302
+ const localResponse = localResult.status === 'fulfilled' ? localResult.value : {
303
+ success: false,
304
+ error: `CLI check failed: ${localResult.reason?.message || 'Unknown error'}`,
305
+ latency_ms: gracefulTimeout,
306
+ timestamp: new Date().toISOString()
307
+ };
296
308
 
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
309
+ const perspectivesResponse = perspectivesResult.status === 'fulfilled' ? perspectivesResult.value : {
310
+ success: false,
311
+ error: `Perspectives failed: ${perspectivesResult.reason?.message || 'Unknown error'}`,
312
+ timestamp: new Date().toISOString()
307
313
  };
308
314
 
315
+ // Record usage locally (file-based analytics) - non-blocking
316
+ this.recordLocalUsage(provider_id, prompt, localResponse).catch(err => {
317
+ console.error('[Stdio Wrapper] Usage recording failed (non-critical):', err.message);
318
+ });
319
+
320
+ // Combine results
321
+ return this.combineCliAndPerspectives(localResponse, perspectivesResponse, args);
322
+
309
323
  } catch (error) {
310
324
  console.error('[Stdio Wrapper] Local CLI prompt error:', error);
311
325
  return {
@@ -317,6 +331,177 @@ class StdioMCPWrapper {
317
331
  }
318
332
  }
319
333
 
334
+ /**
335
+ * Select the best available CLI provider automatically
336
+ */
337
+ async selectBestProvider() {
338
+ try {
339
+ const allStatus = await this.cliManager.getCliStatus();
340
+
341
+ // Priority order: claude_code > codex_cli > gemini_cli
342
+ const priorityOrder = ['claude_code', 'codex_cli', 'gemini_cli'];
343
+
344
+ for (const providerId of priorityOrder) {
345
+ const status = allStatus[providerId];
346
+ if (status && status.available && status.authenticated) {
347
+ return providerId;
348
+ }
349
+ }
350
+
351
+ // If no authenticated provider, return the first available one
352
+ for (const providerId of priorityOrder) {
353
+ const status = allStatus[providerId];
354
+ if (status && status.available) {
355
+ return providerId;
356
+ }
357
+ }
358
+
359
+ // Default fallback to claude_code (will trigger remote perspectives)
360
+ return 'claude_code';
361
+
362
+ } catch (error) {
363
+ console.error('[Stdio Wrapper] Provider selection failed, defaulting to claude_code:', error);
364
+ return 'claude_code';
365
+ }
366
+ }
367
+
368
+ /**
369
+ * Call remote perspectives for CLI prompts
370
+ */
371
+ async callPerspectivesForCli(args, localResult) {
372
+ console.error(`[Stdio Wrapper] Calling remote perspectives for CLI prompt`);
373
+
374
+ try {
375
+ const perspectivesRequest = {
376
+ jsonrpc: '2.0',
377
+ method: 'tools/call',
378
+ params: {
379
+ name: 'get_perspectives',
380
+ arguments: {
381
+ prompt: args.prompt,
382
+ user_token: this.userToken,
383
+ // Let the remote server use user's configured preferences for models
384
+ // Don't specify models to use dashboard defaults
385
+ project_memory: 'none',
386
+ temperature: 0.7,
387
+ max_tokens: 2000
388
+ }
389
+ },
390
+ id: `perspectives-${Date.now()}`
391
+ };
392
+
393
+ const remoteResponse = await this.forwardToRemoteServer(perspectivesRequest);
394
+
395
+ if (remoteResponse.result && remoteResponse.result.content && remoteResponse.result.content[0]) {
396
+ // The remote response already contains formatted "Multiple AI Perspectives" content
397
+ // Return it as-is without additional formatting to avoid duplication
398
+ const rawContent = remoteResponse.result.content[0].text;
399
+ return {
400
+ success: true,
401
+ content: rawContent,
402
+ timestamp: new Date().toISOString(),
403
+ raw: true // Flag to indicate this is pre-formatted content
404
+ };
405
+ } else if (remoteResponse.error) {
406
+ return {
407
+ success: false,
408
+ error: remoteResponse.error.message || 'Remote perspectives failed',
409
+ timestamp: new Date().toISOString()
410
+ };
411
+ } else {
412
+ return {
413
+ success: false,
414
+ error: 'Unexpected remote response format',
415
+ timestamp: new Date().toISOString()
416
+ };
417
+ }
418
+ } catch (error) {
419
+ console.error('[Stdio Wrapper] Perspectives call error:', error);
420
+ return {
421
+ success: false,
422
+ error: `Perspectives request failed: ${error.message}`,
423
+ timestamp: new Date().toISOString()
424
+ };
425
+ }
426
+ }
427
+
428
+ /**
429
+ * Combine local CLI and remote perspectives results
430
+ */
431
+ combineCliAndPerspectives(localResult, perspectivesResult, args) {
432
+ const combinedResult = {
433
+ success: true,
434
+ timestamp: new Date().toISOString(),
435
+ provider: args.provider_id,
436
+ mode: args.mode,
437
+ sections: {
438
+ local: localResult,
439
+ remote: perspectivesResult
440
+ }
441
+ };
442
+
443
+ // Determine overall success and fallback status
444
+ if (localResult.success && perspectivesResult.success) {
445
+ combinedResult.content = this.formatCombinedResponse(localResult, perspectivesResult, false);
446
+ combinedResult.tokens_used = localResult.tokens_used || 0;
447
+ combinedResult.latency_ms = localResult.latency_ms || 0;
448
+ } else if (!localResult.success && perspectivesResult.success) {
449
+ // Fallback case
450
+ combinedResult.content = this.formatCombinedResponse(localResult, perspectivesResult, true);
451
+ combinedResult.fallback_used = true;
452
+ combinedResult.tokens_used = 0; // No local tokens used
453
+ } else if (localResult.success && !perspectivesResult.success) {
454
+ // Local succeeded, remote failed
455
+ combinedResult.content = this.formatCombinedResponse(localResult, perspectivesResult, false);
456
+ combinedResult.tokens_used = localResult.tokens_used || 0;
457
+ combinedResult.latency_ms = localResult.latency_ms || 0;
458
+ } else {
459
+ // Both failed
460
+ combinedResult.success = false;
461
+ combinedResult.error = `Local CLI failed: ${localResult.error}; Perspectives also failed: ${perspectivesResult.error}`;
462
+ }
463
+
464
+ return combinedResult;
465
+ }
466
+
467
+ /**
468
+ * Format combined response text
469
+ */
470
+ formatCombinedResponse(localResult, perspectivesResult, isFallback) {
471
+ let formatted = '';
472
+
473
+ if (localResult.success) {
474
+ // Local CLI succeeded
475
+ formatted += `🟢 **Local CLI Response** (${localResult.provider} - ${localResult.mode} mode)\n\n`;
476
+ formatted += `${localResult.content}\n\n`;
477
+ formatted += `*Latency: ${localResult.latency_ms || 0}ms | Tokens: ${localResult.tokens_used || 0}*\n\n`;
478
+ formatted += `---\n\n`;
479
+ } else if (isFallback) {
480
+ // Local CLI failed, using fallback
481
+ formatted += `⚠️ **Local CLI unavailable**: ${localResult.error}\n`;
482
+ formatted += `Using perspectives fallback.\n\n`;
483
+ formatted += `---\n\n`;
484
+ }
485
+
486
+ if (perspectivesResult.success) {
487
+ if (perspectivesResult.raw) {
488
+ // Raw content is already formatted - use as-is without any additional title
489
+ // The remote server already includes proper headers like "Multiple AI Perspectives"
490
+ formatted += `${perspectivesResult.content}\n\n`;
491
+ } else {
492
+ // Legacy formatting for non-raw content (shouldn't be used with current server)
493
+ const title = isFallback ? '🧠 **Perspectives Fallback**' : '🧠 **Supplemental Multi-Model Perspectives**';
494
+ formatted += `${title}\n\n`;
495
+ formatted += `${perspectivesResult.content}\n\n`;
496
+ }
497
+ } else if (!isFallback) {
498
+ // Show remote error only if not in fallback mode
499
+ formatted += `❌ **Perspectives request failed**: ${perspectivesResult.error}\n\n`;
500
+ }
501
+
502
+ return formatted.trim();
503
+ }
504
+
320
505
  /**
321
506
  * Save CLI status to local file cache
322
507
  */
@@ -354,10 +539,20 @@ class StdioMCPWrapper {
354
539
  const polydevevDir = path.join(homeDir, '.polydev');
355
540
  const usageFile = path.join(polydevevDir, 'cli-usage.json');
356
541
 
542
+ // Ensure directory exists
543
+ if (!fs.existsSync(polydevevDir)) {
544
+ fs.mkdirSync(polydevevDir, { recursive: true });
545
+ }
546
+
357
547
  // Load existing usage data
358
548
  let usageData = [];
359
549
  if (fs.existsSync(usageFile)) {
360
- usageData = JSON.parse(fs.readFileSync(usageFile, 'utf8'));
550
+ try {
551
+ usageData = JSON.parse(fs.readFileSync(usageFile, 'utf8'));
552
+ } catch (parseError) {
553
+ console.error('[Stdio Wrapper] Failed to parse existing usage file, starting fresh:', parseError);
554
+ usageData = [];
555
+ }
361
556
  }
362
557
 
363
558
  // Add new usage record
@@ -366,8 +561,8 @@ class StdioMCPWrapper {
366
561
  provider: providerId,
367
562
  prompt_length: prompt.length,
368
563
  success: response.success,
369
- latency_ms: response.latencyMs,
370
- tokens_used: response.tokensUsed || 0
564
+ latency_ms: response.latency_ms || response.latencyMs || 0,
565
+ tokens_used: response.tokens_used || response.tokensUsed || 0
371
566
  });
372
567
 
373
568
  // Keep only last 1000 records
@@ -379,7 +574,8 @@ class StdioMCPWrapper {
379
574
  console.error(`[Stdio Wrapper] Usage recorded locally`);
380
575
 
381
576
  } catch (error) {
382
- console.error('[Stdio Wrapper] Failed to record local usage:', error);
577
+ console.error('[Stdio Wrapper] Failed to record local usage (non-critical):', error.message);
578
+ // Don't throw - this is non-critical functionality
383
579
  }
384
580
  }
385
581
 
@@ -391,8 +587,13 @@ class StdioMCPWrapper {
391
587
  return `❌ **CLI Error**\n\n${result.error}\n\n*Timestamp: ${result.timestamp}*`;
392
588
  }
393
589
 
590
+ // Handle combined CLI + perspectives response
591
+ if (result.sections) {
592
+ return result.content;
593
+ }
594
+
394
595
  if (result.content) {
395
- // Prompt response
596
+ // Standard prompt response
396
597
  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
598
  } else {
398
599
  // Status/detection response
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polydev-ai",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
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.2.0",
62
63
  "posthog-js": "^1.157.2",
63
64
  "react": "^18.3.1",
64
65
  "react-dom": "^18.3.1",