polydev-ai 1.2.4 → 1.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cliManager.ts +144 -2
- package/lib/smartCliCache.ts +189 -0
- package/mcp/README.md +165 -0
- package/mcp/package.json +3 -3
- package/mcp/stdio-wrapper.js +205 -2
- package/package.json +4 -1
package/lib/cliManager.ts
CHANGED
|
@@ -34,6 +34,9 @@ export interface CLIStatus {
|
|
|
34
34
|
path?: string
|
|
35
35
|
lastChecked: Date
|
|
36
36
|
error?: string
|
|
37
|
+
default_model?: string
|
|
38
|
+
available_models?: string[]
|
|
39
|
+
model_detection_method?: 'interactive' | 'fallback'
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
export interface CLIResponse {
|
|
@@ -280,12 +283,30 @@ export class CLIManager {
|
|
|
280
283
|
authenticated = true
|
|
281
284
|
}
|
|
282
285
|
|
|
286
|
+
// Detect available models for this CLI tool
|
|
287
|
+
let default_model: string | undefined
|
|
288
|
+
let available_models: string[] | undefined
|
|
289
|
+
let model_detection_method: 'interactive' | 'fallback' | undefined
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
const modelDetection = await this.detectDefaultModel(provider.id);
|
|
293
|
+
default_model = modelDetection.defaultModel;
|
|
294
|
+
available_models = modelDetection.availableModels;
|
|
295
|
+
model_detection_method = modelDetection.detectionMethod;
|
|
296
|
+
} catch (error) {
|
|
297
|
+
console.error(`[CLI Manager] Model detection failed for ${provider.name}:`, error);
|
|
298
|
+
// Continue without model info - fallback will be used
|
|
299
|
+
}
|
|
300
|
+
|
|
283
301
|
return {
|
|
284
302
|
available: true,
|
|
285
303
|
authenticated,
|
|
286
304
|
version,
|
|
287
305
|
path: executablePath,
|
|
288
|
-
lastChecked: new Date()
|
|
306
|
+
lastChecked: new Date(),
|
|
307
|
+
default_model,
|
|
308
|
+
available_models,
|
|
309
|
+
model_detection_method
|
|
289
310
|
}
|
|
290
311
|
}
|
|
291
312
|
|
|
@@ -423,6 +444,127 @@ export class CLIManager {
|
|
|
423
444
|
getProvider(providerId: string): CLIProvider | undefined {
|
|
424
445
|
return this.providers.get(providerId)
|
|
425
446
|
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Detect available models for a CLI provider using interactive commands
|
|
450
|
+
*/
|
|
451
|
+
async detectDefaultModel(providerId: string): Promise<{
|
|
452
|
+
defaultModel: string;
|
|
453
|
+
availableModels: string[];
|
|
454
|
+
detectionMethod: 'interactive' | 'fallback';
|
|
455
|
+
}> {
|
|
456
|
+
try {
|
|
457
|
+
// Try interactive detection using CLI commands
|
|
458
|
+
let command = '';
|
|
459
|
+
switch (providerId) {
|
|
460
|
+
case 'claude_code':
|
|
461
|
+
command = 'models'; // Claude Code model listing command
|
|
462
|
+
break;
|
|
463
|
+
case 'codex_cli':
|
|
464
|
+
command = 'list-models'; // Codex CLI model listing command
|
|
465
|
+
break;
|
|
466
|
+
case 'gemini_cli':
|
|
467
|
+
command = 'models'; // Gemini CLI model listing command
|
|
468
|
+
break;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (!command) {
|
|
472
|
+
throw new Error(`No model detection command for ${providerId}`);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const result = await this.sendCliPrompt(providerId, command, 'args', 10000);
|
|
476
|
+
|
|
477
|
+
if (result.success && result.content) {
|
|
478
|
+
const models = this.parseModelsFromOutput(providerId, result.content);
|
|
479
|
+
if (models.length > 0) {
|
|
480
|
+
return {
|
|
481
|
+
defaultModel: this.extractDefaultModel(providerId, models),
|
|
482
|
+
availableModels: models,
|
|
483
|
+
detectionMethod: 'interactive'
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
} catch (error) {
|
|
488
|
+
console.error(`Interactive model detection failed for ${providerId}:`, error);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Fallback to known defaults if interactive detection fails
|
|
492
|
+
return {
|
|
493
|
+
defaultModel: this.getDefaultModelFallback(providerId),
|
|
494
|
+
availableModels: [this.getDefaultModelFallback(providerId)],
|
|
495
|
+
detectionMethod: 'fallback'
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Parse model names from CLI output
|
|
501
|
+
*/
|
|
502
|
+
private parseModelsFromOutput(providerId: string, output: string): string[] {
|
|
503
|
+
const models: string[] = [];
|
|
504
|
+
const lines = output.split('\n');
|
|
505
|
+
|
|
506
|
+
switch (providerId) {
|
|
507
|
+
case 'claude_code':
|
|
508
|
+
// Parse Claude Code output format
|
|
509
|
+
lines.forEach(line => {
|
|
510
|
+
const matches = line.match(/claude-[\w\-.]+/gi);
|
|
511
|
+
if (matches) models.push(...matches);
|
|
512
|
+
});
|
|
513
|
+
break;
|
|
514
|
+
case 'codex_cli':
|
|
515
|
+
// Parse Codex CLI output format
|
|
516
|
+
lines.forEach(line => {
|
|
517
|
+
const matches = line.match(/gpt-[\w\-.]+|o1-[\w\-.]+/gi);
|
|
518
|
+
if (matches) models.push(...matches);
|
|
519
|
+
});
|
|
520
|
+
break;
|
|
521
|
+
case 'gemini_cli':
|
|
522
|
+
// Parse Gemini CLI output format
|
|
523
|
+
lines.forEach(line => {
|
|
524
|
+
const matches = line.match(/gemini-[\w\-.]+/gi);
|
|
525
|
+
if (matches) models.push(...matches);
|
|
526
|
+
});
|
|
527
|
+
break;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
return [...new Set(models)]; // Remove duplicates
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Extract the default model from available models
|
|
535
|
+
*/
|
|
536
|
+
private extractDefaultModel(providerId: string, models: string[]): string {
|
|
537
|
+
if (models.length === 0) return this.getDefaultModelFallback(providerId);
|
|
538
|
+
|
|
539
|
+
switch (providerId) {
|
|
540
|
+
case 'claude_code':
|
|
541
|
+
// Prefer Claude 3.5 Sonnet, then Claude 3 Sonnet
|
|
542
|
+
return models.find(m => m.includes('claude-3-5-sonnet')) ||
|
|
543
|
+
models.find(m => m.includes('claude-3-sonnet')) ||
|
|
544
|
+
models[0];
|
|
545
|
+
case 'codex_cli':
|
|
546
|
+
// Prefer GPT-4, then GPT-3.5
|
|
547
|
+
return models.find(m => m.includes('gpt-4')) || models[0];
|
|
548
|
+
case 'gemini_cli':
|
|
549
|
+
// Prefer Gemini Pro, then Gemini Flash
|
|
550
|
+
return models.find(m => m.includes('gemini-1.5-pro')) ||
|
|
551
|
+
models.find(m => m.includes('gemini-pro')) ||
|
|
552
|
+
models[0];
|
|
553
|
+
}
|
|
554
|
+
return models[0];
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Get fallback default model for a provider
|
|
559
|
+
*/
|
|
560
|
+
private getDefaultModelFallback(providerId: string): string {
|
|
561
|
+
const fallbacks = {
|
|
562
|
+
'claude_code': 'claude-3-sonnet',
|
|
563
|
+
'codex_cli': 'gpt-4',
|
|
564
|
+
'gemini_cli': 'gemini-pro'
|
|
565
|
+
};
|
|
566
|
+
return fallbacks[providerId as keyof typeof fallbacks] || 'unknown';
|
|
567
|
+
}
|
|
426
568
|
}
|
|
427
569
|
|
|
428
|
-
export default CLIManager
|
|
570
|
+
export default CLIManager
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart Cache for CLI Status Management
|
|
3
|
+
* Provides intelligent caching based on CLI reliability and status
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class SmartCliCache {
|
|
7
|
+
private supabase: any;
|
|
8
|
+
|
|
9
|
+
constructor(supabase: any) {
|
|
10
|
+
this.supabase = supabase;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get smart timeout based on CLI configuration
|
|
15
|
+
* Returns timeout in minutes
|
|
16
|
+
*/
|
|
17
|
+
getSmartTimeout(cliConfig: any): number {
|
|
18
|
+
if (!cliConfig.available) {
|
|
19
|
+
return 2; // 2 minutes - check frequently for new installs
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!cliConfig.authenticated) {
|
|
23
|
+
return 3; // 3 minutes - check for authentication
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (cliConfig.model_detection_method === 'fallback') {
|
|
27
|
+
return 5; // 5 minutes - retry interactive detection
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return 10; // 10 minutes - stable, working CLI
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Check if CLI configuration is stale
|
|
35
|
+
*/
|
|
36
|
+
isStale(cliConfig: any): boolean {
|
|
37
|
+
if (!cliConfig.last_checked_at) return true;
|
|
38
|
+
|
|
39
|
+
const now = new Date();
|
|
40
|
+
const lastChecked = new Date(cliConfig.last_checked_at);
|
|
41
|
+
const minutesOld = (now.getTime() - lastChecked.getTime()) / (1000 * 60);
|
|
42
|
+
const timeout = this.getSmartTimeout(cliConfig);
|
|
43
|
+
|
|
44
|
+
return minutesOld > timeout;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get CLI status with smart caching
|
|
49
|
+
* Checks if data is stale and triggers refresh if needed
|
|
50
|
+
*/
|
|
51
|
+
async getCliStatusWithCache(userId: string): Promise<any[]> {
|
|
52
|
+
// 1. Get current CLI configurations from database
|
|
53
|
+
const { data: cliConfigs, error } = await this.supabase
|
|
54
|
+
.from('cli_provider_configurations')
|
|
55
|
+
.select('*')
|
|
56
|
+
.eq('user_id', userId);
|
|
57
|
+
|
|
58
|
+
if (error || !cliConfigs) {
|
|
59
|
+
console.error('[Smart Cache] Failed to get CLI configs:', error);
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (cliConfigs.length === 0) {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 2. Check which configurations are stale
|
|
68
|
+
const staleConfigs = cliConfigs.filter((config: any) => this.isStale(config));
|
|
69
|
+
|
|
70
|
+
// 3. If any are stale, trigger refresh (async, don't wait)
|
|
71
|
+
if (staleConfigs.length > 0) {
|
|
72
|
+
console.log(`[Smart Cache] ${staleConfigs.length} CLI configs are stale, triggering refresh for user ${userId}`);
|
|
73
|
+
this.refreshStaleConfigs(userId, staleConfigs).catch((error: any) => {
|
|
74
|
+
console.error('[Smart Cache] Refresh failed:', error);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 4. Return current data (will be fresh after refresh)
|
|
79
|
+
return cliConfigs;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Refresh stale CLI configurations (called asynchronously)
|
|
84
|
+
*/
|
|
85
|
+
private async refreshStaleConfigs(userId: string, staleConfigs: any[]): Promise<void> {
|
|
86
|
+
console.log(`[Smart Cache] Starting refresh for ${staleConfigs.length} stale CLI configs`);
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
// Trigger forced CLI detection via stdio-wrapper
|
|
90
|
+
for (const config of staleConfigs) {
|
|
91
|
+
await this.updateCliStatus(userId, config.provider);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
console.log('[Smart Cache] Successfully triggered refresh for stale CLI configs');
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error('[Smart Cache] Failed to trigger refresh:', error);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Update CLI status for a specific provider
|
|
102
|
+
*/
|
|
103
|
+
private async updateCliStatus(userId: string, provider: string): Promise<void> {
|
|
104
|
+
try {
|
|
105
|
+
const response = await fetch('/api/cli-status/refresh', {
|
|
106
|
+
method: 'POST',
|
|
107
|
+
headers: {
|
|
108
|
+
'Content-Type': 'application/json',
|
|
109
|
+
'Authorization': `Bearer ${this.getUserToken()}`
|
|
110
|
+
},
|
|
111
|
+
body: JSON.stringify({
|
|
112
|
+
user_id: userId,
|
|
113
|
+
provider: provider
|
|
114
|
+
})
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
if (!response.ok) {
|
|
118
|
+
console.error(`[Smart Cache] Failed to refresh ${provider}:`, response.status);
|
|
119
|
+
}
|
|
120
|
+
} catch (error) {
|
|
121
|
+
console.error(`[Smart Cache] Network error refreshing ${provider}:`, error);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get current user token (helper method)
|
|
127
|
+
*/
|
|
128
|
+
private getUserToken(): string {
|
|
129
|
+
// Try to get from environment or context
|
|
130
|
+
return process.env.POLYDEV_USER_TOKEN || '';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Format last checked time for display
|
|
135
|
+
*/
|
|
136
|
+
formatLastCheckedTime(lastChecked: string): string {
|
|
137
|
+
if (!lastChecked) return 'Unknown';
|
|
138
|
+
|
|
139
|
+
const lastCheckedDate = new Date(lastChecked);
|
|
140
|
+
const now = new Date();
|
|
141
|
+
const minutes = Math.floor((now.getTime() - lastCheckedDate.getTime()) / (1000 * 60));
|
|
142
|
+
|
|
143
|
+
if (minutes < 1) return 'Just now';
|
|
144
|
+
if (minutes < 60) return `${minutes} min ago`;
|
|
145
|
+
if (minutes < 1440) return `${Math.floor(minutes / 60)} hours ago`;
|
|
146
|
+
return `${Math.floor(minutes / 1440)} days ago`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Check if CLI should use local tools based on database status
|
|
151
|
+
*/
|
|
152
|
+
shouldUseLocalCli(model: string, cliConfigs: any[]): boolean {
|
|
153
|
+
// Find CLI that supports this model
|
|
154
|
+
const cliMatch = cliConfigs.find((cli: any) =>
|
|
155
|
+
cli.status === 'available' &&
|
|
156
|
+
cli.authenticated &&
|
|
157
|
+
(cli.available_models?.includes(model) || cli.default_model === model)
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
return !!cliMatch;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get summary statistics for dashboard
|
|
165
|
+
*/
|
|
166
|
+
getClimiStatusSummary(cliConfigs: any[]): {
|
|
167
|
+
total: number;
|
|
168
|
+
available: number;
|
|
169
|
+
authenticated: number;
|
|
170
|
+
stale: number;
|
|
171
|
+
} {
|
|
172
|
+
const now = new Date();
|
|
173
|
+
let staleCount = 0;
|
|
174
|
+
|
|
175
|
+
// Count stale configs
|
|
176
|
+
cliConfigs.forEach(config => {
|
|
177
|
+
if (this.isStale(config)) {
|
|
178
|
+
staleCount++;
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
total: cliConfigs.length,
|
|
184
|
+
available: cliConfigs.filter(cli => cli.status === 'available').length,
|
|
185
|
+
authenticated: cliConfigs.filter(cli => cli.authenticated).length,
|
|
186
|
+
stale: staleCount
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
package/mcp/README.md
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# Polydev AI MCP Server
|
|
2
|
+
|
|
3
|
+
Get diverse AI perspectives from multiple LLMs via Model Context Protocol (MCP). Supports Cline, Claude Code, and other MCP clients with local CLI detection and remote AI perspectives.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🤖 **Multi-Model AI Perspectives**: Get responses from GPT-4, Claude, Gemini, and more
|
|
8
|
+
- 🔧 **Local CLI Detection**: Automatically detect and use Claude Code, Codex CLI, Gemini CLI
|
|
9
|
+
- ⚡ **Smart Caching**: Intelligent refresh intervals based on CLI status
|
|
10
|
+
- 🔄 **Fallback Support**: Local CLI + remote perspectives for comprehensive responses
|
|
11
|
+
- 🔐 **Secure**: Token-based authentication with your Polydev account
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
### For Claude Code Users
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Install globally
|
|
19
|
+
npm install -g polydev-ai
|
|
20
|
+
|
|
21
|
+
# Or use directly with npx
|
|
22
|
+
npx polydev-ai
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### For Cline Users
|
|
26
|
+
|
|
27
|
+
Add to your MCP settings (`.cline/mcp_servers.json`):
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"mcpServers": {
|
|
32
|
+
"polydev": {
|
|
33
|
+
"command": "npx",
|
|
34
|
+
"args": ["polydev-ai"],
|
|
35
|
+
"env": {
|
|
36
|
+
"POLYDEV_USER_TOKEN": "your_token_here"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Configuration
|
|
44
|
+
|
|
45
|
+
### Get Your Token
|
|
46
|
+
|
|
47
|
+
1. Sign up at [polydev.ai](https://polydev.ai)
|
|
48
|
+
2. Go to your dashboard and copy your MCP token
|
|
49
|
+
3. Set the environment variable:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
export POLYDEV_USER_TOKEN="pd_your_token_here"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Claude Code Integration
|
|
56
|
+
|
|
57
|
+
Add to your Claude Code MCP configuration:
|
|
58
|
+
|
|
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
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Available Tools
|
|
74
|
+
|
|
75
|
+
- `get_perspectives` - Get AI responses from multiple models
|
|
76
|
+
- `force_cli_detection` - Force detection of local CLI tools
|
|
77
|
+
- `get_cli_status` - Check status of CLI tools (Claude Code, Codex, Gemini)
|
|
78
|
+
- `send_cli_prompt` - Send prompts to local CLI with perspectives fallback
|
|
79
|
+
|
|
80
|
+
## Usage Examples
|
|
81
|
+
|
|
82
|
+
### Get Multi-Model Perspectives
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// Use the get_perspectives tool
|
|
86
|
+
{
|
|
87
|
+
"prompt": "How do I optimize React performance?",
|
|
88
|
+
"models": ["gpt-4", "claude-3-sonnet", "gemini-pro"]
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Check CLI Status
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
// Check all CLI tools
|
|
96
|
+
await get_cli_status({})
|
|
97
|
+
|
|
98
|
+
// Check specific tool
|
|
99
|
+
await get_cli_status({ provider_id: "claude_code" })
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Send CLI Prompt with Fallback
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
await send_cli_prompt({
|
|
106
|
+
provider_id: "claude_code",
|
|
107
|
+
prompt: "Write a Python function to parse JSON",
|
|
108
|
+
mode: "args"
|
|
109
|
+
})
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Supported CLI Tools
|
|
113
|
+
|
|
114
|
+
- **Claude Code** (`claude`) - Anthropic's official CLI
|
|
115
|
+
- **Codex CLI** (`codex`) - OpenAI's code CLI
|
|
116
|
+
- **Gemini CLI** (`gemini`) - Google's Gemini CLI
|
|
117
|
+
|
|
118
|
+
## Smart Refresh System
|
|
119
|
+
|
|
120
|
+
The MCP server automatically detects CLI status changes:
|
|
121
|
+
|
|
122
|
+
- **Unavailable CLIs**: Check every 2 minutes
|
|
123
|
+
- **Unauthenticated CLIs**: Check every 3 minutes
|
|
124
|
+
- **Working CLIs**: Check every 10 minutes
|
|
125
|
+
- **Fallback detection**: Check every 5 minutes
|
|
126
|
+
|
|
127
|
+
## Environment Variables
|
|
128
|
+
|
|
129
|
+
- `POLYDEV_USER_TOKEN` - Your Polydev authentication token (required)
|
|
130
|
+
- `POLYDEV_CLI_DEBUG` - Enable CLI debugging output
|
|
131
|
+
- `CLAUDE_CODE_PATH` - Custom path to Claude Code CLI
|
|
132
|
+
- `CODEX_CLI_PATH` - Custom path to Codex CLI
|
|
133
|
+
- `GEMINI_CLI_PATH` - Custom path to Gemini CLI
|
|
134
|
+
|
|
135
|
+
## Troubleshooting
|
|
136
|
+
|
|
137
|
+
### CLI Not Detected
|
|
138
|
+
|
|
139
|
+
1. Ensure the CLI is installed and in your PATH
|
|
140
|
+
2. Check authentication: `claude auth status` / `codex login status`
|
|
141
|
+
3. Enable debugging: `export POLYDEV_CLI_DEBUG=1`
|
|
142
|
+
|
|
143
|
+
### Token Issues
|
|
144
|
+
|
|
145
|
+
1. Verify your token at [polydev.ai/dashboard](https://polydev.ai/dashboard)
|
|
146
|
+
2. Ensure the token starts with `pd_`
|
|
147
|
+
3. Check environment variable is set correctly
|
|
148
|
+
|
|
149
|
+
### Permission Errors
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
# Fix npm permissions
|
|
153
|
+
npm config set prefix '~/.npm-global'
|
|
154
|
+
export PATH=~/.npm-global/bin:$PATH
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Support
|
|
158
|
+
|
|
159
|
+
- 📧 Email: [support@polydev.ai](mailto:support@polydev.ai)
|
|
160
|
+
- 📖 Docs: [polydev.ai/docs](https://polydev.ai/docs)
|
|
161
|
+
- 🐛 Issues: [GitHub Issues](https://github.com/backspacevenkat/polydev-website/issues)
|
|
162
|
+
|
|
163
|
+
## License
|
|
164
|
+
|
|
165
|
+
MIT License - see LICENSE file for details.
|
package/mcp/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "polydev-
|
|
3
|
-
"version": "1.
|
|
2
|
+
"name": "polydev-ai",
|
|
3
|
+
"version": "1.2.6",
|
|
4
4
|
"description": "Get diverse AI perspectives from multiple LLMs via MCP - supports Cline, Claude Code, and other MCP clients",
|
|
5
5
|
"main": "stdio-wrapper.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"polydev-
|
|
7
|
+
"polydev-ai": "./stdio-wrapper.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node server.js",
|
package/mcp/stdio-wrapper.js
CHANGED
|
@@ -18,6 +18,9 @@ class StdioMCPWrapper {
|
|
|
18
18
|
|
|
19
19
|
// Load manifest for tool definitions
|
|
20
20
|
this.loadManifest();
|
|
21
|
+
|
|
22
|
+
// Smart refresh scheduler (will be started after initialization)
|
|
23
|
+
this.refreshScheduler = null;
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
loadManifest() {
|
|
@@ -195,10 +198,10 @@ class StdioMCPWrapper {
|
|
|
195
198
|
}
|
|
196
199
|
|
|
197
200
|
/**
|
|
198
|
-
* Local CLI detection implementation
|
|
201
|
+
* Local CLI detection implementation with database updates
|
|
199
202
|
*/
|
|
200
203
|
async localForceCliDetection(args) {
|
|
201
|
-
console.error(`[Stdio Wrapper] Local CLI detection started`);
|
|
204
|
+
console.error(`[Stdio Wrapper] Local CLI detection with model detection started`);
|
|
202
205
|
|
|
203
206
|
try {
|
|
204
207
|
const providerId = args.provider_id; // Optional - detect specific provider
|
|
@@ -209,6 +212,9 @@ class StdioMCPWrapper {
|
|
|
209
212
|
// Save status locally to file-based cache
|
|
210
213
|
await this.saveLocalCliStatus(results);
|
|
211
214
|
|
|
215
|
+
// NEW: Update database with CLI status
|
|
216
|
+
await this.updateCliStatusInDatabase(results);
|
|
217
|
+
|
|
212
218
|
return {
|
|
213
219
|
success: true,
|
|
214
220
|
results,
|
|
@@ -251,6 +257,9 @@ class StdioMCPWrapper {
|
|
|
251
257
|
}
|
|
252
258
|
}
|
|
253
259
|
|
|
260
|
+
// Update database with current status
|
|
261
|
+
await this.updateCliStatusInDatabase(results);
|
|
262
|
+
|
|
254
263
|
return {
|
|
255
264
|
success: true,
|
|
256
265
|
results,
|
|
@@ -507,6 +516,70 @@ class StdioMCPWrapper {
|
|
|
507
516
|
return formatted.trim();
|
|
508
517
|
}
|
|
509
518
|
|
|
519
|
+
/**
|
|
520
|
+
* Update CLI status in database
|
|
521
|
+
*/
|
|
522
|
+
async updateCliStatusInDatabase(results) {
|
|
523
|
+
console.error('[Stdio Wrapper] Updating CLI status in database...');
|
|
524
|
+
|
|
525
|
+
try {
|
|
526
|
+
// Get user token from environment variables
|
|
527
|
+
const userToken = process.env.POLYDEV_MCP_TOKEN || this.userToken;
|
|
528
|
+
|
|
529
|
+
if (!userToken) {
|
|
530
|
+
console.error('[Stdio Wrapper] No user token available for database updates');
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
console.error(`[Stdio Wrapper] Using token: ${userToken ? userToken.substring(0, 20) + '...' : 'MISSING'}`);
|
|
535
|
+
|
|
536
|
+
for (const [providerId, result] of Object.entries(results)) {
|
|
537
|
+
const statusData = {
|
|
538
|
+
provider: providerId,
|
|
539
|
+
status: result.available ? 'available' : 'unavailable',
|
|
540
|
+
mcp_token: userToken,
|
|
541
|
+
cli_version: result.version || null,
|
|
542
|
+
cli_path: result.cli_path || null,
|
|
543
|
+
authenticated: result.authenticated || false,
|
|
544
|
+
last_used: result.last_checked || new Date().toISOString(),
|
|
545
|
+
message: result.error || `${providerId} is ${result.available ? 'available' : 'unavailable'}`,
|
|
546
|
+
additional_info: {
|
|
547
|
+
default_model: result.default_model || null,
|
|
548
|
+
available_models: result.available_models || [],
|
|
549
|
+
model_detection_method: result.model_detection_method || 'fallback',
|
|
550
|
+
model_detected_at: result.model_detected_at || new Date().toISOString()
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
console.error(`[Stdio Wrapper] Updating ${providerId} with data:`, JSON.stringify({
|
|
555
|
+
provider: statusData.provider,
|
|
556
|
+
status: statusData.status,
|
|
557
|
+
user_id: statusData.user_id,
|
|
558
|
+
authenticated: statusData.authenticated
|
|
559
|
+
}, null, 2));
|
|
560
|
+
|
|
561
|
+
const response = await fetch('https://www.polydev.ai/api/cli-status-update', {
|
|
562
|
+
method: 'POST',
|
|
563
|
+
headers: {
|
|
564
|
+
'Content-Type': 'application/json'
|
|
565
|
+
},
|
|
566
|
+
body: JSON.stringify(statusData)
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
if (response.ok) {
|
|
570
|
+
const responseData = await response.json();
|
|
571
|
+
console.error(`[Stdio Wrapper] Successfully updated ${providerId} status in database`);
|
|
572
|
+
} else {
|
|
573
|
+
const errorText = await response.text();
|
|
574
|
+
console.error(`[Stdio Wrapper] Failed to update ${providerId}: ${response.status} - ${errorText}`);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
} catch (error) {
|
|
579
|
+
console.error('[Stdio Wrapper] Error updating database:', error);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
510
583
|
/**
|
|
511
584
|
* Save CLI status to local file cache
|
|
512
585
|
*/
|
|
@@ -535,6 +608,121 @@ class StdioMCPWrapper {
|
|
|
535
608
|
}
|
|
536
609
|
}
|
|
537
610
|
|
|
611
|
+
/**
|
|
612
|
+
* Load CLI status from local file cache
|
|
613
|
+
*/
|
|
614
|
+
async loadLocalCliStatus() {
|
|
615
|
+
try {
|
|
616
|
+
const homeDir = require('os').homedir();
|
|
617
|
+
const statusFile = path.join(homeDir, '.polydev', 'cli-status.json');
|
|
618
|
+
|
|
619
|
+
if (fs.existsSync(statusFile)) {
|
|
620
|
+
const data = JSON.parse(fs.readFileSync(statusFile, 'utf8'));
|
|
621
|
+
return data.results || {};
|
|
622
|
+
}
|
|
623
|
+
} catch (error) {
|
|
624
|
+
console.error('[Stdio Wrapper] Failed to load local status:', error);
|
|
625
|
+
}
|
|
626
|
+
return {};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Get smart timeout based on CLI status (same logic as SmartCliCache)
|
|
631
|
+
* Returns timeout in minutes
|
|
632
|
+
*/
|
|
633
|
+
getSmartTimeout(cliStatus) {
|
|
634
|
+
if (!cliStatus.available) {
|
|
635
|
+
return 2; // 2 minutes - check frequently for new installs
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
if (!cliStatus.authenticated) {
|
|
639
|
+
return 3; // 3 minutes - check for authentication
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
if (cliStatus.model_detection_method === 'fallback') {
|
|
643
|
+
return 5; // 5 minutes - retry interactive detection
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
return 10; // 10 minutes - stable, working CLI
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Check if CLI status is stale based on smart timeout
|
|
651
|
+
*/
|
|
652
|
+
isStale(cliStatus) {
|
|
653
|
+
if (!cliStatus.last_checked) return true;
|
|
654
|
+
|
|
655
|
+
const now = new Date();
|
|
656
|
+
const lastChecked = new Date(cliStatus.last_checked);
|
|
657
|
+
const minutesOld = (now.getTime() - lastChecked.getTime()) / (1000 * 60);
|
|
658
|
+
const timeout = this.getSmartTimeout(cliStatus);
|
|
659
|
+
|
|
660
|
+
return minutesOld > timeout;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Start smart refresh scheduler
|
|
665
|
+
* Checks every minute but only refreshes stale CLIs based on smart timeouts
|
|
666
|
+
*/
|
|
667
|
+
startSmartRefreshScheduler() {
|
|
668
|
+
console.error('[Stdio Wrapper] Starting smart refresh scheduler...');
|
|
669
|
+
|
|
670
|
+
// Check every 1 minute, but only refresh what's actually stale
|
|
671
|
+
this.refreshScheduler = setInterval(async () => {
|
|
672
|
+
try {
|
|
673
|
+
// Read current status from local cache
|
|
674
|
+
const currentStatus = await this.loadLocalCliStatus();
|
|
675
|
+
|
|
676
|
+
if (!currentStatus || Object.keys(currentStatus).length === 0) {
|
|
677
|
+
console.error('[Stdio Wrapper] No local CLI status found, running initial detection...');
|
|
678
|
+
await this.localForceCliDetection({});
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Check which CLIs need refresh based on smart timeouts
|
|
683
|
+
const staleProviders = [];
|
|
684
|
+
for (const [providerId, status] of Object.entries(currentStatus)) {
|
|
685
|
+
if (this.isStale(status)) {
|
|
686
|
+
const minutesOld = Math.floor((new Date().getTime() - new Date(status.last_checked).getTime()) / (1000 * 60));
|
|
687
|
+
const timeout = this.getSmartTimeout(status);
|
|
688
|
+
staleProviders.push({ providerId, minutesOld, timeout });
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
if (staleProviders.length > 0) {
|
|
693
|
+
console.error(`[Stdio Wrapper] Smart refresh: ${staleProviders.length} stale CLI providers detected`);
|
|
694
|
+
staleProviders.forEach(({ providerId, minutesOld, timeout }) => {
|
|
695
|
+
console.error(`[Stdio Wrapper] - ${providerId}: ${minutesOld} min old (timeout: ${timeout} min)`);
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
// Only detect the stale providers
|
|
699
|
+
for (const { providerId } of staleProviders) {
|
|
700
|
+
console.error(`[Stdio Wrapper] Refreshing ${providerId}...`);
|
|
701
|
+
await this.localForceCliDetection({ provider_id: providerId });
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
console.error('[Stdio Wrapper] Smart refresh completed');
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
} catch (error) {
|
|
708
|
+
console.error('[Stdio Wrapper] Smart refresh error:', error);
|
|
709
|
+
}
|
|
710
|
+
}, 60000); // Check every minute
|
|
711
|
+
|
|
712
|
+
console.error('[Stdio Wrapper] Smart refresh scheduler started (checks every 60 seconds)');
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Stop smart refresh scheduler
|
|
717
|
+
*/
|
|
718
|
+
stopSmartRefreshScheduler() {
|
|
719
|
+
if (this.refreshScheduler) {
|
|
720
|
+
clearInterval(this.refreshScheduler);
|
|
721
|
+
this.refreshScheduler = null;
|
|
722
|
+
console.error('[Stdio Wrapper] Smart refresh scheduler stopped');
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
538
726
|
/**
|
|
539
727
|
* Record local usage for analytics
|
|
540
728
|
*/
|
|
@@ -625,6 +813,18 @@ class StdioMCPWrapper {
|
|
|
625
813
|
async start() {
|
|
626
814
|
console.log('Starting Polydev Stdio MCP Wrapper...');
|
|
627
815
|
|
|
816
|
+
// Run initial CLI detection on startup
|
|
817
|
+
console.error('[Stdio Wrapper] Running initial CLI detection...');
|
|
818
|
+
try {
|
|
819
|
+
await this.localForceCliDetection({});
|
|
820
|
+
console.error('[Stdio Wrapper] Initial CLI detection completed');
|
|
821
|
+
} catch (error) {
|
|
822
|
+
console.error('[Stdio Wrapper] Initial CLI detection failed:', error);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// Start smart refresh scheduler for automatic updates
|
|
826
|
+
this.startSmartRefreshScheduler();
|
|
827
|
+
|
|
628
828
|
process.stdin.setEncoding('utf8');
|
|
629
829
|
let buffer = '';
|
|
630
830
|
|
|
@@ -649,17 +849,20 @@ class StdioMCPWrapper {
|
|
|
649
849
|
|
|
650
850
|
process.stdin.on('end', () => {
|
|
651
851
|
console.log('Stdio MCP Wrapper shutting down...');
|
|
852
|
+
this.stopSmartRefreshScheduler();
|
|
652
853
|
process.exit(0);
|
|
653
854
|
});
|
|
654
855
|
|
|
655
856
|
// Handle process signals
|
|
656
857
|
process.on('SIGINT', () => {
|
|
657
858
|
console.log('Received SIGINT, shutting down...');
|
|
859
|
+
this.stopSmartRefreshScheduler();
|
|
658
860
|
process.exit(0);
|
|
659
861
|
});
|
|
660
862
|
|
|
661
863
|
process.on('SIGTERM', () => {
|
|
662
864
|
console.log('Received SIGTERM, shutting down...');
|
|
865
|
+
this.stopSmartRefreshScheduler();
|
|
663
866
|
process.exit(0);
|
|
664
867
|
});
|
|
665
868
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polydev-ai",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.6",
|
|
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",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"@hello-pangea/dnd": "^18.0.1",
|
|
48
48
|
"@radix-ui/react-dialog": "^1.1.15",
|
|
49
49
|
"@radix-ui/react-progress": "^1.1.7",
|
|
50
|
+
"@radix-ui/react-select": "^2.2.6",
|
|
50
51
|
"@radix-ui/react-slot": "^1.2.3",
|
|
51
52
|
"@radix-ui/react-tabs": "^1.1.13",
|
|
52
53
|
"@supabase/ssr": "^0.4.0",
|
|
@@ -56,6 +57,7 @@
|
|
|
56
57
|
"clsx": "^2.1.1",
|
|
57
58
|
"date-fns": "^4.1.0",
|
|
58
59
|
"dotenv": "^17.2.2",
|
|
60
|
+
"framer-motion": "^12.23.22",
|
|
59
61
|
"lucide-react": "^0.542.0",
|
|
60
62
|
"marked": "^16.2.1",
|
|
61
63
|
"next": "15.0.0",
|
|
@@ -70,6 +72,7 @@
|
|
|
70
72
|
"stripe": "^18.5.0",
|
|
71
73
|
"tailwind-merge": "^3.3.1",
|
|
72
74
|
"ts-node": "^10.9.2",
|
|
75
|
+
"use-debounce": "^10.0.6",
|
|
73
76
|
"which": "^5.0.0"
|
|
74
77
|
},
|
|
75
78
|
"devDependencies": {
|