polydev-ai 1.2.14 → 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 +56 -77
- package/package.json +17 -4
package/lib/cliManager.ts
CHANGED
|
@@ -10,7 +10,6 @@ import * as path from 'path'
|
|
|
10
10
|
import * as fs from 'fs'
|
|
11
11
|
import * as os from 'os'
|
|
12
12
|
const which = require('which')
|
|
13
|
-
const shell = require('shelljs')
|
|
14
13
|
|
|
15
14
|
const execAsync = promisify(exec)
|
|
16
15
|
|
|
@@ -20,8 +19,8 @@ export interface CLIProvider {
|
|
|
20
19
|
executable: string
|
|
21
20
|
versionCommand: string
|
|
22
21
|
authCheckCommand: string
|
|
23
|
-
chatCommand: string
|
|
24
|
-
|
|
22
|
+
chatCommand: string | string[]
|
|
23
|
+
alternateChatCommands?: Array<string | string[]>
|
|
25
24
|
supportsArgs: boolean
|
|
26
25
|
installInstructions: string
|
|
27
26
|
authInstructions: string
|
|
@@ -45,6 +44,7 @@ export interface CLIResponse {
|
|
|
45
44
|
error?: string
|
|
46
45
|
tokensUsed?: number
|
|
47
46
|
latencyMs?: number
|
|
47
|
+
mode?: 'stdin' | 'args'
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
export class CLIManager {
|
|
@@ -64,8 +64,7 @@ export class CLIManager {
|
|
|
64
64
|
executable: 'claude',
|
|
65
65
|
versionCommand: 'claude --version',
|
|
66
66
|
authCheckCommand: 'claude auth status',
|
|
67
|
-
chatCommand: 'claude chat',
|
|
68
|
-
supportsStdin: true,
|
|
67
|
+
chatCommand: ['claude', 'chat'],
|
|
69
68
|
supportsArgs: true,
|
|
70
69
|
installInstructions: 'Install via: npm install -g @anthropic-ai/claude-code',
|
|
71
70
|
authInstructions: 'Authenticate with: claude auth login'
|
|
@@ -75,12 +74,16 @@ export class CLIManager {
|
|
|
75
74
|
name: 'Codex CLI',
|
|
76
75
|
executable: 'codex',
|
|
77
76
|
versionCommand: 'codex --version',
|
|
78
|
-
authCheckCommand: 'codex
|
|
79
|
-
chatCommand: '
|
|
80
|
-
|
|
77
|
+
authCheckCommand: 'codex auth status',
|
|
78
|
+
chatCommand: ['exec'],
|
|
79
|
+
alternateChatCommands: [
|
|
80
|
+
['chat'],
|
|
81
|
+
['prompt'],
|
|
82
|
+
['ask']
|
|
83
|
+
],
|
|
81
84
|
supportsArgs: true,
|
|
82
|
-
installInstructions: 'Install
|
|
83
|
-
authInstructions: 'Authenticate with: codex
|
|
85
|
+
installInstructions: 'Install Codex CLI from OpenAI',
|
|
86
|
+
authInstructions: 'Authenticate with: codex auth'
|
|
84
87
|
},
|
|
85
88
|
{
|
|
86
89
|
id: 'gemini_cli',
|
|
@@ -88,8 +91,7 @@ export class CLIManager {
|
|
|
88
91
|
executable: 'gemini',
|
|
89
92
|
versionCommand: 'gemini --version',
|
|
90
93
|
authCheckCommand: 'gemini auth status',
|
|
91
|
-
chatCommand: 'gemini chat',
|
|
92
|
-
supportsStdin: true,
|
|
94
|
+
chatCommand: ['gemini', 'chat'],
|
|
93
95
|
supportsArgs: true,
|
|
94
96
|
installInstructions: 'Install Gemini CLI from Google',
|
|
95
97
|
authInstructions: 'Authenticate with: gemini auth login'
|
|
@@ -197,19 +199,56 @@ export class CLIManager {
|
|
|
197
199
|
}
|
|
198
200
|
}
|
|
199
201
|
|
|
202
|
+
if (providerId === 'codex_cli' && timeoutMs < 90000) {
|
|
203
|
+
timeoutMs = 90000
|
|
204
|
+
}
|
|
205
|
+
|
|
200
206
|
const startTime = Date.now()
|
|
201
207
|
|
|
202
208
|
try {
|
|
203
|
-
|
|
209
|
+
const commandVariants = [
|
|
210
|
+
this.normalizeCommand(provider.chatCommand),
|
|
211
|
+
...(provider.alternateChatCommands || []).map(cmd => this.normalizeCommand(cmd))
|
|
212
|
+
].filter(parts => parts.length > 0)
|
|
204
213
|
|
|
205
|
-
if (
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
|
|
214
|
+
if (commandVariants.length === 0) {
|
|
215
|
+
throw new Error(`${provider.name} does not have a valid chat command configured`)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (provider.id === 'codex_cli') {
|
|
219
|
+
const execArgs = commandVariants.find(parts => parts.includes('exec')) || commandVariants[0]
|
|
220
|
+
const result = await this.executeCodexExec(provider.executable, execArgs, prompt, timeoutMs)
|
|
221
|
+
return {
|
|
222
|
+
success: true,
|
|
223
|
+
content: result,
|
|
224
|
+
latencyMs: Date.now() - startTime,
|
|
225
|
+
mode: 'args'
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
let result: string | undefined
|
|
230
|
+
let effectiveMode: 'stdin' | 'args' = 'args'
|
|
231
|
+
let lastError: Error | undefined
|
|
232
|
+
|
|
233
|
+
for (const commandParts of commandVariants) {
|
|
234
|
+
if (!provider.supportsArgs) {
|
|
235
|
+
lastError = new Error(`${provider.name} does not support args mode`)
|
|
236
|
+
continue
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
result = await this.sendPromptViaArgs(commandParts, prompt, timeoutMs)
|
|
241
|
+
break
|
|
242
|
+
} catch (error) {
|
|
243
|
+
lastError = error instanceof Error ? error : new Error(String(error))
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (typeof result !== 'string') {
|
|
210
248
|
return {
|
|
211
249
|
success: false,
|
|
212
|
-
error:
|
|
250
|
+
error: lastError?.message || 'CLI execution failed',
|
|
251
|
+
latencyMs: Date.now() - startTime
|
|
213
252
|
}
|
|
214
253
|
}
|
|
215
254
|
|
|
@@ -218,7 +257,8 @@ export class CLIManager {
|
|
|
218
257
|
return {
|
|
219
258
|
success: true,
|
|
220
259
|
content: result,
|
|
221
|
-
latencyMs
|
|
260
|
+
latencyMs,
|
|
261
|
+
mode: effectiveMode
|
|
222
262
|
}
|
|
223
263
|
|
|
224
264
|
} catch (error) {
|
|
@@ -310,67 +350,212 @@ export class CLIManager {
|
|
|
310
350
|
}
|
|
311
351
|
}
|
|
312
352
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
353
|
+
private normalizeCommand(command: string | string[]): string[] {
|
|
354
|
+
if (Array.isArray(command)) {
|
|
355
|
+
return command.map(part => part.trim()).filter(Boolean)
|
|
356
|
+
}
|
|
357
|
+
return command.split(/\s+/).map(part => part.trim()).filter(Boolean)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
private async sendPromptViaArgs(
|
|
361
|
+
commandParts: string[],
|
|
318
362
|
prompt: string,
|
|
319
363
|
timeoutMs: number
|
|
320
364
|
): Promise<string> {
|
|
365
|
+
if (commandParts.length === 0) {
|
|
366
|
+
throw new Error('Invalid CLI command configuration')
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const [executable, ...baseArgs] = commandParts
|
|
370
|
+
const args = [...baseArgs, prompt]
|
|
371
|
+
|
|
372
|
+
if (process.env.POLYDEV_CLI_DEBUG) {
|
|
373
|
+
console.log(`[CLI Debug] Executing (args) ${executable} ${args.join(' ')}`)
|
|
374
|
+
}
|
|
375
|
+
|
|
321
376
|
return new Promise((resolve, reject) => {
|
|
322
|
-
const
|
|
377
|
+
const baseTmp = process.env.POLYDEV_CLI_TMPDIR || process.env.TMPDIR || os.tmpdir()
|
|
378
|
+
const tmpDir = path.join(baseTmp, 'polydev-codex')
|
|
379
|
+
try {
|
|
380
|
+
fs.mkdirSync(tmpDir, { recursive: true })
|
|
381
|
+
} catch (error) {
|
|
382
|
+
console.warn('[CLI Debug] Failed to create Codex temp dir:', error)
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const child = spawn(executable, args, {
|
|
323
386
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
324
|
-
|
|
387
|
+
shell: process.platform === 'win32',
|
|
388
|
+
env: {
|
|
389
|
+
...process.env,
|
|
390
|
+
TMPDIR: tmpDir,
|
|
391
|
+
TEMP: tmpDir,
|
|
392
|
+
TMP: tmpDir
|
|
393
|
+
}
|
|
325
394
|
})
|
|
326
395
|
|
|
396
|
+
console.log(`[CLI Debug] Spawning Codex process: ${executable} ${args.join(' ')}`)
|
|
397
|
+
|
|
398
|
+
// No stdin needed for exec mode; close immediately to avoid hangs.
|
|
399
|
+
if (child.stdin) {
|
|
400
|
+
child.stdin.end()
|
|
401
|
+
}
|
|
402
|
+
|
|
327
403
|
let stdout = ''
|
|
328
404
|
let stderr = ''
|
|
405
|
+
let finished = false
|
|
406
|
+
|
|
407
|
+
const timeoutHandle = setTimeout(() => {
|
|
408
|
+
if (!finished) {
|
|
409
|
+
finished = true
|
|
410
|
+
try {
|
|
411
|
+
child.kill('SIGTERM')
|
|
412
|
+
setTimeout(() => {
|
|
413
|
+
if (!child.killed) {
|
|
414
|
+
child.kill('SIGKILL')
|
|
415
|
+
}
|
|
416
|
+
}, 1500)
|
|
417
|
+
} catch {}
|
|
418
|
+
reject(new Error(`CLI command timeout after ${timeoutMs}ms`))
|
|
419
|
+
}
|
|
420
|
+
}, timeoutMs)
|
|
329
421
|
|
|
330
|
-
child.stdout?.on('data',
|
|
422
|
+
child.stdout?.on('data', data => {
|
|
331
423
|
stdout += data.toString()
|
|
332
424
|
})
|
|
333
425
|
|
|
334
|
-
child.stderr?.on('data',
|
|
426
|
+
child.stderr?.on('data', data => {
|
|
335
427
|
stderr += data.toString()
|
|
336
428
|
})
|
|
337
429
|
|
|
338
|
-
child.on('close',
|
|
430
|
+
child.on('close', code => {
|
|
431
|
+
if (finished) return
|
|
432
|
+
finished = true
|
|
433
|
+
clearTimeout(timeoutHandle)
|
|
434
|
+
|
|
435
|
+
if (process.env.POLYDEV_CLI_DEBUG) {
|
|
436
|
+
console.log(`[CLI Debug] (args) exit code ${code}`)
|
|
437
|
+
if (stdout) console.log(`[CLI Debug] stdout: ${stdout.trim().slice(0, 500)}`)
|
|
438
|
+
if (stderr) console.log(`[CLI Debug] stderr: ${stderr.trim().slice(0, 500)}`)
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const trimmedStdout = stdout.trim()
|
|
442
|
+
const trimmedStderr = stderr.trim()
|
|
443
|
+
|
|
339
444
|
if (code === 0) {
|
|
340
|
-
resolve(
|
|
445
|
+
resolve(trimmedStdout || trimmedStderr)
|
|
341
446
|
} else {
|
|
342
|
-
|
|
447
|
+
const message = trimmedStderr || trimmedStdout || `CLI command failed (code ${code})`
|
|
448
|
+
reject(new Error(message))
|
|
343
449
|
}
|
|
344
450
|
})
|
|
345
451
|
|
|
346
|
-
child.on('error',
|
|
452
|
+
child.on('error', error => {
|
|
453
|
+
if (finished) return
|
|
454
|
+
finished = true
|
|
455
|
+
clearTimeout(timeoutHandle)
|
|
347
456
|
reject(error)
|
|
348
457
|
})
|
|
349
|
-
|
|
350
|
-
// Send prompt via stdin
|
|
351
|
-
if (child.stdin) {
|
|
352
|
-
child.stdin.write(prompt)
|
|
353
|
-
child.stdin.end()
|
|
354
|
-
}
|
|
355
458
|
})
|
|
356
459
|
}
|
|
357
460
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
private async sendPromptViaArgs(
|
|
362
|
-
provider: CLIProvider,
|
|
461
|
+
private async executeCodexExec(
|
|
462
|
+
executable: string,
|
|
463
|
+
commandArgs: string[],
|
|
363
464
|
prompt: string,
|
|
364
465
|
timeoutMs: number
|
|
365
466
|
): Promise<string> {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
try {
|
|
369
|
-
const { stdout } = await execAsync(command, { timeout: timeoutMs })
|
|
370
|
-
return stdout.trim()
|
|
371
|
-
} catch (error) {
|
|
372
|
-
throw new Error(`CLI command failed: ${error}`)
|
|
467
|
+
if (!executable) {
|
|
468
|
+
throw new Error('Missing Codex executable')
|
|
373
469
|
}
|
|
470
|
+
|
|
471
|
+
if (!commandArgs || commandArgs.length === 0) {
|
|
472
|
+
throw new Error('Invalid Codex command configuration')
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const workingDir = process.cwd()
|
|
476
|
+
const args = [
|
|
477
|
+
...commandArgs,
|
|
478
|
+
'--sandbox',
|
|
479
|
+
'workspace-write',
|
|
480
|
+
'--skip-git-repo-check',
|
|
481
|
+
'--cd',
|
|
482
|
+
workingDir,
|
|
483
|
+
prompt
|
|
484
|
+
]
|
|
485
|
+
|
|
486
|
+
return new Promise((resolve, reject) => {
|
|
487
|
+
const child = spawn(executable, args, {
|
|
488
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
489
|
+
shell: process.platform === 'win32'
|
|
490
|
+
})
|
|
491
|
+
|
|
492
|
+
if (child.stdin) {
|
|
493
|
+
child.stdin.end()
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
let stdout = ''
|
|
497
|
+
let stderr = ''
|
|
498
|
+
let resolved = false
|
|
499
|
+
|
|
500
|
+
const stop = (handler: () => void) => {
|
|
501
|
+
if (!resolved) {
|
|
502
|
+
resolved = true
|
|
503
|
+
try {
|
|
504
|
+
child.kill('SIGTERM')
|
|
505
|
+
} catch {}
|
|
506
|
+
handler()
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const timeoutHandle = setTimeout(() => {
|
|
511
|
+
stop(() => reject(new Error(`Codex exec timeout after ${timeoutMs}ms`)))
|
|
512
|
+
}, timeoutMs)
|
|
513
|
+
|
|
514
|
+
const flushIfComplete = () => {
|
|
515
|
+
const bulletMatch = stdout.match(/•\s*(.+)/)
|
|
516
|
+
if (bulletMatch && bulletMatch[1]) {
|
|
517
|
+
const answer = bulletMatch[1].trim()
|
|
518
|
+
clearTimeout(timeoutHandle)
|
|
519
|
+
stop(() => resolve(answer))
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
child.stdout?.on('data', data => {
|
|
524
|
+
stdout += data.toString()
|
|
525
|
+
flushIfComplete()
|
|
526
|
+
})
|
|
527
|
+
|
|
528
|
+
child.stderr?.on('data', data => {
|
|
529
|
+
stderr += data.toString()
|
|
530
|
+
})
|
|
531
|
+
|
|
532
|
+
child.on('close', code => {
|
|
533
|
+
if (resolved) return
|
|
534
|
+
resolved = true
|
|
535
|
+
clearTimeout(timeoutHandle)
|
|
536
|
+
|
|
537
|
+
const trimmedStdout = stdout.trim()
|
|
538
|
+
const trimmedStderr = stderr.trim()
|
|
539
|
+
|
|
540
|
+
if (code === 0 && trimmedStdout) {
|
|
541
|
+
const bulletMatch = trimmedStdout.match(/•\s*(.+)/)
|
|
542
|
+
if (bulletMatch && bulletMatch[1]) {
|
|
543
|
+
resolve(bulletMatch[1].trim())
|
|
544
|
+
return
|
|
545
|
+
}
|
|
546
|
+
resolve(trimmedStdout)
|
|
547
|
+
} else {
|
|
548
|
+
reject(new Error(trimmedStderr || trimmedStdout || `Codex exited with code ${code}`))
|
|
549
|
+
}
|
|
550
|
+
})
|
|
551
|
+
|
|
552
|
+
child.on('error', error => {
|
|
553
|
+
if (resolved) return
|
|
554
|
+
resolved = true
|
|
555
|
+
clearTimeout(timeoutHandle)
|
|
556
|
+
reject(error)
|
|
557
|
+
})
|
|
558
|
+
})
|
|
374
559
|
}
|
|
375
560
|
|
|
376
561
|
/**
|
|
@@ -81,7 +81,7 @@ class ZeroKnowledgeEncryption {
|
|
|
81
81
|
const keyData = cursor.value;
|
|
82
82
|
// Check if key needs rotation
|
|
83
83
|
if (new Date() > new Date(keyData.rotationDue)) {
|
|
84
|
-
console.
|
|
84
|
+
console.log('[ZK Encryption] Key rotation needed, generating new key');
|
|
85
85
|
await this.createAndStoreKey();
|
|
86
86
|
}
|
|
87
87
|
else {
|
|
@@ -214,7 +214,7 @@ class ZeroKnowledgeEncryption {
|
|
|
214
214
|
*/
|
|
215
215
|
async rotateKeys() {
|
|
216
216
|
await this.createAndStoreKey();
|
|
217
|
-
console.
|
|
217
|
+
console.log('[ZK Encryption] Key rotation completed');
|
|
218
218
|
}
|
|
219
219
|
/**
|
|
220
220
|
* Generate unique key ID
|
|
@@ -274,7 +274,7 @@ class ZeroKnowledgeEncryption {
|
|
|
274
274
|
await transaction.objectStore('keys').clear();
|
|
275
275
|
await transaction.objectStore('cache').clear();
|
|
276
276
|
this.activeKey = null;
|
|
277
|
-
console.
|
|
277
|
+
console.log('[ZK Encryption] All keys cleared');
|
|
278
278
|
}
|
|
279
279
|
}
|
|
280
280
|
exports.ZeroKnowledgeEncryption = ZeroKnowledgeEncryption;
|
|
@@ -286,4 +286,4 @@ ZeroKnowledgeEncryption.KEY_ROTATION_DAYS = 30;
|
|
|
286
286
|
ZeroKnowledgeEncryption.DB_NAME = 'PolydevZKMemory';
|
|
287
287
|
ZeroKnowledgeEncryption.DB_VERSION = 1;
|
|
288
288
|
// Export singleton instance
|
|
289
|
-
exports.zkEncryption = new ZeroKnowledgeEncryption();
|
|
289
|
+
exports.zkEncryption = new ZeroKnowledgeEncryption();
|
package/mcp/manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polydev-perspectives",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Agentic workflow assistant - get diverse perspectives from multiple LLMs when stuck or need enhanced reasoning",
|
|
5
5
|
"author": "Polydev AI",
|
|
6
6
|
"license": "MIT",
|
|
@@ -685,4 +685,4 @@
|
|
|
685
685
|
"step_4": "Use the token in your MCP tool calls"
|
|
686
686
|
}
|
|
687
687
|
}
|
|
688
|
-
}
|
|
688
|
+
}
|
package/mcp/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polydev-ai",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.6",
|
|
4
4
|
"description": "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": {
|
|
@@ -43,4 +43,4 @@
|
|
|
43
43
|
"bugs": {
|
|
44
44
|
"url": "https://github.com/backspacevenkat/polydev-website/issues"
|
|
45
45
|
}
|
|
46
|
-
}
|
|
46
|
+
}
|
package/mcp/stdio-wrapper.js
CHANGED
|
@@ -3,27 +3,56 @@
|
|
|
3
3
|
// Lightweight stdio wrapper with local CLI functionality and remote Polydev MCP server fallback
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
|
+
const os = require('os');
|
|
6
7
|
const { CLIManager } = require('../lib/cliManager');
|
|
7
8
|
|
|
9
|
+
function ensureWritableTmpDir() {
|
|
10
|
+
const candidates = [
|
|
11
|
+
process.env.POLYDEV_TMPDIR,
|
|
12
|
+
'/tmp/polydev',
|
|
13
|
+
'/tmp',
|
|
14
|
+
path.join(os.tmpdir(), 'polydev'),
|
|
15
|
+
path.join(process.cwd(), '.polydev-tmp')
|
|
16
|
+
].filter(Boolean);
|
|
17
|
+
|
|
18
|
+
for (const candidate of candidates) {
|
|
19
|
+
try {
|
|
20
|
+
fs.mkdirSync(candidate, { recursive: true });
|
|
21
|
+
fs.accessSync(candidate, fs.constants.W_OK);
|
|
22
|
+
return candidate;
|
|
23
|
+
} catch (error) {
|
|
24
|
+
console.warn(`[Stdio Wrapper] TMP candidate not writable (${candidate}):`, error.message);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.error('[Stdio Wrapper] No writable TMP directory found; Codex CLI will fail.');
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const writableTmp = ensureWritableTmpDir();
|
|
33
|
+
if (writableTmp) {
|
|
34
|
+
process.env.TMPDIR = writableTmp;
|
|
35
|
+
process.env.TMP = writableTmp;
|
|
36
|
+
process.env.TEMP = writableTmp;
|
|
37
|
+
console.error(`[Stdio Wrapper] Using TMP directory: ${writableTmp}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
8
40
|
class StdioMCPWrapper {
|
|
9
41
|
constructor() {
|
|
10
42
|
this.userToken = process.env.POLYDEV_USER_TOKEN;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (this.userToken) {
|
|
15
|
-
this.cliManager = new CLIManager();
|
|
43
|
+
if (!this.userToken) {
|
|
44
|
+
console.error('POLYDEV_USER_TOKEN environment variable is required');
|
|
45
|
+
process.exit(1);
|
|
16
46
|
}
|
|
17
47
|
|
|
48
|
+
// Initialize CLI Manager for local CLI functionality
|
|
49
|
+
this.cliManager = new CLIManager();
|
|
50
|
+
|
|
18
51
|
// Load manifest for tool definitions
|
|
19
52
|
this.loadManifest();
|
|
20
53
|
|
|
21
54
|
// Smart refresh scheduler (will be started after initialization)
|
|
22
55
|
this.refreshScheduler = null;
|
|
23
|
-
|
|
24
|
-
// Track if initial CLI detection has been done
|
|
25
|
-
this.initialDetectionDone = false;
|
|
26
|
-
this.initialDetectionPromise = null;
|
|
27
56
|
}
|
|
28
57
|
|
|
29
58
|
loadManifest() {
|
|
@@ -69,18 +98,6 @@ class StdioMCPWrapper {
|
|
|
69
98
|
};
|
|
70
99
|
|
|
71
100
|
case 'tools/call':
|
|
72
|
-
// Check if token is missing
|
|
73
|
-
if (!this.userToken) {
|
|
74
|
-
return {
|
|
75
|
-
jsonrpc: '2.0',
|
|
76
|
-
id,
|
|
77
|
-
error: {
|
|
78
|
-
code: -32603,
|
|
79
|
-
message: 'POLYDEV_USER_TOKEN environment variable is required. Please add it to your MCP configuration.'
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
101
|
// Handle CLI tools locally, forward others to remote server
|
|
85
102
|
const toolName = params.name;
|
|
86
103
|
if (toolName.startsWith('polydev.') && this.isCliTool(toolName)) {
|
|
@@ -230,10 +247,6 @@ class StdioMCPWrapper {
|
|
|
230
247
|
// Update database with CLI status
|
|
231
248
|
await this.updateCliStatusInDatabase(results);
|
|
232
249
|
|
|
233
|
-
// Mark initial detection as done
|
|
234
|
-
this.initialDetectionDone = true;
|
|
235
|
-
this.initialDetectionPromise = null;
|
|
236
|
-
|
|
237
250
|
return {
|
|
238
251
|
success: true,
|
|
239
252
|
results,
|
|
@@ -244,10 +257,6 @@ class StdioMCPWrapper {
|
|
|
244
257
|
|
|
245
258
|
} catch (error) {
|
|
246
259
|
console.error('[Stdio Wrapper] Local CLI detection error:', error);
|
|
247
|
-
// Mark as done even on error to prevent blocking
|
|
248
|
-
this.initialDetectionDone = true;
|
|
249
|
-
this.initialDetectionPromise = null;
|
|
250
|
-
|
|
251
260
|
return {
|
|
252
261
|
success: false,
|
|
253
262
|
error: error.message,
|
|
@@ -309,16 +318,6 @@ class StdioMCPWrapper {
|
|
|
309
318
|
console.error(`[Stdio Wrapper] Local CLI prompt sending with perspectives`);
|
|
310
319
|
|
|
311
320
|
try {
|
|
312
|
-
// Ensure initial CLI detection has completed before sending prompts
|
|
313
|
-
if (!this.initialDetectionDone && this.initialDetectionPromise) {
|
|
314
|
-
console.error('[Stdio Wrapper] Waiting for initial CLI detection to complete...');
|
|
315
|
-
await this.initialDetectionPromise;
|
|
316
|
-
} else if (!this.initialDetectionDone && !this.initialDetectionPromise) {
|
|
317
|
-
console.error('[Stdio Wrapper] Running initial CLI detection before sending prompt...');
|
|
318
|
-
this.initialDetectionPromise = this.localForceCliDetection({});
|
|
319
|
-
await this.initialDetectionPromise;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
321
|
let { provider_id, prompt, mode = 'args', timeout_ms = 30000 } = args;
|
|
323
322
|
|
|
324
323
|
// Ensure timeout_ms is valid (not undefined, null, Infinity, or negative)
|
|
@@ -402,18 +401,7 @@ class StdioMCPWrapper {
|
|
|
402
401
|
*/
|
|
403
402
|
async getAllAvailableProviders() {
|
|
404
403
|
try {
|
|
405
|
-
|
|
406
|
-
const cachedStatus = await this.loadLocalCliStatus();
|
|
407
|
-
let results;
|
|
408
|
-
|
|
409
|
-
if (cachedStatus && Object.keys(cachedStatus).length > 0) {
|
|
410
|
-
console.error('[Stdio Wrapper] Using cached CLI status');
|
|
411
|
-
results = cachedStatus;
|
|
412
|
-
} else {
|
|
413
|
-
console.error('[Stdio Wrapper] No cached status, running detection');
|
|
414
|
-
results = await this.cliManager.forceCliDetection();
|
|
415
|
-
}
|
|
416
|
-
|
|
404
|
+
const results = await this.cliManager.forceCliDetection();
|
|
417
405
|
const availableProviders = [];
|
|
418
406
|
|
|
419
407
|
// Priority order: claude_code > codex_cli > gemini_cli
|
|
@@ -894,7 +882,19 @@ class StdioMCPWrapper {
|
|
|
894
882
|
}
|
|
895
883
|
|
|
896
884
|
async start() {
|
|
897
|
-
console.
|
|
885
|
+
console.log('Starting Polydev Stdio MCP Wrapper...');
|
|
886
|
+
|
|
887
|
+
// Run initial CLI detection on startup
|
|
888
|
+
console.error('[Stdio Wrapper] Running initial CLI detection...');
|
|
889
|
+
try {
|
|
890
|
+
await this.localForceCliDetection({});
|
|
891
|
+
console.error('[Stdio Wrapper] Initial CLI detection completed');
|
|
892
|
+
} catch (error) {
|
|
893
|
+
console.error('[Stdio Wrapper] Initial CLI detection failed:', error);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// Start smart refresh scheduler for automatic updates
|
|
897
|
+
this.startSmartRefreshScheduler();
|
|
898
898
|
|
|
899
899
|
process.stdin.setEncoding('utf8');
|
|
900
900
|
let buffer = '';
|
|
@@ -919,46 +919,25 @@ class StdioMCPWrapper {
|
|
|
919
919
|
});
|
|
920
920
|
|
|
921
921
|
process.stdin.on('end', () => {
|
|
922
|
-
console.
|
|
922
|
+
console.log('Stdio MCP Wrapper shutting down...');
|
|
923
923
|
this.stopSmartRefreshScheduler();
|
|
924
924
|
process.exit(0);
|
|
925
925
|
});
|
|
926
926
|
|
|
927
927
|
// Handle process signals
|
|
928
928
|
process.on('SIGINT', () => {
|
|
929
|
-
console.
|
|
929
|
+
console.log('Received SIGINT, shutting down...');
|
|
930
930
|
this.stopSmartRefreshScheduler();
|
|
931
931
|
process.exit(0);
|
|
932
932
|
});
|
|
933
933
|
|
|
934
934
|
process.on('SIGTERM', () => {
|
|
935
|
-
console.
|
|
935
|
+
console.log('Received SIGTERM, shutting down...');
|
|
936
936
|
this.stopSmartRefreshScheduler();
|
|
937
937
|
process.exit(0);
|
|
938
938
|
});
|
|
939
939
|
|
|
940
|
-
console.
|
|
941
|
-
|
|
942
|
-
// Run initial CLI detection AFTER stdin handler is set up
|
|
943
|
-
if (this.userToken && this.cliManager) {
|
|
944
|
-
console.error('[Stdio Wrapper] Scheduling initial CLI detection...');
|
|
945
|
-
// Run asynchronously after a short delay to not block initialization
|
|
946
|
-
setTimeout(async () => {
|
|
947
|
-
console.error('[Stdio Wrapper] Running initial CLI detection...');
|
|
948
|
-
try {
|
|
949
|
-
this.initialDetectionPromise = this.localForceCliDetection({});
|
|
950
|
-
await this.initialDetectionPromise;
|
|
951
|
-
console.error('[Stdio Wrapper] Initial CLI detection completed');
|
|
952
|
-
} catch (error) {
|
|
953
|
-
console.error('[Stdio Wrapper] Initial CLI detection failed:', error);
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
// Start smart refresh scheduler for automatic updates
|
|
957
|
-
this.startSmartRefreshScheduler();
|
|
958
|
-
}, 100);
|
|
959
|
-
} else {
|
|
960
|
-
console.error('[Stdio Wrapper] No token provided - CLI detection disabled');
|
|
961
|
-
}
|
|
940
|
+
console.log('Stdio MCP Wrapper ready and listening on stdin...');
|
|
962
941
|
}
|
|
963
942
|
}
|
|
964
943
|
|