polydev-ai 1.2.3 → 1.2.5

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Polydev AI Website
2
2
 
3
- Advanced Model Context Protocol platform with comprehensive multi-LLM integration, subscription-based CLI access, OAuth bridges, and advanced tooling for AI development.
3
+ Advanced Model Context Protocol platform with comprehensive multi-LLM integrations, subscription-based CLI access, OAuth bridges, and advanced tooling for AI development.
4
4
 
5
5
  ## Features
6
6
 
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
+ }