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 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-perspectives-mcp",
3
- "version": "1.0.0",
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-mcp": "./stdio-wrapper.js"
7
+ "polydev-ai": "./stdio-wrapper.js"
8
8
  },
9
9
  "scripts": {
10
10
  "start": "node server.js",
@@ -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.4",
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": {