polydev-ai 1.2.7 → 1.2.8
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.js +482 -526
- package/lib/cliManager.ts +5 -5
- package/mcp/package.json +1 -1
- package/package.json +1 -1
package/lib/cliManager.js
CHANGED
|
@@ -1,552 +1,508 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CLI Manager with MCP Server Integration
|
|
4
|
+
* Handles detection, authentication, and prompt sending for Claude Code, Codex CLI, and Gemini CLI
|
|
5
|
+
* Uses MCP servers for database operations and status reporting
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.CLIManager = void 0;
|
|
42
|
+
const child_process_1 = require("child_process");
|
|
43
|
+
const util_1 = require("util");
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
5
45
|
const which = require('which');
|
|
6
|
-
|
|
7
|
-
const execAsync = promisify(exec);
|
|
8
|
-
|
|
46
|
+
const shell = require('shelljs');
|
|
47
|
+
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
9
48
|
class CLIManager {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
providers.forEach(provider => {
|
|
60
|
-
this.providers.set(provider.id, provider);
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async forceCliDetection(specificProvider) {
|
|
65
|
-
const results = {};
|
|
66
|
-
const providersToCheck = specificProvider
|
|
67
|
-
? [this.providers.get(specificProvider)].filter(Boolean)
|
|
68
|
-
: Array.from(this.providers.values());
|
|
69
|
-
|
|
70
|
-
// Log system environment for debugging
|
|
71
|
-
console.log(`[Polydev CLI] Detecting CLI providers - Node.js ${process.version}, Platform: ${process.platform}`);
|
|
72
|
-
|
|
73
|
-
for (const provider of providersToCheck) {
|
|
74
|
-
if (provider) {
|
|
75
|
-
try {
|
|
76
|
-
results[provider.id] = await this.detectCliProvider(provider);
|
|
77
|
-
this.statusCache.set(provider.id, results[provider.id]);
|
|
78
|
-
|
|
79
|
-
// Log compatibility issues for user awareness
|
|
80
|
-
if (results[provider.id].error && results[provider.id].error.includes('Compatibility Issue')) {
|
|
81
|
-
console.warn(`[Polydev CLI] ⚠️ ${provider.name} compatibility issue detected. See error details for solutions.`);
|
|
82
|
-
}
|
|
83
|
-
} catch (error) {
|
|
84
|
-
results[provider.id] = {
|
|
85
|
-
available: false,
|
|
86
|
-
authenticated: false,
|
|
87
|
-
error: `Detection failed: ${error.message}`,
|
|
88
|
-
last_checked: new Date()
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
}
|
|
49
|
+
constructor() {
|
|
50
|
+
this.providers = new Map();
|
|
51
|
+
this.statusCache = new Map();
|
|
52
|
+
this.cacheTimeout = 5 * 60 * 1000; // 5 minutes
|
|
53
|
+
this.initializeProviders();
|
|
54
|
+
}
|
|
55
|
+
initializeProviders() {
|
|
56
|
+
const providers = [
|
|
57
|
+
{
|
|
58
|
+
id: 'claude_code',
|
|
59
|
+
name: 'Claude Code',
|
|
60
|
+
executable: 'claude',
|
|
61
|
+
versionCommand: 'claude --version',
|
|
62
|
+
authCheckCommand: 'claude auth status',
|
|
63
|
+
chatCommand: 'claude chat',
|
|
64
|
+
supportsStdin: true,
|
|
65
|
+
supportsArgs: true,
|
|
66
|
+
installInstructions: 'Install via: npm install -g @anthropic-ai/claude-code',
|
|
67
|
+
authInstructions: 'Authenticate with: claude auth login'
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: 'codex_cli',
|
|
71
|
+
name: 'Codex CLI',
|
|
72
|
+
executable: 'codex',
|
|
73
|
+
versionCommand: 'codex --version',
|
|
74
|
+
authCheckCommand: 'codex login --check', // Changed to check login status
|
|
75
|
+
chatCommand: 'codex exec', // Changed from 'codex chat' to 'codex exec'
|
|
76
|
+
supportsStdin: false, // Codex exec doesn't use stdin
|
|
77
|
+
supportsArgs: true,
|
|
78
|
+
installInstructions: 'Install via: npm install -g codex-cli',
|
|
79
|
+
authInstructions: 'Authenticate with: codex login'
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: 'gemini_cli',
|
|
83
|
+
name: 'Gemini CLI',
|
|
84
|
+
executable: 'gemini',
|
|
85
|
+
versionCommand: 'gemini --version',
|
|
86
|
+
authCheckCommand: 'gemini auth status',
|
|
87
|
+
chatCommand: 'gemini chat',
|
|
88
|
+
supportsStdin: true,
|
|
89
|
+
supportsArgs: true,
|
|
90
|
+
installInstructions: 'Install Gemini CLI from Google',
|
|
91
|
+
authInstructions: 'Authenticate with: gemini auth login'
|
|
92
|
+
}
|
|
93
|
+
];
|
|
94
|
+
providers.forEach(provider => {
|
|
95
|
+
this.providers.set(provider.id, provider);
|
|
96
|
+
});
|
|
92
97
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Force CLI detection for all providers or specific provider
|
|
100
|
+
* Updates status cache and reports to MCP server via Supabase
|
|
101
|
+
*/
|
|
102
|
+
async forceCliDetection(userId, providerId) {
|
|
103
|
+
console.log(`[CLI Manager] Force detection started for ${providerId || 'all providers'}`);
|
|
104
|
+
const results = {};
|
|
105
|
+
const providersToCheck = providerId ? [providerId] : Array.from(this.providers.keys());
|
|
106
|
+
for (const id of providersToCheck) {
|
|
107
|
+
const provider = this.providers.get(id);
|
|
108
|
+
if (!provider)
|
|
109
|
+
continue;
|
|
110
|
+
try {
|
|
111
|
+
const status = await this.detectCLI(provider);
|
|
112
|
+
this.statusCache.set(id, status);
|
|
113
|
+
results[id] = status;
|
|
114
|
+
// Update database via MCP Supabase if userId provided
|
|
115
|
+
if (userId) {
|
|
116
|
+
await this.updateCliStatusInDatabase(userId, id, status);
|
|
117
|
+
}
|
|
118
|
+
console.log(`[CLI Manager] ${provider.name}: ${status.available ? 'Available' : 'Not Available'}`);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
console.error(`[CLI Manager] Error detecting ${provider.name}:`, error);
|
|
122
|
+
const errorStatus = {
|
|
123
|
+
available: false,
|
|
124
|
+
authenticated: false,
|
|
125
|
+
lastChecked: new Date(),
|
|
126
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
127
|
+
};
|
|
128
|
+
this.statusCache.set(id, errorStatus);
|
|
129
|
+
results[id] = errorStatus;
|
|
130
|
+
}
|
|
111
131
|
}
|
|
112
|
-
|
|
132
|
+
return results;
|
|
113
133
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
authenticated: false,
|
|
132
|
-
error: `${provider.name} not found in PATH. ${provider.install_instructions}`,
|
|
133
|
-
last_checked: new Date()
|
|
134
|
+
/**
|
|
135
|
+
* Get CLI status with cache support
|
|
136
|
+
*/
|
|
137
|
+
async getCliStatus(providerId, userId) {
|
|
138
|
+
const cached = this.statusCache.get(providerId);
|
|
139
|
+
const now = new Date();
|
|
140
|
+
// Return cached result if still valid
|
|
141
|
+
if (cached && (now.getTime() - cached.lastChecked.getTime()) < this.cacheTimeout) {
|
|
142
|
+
return cached;
|
|
143
|
+
}
|
|
144
|
+
// Force detection if cache is stale or missing
|
|
145
|
+
const results = await this.forceCliDetection(userId, providerId);
|
|
146
|
+
return results[providerId] || {
|
|
147
|
+
available: false,
|
|
148
|
+
authenticated: false,
|
|
149
|
+
lastChecked: now,
|
|
150
|
+
error: 'Provider not found'
|
|
134
151
|
};
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
} catch (versionError) {
|
|
147
|
-
if (process.env.POLYDEV_CLI_DEBUG) {
|
|
148
|
-
console.log(`[CLI Debug] Version check failed for ${provider.id}:`, versionError);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Send prompt to CLI provider
|
|
155
|
+
*/
|
|
156
|
+
async sendCliPrompt(providerId, prompt, mode = 'args', timeoutMs = 30000) {
|
|
157
|
+
const provider = this.providers.get(providerId);
|
|
158
|
+
if (!provider) {
|
|
159
|
+
return {
|
|
160
|
+
success: false,
|
|
161
|
+
error: `Unknown CLI provider: ${providerId}`
|
|
162
|
+
};
|
|
149
163
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
authenticated = await this.checkAuthenticationByFiles(provider.id);
|
|
158
|
-
|
|
159
|
-
if (process.env.POLYDEV_CLI_DEBUG) {
|
|
160
|
-
console.log(`[CLI Debug] File-based auth check for ${provider.id}:`, authenticated);
|
|
164
|
+
// Check if CLI is available and authenticated
|
|
165
|
+
const status = await this.getCliStatus(providerId);
|
|
166
|
+
if (!status.available) {
|
|
167
|
+
return {
|
|
168
|
+
success: false,
|
|
169
|
+
error: `${provider.name} is not available. ${provider.installInstructions}`
|
|
170
|
+
};
|
|
161
171
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
provider.subcommands.auth_status,
|
|
168
|
-
'args',
|
|
169
|
-
5000 // Reduced timeout to 5 seconds
|
|
170
|
-
);
|
|
171
|
-
|
|
172
|
-
// If command succeeds, check output for authentication indicators
|
|
173
|
-
const authOutput = (authResult.stdout + ' ' + authResult.stderr).toLowerCase();
|
|
174
|
-
|
|
175
|
-
if (process.env.POLYDEV_CLI_DEBUG) {
|
|
176
|
-
console.log(`[CLI Debug] Auth output for ${provider.id}: "${authOutput}"`);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
authenticated = this.parseAuthenticationStatus(provider.id, authOutput);
|
|
180
|
-
|
|
181
|
-
} catch (authError) {
|
|
182
|
-
if (process.env.POLYDEV_CLI_DEBUG) {
|
|
183
|
-
console.log(`[CLI Debug] Auth check failed for ${provider.id}:`, authError);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Fallback to file-based authentication detection
|
|
187
|
-
authenticated = await this.checkAuthenticationByFiles(provider.id);
|
|
188
|
-
|
|
189
|
-
if (process.env.POLYDEV_CLI_DEBUG) {
|
|
190
|
-
console.log(`[CLI Debug] File-based auth check for ${provider.id}:`, authenticated);
|
|
191
|
-
}
|
|
172
|
+
if (!status.authenticated) {
|
|
173
|
+
return {
|
|
174
|
+
success: false,
|
|
175
|
+
error: `${provider.name} is not authenticated. ${provider.authInstructions}`
|
|
176
|
+
};
|
|
192
177
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const authResult = await this.executeCliCommand(
|
|
202
|
-
provider.command,
|
|
203
|
-
['--help'],
|
|
204
|
-
'args',
|
|
205
|
-
2000
|
|
206
|
-
);
|
|
207
|
-
const testOutput = (authResult.stdout + ' ' + authResult.stderr).toLowerCase();
|
|
208
|
-
if (testOutput.includes('referenceerror: file is not defined') ||
|
|
209
|
-
testOutput.includes('undici/lib/web/webidl')) {
|
|
210
|
-
errorMessage = `⚠️ Gemini CLI Compatibility Issue: Node.js v${process.version} doesn't support the 'File' global that Gemini CLI requires.
|
|
211
|
-
|
|
212
|
-
Solutions:
|
|
213
|
-
• Update to Node.js v20+ (recommended): nvm install 20 && nvm use 20
|
|
214
|
-
• Reinstall Gemini CLI: npm uninstall -g @google/gemini-cli && npm install -g @google/gemini-cli@latest
|
|
215
|
-
• Alternative: Use Google AI Studio directly or switch to Claude/OpenAI providers
|
|
216
|
-
|
|
217
|
-
This is a known issue with @google/gemini-cli@0.3.4 and older Node.js versions.`;
|
|
218
|
-
} else {
|
|
219
|
-
errorMessage = `Not authenticated. ${provider.auth_instructions}`;
|
|
178
|
+
const startTime = Date.now();
|
|
179
|
+
try {
|
|
180
|
+
let result;
|
|
181
|
+
if (mode === 'stdin' && provider.supportsStdin) {
|
|
182
|
+
result = await this.sendPromptViaStdin(provider, prompt, timeoutMs);
|
|
183
|
+
}
|
|
184
|
+
else if (mode === 'args' && provider.supportsArgs) {
|
|
185
|
+
result = await this.sendPromptViaArgs(provider, prompt, timeoutMs);
|
|
220
186
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
187
|
+
else {
|
|
188
|
+
return {
|
|
189
|
+
success: false,
|
|
190
|
+
error: `${provider.name} does not support ${mode} mode`
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
const latencyMs = Date.now() - startTime;
|
|
194
|
+
return {
|
|
195
|
+
success: true,
|
|
196
|
+
content: result,
|
|
197
|
+
latencyMs
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
return {
|
|
202
|
+
success: false,
|
|
203
|
+
error: error instanceof Error ? error.message : 'CLI execution failed',
|
|
204
|
+
latencyMs: Date.now() - startTime
|
|
205
|
+
};
|
|
226
206
|
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return {
|
|
230
|
-
available: true,
|
|
231
|
-
authenticated,
|
|
232
|
-
version,
|
|
233
|
-
path: cliPath,
|
|
234
|
-
last_checked: new Date(),
|
|
235
|
-
error: errorMessage
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
} catch (error) {
|
|
239
|
-
return {
|
|
240
|
-
available: false,
|
|
241
|
-
authenticated: false,
|
|
242
|
-
error: `Detection failed: ${error.message}`,
|
|
243
|
-
last_checked: new Date()
|
|
244
|
-
};
|
|
245
207
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
208
|
+
/**
|
|
209
|
+
* Detect CLI installation and authentication
|
|
210
|
+
*/
|
|
211
|
+
async detectCLI(provider) {
|
|
212
|
+
const customPath = process.env[`${provider.id.toUpperCase()}_PATH`];
|
|
213
|
+
let executablePath;
|
|
214
|
+
try {
|
|
215
|
+
// Try custom path first, then system PATH
|
|
216
|
+
if (customPath && fs.existsSync(customPath)) {
|
|
217
|
+
executablePath = customPath;
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
executablePath = await which(provider.executable);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
return {
|
|
225
|
+
available: false,
|
|
226
|
+
authenticated: false,
|
|
227
|
+
lastChecked: new Date(),
|
|
228
|
+
error: `${provider.name} not found in PATH. ${provider.installInstructions}`
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
// Check version
|
|
232
|
+
let version;
|
|
233
|
+
try {
|
|
234
|
+
const { stdout } = await execAsync(provider.versionCommand, { timeout: 10000 });
|
|
235
|
+
version = stdout.trim();
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
return {
|
|
239
|
+
available: false,
|
|
240
|
+
authenticated: false,
|
|
241
|
+
lastChecked: new Date(),
|
|
242
|
+
path: executablePath,
|
|
243
|
+
error: `Failed to get ${provider.name} version`
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
// Check authentication
|
|
247
|
+
let authenticated = false;
|
|
248
|
+
try {
|
|
249
|
+
const { stdout, stderr } = await execAsync(provider.authCheckCommand, { timeout: 10000 });
|
|
250
|
+
// Look for success indicators in output
|
|
251
|
+
const output = (stdout + stderr).toLowerCase();
|
|
252
|
+
authenticated = output.includes('authenticated') ||
|
|
253
|
+
output.includes('logged in') ||
|
|
254
|
+
output.includes('valid') ||
|
|
255
|
+
!output.includes('not authenticated');
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
// Some CLIs might not have auth status command, assume authenticated if version works
|
|
259
|
+
authenticated = true;
|
|
260
|
+
}
|
|
261
|
+
// Detect available models for this CLI tool
|
|
262
|
+
let default_model;
|
|
263
|
+
let available_models;
|
|
264
|
+
let model_detection_method;
|
|
265
|
+
try {
|
|
266
|
+
const modelDetection = await this.detectDefaultModel(provider.id);
|
|
267
|
+
default_model = modelDetection.defaultModel;
|
|
268
|
+
available_models = modelDetection.availableModels;
|
|
269
|
+
model_detection_method = modelDetection.detectionMethod;
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
console.error(`[CLI Manager] Model detection failed for ${provider.name}:`, error);
|
|
273
|
+
// Continue without model info - fallback will be used
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
available: true,
|
|
277
|
+
authenticated,
|
|
278
|
+
version,
|
|
279
|
+
path: executablePath,
|
|
280
|
+
lastChecked: new Date(),
|
|
281
|
+
default_model,
|
|
282
|
+
available_models,
|
|
283
|
+
model_detection_method
|
|
284
|
+
};
|
|
253
285
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
286
|
+
/**
|
|
287
|
+
* Send prompt via stdin mode
|
|
288
|
+
*/
|
|
289
|
+
async sendPromptViaStdin(provider, prompt, timeoutMs) {
|
|
290
|
+
return new Promise((resolve, reject) => {
|
|
291
|
+
const child = (0, child_process_1.spawn)(provider.chatCommand, [], {
|
|
292
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
293
|
+
timeout: timeoutMs
|
|
294
|
+
});
|
|
295
|
+
let stdout = '';
|
|
296
|
+
let stderr = '';
|
|
297
|
+
child.stdout?.on('data', (data) => {
|
|
298
|
+
stdout += data.toString();
|
|
299
|
+
});
|
|
300
|
+
child.stderr?.on('data', (data) => {
|
|
301
|
+
stderr += data.toString();
|
|
302
|
+
});
|
|
303
|
+
child.on('close', (code) => {
|
|
304
|
+
if (code === 0) {
|
|
305
|
+
resolve(stdout.trim());
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
reject(new Error(`CLI exited with code ${code}: ${stderr}`));
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
child.on('error', (error) => {
|
|
312
|
+
reject(error);
|
|
313
|
+
});
|
|
314
|
+
// Send prompt via stdin
|
|
315
|
+
if (child.stdin) {
|
|
316
|
+
child.stdin.write(prompt);
|
|
317
|
+
child.stdin.end();
|
|
284
318
|
}
|
|
285
|
-
|
|
286
|
-
return false;
|
|
287
|
-
|
|
288
|
-
case 'gemini_cli':
|
|
289
|
-
// Check for Gemini CLI auth files (if any)
|
|
290
|
-
const geminiConfigPath = path.join(os.homedir(), '.config', 'gemini-cli', 'config.json');
|
|
291
|
-
if (fs.existsSync(geminiConfigPath)) {
|
|
292
|
-
const configContent = fs.readFileSync(geminiConfigPath, 'utf8');
|
|
293
|
-
return configContent.includes('auth') || configContent.includes('token');
|
|
294
|
-
}
|
|
295
|
-
return false;
|
|
296
|
-
|
|
297
|
-
default:
|
|
298
|
-
return false;
|
|
299
|
-
}
|
|
300
|
-
} catch (error) {
|
|
301
|
-
if (process.env.POLYDEV_CLI_DEBUG) {
|
|
302
|
-
console.log(`[CLI Debug] File-based auth check error for ${providerId}:`, error.message);
|
|
303
|
-
}
|
|
304
|
-
return false;
|
|
319
|
+
});
|
|
305
320
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
!authOutput.includes('login required') &&
|
|
318
|
-
authOutput.length > 10; // Has actual content response
|
|
319
|
-
|
|
320
|
-
return claudeAuth;
|
|
321
|
-
|
|
322
|
-
case 'codex_cli':
|
|
323
|
-
// Look for specific codex login status responses
|
|
324
|
-
const hasLoggedIn = authOutput.includes('logged in using');
|
|
325
|
-
const hasAuthenticated = authOutput.includes('authenticated');
|
|
326
|
-
const hasChatGpt = authOutput.includes('chatgpt') && !authOutput.includes('not logged in');
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
return hasLoggedIn || hasAuthenticated || hasChatGpt;
|
|
330
|
-
|
|
331
|
-
case 'gemini_cli':
|
|
332
|
-
// Check for Node.js compatibility issues first
|
|
333
|
-
if (authOutput.includes('referenceerror: file is not defined') ||
|
|
334
|
-
authOutput.includes('undici/lib/web/webidl') ||
|
|
335
|
-
authOutput.includes('file is not defined')) {
|
|
336
|
-
return false; // CLI is broken due to Node.js compatibility
|
|
321
|
+
/**
|
|
322
|
+
* Send prompt via command arguments
|
|
323
|
+
*/
|
|
324
|
+
async sendPromptViaArgs(provider, prompt, timeoutMs) {
|
|
325
|
+
const command = `${provider.chatCommand} "${prompt.replace(/"/g, '\\"')}"`;
|
|
326
|
+
try {
|
|
327
|
+
const { stdout } = await execAsync(command, { timeout: timeoutMs });
|
|
328
|
+
return stdout.trim();
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
throw new Error(`CLI command failed: ${error}`);
|
|
337
332
|
}
|
|
338
|
-
|
|
339
|
-
return !authOutput.includes('not authenticated') &&
|
|
340
|
-
!authOutput.includes('please login') &&
|
|
341
|
-
(authOutput.includes('authenticated') || authOutput.includes('logged in'));
|
|
342
|
-
|
|
343
|
-
default:
|
|
344
|
-
return authOutput.includes('authenticated') || authOutput.includes('logged in');
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
async sendCliPrompt(providerId, prompt, mode = 'args', timeoutMs = null) {
|
|
349
|
-
// Set provider-specific default timeouts
|
|
350
|
-
if (timeoutMs === null) {
|
|
351
|
-
timeoutMs = providerId === 'claude_code' ? 60000 : 30000; // 60s for Claude Code, 30s for others
|
|
352
333
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
334
|
+
/**
|
|
335
|
+
* Update CLI status in database using MCP Supabase server
|
|
336
|
+
* This integrates with existing MCP infrastructure
|
|
337
|
+
*/
|
|
338
|
+
async updateCliStatusInDatabase(userId, providerId, status) {
|
|
339
|
+
try {
|
|
340
|
+
// Use existing CLI status API endpoint with MCP Supabase integration
|
|
341
|
+
const statusUpdate = {
|
|
342
|
+
server: this.getServerNameForProvider(providerId),
|
|
343
|
+
tool: 'cli_detection',
|
|
344
|
+
args: {
|
|
345
|
+
provider: providerId,
|
|
346
|
+
available: status.available,
|
|
347
|
+
authenticated: status.authenticated,
|
|
348
|
+
version: status.version,
|
|
349
|
+
path: status.path,
|
|
350
|
+
error: status.error
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
// Call existing API endpoint that has MCP Supabase integration
|
|
354
|
+
const response = await fetch('/api/cli-status', {
|
|
355
|
+
method: 'POST',
|
|
356
|
+
headers: {
|
|
357
|
+
'Content-Type': 'application/json',
|
|
358
|
+
'User-Agent': 'polydev-cli-manager/1.0.0'
|
|
359
|
+
},
|
|
360
|
+
body: JSON.stringify(statusUpdate)
|
|
361
|
+
});
|
|
362
|
+
if (!response.ok) {
|
|
363
|
+
throw new Error(`Failed to update CLI status: ${response.status}`);
|
|
364
|
+
}
|
|
365
|
+
const result = await response.json();
|
|
366
|
+
console.log(`[CLI Manager] Updated database via MCP Supabase for ${providerId}: ${status.available}`);
|
|
367
|
+
}
|
|
368
|
+
catch (error) {
|
|
369
|
+
console.error(`[CLI Manager] Failed to update database via MCP Supabase:`, error);
|
|
370
|
+
}
|
|
357
371
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
error: `Unknown provider: ${providerId}`,
|
|
367
|
-
latency_ms: Date.now() - startTime,
|
|
368
|
-
timestamp: new Date()
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
const status = await this.getCliStatus(providerId);
|
|
373
|
-
const providerStatus = status[providerId];
|
|
374
|
-
|
|
375
|
-
if (!providerStatus?.available) {
|
|
376
|
-
return {
|
|
377
|
-
success: false,
|
|
378
|
-
error: `${provider.name} is not available. ${provider.install_instructions}`,
|
|
379
|
-
latency_ms: Date.now() - startTime,
|
|
380
|
-
timestamp: new Date()
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
if (!providerStatus.authenticated) {
|
|
385
|
-
return {
|
|
386
|
-
success: false,
|
|
387
|
-
error: `${provider.name} is not authenticated. ${provider.auth_instructions}`,
|
|
388
|
-
latency_ms: Date.now() - startTime,
|
|
389
|
-
timestamp: new Date()
|
|
372
|
+
/**
|
|
373
|
+
* Map provider ID to server name for MCP integration
|
|
374
|
+
*/
|
|
375
|
+
getServerNameForProvider(providerId) {
|
|
376
|
+
const serverMap = {
|
|
377
|
+
'claude_code': 'claude-code-cli-bridge',
|
|
378
|
+
'codex_cli': 'cross-llm-bridge-test',
|
|
379
|
+
'gemini_cli': 'gemini-cli-bridge'
|
|
390
380
|
};
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
const args = [...provider.subcommands.test_prompt];
|
|
394
|
-
if (mode === 'args') {
|
|
395
|
-
args.push(prompt);
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
const result = await this.executeCliCommand(
|
|
399
|
-
provider.command,
|
|
400
|
-
args,
|
|
401
|
-
mode,
|
|
402
|
-
timeoutMs,
|
|
403
|
-
mode === 'stdin' ? prompt : undefined
|
|
404
|
-
);
|
|
405
|
-
|
|
406
|
-
if (result.error) {
|
|
407
|
-
return {
|
|
408
|
-
success: false,
|
|
409
|
-
error: `CLI command failed: ${result.error}`,
|
|
410
|
-
latency_ms: Date.now() - startTime,
|
|
411
|
-
provider: providerId,
|
|
412
|
-
mode,
|
|
413
|
-
timestamp: new Date()
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
const content = this.cleanCliResponse(result.stdout || '');
|
|
418
|
-
const tokens_used = this.estimateTokens(prompt + content);
|
|
419
|
-
|
|
420
|
-
return {
|
|
421
|
-
success: true,
|
|
422
|
-
content,
|
|
423
|
-
tokens_used,
|
|
424
|
-
latency_ms: Date.now() - startTime,
|
|
425
|
-
provider: providerId,
|
|
426
|
-
mode,
|
|
427
|
-
timestamp: new Date()
|
|
428
|
-
};
|
|
429
|
-
|
|
430
|
-
} catch (error) {
|
|
431
|
-
return {
|
|
432
|
-
success: false,
|
|
433
|
-
error: `CLI execution failed: ${error.message}`,
|
|
434
|
-
latency_ms: Date.now() - startTime,
|
|
435
|
-
provider: providerId,
|
|
436
|
-
mode,
|
|
437
|
-
timestamp: new Date()
|
|
438
|
-
};
|
|
381
|
+
return serverMap[providerId] || 'unknown-cli-bridge';
|
|
439
382
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
timeoutMs = 30000 // Default to 30 seconds
|
|
383
|
+
/**
|
|
384
|
+
* Get all CLI providers configuration
|
|
385
|
+
*/
|
|
386
|
+
getProviders() {
|
|
387
|
+
return Array.from(this.providers.values());
|
|
446
388
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
stderr,
|
|
486
|
-
error: `Command exited with code ${code}`
|
|
487
|
-
});
|
|
389
|
+
/**
|
|
390
|
+
* Get provider by ID
|
|
391
|
+
*/
|
|
392
|
+
getProvider(providerId) {
|
|
393
|
+
return this.providers.get(providerId);
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Detect available models for a CLI provider using interactive commands
|
|
397
|
+
*/
|
|
398
|
+
async detectDefaultModel(providerId) {
|
|
399
|
+
try {
|
|
400
|
+
// Try interactive detection using CLI commands
|
|
401
|
+
let command = '';
|
|
402
|
+
switch (providerId) {
|
|
403
|
+
case 'claude_code':
|
|
404
|
+
command = 'models'; // Claude Code model listing command
|
|
405
|
+
break;
|
|
406
|
+
case 'codex_cli':
|
|
407
|
+
command = 'list-models'; // Codex CLI model listing command
|
|
408
|
+
break;
|
|
409
|
+
case 'gemini_cli':
|
|
410
|
+
command = 'models'; // Gemini CLI model listing command
|
|
411
|
+
break;
|
|
412
|
+
}
|
|
413
|
+
if (!command) {
|
|
414
|
+
throw new Error(`No model detection command for ${providerId}`);
|
|
415
|
+
}
|
|
416
|
+
const result = await this.sendCliPrompt(providerId, command, 'args', 10000);
|
|
417
|
+
if (result.success && result.content) {
|
|
418
|
+
const models = this.parseModelsFromOutput(providerId, result.content);
|
|
419
|
+
if (models.length > 0) {
|
|
420
|
+
return {
|
|
421
|
+
defaultModel: this.extractDefaultModel(providerId, models),
|
|
422
|
+
availableModels: models,
|
|
423
|
+
detectionMethod: 'interactive'
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
}
|
|
488
427
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
child.on('error', (error) => {
|
|
492
|
-
if (process.env.POLYDEV_CLI_DEBUG) {
|
|
493
|
-
console.log(`[CLI Debug] Command error:`, error);
|
|
428
|
+
catch (error) {
|
|
429
|
+
console.error(`Interactive model detection failed for ${providerId}:`, error);
|
|
494
430
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
431
|
+
// Fallback to known defaults if interactive detection fails
|
|
432
|
+
return {
|
|
433
|
+
defaultModel: this.getDefaultModelFallback(providerId),
|
|
434
|
+
availableModels: [this.getDefaultModelFallback(providerId)],
|
|
435
|
+
detectionMethod: 'fallback'
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Parse model names from CLI output
|
|
440
|
+
*/
|
|
441
|
+
parseModelsFromOutput(providerId, output) {
|
|
442
|
+
const models = [];
|
|
443
|
+
const lines = output.split('\n');
|
|
444
|
+
switch (providerId) {
|
|
445
|
+
case 'claude_code':
|
|
446
|
+
// Parse Claude Code output format
|
|
447
|
+
lines.forEach(line => {
|
|
448
|
+
const matches = line.match(/claude-[\w\-.]+/gi);
|
|
449
|
+
if (matches)
|
|
450
|
+
models.push(...matches);
|
|
451
|
+
});
|
|
452
|
+
break;
|
|
453
|
+
case 'codex_cli':
|
|
454
|
+
// Parse Codex CLI output format
|
|
455
|
+
lines.forEach(line => {
|
|
456
|
+
const matches = line.match(/gpt-[\w\-.]+|o1-[\w\-.]+/gi);
|
|
457
|
+
if (matches)
|
|
458
|
+
models.push(...matches);
|
|
459
|
+
});
|
|
460
|
+
break;
|
|
461
|
+
case 'gemini_cli':
|
|
462
|
+
// Parse Gemini CLI output format
|
|
463
|
+
lines.forEach(line => {
|
|
464
|
+
const matches = line.match(/gemini-[\w\-.]+/gi);
|
|
465
|
+
if (matches)
|
|
466
|
+
models.push(...matches);
|
|
467
|
+
});
|
|
468
|
+
break;
|
|
503
469
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
470
|
+
return [...new Set(models)]; // Remove duplicates
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Extract the default model from available models
|
|
474
|
+
*/
|
|
475
|
+
extractDefaultModel(providerId, models) {
|
|
476
|
+
if (models.length === 0)
|
|
477
|
+
return this.getDefaultModelFallback(providerId);
|
|
478
|
+
switch (providerId) {
|
|
479
|
+
case 'claude_code':
|
|
480
|
+
// Prefer Claude 3.5 Sonnet, then Claude 3 Sonnet
|
|
481
|
+
return models.find(m => m.includes('claude-3-5-sonnet')) ||
|
|
482
|
+
models.find(m => m.includes('claude-3-sonnet')) ||
|
|
483
|
+
models[0];
|
|
484
|
+
case 'codex_cli':
|
|
485
|
+
// Prefer GPT-4, then GPT-3.5
|
|
486
|
+
return models.find(m => m.includes('gpt-4')) || models[0];
|
|
487
|
+
case 'gemini_cli':
|
|
488
|
+
// Prefer Gemini Pro, then Gemini Flash
|
|
489
|
+
return models.find(m => m.includes('gemini-1.5-pro')) ||
|
|
490
|
+
models.find(m => m.includes('gemini-pro')) ||
|
|
491
|
+
models[0];
|
|
516
492
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
cleanCliResponse(response) {
|
|
531
|
-
const cleanResponse = response.replace(/\x1b\[[0-9;]*m/g, '');
|
|
532
|
-
|
|
533
|
-
return cleanResponse
|
|
534
|
-
.replace(/^(\s*>\s*|\s*$)/gm, '')
|
|
535
|
-
.replace(/\n{3,}/g, '\n\n')
|
|
536
|
-
.trim();
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
estimateTokens(text) {
|
|
540
|
-
return Math.ceil(text.length / 4);
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
getAvailableProviders() {
|
|
544
|
-
return Array.from(this.providers.values());
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
getProvider(providerId) {
|
|
548
|
-
return this.providers.get(providerId);
|
|
549
|
-
}
|
|
493
|
+
return models[0];
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Get fallback default model for a provider
|
|
497
|
+
*/
|
|
498
|
+
getDefaultModelFallback(providerId) {
|
|
499
|
+
const fallbacks = {
|
|
500
|
+
'claude_code': 'claude-3-sonnet',
|
|
501
|
+
'codex_cli': 'gpt-4',
|
|
502
|
+
'gemini_cli': 'gemini-pro'
|
|
503
|
+
};
|
|
504
|
+
return fallbacks[providerId] || 'unknown';
|
|
505
|
+
}
|
|
550
506
|
}
|
|
551
|
-
|
|
552
|
-
|
|
507
|
+
exports.CLIManager = CLIManager;
|
|
508
|
+
exports.default = CLIManager;
|
package/lib/cliManager.ts
CHANGED
|
@@ -75,12 +75,12 @@ export class CLIManager {
|
|
|
75
75
|
name: 'Codex CLI',
|
|
76
76
|
executable: 'codex',
|
|
77
77
|
versionCommand: 'codex --version',
|
|
78
|
-
authCheckCommand: 'codex
|
|
79
|
-
chatCommand: 'codex chat'
|
|
80
|
-
supportsStdin:
|
|
78
|
+
authCheckCommand: 'codex login --check', // Changed to check login status
|
|
79
|
+
chatCommand: 'codex exec', // Changed from 'codex chat' to 'codex exec'
|
|
80
|
+
supportsStdin: false, // Codex exec doesn't use stdin
|
|
81
81
|
supportsArgs: true,
|
|
82
|
-
installInstructions: 'Install
|
|
83
|
-
authInstructions: 'Authenticate with: codex
|
|
82
|
+
installInstructions: 'Install via: npm install -g codex-cli',
|
|
83
|
+
authInstructions: 'Authenticate with: codex login'
|
|
84
84
|
},
|
|
85
85
|
{
|
|
86
86
|
id: 'gemini_cli',
|
package/mcp/package.json
CHANGED
package/package.json
CHANGED