polydev-ai 1.2.6 → 1.2.7

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/README.md CHANGED
@@ -57,17 +57,12 @@ export POLYDEV_USER_TOKEN="pd_your_token_here"
57
57
  Add to your Claude Code MCP configuration:
58
58
 
59
59
  ```json
60
- {
61
- "mcpServers": {
62
- "polydev": {
63
- "command": "npx",
64
- "args": ["polydev-ai"],
65
- "env": {
66
- "POLYDEV_USER_TOKEN": "pd_your_token_here"
67
- }
68
- }
69
- }
70
- }
60
+ "polydev": {
61
+ "disabled": false,
62
+ "timeout": 120,
63
+ "type": "http",
64
+ "url": "https://www.polydev.ai/api/mcp"
65
+ },
71
66
  ```
72
67
 
73
68
  ## Available Tools
@@ -212,7 +212,7 @@ class StdioMCPWrapper {
212
212
  // Save status locally to file-based cache
213
213
  await this.saveLocalCliStatus(results);
214
214
 
215
- // NEW: Update database with CLI status
215
+ // Update database with CLI status
216
216
  await this.updateCliStatusInDatabase(results);
217
217
 
218
218
  return {
@@ -250,10 +250,10 @@ class StdioMCPWrapper {
250
250
  results = status;
251
251
  } else {
252
252
  // Get all providers status
253
- const providers = this.cliManager.getAvailableProviders();
253
+ const providers = this.cliManager.getProviders();
254
254
  for (const provider of providers) {
255
255
  const status = await this.cliManager.getCliStatus(provider.id);
256
- results[provider.id] = status[provider.id];
256
+ results[provider.id] = status;
257
257
  }
258
258
  }
259
259
 
@@ -280,7 +280,7 @@ class StdioMCPWrapper {
280
280
  }
281
281
 
282
282
  /**
283
- * Local CLI prompt sending with remote perspectives fallback/supplement
283
+ * Local CLI prompt sending with ALL available CLIs + remote perspectives
284
284
  */
285
285
  async localSendCliPrompt(args) {
286
286
  console.error(`[Stdio Wrapper] Local CLI prompt sending with perspectives`);
@@ -290,49 +290,68 @@ class StdioMCPWrapper {
290
290
 
291
291
  // Ensure timeout_ms is valid (not undefined, null, Infinity, or negative)
292
292
  if (!timeout_ms || timeout_ms === Infinity || timeout_ms < 1 || timeout_ms > 300000) {
293
- timeout_ms = 30000 // Default to 30 seconds
293
+ timeout_ms = 30000; // Default to 30 seconds
294
294
  }
295
295
 
296
296
  if (!prompt) {
297
297
  throw new Error('prompt is required');
298
298
  }
299
299
 
300
- // Auto-select best available provider if none specified
301
- if (!provider_id) {
302
- provider_id = await this.selectBestProvider();
303
- console.error(`[Stdio Wrapper] Auto-selected provider: ${provider_id}`);
304
- }
305
-
306
300
  // Use reasonable timeout for CLI responses (15 seconds instead of 5)
307
301
  const gracefulTimeout = Math.min(timeout_ms, 15000);
308
-
309
- // Start both operations concurrently for better performance
310
- const [localResult, perspectivesResult] = await Promise.allSettled([
311
- this.cliManager.sendCliPrompt(provider_id, prompt, mode, gracefulTimeout),
312
- this.callPerspectivesForCli(args, null)
313
- ]);
314
-
315
- // Process results
316
- const localResponse = localResult.status === 'fulfilled' ? localResult.value : {
317
- success: false,
318
- error: `CLI check failed: ${localResult.reason?.message || 'Unknown error'}`,
319
- latency_ms: gracefulTimeout,
320
- timestamp: new Date().toISOString()
321
- };
322
302
 
323
- const perspectivesResponse = perspectivesResult.status === 'fulfilled' ? perspectivesResult.value : {
324
- success: false,
325
- error: `Perspectives failed: ${perspectivesResult.reason?.message || 'Unknown error'}`,
326
- timestamp: new Date().toISOString()
327
- };
303
+ let localResults = [];
328
304
 
329
- // Record usage locally (file-based analytics) - non-blocking
330
- this.recordLocalUsage(provider_id, prompt, localResponse).catch(err => {
331
- console.error('[Stdio Wrapper] Usage recording failed (non-critical):', err.message);
332
- });
305
+ if (provider_id) {
306
+ // Specific provider requested - use only that one
307
+ console.error(`[Stdio Wrapper] Using specific provider: ${provider_id}`);
308
+ const result = await this.cliManager.sendCliPrompt(provider_id, prompt, mode, gracefulTimeout);
309
+ localResults = [{ provider_id, ...result }];
310
+ } else {
311
+ // No specific provider - use ALL available local CLIs
312
+ console.error(`[Stdio Wrapper] Using all available local CLIs`);
313
+ const availableProviders = await this.getAllAvailableProviders();
314
+
315
+ if (availableProviders.length === 0) {
316
+ console.error(`[Stdio Wrapper] No local CLIs available, will use remote perspectives only`);
317
+ localResults = [];
318
+ } else {
319
+ console.error(`[Stdio Wrapper] Found ${availableProviders.length} available CLIs: ${availableProviders.join(', ')}`);
320
+
321
+ // Run all CLI prompts concurrently
322
+ const cliPromises = availableProviders.map(async (providerId) => {
323
+ try {
324
+ const result = await this.cliManager.sendCliPrompt(providerId, prompt, mode, gracefulTimeout);
325
+ return { provider_id: providerId, ...result };
326
+ } catch (error) {
327
+ console.error(`[Stdio Wrapper] CLI ${providerId} failed:`, error.message);
328
+ return {
329
+ provider_id: providerId,
330
+ success: false,
331
+ error: error.message,
332
+ latency_ms: gracefulTimeout
333
+ };
334
+ }
335
+ });
336
+
337
+ localResults = await Promise.all(cliPromises);
338
+ }
339
+ }
333
340
 
334
- // Combine results
335
- return this.combineCliAndPerspectives(localResponse, perspectivesResponse, args);
341
+ // Get remote perspectives (only for models not covered by local CLIs)
342
+ const perspectivesResult = await this.callPerspectivesForCli(args, localResults);
343
+
344
+ // Record usage for all CLI responses
345
+ for (const localResult of localResults) {
346
+ if (localResult.provider_id) {
347
+ this.recordLocalUsage(localResult.provider_id, prompt, localResult).catch(err => {
348
+ console.error(`[Stdio Wrapper] Usage recording failed for ${localResult.provider_id} (non-critical):`, err.message);
349
+ });
350
+ }
351
+ }
352
+
353
+ // Combine all results
354
+ return this.combineAllCliAndPerspectives(localResults, perspectivesResult, args);
336
355
 
337
356
  } catch (error) {
338
357
  console.error('[Stdio Wrapper] Local CLI prompt error:', error);
@@ -346,43 +365,35 @@ class StdioMCPWrapper {
346
365
  }
347
366
 
348
367
  /**
349
- * Select the best available CLI provider automatically
368
+ * Get all available and authenticated CLI providers
350
369
  */
351
- async selectBestProvider() {
370
+ async getAllAvailableProviders() {
352
371
  try {
353
- const allStatus = await this.cliManager.getCliStatus();
372
+ const results = await this.cliManager.forceCliDetection();
373
+ const availableProviders = [];
354
374
 
355
375
  // Priority order: claude_code > codex_cli > gemini_cli
356
376
  const priorityOrder = ['claude_code', 'codex_cli', 'gemini_cli'];
357
377
 
358
378
  for (const providerId of priorityOrder) {
359
- const status = allStatus[providerId];
379
+ const status = results[providerId];
360
380
  if (status && status.available && status.authenticated) {
361
- return providerId;
362
- }
363
- }
364
-
365
- // If no authenticated provider, return the first available one
366
- for (const providerId of priorityOrder) {
367
- const status = allStatus[providerId];
368
- if (status && status.available) {
369
- return providerId;
381
+ availableProviders.push(providerId);
370
382
  }
371
383
  }
372
384
 
373
- // Default fallback to claude_code (will trigger remote perspectives)
374
- return 'claude_code';
385
+ return availableProviders;
375
386
 
376
387
  } catch (error) {
377
- console.error('[Stdio Wrapper] Provider selection failed, defaulting to claude_code:', error);
378
- return 'claude_code';
388
+ console.error('[Stdio Wrapper] Failed to get available providers:', error);
389
+ return [];
379
390
  }
380
391
  }
381
392
 
382
393
  /**
383
394
  * Call remote perspectives for CLI prompts
384
395
  */
385
- async callPerspectivesForCli(args, localResult) {
396
+ async callPerspectivesForCli(args, localResults) {
386
397
  console.error(`[Stdio Wrapper] Calling remote perspectives for CLI prompt`);
387
398
 
388
399
  try {
@@ -440,76 +451,99 @@ class StdioMCPWrapper {
440
451
  }
441
452
 
442
453
  /**
443
- * Combine local CLI and remote perspectives results
454
+ * Combine multiple CLI results and remote perspectives
444
455
  */
445
- combineCliAndPerspectives(localResult, perspectivesResult, args) {
456
+ combineAllCliAndPerspectives(localResults, perspectivesResult, args) {
446
457
  const combinedResult = {
447
458
  success: true,
448
459
  timestamp: new Date().toISOString(),
449
- provider: args.provider_id,
450
460
  mode: args.mode,
461
+ local_cli_count: localResults.length,
451
462
  sections: {
452
- local: localResult,
463
+ local: localResults,
453
464
  remote: perspectivesResult
454
465
  }
455
466
  };
456
467
 
457
- // Determine overall success and fallback status
458
- if (localResult.success && perspectivesResult.success) {
459
- combinedResult.content = this.formatCombinedResponse(localResult, perspectivesResult, false);
460
- combinedResult.tokens_used = localResult.tokens_used || 0;
461
- combinedResult.latency_ms = localResult.latency_ms || 0;
462
- } else if (!localResult.success && perspectivesResult.success) {
463
- // Fallback case
464
- combinedResult.content = this.formatCombinedResponse(localResult, perspectivesResult, true);
468
+ // Check if any local CLIs succeeded
469
+ const successfulClis = localResults.filter(result => result.success);
470
+ const hasSomeLocalSuccess = successfulClis.length > 0;
471
+
472
+ // Determine overall success and content
473
+ if (hasSomeLocalSuccess && perspectivesResult.success) {
474
+ combinedResult.content = this.formatMultipleCliResponse(localResults, perspectivesResult, false);
475
+ combinedResult.tokens_used = successfulClis.reduce((total, cli) => total + (cli.tokens_used || 0), 0);
476
+ combinedResult.latency_ms = Math.max(...successfulClis.map(cli => cli.latency_ms || 0));
477
+ } else if (!hasSomeLocalSuccess && perspectivesResult.success) {
478
+ // Complete fallback case - no local CLIs worked
479
+ combinedResult.content = this.formatMultipleCliResponse(localResults, perspectivesResult, true);
465
480
  combinedResult.fallback_used = true;
466
481
  combinedResult.tokens_used = 0; // No local tokens used
467
- } else if (localResult.success && !perspectivesResult.success) {
468
- // Local succeeded, remote failed
469
- combinedResult.content = this.formatCombinedResponse(localResult, perspectivesResult, false);
470
- combinedResult.tokens_used = localResult.tokens_used || 0;
471
- combinedResult.latency_ms = localResult.latency_ms || 0;
482
+ } else if (hasSomeLocalSuccess && !perspectivesResult.success) {
483
+ // Local CLIs succeeded, remote failed
484
+ combinedResult.content = this.formatMultipleCliResponse(localResults, perspectivesResult, false);
485
+ combinedResult.tokens_used = successfulClis.reduce((total, cli) => total + (cli.tokens_used || 0), 0);
486
+ combinedResult.latency_ms = Math.max(...successfulClis.map(cli => cli.latency_ms || 0));
472
487
  } else {
473
488
  // Both failed
474
489
  combinedResult.success = false;
475
- combinedResult.error = `Local CLI failed: ${localResult.error}; Perspectives also failed: ${perspectivesResult.error}`;
490
+ const cliErrors = localResults.map(cli => `${cli.provider_id}: ${cli.error}`).join('; ');
491
+ combinedResult.error = `All local CLIs failed: ${cliErrors}; Perspectives also failed: ${perspectivesResult.error}`;
476
492
  }
477
493
 
478
494
  return combinedResult;
479
495
  }
480
496
 
481
497
  /**
482
- * Format combined response text
498
+ * Format multiple CLI responses with remote perspectives
483
499
  */
484
- formatCombinedResponse(localResult, perspectivesResult, isFallback) {
500
+ formatMultipleCliResponse(localResults, perspectivesResult, isFallback) {
485
501
  let formatted = '';
486
502
 
487
- if (localResult.success) {
488
- // Local CLI succeeded
489
- formatted += `🟢 **Local CLI Response** (${localResult.provider} - ${localResult.mode} mode)\n\n`;
490
- formatted += `${localResult.content}\n\n`;
491
- formatted += `*Latency: ${localResult.latency_ms || 0}ms | Tokens: ${localResult.tokens_used || 0}*\n\n`;
492
- formatted += `---\n\n`;
493
- } else if (isFallback) {
494
- // Local CLI failed, using fallback
495
- formatted += `⚠️ **Local CLI unavailable**: ${localResult.error}\n`;
503
+ // Show all local CLI responses
504
+ const successfulClis = localResults.filter(result => result.success);
505
+ const failedClis = localResults.filter(result => !result.success);
506
+
507
+ if (successfulClis.length > 0) {
508
+ // Show successful CLI responses
509
+ for (const cliResult of successfulClis) {
510
+ formatted += `🟢 **Local CLI Response** (${cliResult.provider_id} - ${cliResult.mode || 'args'} mode)\n\n`;
511
+ formatted += `${cliResult.content}\n\n`;
512
+ formatted += `*Latency: ${cliResult.latency_ms || 0}ms | Tokens: ${cliResult.tokens_used || 0}*\n\n`;
513
+ formatted += `---\n\n`;
514
+ }
515
+ }
516
+
517
+ if (failedClis.length > 0 && successfulClis.length === 0) {
518
+ // All local CLIs failed
519
+ formatted += `⚠️ **All Local CLIs Unavailable**\n`;
520
+ for (const cliResult of failedClis) {
521
+ formatted += `- ${cliResult.provider_id}: ${cliResult.error}\n`;
522
+ }
496
523
  formatted += `Using perspectives fallback.\n\n`;
497
524
  formatted += `---\n\n`;
525
+ } else if (failedClis.length > 0) {
526
+ // Some CLIs failed, some succeeded
527
+ formatted += `⚠️ **Some CLIs Failed**\n`;
528
+ for (const cliResult of failedClis) {
529
+ formatted += `- ${cliResult.provider_id}: ${cliResult.error}\n`;
530
+ }
531
+ formatted += `\n---\n\n`;
498
532
  }
499
533
 
534
+ // Add remote perspectives
500
535
  if (perspectivesResult.success) {
501
536
  if (perspectivesResult.raw) {
502
- // Raw content is already formatted - use as-is without any additional title
503
- // The remote server already includes proper headers like "Multiple AI Perspectives"
537
+ // Raw content is already formatted - use as-is
504
538
  formatted += `${perspectivesResult.content}\n\n`;
505
539
  } else {
506
- // Legacy formatting for non-raw content (shouldn't be used with current server)
507
- const title = isFallback ? '🧠 **Perspectives Fallback**' : '🧠 **Supplemental Multi-Model Perspectives**';
540
+ // Legacy formatting
541
+ const title = (successfulClis.length === 0) ? '🧠 **Perspectives Fallback**' : '🧠 **Supplemental Multi-Model Perspectives**';
508
542
  formatted += `${title}\n\n`;
509
543
  formatted += `${perspectivesResult.content}\n\n`;
510
544
  }
511
- } else if (!isFallback) {
512
- // Show remote error only if not in fallback mode
545
+ } else if (successfulClis.length > 0) {
546
+ // Show remote error only if we have local success
513
547
  formatted += `❌ **Perspectives request failed**: ${perspectivesResult.error}\n\n`;
514
548
  }
515
549
 
@@ -554,7 +588,6 @@ class StdioMCPWrapper {
554
588
  console.error(`[Stdio Wrapper] Updating ${providerId} with data:`, JSON.stringify({
555
589
  provider: statusData.provider,
556
590
  status: statusData.status,
557
- user_id: statusData.user_id,
558
591
  authenticated: statusData.authenticated
559
592
  }, null, 2));
560
593
 
@@ -780,14 +813,20 @@ class StdioMCPWrapper {
780
813
  return `❌ **CLI Error**\n\n${result.error}\n\n*Timestamp: ${result.timestamp}*`;
781
814
  }
782
815
 
783
- // Handle combined CLI + perspectives response
816
+ // Handle combined CLI + perspectives response (single or multiple CLIs)
784
817
  if (result.sections) {
785
818
  return result.content;
786
819
  }
787
820
 
788
821
  if (result.content) {
789
822
  // Standard prompt response
790
- 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}*`;
823
+ const cliCount = result.local_cli_count || 1;
824
+
825
+ if (cliCount > 1) {
826
+ return `✅ **Multi-CLI Response** (${cliCount} local CLIs + perspectives)\n\n${result.content}\n\n*Total Latency: ${result.latency_ms || 0}ms | Total Tokens: ${result.tokens_used || 0} | ${result.timestamp}*`;
827
+ } else {
828
+ 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}*`;
829
+ }
791
830
  } else {
792
831
  // Status/detection response
793
832
  let formatted = `✅ **CLI Operation Success**\n\n`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polydev-ai",
3
- "version": "1.2.6",
3
+ "version": "1.2.7",
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",