polydev-ai 1.2.13 → 1.2.15
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 +682 -475
- package/lib/cliManager.ts +236 -51
- package/lib/zeroKnowledgeEncryption.js +4 -4
- package/mcp/manifest.json +2 -2
- package/mcp/package.json +2 -2
- package/mcp/stdio-wrapper.js +55 -42
- package/package.json +17 -4
package/lib/cliManager.js
CHANGED
|
@@ -1,508 +1,715 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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"));
|
|
1
|
+
const { exec, spawn } = require('child_process');
|
|
2
|
+
const { promisify } = require('util');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const os = require('os');
|
|
45
6
|
const which = require('which');
|
|
46
|
-
|
|
47
|
-
const execAsync =
|
|
7
|
+
|
|
8
|
+
const execAsync = promisify(exec);
|
|
9
|
+
|
|
48
10
|
class CLIManager {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
this.statusCache.set(id, errorStatus);
|
|
129
|
-
results[id] = errorStatus;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
return results;
|
|
133
|
-
}
|
|
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] || {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.providers = new Map();
|
|
13
|
+
this.statusCache = new Map();
|
|
14
|
+
this.CACHE_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
|
|
15
|
+
this.initializeProviders();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
initializeProviders() {
|
|
19
|
+
const providers = [
|
|
20
|
+
{
|
|
21
|
+
id: 'claude_code',
|
|
22
|
+
name: 'Claude Code',
|
|
23
|
+
command: process.env.CLAUDE_CODE_PATH || 'claude',
|
|
24
|
+
subcommands: {
|
|
25
|
+
chat: [],
|
|
26
|
+
version: ['--version'],
|
|
27
|
+
auth_status: ['--print', 'test auth'], // Use --print to test auth
|
|
28
|
+
test_prompt: ['--print']
|
|
29
|
+
},
|
|
30
|
+
install_instructions: 'Install via: npm install -g @anthropic-ai/claude-code',
|
|
31
|
+
auth_instructions: 'Authenticate with Claude Code'
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'codex_cli',
|
|
35
|
+
name: 'Codex CLI',
|
|
36
|
+
command: process.env.CODEX_CLI_PATH || 'codex',
|
|
37
|
+
subcommands: {
|
|
38
|
+
chat: ['chat'],
|
|
39
|
+
version: ['--version'],
|
|
40
|
+
auth_status: ['login', 'status'], // Correct command: codex login status
|
|
41
|
+
test_prompt: ['exec'],
|
|
42
|
+
alternate_test_prompts: [
|
|
43
|
+
['prompt'],
|
|
44
|
+
['ask']
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
install_instructions: 'Install Codex CLI from OpenAI',
|
|
48
|
+
auth_instructions: 'Authenticate with: codex login'
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: 'gemini_cli',
|
|
52
|
+
name: 'Gemini CLI',
|
|
53
|
+
command: process.env.GEMINI_CLI_PATH || 'gemini',
|
|
54
|
+
subcommands: {
|
|
55
|
+
chat: ['chat'],
|
|
56
|
+
version: ['--version'],
|
|
57
|
+
auth_status: ['auth-status'] // gemini-mcp auth-status command
|
|
58
|
+
},
|
|
59
|
+
install_instructions: 'Install Gemini CLI from Google',
|
|
60
|
+
auth_instructions: 'Authenticate with: gemini (then /auth login)'
|
|
61
|
+
}
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
providers.forEach(provider => {
|
|
65
|
+
this.providers.set(provider.id, provider);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async forceCliDetection(specificProvider) {
|
|
70
|
+
const results = {};
|
|
71
|
+
const providersToCheck = specificProvider
|
|
72
|
+
? [this.providers.get(specificProvider)].filter(Boolean)
|
|
73
|
+
: Array.from(this.providers.values());
|
|
74
|
+
|
|
75
|
+
// Log system environment for debugging
|
|
76
|
+
console.log(`[Polydev CLI] Detecting CLI providers - Node.js ${process.version}, Platform: ${process.platform}`);
|
|
77
|
+
|
|
78
|
+
for (const provider of providersToCheck) {
|
|
79
|
+
if (provider) {
|
|
80
|
+
try {
|
|
81
|
+
results[provider.id] = await this.detectCliProvider(provider);
|
|
82
|
+
this.statusCache.set(provider.id, results[provider.id]);
|
|
83
|
+
|
|
84
|
+
// Log compatibility issues for user awareness
|
|
85
|
+
if (results[provider.id].error && results[provider.id].error.includes('Compatibility Issue')) {
|
|
86
|
+
console.warn(`[Polydev CLI] ⚠️ ${provider.name} compatibility issue detected. See error details for solutions.`);
|
|
87
|
+
}
|
|
88
|
+
} catch (error) {
|
|
89
|
+
results[provider.id] = {
|
|
147
90
|
available: false,
|
|
148
91
|
authenticated: false,
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
92
|
+
error: `Detection failed: ${error.message}`,
|
|
93
|
+
last_checked: new Date()
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
152
97
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
98
|
+
|
|
99
|
+
return results;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async getCliStatus(specificProvider) {
|
|
103
|
+
const results = {};
|
|
104
|
+
const providersToCheck = specificProvider
|
|
105
|
+
? [this.providers.get(specificProvider)].filter(Boolean)
|
|
106
|
+
: Array.from(this.providers.values());
|
|
107
|
+
|
|
108
|
+
for (const provider of providersToCheck) {
|
|
109
|
+
if (provider) {
|
|
110
|
+
const cached = this.statusCache.get(provider.id);
|
|
111
|
+
if (cached && this.isCacheValid(cached)) {
|
|
112
|
+
results[provider.id] = cached;
|
|
113
|
+
} else {
|
|
114
|
+
const detection = await this.forceCliDetection(provider.id);
|
|
115
|
+
results[provider.id] = detection[provider.id];
|
|
163
116
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return results;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
isCacheValid(status) {
|
|
124
|
+
if (!status.last_checked) return false;
|
|
125
|
+
const now = new Date().getTime();
|
|
126
|
+
const checked = new Date(status.last_checked).getTime();
|
|
127
|
+
return (now - checked) < this.CACHE_TIMEOUT_MS;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async detectCliProvider(provider) {
|
|
131
|
+
try {
|
|
132
|
+
const cliPath = await this.findCliPath(provider.command);
|
|
133
|
+
if (!cliPath) {
|
|
134
|
+
return {
|
|
135
|
+
available: false,
|
|
136
|
+
authenticated: false,
|
|
137
|
+
error: `${provider.name} not found in PATH. ${provider.install_instructions}`,
|
|
138
|
+
last_checked: new Date()
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let version;
|
|
143
|
+
try {
|
|
144
|
+
const versionResult = await this.executeCliCommand(
|
|
145
|
+
provider.command,
|
|
146
|
+
provider.subcommands.version,
|
|
147
|
+
'args',
|
|
148
|
+
5000
|
|
149
|
+
);
|
|
150
|
+
version = versionResult.stdout?.trim();
|
|
151
|
+
} catch (versionError) {
|
|
152
|
+
if (process.env.POLYDEV_CLI_DEBUG) {
|
|
153
|
+
console.log(`[CLI Debug] Version check failed for ${provider.id}:`, versionError);
|
|
171
154
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let authenticated = false;
|
|
158
|
+
|
|
159
|
+
// For Claude Code, skip command-based auth check and use file-based detection directly
|
|
160
|
+
// This avoids the recursion issue when running from within Claude Code
|
|
161
|
+
if (provider.id === 'claude_code') {
|
|
162
|
+
authenticated = await this.checkAuthenticationByFiles(provider.id);
|
|
163
|
+
|
|
164
|
+
if (process.env.POLYDEV_CLI_DEBUG) {
|
|
165
|
+
console.log(`[CLI Debug] File-based auth check for ${provider.id}:`, authenticated);
|
|
177
166
|
}
|
|
178
|
-
|
|
167
|
+
} else {
|
|
168
|
+
// For other providers, try command-based auth check first
|
|
179
169
|
try {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
};
|
|
170
|
+
const authResult = await this.executeCliCommand(
|
|
171
|
+
provider.command,
|
|
172
|
+
provider.subcommands.auth_status,
|
|
173
|
+
'args',
|
|
174
|
+
5000 // Reduced timeout to 5 seconds
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
// If command succeeds, check output for authentication indicators
|
|
178
|
+
const authOutput = (authResult.stdout + ' ' + authResult.stderr).toLowerCase();
|
|
179
|
+
|
|
180
|
+
if (process.env.POLYDEV_CLI_DEBUG) {
|
|
181
|
+
console.log(`[CLI Debug] Auth output for ${provider.id}: "${authOutput}"`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
authenticated = this.parseAuthenticationStatus(provider.id, authOutput);
|
|
185
|
+
|
|
186
|
+
} catch (authError) {
|
|
187
|
+
if (process.env.POLYDEV_CLI_DEBUG) {
|
|
188
|
+
console.log(`[CLI Debug] Auth check failed for ${provider.id}:`, authError);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Fallback to file-based authentication detection
|
|
192
|
+
authenticated = await this.checkAuthenticationByFiles(provider.id);
|
|
193
|
+
|
|
194
|
+
if (process.env.POLYDEV_CLI_DEBUG) {
|
|
195
|
+
console.log(`[CLI Debug] File-based auth check for ${provider.id}:`, authenticated);
|
|
196
|
+
}
|
|
199
197
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Special handling for Gemini CLI Node.js compatibility issues
|
|
201
|
+
let errorMessage = undefined;
|
|
202
|
+
if (!authenticated) {
|
|
203
|
+
if (provider.id === 'gemini_cli') {
|
|
204
|
+
// Check if the issue is Node.js compatibility
|
|
205
|
+
try {
|
|
206
|
+
const authResult = await this.executeCliCommand(
|
|
207
|
+
provider.command,
|
|
208
|
+
['--help'],
|
|
209
|
+
'args',
|
|
210
|
+
2000
|
|
211
|
+
);
|
|
212
|
+
const testOutput = (authResult.stdout + ' ' + authResult.stderr).toLowerCase();
|
|
213
|
+
if (testOutput.includes('referenceerror: file is not defined') ||
|
|
214
|
+
testOutput.includes('undici/lib/web/webidl')) {
|
|
215
|
+
errorMessage = `⚠️ Gemini CLI Compatibility Issue: Node.js v${process.version} doesn't support the 'File' global that Gemini CLI requires.
|
|
216
|
+
|
|
217
|
+
Solutions:
|
|
218
|
+
• Update to Node.js v20+ (recommended): nvm install 20 && nvm use 20
|
|
219
|
+
• Reinstall Gemini CLI: npm uninstall -g @google/gemini-cli && npm install -g @google/gemini-cli@latest
|
|
220
|
+
• Alternative: Use Google AI Studio directly or switch to Claude/OpenAI providers
|
|
221
|
+
|
|
222
|
+
This is a known issue with @google/gemini-cli@0.3.4 and older Node.js versions.`;
|
|
223
|
+
} else {
|
|
224
|
+
errorMessage = `Not authenticated. ${provider.auth_instructions}`;
|
|
225
|
+
}
|
|
226
|
+
} catch {
|
|
227
|
+
errorMessage = `Not authenticated. ${provider.auth_instructions}`;
|
|
228
|
+
}
|
|
229
|
+
} else {
|
|
230
|
+
errorMessage = `Not authenticated. ${provider.auth_instructions}`;
|
|
206
231
|
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
available: true,
|
|
236
|
+
authenticated,
|
|
237
|
+
version,
|
|
238
|
+
path: cliPath,
|
|
239
|
+
last_checked: new Date(),
|
|
240
|
+
error: errorMessage
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
} catch (error) {
|
|
244
|
+
return {
|
|
245
|
+
available: false,
|
|
246
|
+
authenticated: false,
|
|
247
|
+
error: `Detection failed: ${error.message}`,
|
|
248
|
+
last_checked: new Date()
|
|
249
|
+
};
|
|
207
250
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async findCliPath(command) {
|
|
254
|
+
try {
|
|
255
|
+
return await which(command);
|
|
256
|
+
} catch (error) {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async checkAuthenticationByFiles(providerId) {
|
|
262
|
+
const os = require('os');
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
switch (providerId) {
|
|
266
|
+
case 'claude_code':
|
|
267
|
+
// Check for Claude Code session files
|
|
268
|
+
const claudeConfigPath = path.join(os.homedir(), '.claude.json');
|
|
269
|
+
if (fs.existsSync(claudeConfigPath)) {
|
|
270
|
+
const configContent = fs.readFileSync(claudeConfigPath, 'utf8');
|
|
271
|
+
// Look for session or auth tokens in the config
|
|
272
|
+
return configContent.length > 100 &&
|
|
273
|
+
(configContent.includes('session') ||
|
|
274
|
+
configContent.includes('token') ||
|
|
275
|
+
configContent.includes('auth'));
|
|
276
|
+
}
|
|
277
|
+
return false;
|
|
278
|
+
|
|
279
|
+
case 'codex_cli':
|
|
280
|
+
// Check for Codex auth files
|
|
281
|
+
const codexAuthPath = path.join(os.homedir(), '.codex', 'auth.json');
|
|
282
|
+
if (fs.existsSync(codexAuthPath)) {
|
|
283
|
+
const authContent = fs.readFileSync(codexAuthPath, 'utf8');
|
|
284
|
+
try {
|
|
285
|
+
const authData = JSON.parse(authContent);
|
|
286
|
+
return authData && (authData.token || authData.access_token || authData.authenticated);
|
|
287
|
+
} catch {
|
|
288
|
+
return authContent.length > 10; // Has some auth content
|
|
221
289
|
}
|
|
290
|
+
}
|
|
291
|
+
return false;
|
|
292
|
+
|
|
293
|
+
case 'gemini_cli':
|
|
294
|
+
// Check for Gemini CLI auth files (if any)
|
|
295
|
+
const geminiConfigPath = path.join(os.homedir(), '.config', 'gemini-cli', 'config.json');
|
|
296
|
+
if (fs.existsSync(geminiConfigPath)) {
|
|
297
|
+
const configContent = fs.readFileSync(geminiConfigPath, 'utf8');
|
|
298
|
+
return configContent.includes('auth') || configContent.includes('token');
|
|
299
|
+
}
|
|
300
|
+
return false;
|
|
301
|
+
|
|
302
|
+
default:
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
} catch (error) {
|
|
306
|
+
if (process.env.POLYDEV_CLI_DEBUG) {
|
|
307
|
+
console.log(`[CLI Debug] File-based auth check error for ${providerId}:`, error.message);
|
|
308
|
+
}
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
parseAuthenticationStatus(providerId, authOutput) {
|
|
314
|
+
|
|
315
|
+
switch (providerId) {
|
|
316
|
+
case 'claude_code':
|
|
317
|
+
// If --print "test auth" works without error, Claude Code is authenticated
|
|
318
|
+
// Look for actual response content (not authentication errors)
|
|
319
|
+
const claudeAuth = !authOutput.includes('not authenticated') &&
|
|
320
|
+
!authOutput.includes('please log in') &&
|
|
321
|
+
!authOutput.includes('authentication required') &&
|
|
322
|
+
!authOutput.includes('login required') &&
|
|
323
|
+
authOutput.length > 10; // Has actual content response
|
|
324
|
+
|
|
325
|
+
return claudeAuth;
|
|
326
|
+
|
|
327
|
+
case 'codex_cli':
|
|
328
|
+
// Look for specific codex login status responses
|
|
329
|
+
const hasLoggedIn = authOutput.includes('logged in using');
|
|
330
|
+
const hasAuthenticated = authOutput.includes('authenticated');
|
|
331
|
+
const hasChatGpt = authOutput.includes('chatgpt') && !authOutput.includes('not logged in');
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
return hasLoggedIn || hasAuthenticated || hasChatGpt;
|
|
335
|
+
|
|
336
|
+
case 'gemini_cli':
|
|
337
|
+
// Check for Node.js compatibility issues first
|
|
338
|
+
if (authOutput.includes('referenceerror: file is not defined') ||
|
|
339
|
+
authOutput.includes('undici/lib/web/webidl') ||
|
|
340
|
+
authOutput.includes('file is not defined')) {
|
|
341
|
+
return false; // CLI is broken due to Node.js compatibility
|
|
222
342
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
343
|
+
|
|
344
|
+
return !authOutput.includes('not authenticated') &&
|
|
345
|
+
!authOutput.includes('please login') &&
|
|
346
|
+
(authOutput.includes('authenticated') || authOutput.includes('logged in'));
|
|
347
|
+
|
|
348
|
+
default:
|
|
349
|
+
return authOutput.includes('authenticated') || authOutput.includes('logged in');
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async sendCliPrompt(providerId, prompt, mode = 'args', timeoutMs = null) {
|
|
354
|
+
// Set provider-specific default timeouts
|
|
355
|
+
if (timeoutMs === null) {
|
|
356
|
+
timeoutMs = providerId === 'claude_code' ? 60000 : 30000; // 60s for Claude Code, 30s for others
|
|
357
|
+
}
|
|
358
|
+
if (providerId === 'codex_cli' && timeoutMs < 90000) {
|
|
359
|
+
timeoutMs = 90000;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Ensure timeoutMs is valid (not undefined, null, Infinity, or negative)
|
|
363
|
+
if (!timeoutMs || timeoutMs === Infinity || timeoutMs < 1 || timeoutMs > 300000) {
|
|
364
|
+
timeoutMs = 30000 // Default to 30 seconds
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const startTime = Date.now();
|
|
368
|
+
|
|
369
|
+
try {
|
|
370
|
+
const provider = this.providers.get(providerId);
|
|
371
|
+
if (!provider) {
|
|
372
|
+
return {
|
|
373
|
+
success: false,
|
|
374
|
+
error: `Unknown provider: ${providerId}`,
|
|
375
|
+
latency_ms: Date.now() - startTime,
|
|
376
|
+
timestamp: new Date()
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const status = await this.getCliStatus(providerId);
|
|
381
|
+
const providerStatus = status[providerId];
|
|
382
|
+
|
|
383
|
+
if (!providerStatus?.available) {
|
|
384
|
+
return {
|
|
385
|
+
success: false,
|
|
386
|
+
error: `${provider.name} is not available. ${provider.install_instructions}`,
|
|
387
|
+
latency_ms: Date.now() - startTime,
|
|
388
|
+
timestamp: new Date()
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (!providerStatus.authenticated) {
|
|
393
|
+
return {
|
|
394
|
+
success: false,
|
|
395
|
+
error: `${provider.name} is not authenticated. ${provider.auth_instructions}`,
|
|
396
|
+
latency_ms: Date.now() - startTime,
|
|
397
|
+
timestamp: new Date()
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const promptVariants = [
|
|
402
|
+
provider.subcommands?.test_prompt ? [...provider.subcommands.test_prompt] : []
|
|
403
|
+
];
|
|
404
|
+
|
|
405
|
+
if (Array.isArray(provider?.subcommands?.alternate_test_prompts)) {
|
|
406
|
+
for (const altArgs of provider.subcommands.alternate_test_prompts) {
|
|
407
|
+
promptVariants.push(Array.isArray(altArgs) ? [...altArgs] : []);
|
|
230
408
|
}
|
|
231
|
-
|
|
232
|
-
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (providerId === 'codex_cli') {
|
|
412
|
+
const execArgs = promptVariants.find(args => args.includes('exec')) || promptVariants[0];
|
|
233
413
|
try {
|
|
234
|
-
|
|
235
|
-
|
|
414
|
+
const content = await this.executeCodexExec(provider.command, execArgs, prompt, timeoutMs);
|
|
415
|
+
return {
|
|
416
|
+
success: true,
|
|
417
|
+
content,
|
|
418
|
+
tokens_used: this.estimateTokens(prompt + content),
|
|
419
|
+
latency_ms: Date.now() - startTime,
|
|
420
|
+
provider: providerId,
|
|
421
|
+
mode: 'args',
|
|
422
|
+
timestamp: new Date()
|
|
423
|
+
};
|
|
424
|
+
} catch (error) {
|
|
425
|
+
return {
|
|
426
|
+
success: false,
|
|
427
|
+
error: `CLI execution failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
428
|
+
latency_ms: Date.now() - startTime,
|
|
429
|
+
provider: providerId,
|
|
430
|
+
mode,
|
|
431
|
+
timestamp: new Date()
|
|
432
|
+
};
|
|
236
433
|
}
|
|
237
|
-
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
let lastErrorMessage = null;
|
|
437
|
+
|
|
438
|
+
for (const promptArgs of promptVariants) {
|
|
439
|
+
const args = Array.isArray(promptArgs) ? [...promptArgs, prompt] : [prompt];
|
|
440
|
+
try {
|
|
441
|
+
const result = await this.executeCliCommand(
|
|
442
|
+
provider.command,
|
|
443
|
+
args,
|
|
444
|
+
'args',
|
|
445
|
+
timeoutMs,
|
|
446
|
+
undefined
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
if (!result.error) {
|
|
450
|
+
const content = this.cleanCliResponse(result.stdout || '');
|
|
238
451
|
return {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
452
|
+
success: true,
|
|
453
|
+
content,
|
|
454
|
+
tokens_used: this.estimateTokens(prompt + content),
|
|
455
|
+
latency_ms: Date.now() - startTime,
|
|
456
|
+
provider: providerId,
|
|
457
|
+
mode: 'args',
|
|
458
|
+
timestamp: new Date()
|
|
244
459
|
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
lastErrorMessage = result.error;
|
|
463
|
+
} catch (error) {
|
|
464
|
+
lastErrorMessage = error instanceof Error ? error.message : String(error);
|
|
245
465
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return {
|
|
469
|
+
success: false,
|
|
470
|
+
error: `CLI command failed: ${lastErrorMessage || 'Unknown error'}`,
|
|
471
|
+
latency_ms: Date.now() - startTime,
|
|
472
|
+
provider: providerId,
|
|
473
|
+
mode: 'args',
|
|
474
|
+
timestamp: new Date()
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
} catch (error) {
|
|
478
|
+
return {
|
|
479
|
+
success: false,
|
|
480
|
+
error: `CLI execution failed: ${error.message}`,
|
|
481
|
+
latency_ms: Date.now() - startTime,
|
|
482
|
+
provider: providerId,
|
|
483
|
+
mode,
|
|
484
|
+
timestamp: new Date()
|
|
485
|
+
};
|
|
285
486
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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();
|
|
318
|
-
}
|
|
319
|
-
});
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
async executeCliCommand(command, args, mode = 'args', timeoutMs = 30000, stdinInput) {
|
|
490
|
+
// Ensure timeoutMs is valid (not undefined, null, Infinity, or negative)
|
|
491
|
+
if (!timeoutMs || timeoutMs === Infinity || timeoutMs < 1 || timeoutMs > 300000) {
|
|
492
|
+
timeoutMs = 30000 // Default to 30 seconds
|
|
320
493
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
494
|
+
|
|
495
|
+
return new Promise((resolve, reject) => {
|
|
496
|
+
if (process.env.POLYDEV_CLI_DEBUG) {
|
|
497
|
+
console.log(`[CLI Debug] Executing: ${command} ${args.join(' ')} (mode: ${mode})`);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const child = spawn(command, args, {
|
|
501
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
502
|
+
shell: process.platform === 'win32',
|
|
503
|
+
timeout: timeoutMs
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
if (child.stdin) {
|
|
507
|
+
child.stdin.end();
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
let stdout = '';
|
|
511
|
+
let stderr = '';
|
|
512
|
+
|
|
513
|
+
child.stdout?.on('data', (data) => {
|
|
514
|
+
stdout += data.toString();
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
child.stderr?.on('data', (data) => {
|
|
518
|
+
stderr += data.toString();
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
if (mode === 'stdin' && stdinInput && child.stdin) {
|
|
522
|
+
child.stdin.write(`${stdinInput}\n`);
|
|
523
|
+
child.stdin.end();
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
child.on('close', (code) => {
|
|
527
|
+
if (process.env.POLYDEV_CLI_DEBUG) {
|
|
528
|
+
console.log(`[CLI Debug] Command finished with code ${code}`);
|
|
329
529
|
}
|
|
330
|
-
|
|
331
|
-
|
|
530
|
+
|
|
531
|
+
if (code === 0) {
|
|
532
|
+
resolve({ stdout, stderr });
|
|
533
|
+
} else {
|
|
534
|
+
const trimmedStdErr = stderr.trim();
|
|
535
|
+
const trimmedStdOut = stdout.trim();
|
|
536
|
+
const errorMessage = trimmedStdErr || trimmedStdOut || `Command exited with code ${code}`;
|
|
537
|
+
resolve({
|
|
538
|
+
stdout,
|
|
539
|
+
stderr,
|
|
540
|
+
error: errorMessage,
|
|
541
|
+
exit_code: code
|
|
542
|
+
});
|
|
332
543
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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.error(`[CLI Manager] Updated database via MCP Supabase for ${providerId}: ${status.available}`);
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
child.on('error', (error) => {
|
|
547
|
+
if (process.env.POLYDEV_CLI_DEBUG) {
|
|
548
|
+
console.log(`[CLI Debug] Command error:`, error);
|
|
367
549
|
}
|
|
368
|
-
|
|
369
|
-
|
|
550
|
+
reject(error);
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
let timeoutId;
|
|
554
|
+
const cleanup = () => {
|
|
555
|
+
if (timeoutId) {
|
|
556
|
+
clearTimeout(timeoutId);
|
|
557
|
+
timeoutId = null;
|
|
370
558
|
}
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
timeoutId = setTimeout(() => {
|
|
562
|
+
cleanup();
|
|
563
|
+
if (!child.killed) {
|
|
564
|
+
child.kill('SIGTERM');
|
|
565
|
+
// Force kill after 2 seconds if still running
|
|
566
|
+
setTimeout(() => {
|
|
567
|
+
if (!child.killed) {
|
|
568
|
+
child.kill('SIGKILL');
|
|
569
|
+
}
|
|
570
|
+
}, 2000);
|
|
571
|
+
}
|
|
572
|
+
reject(new Error(`Command timeout after ${timeoutMs}ms`));
|
|
573
|
+
}, timeoutMs);
|
|
574
|
+
|
|
575
|
+
child.on('close', () => {
|
|
576
|
+
cleanup();
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
child.on('exit', () => {
|
|
580
|
+
cleanup();
|
|
581
|
+
});
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
async executeCodexExec(executable, commandArgs, prompt, timeoutMs) {
|
|
586
|
+
if (!executable) {
|
|
587
|
+
throw new Error('Missing Codex executable');
|
|
371
588
|
}
|
|
372
|
-
|
|
373
|
-
|
|
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'
|
|
380
|
-
};
|
|
381
|
-
return serverMap[providerId] || 'unknown-cli-bridge';
|
|
382
|
-
}
|
|
383
|
-
/**
|
|
384
|
-
* Get all CLI providers configuration
|
|
385
|
-
*/
|
|
386
|
-
getProviders() {
|
|
387
|
-
return Array.from(this.providers.values());
|
|
388
|
-
}
|
|
389
|
-
/**
|
|
390
|
-
* Get provider by ID
|
|
391
|
-
*/
|
|
392
|
-
getProvider(providerId) {
|
|
393
|
-
return this.providers.get(providerId);
|
|
589
|
+
|
|
590
|
+
if (!commandArgs || commandArgs.length === 0) {
|
|
591
|
+
throw new Error('Invalid Codex command configuration');
|
|
394
592
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
};
|
|
425
|
-
}
|
|
426
|
-
}
|
|
593
|
+
|
|
594
|
+
const workingDir = process.cwd();
|
|
595
|
+
const args = [
|
|
596
|
+
...commandArgs,
|
|
597
|
+
'--sandbox',
|
|
598
|
+
'workspace-write',
|
|
599
|
+
'--skip-git-repo-check',
|
|
600
|
+
'--cd',
|
|
601
|
+
workingDir,
|
|
602
|
+
prompt
|
|
603
|
+
];
|
|
604
|
+
|
|
605
|
+
return new Promise((resolve, reject) => {
|
|
606
|
+
const baseTmp = process.env.POLYDEV_CLI_TMPDIR || process.env.TMPDIR || os.tmpdir();
|
|
607
|
+
const tmpDir = path.join(baseTmp, 'polydev-codex');
|
|
608
|
+
try {
|
|
609
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
610
|
+
} catch (error) {
|
|
611
|
+
console.warn('[CLI Debug] Failed to create Codex temp dir:', error);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const child = spawn(executable, args, {
|
|
615
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
616
|
+
shell: process.platform === 'win32',
|
|
617
|
+
env: {
|
|
618
|
+
...process.env,
|
|
619
|
+
TMPDIR: tmpDir,
|
|
620
|
+
TEMP: tmpDir,
|
|
621
|
+
TMP: tmpDir
|
|
427
622
|
}
|
|
428
|
-
|
|
429
|
-
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
console.log(`[CLI Debug] Spawning Codex process: ${executable} ${args.join(' ')}`);
|
|
626
|
+
|
|
627
|
+
if (child.stdin) {
|
|
628
|
+
child.stdin.end();
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
let stdout = '';
|
|
632
|
+
let stderr = '';
|
|
633
|
+
let resolved = false;
|
|
634
|
+
|
|
635
|
+
const stop = (handler) => {
|
|
636
|
+
if (!resolved) {
|
|
637
|
+
resolved = true;
|
|
638
|
+
try { child.kill('SIGTERM'); } catch (_) {}
|
|
639
|
+
handler();
|
|
430
640
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
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;
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
const timeoutHandle = setTimeout(() => {
|
|
644
|
+
stop(() => reject(new Error(`Codex exec timeout after ${timeoutMs}ms`)));
|
|
645
|
+
}, timeoutMs);
|
|
646
|
+
|
|
647
|
+
const flushIfComplete = () => {
|
|
648
|
+
const match = stdout.match(/•\s*(.+)/);
|
|
649
|
+
if (match && match[1]) {
|
|
650
|
+
clearTimeout(timeoutHandle);
|
|
651
|
+
stop(() => resolve(match[1].trim()));
|
|
469
652
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
child.stdout?.on('data', (data) => {
|
|
656
|
+
stdout += data.toString();
|
|
657
|
+
flushIfComplete();
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
child.stderr?.on('data', (data) => {
|
|
661
|
+
stderr += data.toString();
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
child.on('close', (code) => {
|
|
665
|
+
if (resolved) return;
|
|
666
|
+
resolved = true;
|
|
667
|
+
clearTimeout(timeoutHandle);
|
|
668
|
+
|
|
669
|
+
const trimmedStdout = stdout.trim();
|
|
670
|
+
const trimmedStderr = stderr.trim();
|
|
671
|
+
|
|
672
|
+
if (code === 0 && trimmedStdout) {
|
|
673
|
+
const match = trimmedStdout.match(/•\s*(.+)/);
|
|
674
|
+
if (match && match[1]) {
|
|
675
|
+
resolve(match[1].trim());
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
resolve(trimmedStdout);
|
|
679
|
+
} else {
|
|
680
|
+
reject(new Error(trimmedStderr || trimmedStdout || `Codex exited with code ${code}`));
|
|
492
681
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
child.on('error', (error) => {
|
|
685
|
+
if (resolved) return;
|
|
686
|
+
resolved = true;
|
|
687
|
+
clearTimeout(timeoutHandle);
|
|
688
|
+
reject(error);
|
|
689
|
+
});
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
cleanCliResponse(response) {
|
|
694
|
+
const cleanResponse = response.replace(/\x1b\[[0-9;]*m/g, '');
|
|
695
|
+
|
|
696
|
+
return cleanResponse
|
|
697
|
+
.replace(/^(\s*>\s*|\s*$)/gm, '')
|
|
698
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
699
|
+
.trim();
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
estimateTokens(text) {
|
|
703
|
+
return Math.ceil(text.length / 4);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
getAvailableProviders() {
|
|
707
|
+
return Array.from(this.providers.values());
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
getProvider(providerId) {
|
|
711
|
+
return this.providers.get(providerId);
|
|
712
|
+
}
|
|
506
713
|
}
|
|
507
|
-
|
|
508
|
-
exports
|
|
714
|
+
|
|
715
|
+
module.exports = { CLIManager, default: CLIManager };
|