polydev-ai 1.0.0

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.
@@ -0,0 +1,472 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Lightweight stdio wrapper with local CLI functionality and remote Polydev MCP server fallback
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const CLIManager = require('../lib/cliManager').default;
7
+
8
+ class StdioMCPWrapper {
9
+ constructor() {
10
+ this.userToken = process.env.POLYDEV_USER_TOKEN;
11
+ if (!this.userToken) {
12
+ console.error('POLYDEV_USER_TOKEN environment variable is required');
13
+ process.exit(1);
14
+ }
15
+
16
+ // Initialize CLI Manager for local CLI functionality
17
+ this.cliManager = new CLIManager();
18
+
19
+ // Load manifest for tool definitions
20
+ this.loadManifest();
21
+ }
22
+
23
+ loadManifest() {
24
+ try {
25
+ const manifestPath = path.join(__dirname, 'manifest.json');
26
+ this.manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
27
+ } catch (error) {
28
+ console.error('Failed to load manifest:', error);
29
+ process.exit(1);
30
+ }
31
+ }
32
+
33
+ async handleRequest(request) {
34
+ const { method, params, id } = request;
35
+
36
+ try {
37
+ switch (method) {
38
+ case 'initialize':
39
+ return {
40
+ jsonrpc: '2.0',
41
+ id,
42
+ result: {
43
+ protocolVersion: '2024-11-05',
44
+ capabilities: { tools: {} },
45
+ serverInfo: {
46
+ name: this.manifest.name,
47
+ version: this.manifest.version
48
+ }
49
+ }
50
+ };
51
+
52
+ case 'tools/list':
53
+ const tools = this.manifest.tools.map(tool => ({
54
+ name: tool.name,
55
+ description: tool.description,
56
+ inputSchema: tool.inputSchema
57
+ }));
58
+
59
+ return {
60
+ jsonrpc: '2.0',
61
+ id,
62
+ result: { tools }
63
+ };
64
+
65
+ case 'tools/call':
66
+ // Handle CLI tools locally, forward others to remote server
67
+ const toolName = params.name;
68
+ if (toolName.startsWith('polydev.') && this.isCliTool(toolName)) {
69
+ return await this.handleLocalCliTool(request);
70
+ } else {
71
+ // Forward non-CLI tools to remote server
72
+ const result = await this.forwardToRemoteServer(request);
73
+ return result;
74
+ }
75
+
76
+ default:
77
+ return {
78
+ jsonrpc: '2.0',
79
+ id,
80
+ error: {
81
+ code: -32601,
82
+ message: 'Method not found'
83
+ }
84
+ };
85
+ }
86
+ } catch (error) {
87
+ return {
88
+ jsonrpc: '2.0',
89
+ id,
90
+ error: {
91
+ code: -32603,
92
+ message: error.message
93
+ }
94
+ };
95
+ }
96
+ }
97
+
98
+ async forwardToRemoteServer(request) {
99
+ console.error(`[Stdio Wrapper] Forwarding request to remote server`);
100
+
101
+ const response = await fetch('https://www.polydev.ai/api/mcp', {
102
+ method: 'POST',
103
+ headers: {
104
+ 'Content-Type': 'application/json',
105
+ 'Authorization': `Bearer ${this.userToken}`,
106
+ 'User-Agent': 'polydev-stdio-wrapper/1.0.0'
107
+ },
108
+ body: JSON.stringify(request)
109
+ });
110
+
111
+ if (!response.ok) {
112
+ const errorText = await response.text();
113
+ console.error(`[Stdio Wrapper] Remote server error: ${response.status} - ${errorText}`);
114
+
115
+ return {
116
+ jsonrpc: '2.0',
117
+ id: request.id,
118
+ error: {
119
+ code: -32603,
120
+ message: `Remote server error: ${response.status} - ${errorText}`
121
+ }
122
+ };
123
+ }
124
+
125
+ const result = await response.json();
126
+ console.error(`[Stdio Wrapper] Got response from remote server`);
127
+ return result;
128
+ }
129
+
130
+ /**
131
+ * Check if a tool is a CLI tool that should be handled locally
132
+ */
133
+ isCliTool(toolName) {
134
+ const cliTools = [
135
+ 'polydev.force_cli_detection',
136
+ 'polydev.get_cli_status',
137
+ 'polydev.send_cli_prompt'
138
+ ];
139
+ return cliTools.includes(toolName);
140
+ }
141
+
142
+ /**
143
+ * Handle CLI tools locally without remote server calls
144
+ */
145
+ async handleLocalCliTool(request) {
146
+ const { method, params, id } = request;
147
+ const { name: toolName, arguments: args } = params;
148
+
149
+ console.error(`[Stdio Wrapper] Handling local CLI tool: ${toolName}`);
150
+
151
+ try {
152
+ let result;
153
+
154
+ switch (toolName) {
155
+ case 'polydev.force_cli_detection':
156
+ result = await this.localForceCliDetection(args);
157
+ break;
158
+
159
+ case 'polydev.get_cli_status':
160
+ result = await this.localGetCliStatus(args);
161
+ break;
162
+
163
+ case 'polydev.send_cli_prompt':
164
+ result = await this.localSendCliPrompt(args);
165
+ break;
166
+
167
+ default:
168
+ throw new Error(`Unknown CLI tool: ${toolName}`);
169
+ }
170
+
171
+ return {
172
+ jsonrpc: '2.0',
173
+ id,
174
+ result: {
175
+ content: [
176
+ {
177
+ type: 'text',
178
+ text: this.formatCliResponse(result)
179
+ }
180
+ ]
181
+ }
182
+ };
183
+
184
+ } catch (error) {
185
+ console.error(`[Stdio Wrapper] CLI tool error:`, error);
186
+ return {
187
+ jsonrpc: '2.0',
188
+ id,
189
+ error: {
190
+ code: -32603,
191
+ message: error.message
192
+ }
193
+ };
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Local CLI detection implementation
199
+ */
200
+ async localForceCliDetection(args) {
201
+ console.error(`[Stdio Wrapper] Local CLI detection started`);
202
+
203
+ try {
204
+ const providerId = args.provider_id; // Optional - detect specific provider
205
+
206
+ // Force detection using CLI Manager (no remote API calls)
207
+ const results = await this.cliManager.forceCliDetection(null, providerId);
208
+
209
+ // Save status locally to file-based cache
210
+ await this.saveLocalCliStatus(results);
211
+
212
+ return {
213
+ success: true,
214
+ results,
215
+ message: `Local CLI detection completed for ${providerId || 'all providers'}`,
216
+ timestamp: new Date().toISOString(),
217
+ local_only: true
218
+ };
219
+
220
+ } catch (error) {
221
+ console.error('[Stdio Wrapper] Local CLI detection error:', error);
222
+ return {
223
+ success: false,
224
+ error: error.message,
225
+ timestamp: new Date().toISOString(),
226
+ local_only: true
227
+ };
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Local CLI status retrieval
233
+ */
234
+ async localGetCliStatus(args) {
235
+ console.error(`[Stdio Wrapper] Local CLI status retrieval`);
236
+
237
+ try {
238
+ const providerId = args.provider_id;
239
+ let results = {};
240
+
241
+ if (providerId) {
242
+ // Get specific provider status
243
+ const status = await this.cliManager.getCliStatus(providerId);
244
+ results[providerId] = status;
245
+ } else {
246
+ // Get all providers status
247
+ const providers = this.cliManager.getProviders();
248
+ for (const provider of providers) {
249
+ const status = await this.cliManager.getCliStatus(provider.id);
250
+ results[provider.id] = status;
251
+ }
252
+ }
253
+
254
+ return {
255
+ success: true,
256
+ results,
257
+ message: 'Local CLI status retrieved successfully',
258
+ timestamp: new Date().toISOString(),
259
+ local_only: true
260
+ };
261
+
262
+ } catch (error) {
263
+ console.error('[Stdio Wrapper] Local CLI status error:', error);
264
+ return {
265
+ success: false,
266
+ error: error.message,
267
+ timestamp: new Date().toISOString(),
268
+ local_only: true
269
+ };
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Local CLI prompt sending
275
+ */
276
+ async localSendCliPrompt(args) {
277
+ console.error(`[Stdio Wrapper] Local CLI prompt sending`);
278
+
279
+ try {
280
+ const { provider_id, prompt, mode = 'args', timeout_ms = 30000 } = args;
281
+
282
+ if (!provider_id || !prompt) {
283
+ throw new Error('provider_id and prompt are required');
284
+ }
285
+
286
+ // Send prompt using CLI Manager (local execution)
287
+ const response = await this.cliManager.sendCliPrompt(
288
+ provider_id,
289
+ prompt,
290
+ mode,
291
+ timeout_ms
292
+ );
293
+
294
+ // Record usage locally (file-based analytics)
295
+ await this.recordLocalUsage(provider_id, prompt, response);
296
+
297
+ return {
298
+ success: response.success,
299
+ content: response.content,
300
+ error: response.error,
301
+ tokens_used: response.tokensUsed,
302
+ latency_ms: response.latencyMs,
303
+ provider: provider_id,
304
+ mode,
305
+ timestamp: new Date().toISOString(),
306
+ local_only: true
307
+ };
308
+
309
+ } catch (error) {
310
+ console.error('[Stdio Wrapper] Local CLI prompt error:', error);
311
+ return {
312
+ success: false,
313
+ error: error.message,
314
+ timestamp: new Date().toISOString(),
315
+ local_only: true
316
+ };
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Save CLI status to local file cache
322
+ */
323
+ async saveLocalCliStatus(results) {
324
+ try {
325
+ const homeDir = require('os').homedir();
326
+ const polydevevDir = path.join(homeDir, '.polydev');
327
+ const statusFile = path.join(polydevevDir, 'cli-status.json');
328
+
329
+ // Ensure directory exists
330
+ if (!fs.existsSync(polydevevDir)) {
331
+ fs.mkdirSync(polydevevDir, { recursive: true });
332
+ }
333
+
334
+ // Save status with timestamp
335
+ const statusData = {
336
+ last_updated: new Date().toISOString(),
337
+ results
338
+ };
339
+
340
+ fs.writeFileSync(statusFile, JSON.stringify(statusData, null, 2));
341
+ console.error(`[Stdio Wrapper] CLI status saved to ${statusFile}`);
342
+
343
+ } catch (error) {
344
+ console.error('[Stdio Wrapper] Failed to save local CLI status:', error);
345
+ }
346
+ }
347
+
348
+ /**
349
+ * Record local usage for analytics
350
+ */
351
+ async recordLocalUsage(providerId, prompt, response) {
352
+ try {
353
+ const homeDir = require('os').homedir();
354
+ const polydevevDir = path.join(homeDir, '.polydev');
355
+ const usageFile = path.join(polydevevDir, 'cli-usage.json');
356
+
357
+ // Load existing usage data
358
+ let usageData = [];
359
+ if (fs.existsSync(usageFile)) {
360
+ usageData = JSON.parse(fs.readFileSync(usageFile, 'utf8'));
361
+ }
362
+
363
+ // Add new usage record
364
+ usageData.push({
365
+ timestamp: new Date().toISOString(),
366
+ provider: providerId,
367
+ prompt_length: prompt.length,
368
+ success: response.success,
369
+ latency_ms: response.latencyMs,
370
+ tokens_used: response.tokensUsed || 0
371
+ });
372
+
373
+ // Keep only last 1000 records
374
+ if (usageData.length > 1000) {
375
+ usageData = usageData.slice(-1000);
376
+ }
377
+
378
+ fs.writeFileSync(usageFile, JSON.stringify(usageData, null, 2));
379
+ console.error(`[Stdio Wrapper] Usage recorded locally`);
380
+
381
+ } catch (error) {
382
+ console.error('[Stdio Wrapper] Failed to record local usage:', error);
383
+ }
384
+ }
385
+
386
+ /**
387
+ * Format CLI response for MCP output
388
+ */
389
+ formatCliResponse(result) {
390
+ if (!result.success) {
391
+ return `❌ **CLI Error**\n\n${result.error}\n\n*Timestamp: ${result.timestamp}*`;
392
+ }
393
+
394
+ if (result.content) {
395
+ // Prompt response
396
+ return `✅ **CLI Response** (${result.provider || 'Unknown'} - ${result.mode || 'unknown'} mode)\n\n${result.content}\n\n*Latency: ${result.latency_ms || 0}ms | Tokens: ${result.tokens_used || 0} | ${result.timestamp}*`;
397
+ } else {
398
+ // Status/detection response
399
+ let formatted = `✅ **CLI Operation Success**\n\n`;
400
+ formatted += `${result.message}\n\n`;
401
+
402
+ if (result.results) {
403
+ formatted += `**Results:**\n`;
404
+ for (const [providerId, status] of Object.entries(result.results)) {
405
+ const icon = status.available ? '🟢' : '🔴';
406
+ const authIcon = status.authenticated ? '🔐' : '🔓';
407
+ formatted += `- ${icon} ${authIcon} **${providerId}**: ${status.available ? 'Available' : 'Not Available'}`;
408
+ if (status.version) formatted += ` (${status.version})`;
409
+ if (status.error) formatted += ` - ${status.error}`;
410
+ formatted += `\n`;
411
+ }
412
+ }
413
+
414
+ formatted += `\n*${result.local_only ? 'Local execution' : 'Remote execution'} | ${result.timestamp}*`;
415
+ return formatted;
416
+ }
417
+ }
418
+
419
+ async start() {
420
+ console.log('Starting Polydev Stdio MCP Wrapper...');
421
+
422
+ process.stdin.setEncoding('utf8');
423
+ let buffer = '';
424
+
425
+ process.stdin.on('data', async (chunk) => {
426
+ buffer += chunk;
427
+
428
+ const lines = buffer.split('\n');
429
+ buffer = lines.pop() || '';
430
+
431
+ for (const line of lines) {
432
+ if (line.trim()) {
433
+ try {
434
+ const request = JSON.parse(line);
435
+ const response = await this.handleRequest(request);
436
+ process.stdout.write(JSON.stringify(response) + '\n');
437
+ } catch (error) {
438
+ console.error('Failed to process request:', error);
439
+ }
440
+ }
441
+ }
442
+ });
443
+
444
+ process.stdin.on('end', () => {
445
+ console.log('Stdio MCP Wrapper shutting down...');
446
+ process.exit(0);
447
+ });
448
+
449
+ // Handle process signals
450
+ process.on('SIGINT', () => {
451
+ console.log('Received SIGINT, shutting down...');
452
+ process.exit(0);
453
+ });
454
+
455
+ process.on('SIGTERM', () => {
456
+ console.log('Received SIGTERM, shutting down...');
457
+ process.exit(0);
458
+ });
459
+
460
+ console.log('Stdio MCP Wrapper ready and listening on stdin...');
461
+ }
462
+ }
463
+
464
+ if (require.main === module) {
465
+ const wrapper = new StdioMCPWrapper();
466
+ wrapper.start().catch(error => {
467
+ console.error('Failed to start wrapper:', error);
468
+ process.exit(1);
469
+ });
470
+ }
471
+
472
+ module.exports = StdioMCPWrapper;
package/package.json ADDED
@@ -0,0 +1,83 @@
1
+ {
2
+ "name": "polydev-ai",
3
+ "version": "1.0.0",
4
+ "description": "Agentic workflow assistant with CLI integration - get diverse perspectives from multiple LLMs when stuck or need enhanced reasoning",
5
+ "keywords": [
6
+ "mcp",
7
+ "model-context-protocol",
8
+ "ai",
9
+ "claude",
10
+ "openai",
11
+ "perspectives",
12
+ "cli",
13
+ "agent",
14
+ "assistant"
15
+ ],
16
+ "author": "Polydev AI",
17
+ "license": "MIT",
18
+ "private": false,
19
+ "main": "mcp/server.js",
20
+ "bin": {
21
+ "polydev-stdio": "mcp/stdio-wrapper.js"
22
+ },
23
+ "files": [
24
+ "mcp/",
25
+ "lib/",
26
+ "README.md",
27
+ "LICENSE"
28
+ ],
29
+ "scripts": {
30
+ "dev": "next dev",
31
+ "build": "next build",
32
+ "start": "next start",
33
+ "lint": "next lint",
34
+ "mcp": "node mcp/server.js",
35
+ "mcp-stdio": "node mcp/stdio-wrapper.js",
36
+ "cli-detect": "node -e \"const CLIManager = require('./lib/cliManager').default; const m = new CLIManager(); m.forceCliDetection().then(console.log);\""
37
+ },
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "https://github.com/polydev-ai/perspectives-mcp.git"
41
+ },
42
+ "homepage": "https://polydev.ai",
43
+ "bugs": {
44
+ "url": "https://github.com/polydev-ai/perspectives-mcp/issues"
45
+ },
46
+ "dependencies": {
47
+ "@hello-pangea/dnd": "^18.0.1",
48
+ "@radix-ui/react-dialog": "^1.1.15",
49
+ "@radix-ui/react-progress": "^1.1.7",
50
+ "@radix-ui/react-slot": "^1.2.3",
51
+ "@radix-ui/react-tabs": "^1.1.13",
52
+ "@supabase/ssr": "^0.4.0",
53
+ "@supabase/supabase-js": "^2.45.0",
54
+ "@upstash/redis": "^1.34.0",
55
+ "class-variance-authority": "^0.7.1",
56
+ "clsx": "^2.1.1",
57
+ "date-fns": "^4.1.0",
58
+ "dotenv": "^17.2.2",
59
+ "lucide-react": "^0.542.0",
60
+ "marked": "^16.2.1",
61
+ "next": "15.0.0",
62
+ "posthog-js": "^1.157.2",
63
+ "react": "^18.3.1",
64
+ "react-dom": "^18.3.1",
65
+ "resend": "^6.0.2",
66
+ "shelljs": "^0.8.5",
67
+ "sonner": "^2.0.7",
68
+ "stripe": "^18.5.0",
69
+ "tailwind-merge": "^3.3.1",
70
+ "which": "^5.0.0"
71
+ },
72
+ "devDependencies": {
73
+ "@types/node": "^20.16.1",
74
+ "@types/react": "^18.3.3",
75
+ "@types/react-dom": "^18.3.0",
76
+ "autoprefixer": "^10.4.20",
77
+ "eslint": "^8.57.0",
78
+ "eslint-config-next": "15.0.0",
79
+ "postcss": "^8.4.41",
80
+ "tailwindcss": "^3.4.10",
81
+ "typescript": "^5.5.4"
82
+ }
83
+ }