polydev-ai 1.8.74 → 1.8.76
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/mcp/login.js +397 -120
- package/mcp/manifest.json +1 -1
- package/mcp/stdio-wrapper.js +537 -28
- package/package.json +1 -1
package/mcp/login.js
CHANGED
|
@@ -1,169 +1,396 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Polydev CLI
|
|
4
|
+
* Polydev CLI - Clean, professional onboarding
|
|
5
|
+
* Works in: Claude Code, Cursor, Cline, VS Code, any terminal
|
|
5
6
|
*
|
|
6
|
-
* Usage:
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* 3. After authentication, receives the token via callback
|
|
12
|
-
* 4. Saves the token to your shell config (~/.zshrc or ~/.bashrc)
|
|
7
|
+
* Usage:
|
|
8
|
+
* npx polydev-ai # Full setup
|
|
9
|
+
* npx polydev-ai login # Just login
|
|
10
|
+
* npx polydev-ai status # Show status
|
|
11
|
+
* npx polydev-ai models # Open models page
|
|
13
12
|
*/
|
|
14
13
|
|
|
15
14
|
const fs = require('fs');
|
|
16
15
|
const path = require('path');
|
|
17
16
|
const os = require('os');
|
|
18
17
|
const http = require('http');
|
|
19
|
-
const { execFile } = require('child_process');
|
|
18
|
+
const { execFile, exec } = require('child_process');
|
|
19
|
+
const { promisify } = require('util');
|
|
20
|
+
|
|
21
|
+
const execAsync = promisify(exec);
|
|
22
|
+
|
|
23
|
+
// CLI tool definitions
|
|
24
|
+
const CLI_TOOLS = [
|
|
25
|
+
{ id: 'claude_code', name: 'Claude Code', command: 'claude', desc: 'Anthropic' },
|
|
26
|
+
{ id: 'codex_cli', name: 'Codex CLI', command: 'codex', desc: 'OpenAI' },
|
|
27
|
+
{ id: 'gemini_cli', name: 'Gemini CLI', command: 'gemini', desc: 'Google' },
|
|
28
|
+
{ id: 'aider', name: 'Aider', command: 'aider', desc: 'AI Pair Programming' }
|
|
29
|
+
];
|
|
20
30
|
|
|
21
31
|
const command = process.argv[2];
|
|
22
32
|
|
|
33
|
+
// Route commands
|
|
23
34
|
if (command === 'status') {
|
|
24
|
-
|
|
35
|
+
showFullStatus();
|
|
36
|
+
} else if (command === 'login') {
|
|
37
|
+
runLogin();
|
|
38
|
+
} else if (command === 'setup') {
|
|
39
|
+
runFullSetup();
|
|
25
40
|
} else if (command === 'help' || command === '--help' || command === '-h') {
|
|
26
41
|
showHelp();
|
|
42
|
+
} else if (command === 'models') {
|
|
43
|
+
openModelsPage();
|
|
27
44
|
} else {
|
|
28
|
-
|
|
45
|
+
smartOnboarding();
|
|
29
46
|
}
|
|
30
47
|
|
|
31
48
|
function showHelp() {
|
|
32
49
|
console.log(`
|
|
33
|
-
Polydev AI
|
|
50
|
+
Polydev CLI - Multi-model AI perspectives
|
|
34
51
|
|
|
35
52
|
Commands:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
npx polydev-ai login # Opens browser to authenticate
|
|
42
|
-
npx polydev-ai status # Check if authenticated
|
|
53
|
+
npx polydev-ai Full setup (login + detect tools)
|
|
54
|
+
npx polydev-ai login Authenticate via browser
|
|
55
|
+
npx polydev-ai status Show current status
|
|
56
|
+
npx polydev-ai models Open models configuration
|
|
57
|
+
npx polydev-ai help Show this help
|
|
43
58
|
|
|
44
59
|
After login, restart your terminal or run: source ~/.zshrc
|
|
60
|
+
|
|
61
|
+
Links:
|
|
62
|
+
Dashboard: https://polydev.ai/dashboard
|
|
63
|
+
Docs: https://polydev.ai/docs
|
|
45
64
|
`);
|
|
46
65
|
}
|
|
47
66
|
|
|
48
|
-
function
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const token = process.env.POLYDEV_USER_TOKEN;
|
|
53
|
-
if (token && token.startsWith('pd_')) {
|
|
54
|
-
console.log('✓ Authenticated with Polydev');
|
|
55
|
-
console.log(` Token: ${token.slice(0, 12)}...${token.slice(-4)}`);
|
|
67
|
+
async function smartOnboarding() {
|
|
68
|
+
const token = getExistingToken();
|
|
69
|
+
if (token) {
|
|
70
|
+
await showFullStatus();
|
|
56
71
|
} else {
|
|
57
|
-
|
|
58
|
-
console.log(' Run: npx polydev-ai login');
|
|
72
|
+
await runFullSetup();
|
|
59
73
|
}
|
|
60
74
|
}
|
|
61
75
|
|
|
62
|
-
function
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
76
|
+
async function runFullSetup() {
|
|
77
|
+
printHeader();
|
|
78
|
+
|
|
79
|
+
// Step 1: Authentication
|
|
80
|
+
console.log('STEP 1 of 3: Authentication');
|
|
81
|
+
console.log('─'.repeat(40));
|
|
82
|
+
|
|
83
|
+
let token = getExistingToken();
|
|
84
|
+
|
|
85
|
+
if (token) {
|
|
86
|
+
console.log('[OK] Already authenticated');
|
|
87
|
+
console.log(` Token: ${token.slice(0, 12)}...${token.slice(-4)}\n`);
|
|
88
|
+
} else {
|
|
89
|
+
console.log('[..] Not authenticated - opening browser...\n');
|
|
90
|
+
token = await runLoginFlow();
|
|
91
|
+
|
|
92
|
+
if (!token) {
|
|
93
|
+
console.log('\n[!] Login cancelled or timed out.');
|
|
94
|
+
console.log(' Run: npx polydev-ai login\n');
|
|
95
|
+
return;
|
|
69
96
|
}
|
|
70
|
-
} catch (e) {
|
|
71
|
-
// ignore
|
|
72
97
|
}
|
|
98
|
+
|
|
99
|
+
// Step 2: Detect CLI tools
|
|
100
|
+
console.log('\nSTEP 2 of 3: Local AI Tools');
|
|
101
|
+
console.log('─'.repeat(40));
|
|
102
|
+
|
|
103
|
+
const cliStatus = await detectAllCLIs();
|
|
104
|
+
printCLIStatus(cliStatus);
|
|
105
|
+
|
|
106
|
+
// Step 3: Verify API
|
|
107
|
+
console.log('\nSTEP 3 of 3: API Connection');
|
|
108
|
+
console.log('─'.repeat(40));
|
|
109
|
+
|
|
110
|
+
const apiStatus = await verifyAPIConnection(token);
|
|
111
|
+
printAPIStatus(apiStatus);
|
|
112
|
+
|
|
113
|
+
// Summary
|
|
114
|
+
printSetupComplete(token, cliStatus, apiStatus);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function printHeader() {
|
|
118
|
+
console.log(`
|
|
119
|
+
___ ____ _ _ ___ ____ _ _
|
|
120
|
+
|__] | | | | | | |__ | |
|
|
121
|
+
| |__| |__ | | |___| \\/
|
|
122
|
+
|
|
123
|
+
Multi-model AI: GPT-5 | Claude | Gemini | Grok
|
|
124
|
+
${'─'.repeat(50)}
|
|
125
|
+
`);
|
|
73
126
|
}
|
|
74
127
|
|
|
75
128
|
async function runLogin() {
|
|
76
|
-
console.log('\
|
|
77
|
-
|
|
78
|
-
const server = http.createServer((req, res) => {
|
|
79
|
-
const url = new URL(req.url, `http://localhost`);
|
|
80
|
-
|
|
81
|
-
if (req.method === 'OPTIONS') {
|
|
82
|
-
res.writeHead(204, {
|
|
83
|
-
'Access-Control-Allow-Origin': '*',
|
|
84
|
-
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
85
|
-
'Access-Control-Allow-Headers': 'Content-Type'
|
|
86
|
-
});
|
|
87
|
-
res.end();
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
129
|
+
console.log('\nPolydev Authentication\n');
|
|
130
|
+
const token = await runLoginFlow();
|
|
90
131
|
|
|
91
|
-
|
|
92
|
-
|
|
132
|
+
if (token) {
|
|
133
|
+
console.log('\n[OK] Authentication successful!');
|
|
134
|
+
console.log(' Restart your terminal or run: source ~/.zshrc\n');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
93
137
|
|
|
94
|
-
|
|
95
|
-
|
|
138
|
+
function runLoginFlow() {
|
|
139
|
+
return new Promise((resolve) => {
|
|
140
|
+
const server = http.createServer((req, res) => {
|
|
141
|
+
const url = new URL(req.url, `http://localhost`);
|
|
96
142
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
'Access-Control-Allow-Origin': '*'
|
|
143
|
+
if (req.method === 'OPTIONS') {
|
|
144
|
+
res.writeHead(204, {
|
|
145
|
+
'Access-Control-Allow-Origin': '*',
|
|
146
|
+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
147
|
+
'Access-Control-Allow-Headers': 'Content-Type'
|
|
100
148
|
});
|
|
101
|
-
res.end(
|
|
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
|
-
|
|
149
|
+
res.end();
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (url.pathname === '/callback') {
|
|
154
|
+
const token = url.searchParams.get('token');
|
|
155
|
+
|
|
156
|
+
if (token && token.startsWith('pd_')) {
|
|
157
|
+
const savedTo = saveToken(token);
|
|
158
|
+
|
|
159
|
+
res.writeHead(200, {
|
|
160
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
161
|
+
'Access-Control-Allow-Origin': '*'
|
|
162
|
+
});
|
|
163
|
+
res.end(getSuccessHTML());
|
|
164
|
+
|
|
165
|
+
console.log(`[OK] Token saved to ${savedTo}`);
|
|
166
|
+
|
|
167
|
+
setTimeout(() => {
|
|
168
|
+
server.close();
|
|
169
|
+
resolve(token);
|
|
170
|
+
}, 500);
|
|
171
|
+
} else {
|
|
172
|
+
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
173
|
+
res.end('Invalid or missing token');
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
res.writeHead(404);
|
|
177
|
+
res.end('Not found');
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
server.listen(0, 'localhost', () => {
|
|
182
|
+
const port = server.address().port;
|
|
183
|
+
const callbackUrl = `http://localhost:${port}/callback`;
|
|
184
|
+
const authUrl = `https://polydev.ai/auth/cli?callback=${encodeURIComponent(callbackUrl)}&redirect=claude-code`;
|
|
185
|
+
|
|
186
|
+
console.log('Opening browser for authentication...');
|
|
187
|
+
console.log(`If browser doesn\'t open, visit:\n${authUrl}\n`);
|
|
127
188
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
189
|
+
openBrowser(authUrl);
|
|
190
|
+
|
|
191
|
+
setTimeout(() => {
|
|
192
|
+
server.close();
|
|
193
|
+
resolve(null);
|
|
194
|
+
}, 5 * 60 * 1000);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
server.on('error', (err) => {
|
|
198
|
+
console.error('[!] Server error:', err.message);
|
|
199
|
+
resolve(null);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
}
|
|
131
203
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
204
|
+
async function detectAllCLIs() {
|
|
205
|
+
const results = {};
|
|
206
|
+
|
|
207
|
+
for (const tool of CLI_TOOLS) {
|
|
208
|
+
try {
|
|
209
|
+
const { stdout } = await execAsync(`which ${tool.command} 2>/dev/null`);
|
|
210
|
+
const cliPath = stdout.trim();
|
|
211
|
+
|
|
212
|
+
if (cliPath) {
|
|
213
|
+
let version = 'installed';
|
|
214
|
+
try {
|
|
215
|
+
const { stdout: versionOut } = await execAsync(`${tool.command} --version 2>/dev/null || ${tool.command} -v 2>/dev/null`);
|
|
216
|
+
version = versionOut.trim().split('\n')[0].substring(0, 30);
|
|
217
|
+
} catch (e) {}
|
|
218
|
+
|
|
219
|
+
let authenticated = false;
|
|
220
|
+
try {
|
|
221
|
+
if (tool.id === 'claude_code') {
|
|
222
|
+
const { stdout: authOut } = await execAsync(`${tool.command} auth status 2>/dev/null || echo "not"`);
|
|
223
|
+
authenticated = !authOut.toLowerCase().includes('not');
|
|
224
|
+
} else if (tool.id === 'codex_cli') {
|
|
225
|
+
authenticated = !!process.env.OPENAI_API_KEY;
|
|
226
|
+
} else if (tool.id === 'gemini_cli') {
|
|
227
|
+
authenticated = !!process.env.GOOGLE_API_KEY;
|
|
228
|
+
}
|
|
229
|
+
} catch (e) {}
|
|
230
|
+
|
|
231
|
+
results[tool.id] = { available: true, authenticated, version, path: cliPath, tool };
|
|
232
|
+
console.log(`[OK] ${tool.name.padEnd(12)} ${version}`);
|
|
136
233
|
} else {
|
|
137
|
-
|
|
138
|
-
res.end('Invalid or missing token');
|
|
234
|
+
throw new Error('Not found');
|
|
139
235
|
}
|
|
236
|
+
} catch (e) {
|
|
237
|
+
results[tool.id] = { available: false, authenticated: false, tool };
|
|
238
|
+
console.log(`[--] ${tool.name.padEnd(12)} not installed`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return results;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function printCLIStatus(cliStatus) {
|
|
246
|
+
const available = Object.values(cliStatus).filter(s => s.available);
|
|
247
|
+
const authenticated = available.filter(s => s.authenticated);
|
|
248
|
+
|
|
249
|
+
if (available.length === 0) {
|
|
250
|
+
console.log('\n No local CLI tools detected.');
|
|
251
|
+
console.log(' Install Claude Code or Codex CLI to use local tools (free).');
|
|
252
|
+
} else {
|
|
253
|
+
console.log(`\n ${available.length} tool(s) found, ${authenticated.length} authenticated`);
|
|
254
|
+
console.log(' Local tools = FREE (no API credits used)');
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function verifyAPIConnection(token) {
|
|
259
|
+
try {
|
|
260
|
+
const response = await fetch('https://www.polydev.ai/api/mcp', {
|
|
261
|
+
method: 'POST',
|
|
262
|
+
headers: {
|
|
263
|
+
'Content-Type': 'application/json',
|
|
264
|
+
'Authorization': `Bearer ${token}`,
|
|
265
|
+
'User-Agent': 'polydev-cli/1.0.0'
|
|
266
|
+
},
|
|
267
|
+
body: JSON.stringify({ action: 'check_status' })
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
if (response.ok) {
|
|
271
|
+
const data = await response.json();
|
|
272
|
+
return {
|
|
273
|
+
connected: true,
|
|
274
|
+
email: data.email,
|
|
275
|
+
credits: data.credits_remaining,
|
|
276
|
+
tier: data.subscription_tier || 'Free',
|
|
277
|
+
models: data.enabled_models || ['GPT-5', 'Gemini', 'Grok', 'GLM']
|
|
278
|
+
};
|
|
140
279
|
} else {
|
|
141
|
-
|
|
142
|
-
res.end('Not found');
|
|
280
|
+
return { connected: false, error: 'Invalid token' };
|
|
143
281
|
}
|
|
144
|
-
})
|
|
282
|
+
} catch (error) {
|
|
283
|
+
return { connected: false, error: error.message };
|
|
284
|
+
}
|
|
285
|
+
}
|
|
145
286
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
287
|
+
function printAPIStatus(apiStatus) {
|
|
288
|
+
if (apiStatus.connected) {
|
|
289
|
+
console.log(`[OK] Connected to Polydev API`);
|
|
290
|
+
console.log(` Account: ${apiStatus.email || 'Connected'}`);
|
|
291
|
+
console.log(` Credits: ${apiStatus.credits?.toLocaleString() || 0} | Tier: ${apiStatus.tier}`);
|
|
292
|
+
} else {
|
|
293
|
+
console.log(`[!] Could not verify API connection`);
|
|
294
|
+
console.log(` Error: ${apiStatus.error}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
150
297
|
|
|
151
|
-
|
|
152
|
-
|
|
298
|
+
function printSetupComplete(token, cliStatus, apiStatus) {
|
|
299
|
+
const availableCLIs = Object.values(cliStatus).filter(s => s.available);
|
|
153
300
|
|
|
154
|
-
|
|
301
|
+
console.log(`
|
|
302
|
+
${'═'.repeat(50)}
|
|
303
|
+
SETUP COMPLETE
|
|
304
|
+
${'═'.repeat(50)}
|
|
155
305
|
|
|
156
|
-
|
|
157
|
-
console.log('\n✗ Login timed out. Please try again.\n');
|
|
158
|
-
server.close();
|
|
159
|
-
process.exit(1);
|
|
160
|
-
}, 5 * 60 * 1000);
|
|
161
|
-
});
|
|
306
|
+
What's ready:`);
|
|
162
307
|
|
|
163
|
-
|
|
164
|
-
console.
|
|
165
|
-
|
|
166
|
-
|
|
308
|
+
if (availableCLIs.length > 0) {
|
|
309
|
+
console.log('\n Local Tools (FREE):');
|
|
310
|
+
for (const cli of availableCLIs) {
|
|
311
|
+
const status = cli.authenticated ? 'ready' : 'needs auth';
|
|
312
|
+
console.log(` - ${cli.tool.name} [${status}]`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (apiStatus.connected) {
|
|
317
|
+
console.log(`\n Polydev API (${apiStatus.credits?.toLocaleString() || 0} credits):`);
|
|
318
|
+
console.log(' - GPT-5, Claude, Gemini, Grok in parallel');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
console.log(`
|
|
322
|
+
Next steps:
|
|
323
|
+
|
|
324
|
+
1. Restart terminal or run: source ~/.zshrc
|
|
325
|
+
2. Start your IDE (Claude Code, Cursor, Cline, etc.)
|
|
326
|
+
3. Use "get_perspectives" tool or /polydev command
|
|
327
|
+
|
|
328
|
+
Links:
|
|
329
|
+
Dashboard: https://polydev.ai/dashboard
|
|
330
|
+
Models: https://polydev.ai/dashboard/models
|
|
331
|
+
`);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async function showFullStatus() {
|
|
335
|
+
console.log('\nPolydev Status');
|
|
336
|
+
console.log('─'.repeat(40));
|
|
337
|
+
|
|
338
|
+
const token = getExistingToken();
|
|
339
|
+
|
|
340
|
+
console.log('\nAuthentication:');
|
|
341
|
+
if (token) {
|
|
342
|
+
const apiStatus = await verifyAPIConnection(token);
|
|
343
|
+
if (apiStatus.connected) {
|
|
344
|
+
console.log(` [OK] ${apiStatus.email || 'Authenticated'}`);
|
|
345
|
+
console.log(` Credits: ${apiStatus.credits?.toLocaleString() || 0} | Tier: ${apiStatus.tier}`);
|
|
346
|
+
} else {
|
|
347
|
+
console.log(' [!] Token found but invalid');
|
|
348
|
+
console.log(' Run: npx polydev-ai login');
|
|
349
|
+
}
|
|
350
|
+
} else {
|
|
351
|
+
console.log(' [--] Not authenticated');
|
|
352
|
+
console.log(' Run: npx polydev-ai login');
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
console.log('\nLocal AI Tools (free):');
|
|
356
|
+
await detectAllCLIs();
|
|
357
|
+
|
|
358
|
+
if (token) {
|
|
359
|
+
console.log('\nAPI Models (uses credits):');
|
|
360
|
+
console.log(' GPT-5, Gemini, Grok, GLM - query in parallel');
|
|
361
|
+
console.log(' Configure: https://polydev.ai/dashboard/models');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
console.log('');
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function openModelsPage() {
|
|
368
|
+
const url = 'https://polydev.ai/dashboard/models';
|
|
369
|
+
console.log(`\nOpening: ${url}\n`);
|
|
370
|
+
openBrowser(url);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Utility functions
|
|
374
|
+
function getExistingToken() {
|
|
375
|
+
if (process.env.POLYDEV_USER_TOKEN) {
|
|
376
|
+
return process.env.POLYDEV_USER_TOKEN;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const locations = [
|
|
380
|
+
path.join(os.homedir(), '.polydev.env'),
|
|
381
|
+
path.join(os.homedir(), '.zshrc'),
|
|
382
|
+
path.join(os.homedir(), '.bashrc')
|
|
383
|
+
];
|
|
384
|
+
|
|
385
|
+
for (const loc of locations) {
|
|
386
|
+
if (fs.existsSync(loc)) {
|
|
387
|
+
const content = fs.readFileSync(loc, 'utf8');
|
|
388
|
+
const match = content.match(/POLYDEV_USER_TOKEN[=\s]["']?([^"'\n]+)["']?/);
|
|
389
|
+
if (match) return match[1];
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return null;
|
|
167
394
|
}
|
|
168
395
|
|
|
169
396
|
function saveToken(token) {
|
|
@@ -173,9 +400,7 @@ function saveToken(token) {
|
|
|
173
400
|
path.join(os.homedir(), '.profile');
|
|
174
401
|
|
|
175
402
|
let content = '';
|
|
176
|
-
try {
|
|
177
|
-
content = fs.readFileSync(rcFile, 'utf8');
|
|
178
|
-
} catch (e) {}
|
|
403
|
+
try { content = fs.readFileSync(rcFile, 'utf8'); } catch (e) {}
|
|
179
404
|
|
|
180
405
|
const lines = content.split('\n').filter(line =>
|
|
181
406
|
!line.trim().startsWith('export POLYDEV_USER_TOKEN') &&
|
|
@@ -192,7 +417,6 @@ function saveToken(token) {
|
|
|
192
417
|
}
|
|
193
418
|
|
|
194
419
|
function openBrowser(url) {
|
|
195
|
-
// Use execFile with explicit command and arguments for safety
|
|
196
420
|
const platform = process.platform;
|
|
197
421
|
let cmd, args;
|
|
198
422
|
|
|
@@ -209,8 +433,61 @@ function openBrowser(url) {
|
|
|
209
433
|
|
|
210
434
|
execFile(cmd, args, (err) => {
|
|
211
435
|
if (err) {
|
|
212
|
-
console.log('Could not open browser automatically.');
|
|
213
|
-
console.log(`Please open
|
|
436
|
+
console.log('[!] Could not open browser automatically.');
|
|
437
|
+
console.log(` Please open: ${url}`);
|
|
214
438
|
}
|
|
215
439
|
});
|
|
216
440
|
}
|
|
441
|
+
|
|
442
|
+
function getSuccessHTML() {
|
|
443
|
+
return `<!DOCTYPE html>
|
|
444
|
+
<html>
|
|
445
|
+
<head>
|
|
446
|
+
<meta charset="utf-8">
|
|
447
|
+
<title>Polydev - Success</title>
|
|
448
|
+
<style>
|
|
449
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
450
|
+
body {
|
|
451
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
452
|
+
display: flex; justify-content: center; align-items: center;
|
|
453
|
+
height: 100vh; background: #f5f5f5;
|
|
454
|
+
}
|
|
455
|
+
.container {
|
|
456
|
+
text-align: center; padding: 48px; max-width: 400px;
|
|
457
|
+
background: white; border-radius: 8px;
|
|
458
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
459
|
+
}
|
|
460
|
+
.logo { font-size: 24px; font-weight: 700; color: #111; margin-bottom: 24px; letter-spacing: -0.5px; }
|
|
461
|
+
.check {
|
|
462
|
+
width: 48px; height: 48px; border: 3px solid #111; border-radius: 50%;
|
|
463
|
+
display: flex; align-items: center; justify-content: center;
|
|
464
|
+
margin: 0 auto 24px; font-size: 24px;
|
|
465
|
+
}
|
|
466
|
+
h1 { color: #111; font-size: 20px; font-weight: 600; margin-bottom: 12px; }
|
|
467
|
+
p { color: #666; font-size: 14px; line-height: 1.6; margin-bottom: 8px; }
|
|
468
|
+
.steps {
|
|
469
|
+
background: #f9f9f9; border-radius: 6px; padding: 16px;
|
|
470
|
+
margin: 20px 0; text-align: left;
|
|
471
|
+
}
|
|
472
|
+
.step { color: #444; font-size: 13px; margin-bottom: 8px; }
|
|
473
|
+
.step:last-child { margin-bottom: 0; }
|
|
474
|
+
code { background: #eee; padding: 2px 6px; border-radius: 3px; font-size: 12px; }
|
|
475
|
+
.close { color: #999; font-size: 12px; margin-top: 20px; }
|
|
476
|
+
</style>
|
|
477
|
+
</head>
|
|
478
|
+
<body>
|
|
479
|
+
<div class="container">
|
|
480
|
+
<div class="logo">Polydev</div>
|
|
481
|
+
<div class="check">✓</div>
|
|
482
|
+
<h1>Authentication Complete</h1>
|
|
483
|
+
<p>Your token has been saved automatically.</p>
|
|
484
|
+
<div class="steps">
|
|
485
|
+
<div class="step">1. Restart terminal or run <code>source ~/.zshrc</code></div>
|
|
486
|
+
<div class="step">2. Start your IDE (Claude Code, Cursor, etc.)</div>
|
|
487
|
+
<div class="step">3. Use the <code>get_perspectives</code> tool</div>
|
|
488
|
+
</div>
|
|
489
|
+
<p class="close">You can close this window.</p>
|
|
490
|
+
</div>
|
|
491
|
+
</body>
|
|
492
|
+
</html>`;
|
|
493
|
+
}
|
package/mcp/manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polydev-perspectives",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.76",
|
|
4
4
|
"description": "Multi-model AI perspectives - query GPT 5.2, Claude Opus 4.5, Gemini 3, and Grok 4.1 simultaneously. Get diverse perspectives when stuck or need enhanced reasoning. Achieved 74.6% on SWE-bench Verified.",
|
|
5
5
|
"author": "Polydev AI",
|
|
6
6
|
"license": "MIT",
|
package/mcp/stdio-wrapper.js
CHANGED
|
@@ -248,20 +248,7 @@ function cleanCliResponse(content) {
|
|
|
248
248
|
class StdioMCPWrapper {
|
|
249
249
|
constructor() {
|
|
250
250
|
this.userToken = process.env.POLYDEV_USER_TOKEN;
|
|
251
|
-
|
|
252
|
-
console.error('\n╔════════════════════════════════════════════════════════════════╗');
|
|
253
|
-
console.error('║ POLYDEV_USER_TOKEN is not set ║');
|
|
254
|
-
console.error('╠════════════════════════════════════════════════════════════════╣');
|
|
255
|
-
console.error('║ Get your free token at: ║');
|
|
256
|
-
console.error('║ 👉 https://polydev.ai/dashboard/mcp-tokens ║');
|
|
257
|
-
console.error('║ ║');
|
|
258
|
-
console.error('║ Then add to ~/.zshrc or ~/.bashrc: ║');
|
|
259
|
-
console.error('║ export POLYDEV_USER_TOKEN="pd_your_token_here" ║');
|
|
260
|
-
console.error('║ ║');
|
|
261
|
-
console.error('║ Restart your terminal and Claude Code after adding the token ║');
|
|
262
|
-
console.error('╚════════════════════════════════════════════════════════════════╝\n');
|
|
263
|
-
process.exit(1);
|
|
264
|
-
}
|
|
251
|
+
this.isAuthenticated = !!this.userToken;
|
|
265
252
|
|
|
266
253
|
// Server URL for API calls
|
|
267
254
|
this.serverUrl = 'https://www.polydev.ai/api/mcp';
|
|
@@ -272,6 +259,14 @@ class StdioMCPWrapper {
|
|
|
272
259
|
// Load manifest for tool definitions
|
|
273
260
|
this.loadManifest();
|
|
274
261
|
|
|
262
|
+
// Log startup status
|
|
263
|
+
if (this.userToken) {
|
|
264
|
+
console.error('[Polydev] Starting with authentication token');
|
|
265
|
+
} else {
|
|
266
|
+
console.error('[Polydev] Starting without token - login required');
|
|
267
|
+
console.error('[Polydev] Use the "login" tool or run: npx polydev-ai');
|
|
268
|
+
}
|
|
269
|
+
|
|
275
270
|
// Smart refresh scheduler (will be started after initialization)
|
|
276
271
|
this.refreshScheduler = null;
|
|
277
272
|
|
|
@@ -287,6 +282,9 @@ class StdioMCPWrapper {
|
|
|
287
282
|
this.perspectivesPerMessage = 2; // Default to 2 perspectives
|
|
288
283
|
this.modelPreferencesCacheTime = null;
|
|
289
284
|
this.MODEL_PREFERENCES_CACHE_TTL = 5 * 60 * 1000; // 5 minutes cache
|
|
285
|
+
|
|
286
|
+
// Login server reference (for cleanup)
|
|
287
|
+
this._loginServer = null;
|
|
290
288
|
}
|
|
291
289
|
|
|
292
290
|
loadManifest() {
|
|
@@ -332,9 +330,36 @@ class StdioMCPWrapper {
|
|
|
332
330
|
};
|
|
333
331
|
|
|
334
332
|
case 'tools/call':
|
|
335
|
-
// Handle get_perspectives with local CLIs + remote perspectives
|
|
336
333
|
const toolName = params.name;
|
|
337
334
|
|
|
335
|
+
// These tools work without authentication
|
|
336
|
+
const noAuthTools = ['login', 'get_auth_status', 'polydev.login', 'polydev.get_auth_status'];
|
|
337
|
+
|
|
338
|
+
if (noAuthTools.includes(toolName)) {
|
|
339
|
+
if (toolName === 'login' || toolName === 'polydev.login') {
|
|
340
|
+
return await this.handleLoginTool(params, id);
|
|
341
|
+
}
|
|
342
|
+
if (toolName === 'get_auth_status' || toolName === 'polydev.get_auth_status') {
|
|
343
|
+
return await this.handleGetAuthStatus(params, id);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// All other tools require authentication
|
|
348
|
+
if (!this.isAuthenticated) {
|
|
349
|
+
return {
|
|
350
|
+
jsonrpc: '2.0',
|
|
351
|
+
id,
|
|
352
|
+
result: {
|
|
353
|
+
content: [{
|
|
354
|
+
type: 'text',
|
|
355
|
+
text: `Authentication required.\n\nTo login, either:\n1. Use the "login" tool (opens browser)\n2. Run in terminal: npx polydev-ai\n\nAfter login, restart your IDE.`
|
|
356
|
+
}],
|
|
357
|
+
isError: true
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Handle get_perspectives with local CLIs + remote perspectives
|
|
338
363
|
if (toolName === 'get_perspectives' || toolName === 'polydev.get_perspectives') {
|
|
339
364
|
return await this.handleGetPerspectivesWithCLIs(params, id);
|
|
340
365
|
}
|
|
@@ -370,6 +395,277 @@ class StdioMCPWrapper {
|
|
|
370
395
|
}
|
|
371
396
|
}
|
|
372
397
|
|
|
398
|
+
/**
|
|
399
|
+
* Handle login tool - opens browser for authentication
|
|
400
|
+
*/
|
|
401
|
+
async handleLoginTool(params, id) {
|
|
402
|
+
const http = require('http');
|
|
403
|
+
const { execFile } = require('child_process');
|
|
404
|
+
|
|
405
|
+
return new Promise((resolve) => {
|
|
406
|
+
// Check if already authenticated
|
|
407
|
+
if (this.isAuthenticated && this.userToken) {
|
|
408
|
+
resolve({
|
|
409
|
+
jsonrpc: '2.0',
|
|
410
|
+
id,
|
|
411
|
+
result: {
|
|
412
|
+
content: [{
|
|
413
|
+
type: 'text',
|
|
414
|
+
text: `Already authenticated.\n\nYour token is configured. Use get_perspectives to query multiple AI models.\n\nTo re-login, run in terminal: npx polydev-ai login`
|
|
415
|
+
}]
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const server = http.createServer((req, res) => {
|
|
422
|
+
const url = new URL(req.url, `http://localhost`);
|
|
423
|
+
|
|
424
|
+
if (req.method === 'OPTIONS') {
|
|
425
|
+
res.writeHead(204, {
|
|
426
|
+
'Access-Control-Allow-Origin': '*',
|
|
427
|
+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
428
|
+
'Access-Control-Allow-Headers': 'Content-Type'
|
|
429
|
+
});
|
|
430
|
+
res.end();
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (url.pathname === '/callback') {
|
|
435
|
+
const token = url.searchParams.get('token');
|
|
436
|
+
|
|
437
|
+
if (token && token.startsWith('pd_')) {
|
|
438
|
+
// Save token
|
|
439
|
+
this.saveTokenToFiles(token);
|
|
440
|
+
this.userToken = token;
|
|
441
|
+
this.isAuthenticated = true;
|
|
442
|
+
|
|
443
|
+
res.writeHead(200, {
|
|
444
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
445
|
+
'Access-Control-Allow-Origin': '*'
|
|
446
|
+
});
|
|
447
|
+
res.end(this.getLoginSuccessHTML());
|
|
448
|
+
|
|
449
|
+
console.error('[Polydev] Login successful, token saved');
|
|
450
|
+
|
|
451
|
+
setTimeout(() => {
|
|
452
|
+
server.close();
|
|
453
|
+
resolve({
|
|
454
|
+
jsonrpc: '2.0',
|
|
455
|
+
id,
|
|
456
|
+
result: {
|
|
457
|
+
content: [{
|
|
458
|
+
type: 'text',
|
|
459
|
+
text: `Login successful!\n\nYour token has been saved to ~/.zshrc and ~/.polydev.env\n\nIMPORTANT: Restart your IDE to use the new token.\n\nAfter restart, use get_perspectives to query GPT-5, Claude, Gemini, and Grok in parallel.`
|
|
460
|
+
}]
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
}, 500);
|
|
464
|
+
} else {
|
|
465
|
+
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
466
|
+
res.end('Invalid or missing token');
|
|
467
|
+
}
|
|
468
|
+
} else {
|
|
469
|
+
res.writeHead(404);
|
|
470
|
+
res.end('Not found');
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
server.listen(0, 'localhost', () => {
|
|
475
|
+
const port = server.address().port;
|
|
476
|
+
const callbackUrl = `http://localhost:${port}/callback`;
|
|
477
|
+
const authUrl = `https://polydev.ai/auth/cli?callback=${encodeURIComponent(callbackUrl)}&redirect=ide-plugin`;
|
|
478
|
+
|
|
479
|
+
console.error(`[Polydev] Opening browser for authentication: ${authUrl}`);
|
|
480
|
+
|
|
481
|
+
// Open browser
|
|
482
|
+
const platform = process.platform;
|
|
483
|
+
let cmd, args;
|
|
484
|
+
if (platform === 'darwin') {
|
|
485
|
+
cmd = 'open';
|
|
486
|
+
args = [authUrl];
|
|
487
|
+
} else if (platform === 'win32') {
|
|
488
|
+
cmd = 'cmd';
|
|
489
|
+
args = ['/c', 'start', '', authUrl];
|
|
490
|
+
} else {
|
|
491
|
+
cmd = 'xdg-open';
|
|
492
|
+
args = [authUrl];
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
execFile(cmd, args, (err) => {
|
|
496
|
+
if (err) {
|
|
497
|
+
console.error('[Polydev] Could not open browser:', err.message);
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
// Timeout after 5 minutes
|
|
502
|
+
setTimeout(() => {
|
|
503
|
+
server.close();
|
|
504
|
+
resolve({
|
|
505
|
+
jsonrpc: '2.0',
|
|
506
|
+
id,
|
|
507
|
+
result: {
|
|
508
|
+
content: [{
|
|
509
|
+
type: 'text',
|
|
510
|
+
text: `Login timed out.\n\nPlease try again or run in terminal: npx polydev-ai`
|
|
511
|
+
}],
|
|
512
|
+
isError: true
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
}, 5 * 60 * 1000);
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
server.on('error', (err) => {
|
|
519
|
+
console.error('[Polydev] Login server error:', err.message);
|
|
520
|
+
resolve({
|
|
521
|
+
jsonrpc: '2.0',
|
|
522
|
+
id,
|
|
523
|
+
result: {
|
|
524
|
+
content: [{
|
|
525
|
+
type: 'text',
|
|
526
|
+
text: `Login failed: ${err.message}\n\nPlease run in terminal: npx polydev-ai`
|
|
527
|
+
}],
|
|
528
|
+
isError: true
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Handle get_auth_status tool
|
|
537
|
+
*/
|
|
538
|
+
async handleGetAuthStatus(params, id) {
|
|
539
|
+
if (!this.isAuthenticated || !this.userToken) {
|
|
540
|
+
return {
|
|
541
|
+
jsonrpc: '2.0',
|
|
542
|
+
id,
|
|
543
|
+
result: {
|
|
544
|
+
content: [{
|
|
545
|
+
type: 'text',
|
|
546
|
+
text: `Not authenticated.\n\nTo login:\n1. Use the "login" tool (opens browser)\n2. Or run in terminal: npx polydev-ai\n\nAfter login, restart your IDE.`
|
|
547
|
+
}]
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
try {
|
|
553
|
+
const response = await fetch('https://www.polydev.ai/api/mcp', {
|
|
554
|
+
method: 'POST',
|
|
555
|
+
headers: {
|
|
556
|
+
'Content-Type': 'application/json',
|
|
557
|
+
'Authorization': `Bearer ${this.userToken}`,
|
|
558
|
+
'User-Agent': 'polydev-stdio-wrapper/1.0.0'
|
|
559
|
+
},
|
|
560
|
+
body: JSON.stringify({ action: 'check_status' })
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
if (response.ok) {
|
|
564
|
+
const data = await response.json();
|
|
565
|
+
const credits = data.credits_remaining?.toLocaleString() || 0;
|
|
566
|
+
const tier = data.subscription_tier || 'Free';
|
|
567
|
+
|
|
568
|
+
return {
|
|
569
|
+
jsonrpc: '2.0',
|
|
570
|
+
id,
|
|
571
|
+
result: {
|
|
572
|
+
content: [{
|
|
573
|
+
type: 'text',
|
|
574
|
+
text: `Polydev Status\n${'─'.repeat(40)}\n\nAuthenticated: Yes\nAccount: ${data.email || 'Connected'}\nCredits: ${credits}\nTier: ${tier}\n\nAvailable tools:\n- get_perspectives (query multiple AI models)\n- get_cli_status (check local CLI tools)\n- send_cli_prompt (use local CLIs)\n\nDashboard: https://polydev.ai/dashboard`
|
|
575
|
+
}]
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
} else {
|
|
579
|
+
return {
|
|
580
|
+
jsonrpc: '2.0',
|
|
581
|
+
id,
|
|
582
|
+
result: {
|
|
583
|
+
content: [{
|
|
584
|
+
type: 'text',
|
|
585
|
+
text: `Token invalid or expired.\n\nPlease re-login:\n1. Use the "login" tool\n2. Or run: npx polydev-ai login`
|
|
586
|
+
}],
|
|
587
|
+
isError: true
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
} catch (error) {
|
|
592
|
+
return {
|
|
593
|
+
jsonrpc: '2.0',
|
|
594
|
+
id,
|
|
595
|
+
result: {
|
|
596
|
+
content: [{
|
|
597
|
+
type: 'text',
|
|
598
|
+
text: `Could not verify status (offline?)\n\nError: ${error.message}`
|
|
599
|
+
}],
|
|
600
|
+
isError: true
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Save token to shell config files
|
|
608
|
+
*/
|
|
609
|
+
saveTokenToFiles(token) {
|
|
610
|
+
const shell = process.env.SHELL || '/bin/zsh';
|
|
611
|
+
const rcFile = shell.includes('zsh') ? path.join(os.homedir(), '.zshrc') :
|
|
612
|
+
shell.includes('bash') ? path.join(os.homedir(), '.bashrc') :
|
|
613
|
+
path.join(os.homedir(), '.profile');
|
|
614
|
+
|
|
615
|
+
let content = '';
|
|
616
|
+
try { content = fs.readFileSync(rcFile, 'utf8'); } catch (e) {}
|
|
617
|
+
|
|
618
|
+
const lines = content.split('\n').filter(line =>
|
|
619
|
+
!line.trim().startsWith('export POLYDEV_USER_TOKEN') &&
|
|
620
|
+
!line.trim().startsWith('POLYDEV_USER_TOKEN')
|
|
621
|
+
);
|
|
622
|
+
|
|
623
|
+
lines.push(`export POLYDEV_USER_TOKEN="${token}"`);
|
|
624
|
+
fs.writeFileSync(rcFile, lines.join('\n').replace(/\n+$/, '') + '\n');
|
|
625
|
+
|
|
626
|
+
// Also save to .polydev.env
|
|
627
|
+
const envFile = path.join(os.homedir(), '.polydev.env');
|
|
628
|
+
fs.writeFileSync(envFile, `POLYDEV_USER_TOKEN="${token}"\n`);
|
|
629
|
+
|
|
630
|
+
console.error(`[Polydev] Token saved to ${rcFile} and ${envFile}`);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Get login success HTML page
|
|
635
|
+
*/
|
|
636
|
+
getLoginSuccessHTML() {
|
|
637
|
+
return `<!DOCTYPE html>
|
|
638
|
+
<html>
|
|
639
|
+
<head>
|
|
640
|
+
<meta charset="utf-8">
|
|
641
|
+
<title>Polydev - Success</title>
|
|
642
|
+
<style>
|
|
643
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
644
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; background: #f5f5f5; }
|
|
645
|
+
.container { text-align: center; padding: 48px; max-width: 400px; background: white; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
|
|
646
|
+
.logo { font-size: 24px; font-weight: 700; color: #111; margin-bottom: 24px; }
|
|
647
|
+
.check { width: 48px; height: 48px; border: 3px solid #111; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 24px; font-size: 24px; }
|
|
648
|
+
h1 { color: #111; font-size: 20px; font-weight: 600; margin-bottom: 12px; }
|
|
649
|
+
p { color: #666; font-size: 14px; line-height: 1.6; }
|
|
650
|
+
.important { background: #f0f0f0; padding: 16px; border-radius: 6px; margin: 20px 0; }
|
|
651
|
+
.important strong { color: #111; }
|
|
652
|
+
</style>
|
|
653
|
+
</head>
|
|
654
|
+
<body>
|
|
655
|
+
<div class="container">
|
|
656
|
+
<div class="logo">Polydev</div>
|
|
657
|
+
<div class="check">✓</div>
|
|
658
|
+
<h1>Login Successful</h1>
|
|
659
|
+
<p>Your token has been saved.</p>
|
|
660
|
+
<div class="important">
|
|
661
|
+
<strong>Important:</strong> Restart your IDE to use the new token.
|
|
662
|
+
</div>
|
|
663
|
+
<p>You can close this window.</p>
|
|
664
|
+
</div>
|
|
665
|
+
</body>
|
|
666
|
+
</html>`;
|
|
667
|
+
}
|
|
668
|
+
|
|
373
669
|
async forwardToRemoteServer(request) {
|
|
374
670
|
console.error(`[Stdio Wrapper] Forwarding request to remote server`);
|
|
375
671
|
|
|
@@ -1634,7 +1930,7 @@ class StdioMCPWrapper {
|
|
|
1634
1930
|
}
|
|
1635
1931
|
|
|
1636
1932
|
async start() {
|
|
1637
|
-
console.error('Starting Polydev
|
|
1933
|
+
console.error('Starting Polydev MCP Server...');
|
|
1638
1934
|
|
|
1639
1935
|
process.stdin.setEncoding('utf8');
|
|
1640
1936
|
let buffer = '';
|
|
@@ -1651,7 +1947,7 @@ class StdioMCPWrapper {
|
|
|
1651
1947
|
const request = JSON.parse(line);
|
|
1652
1948
|
// Notifications have no id - don't respond to them (JSON-RPC spec)
|
|
1653
1949
|
if (request.id === undefined) {
|
|
1654
|
-
console.error(`[
|
|
1950
|
+
console.error(`[Polydev] Received notification: ${request.method}`);
|
|
1655
1951
|
continue;
|
|
1656
1952
|
}
|
|
1657
1953
|
const response = await this.handleRequest(request);
|
|
@@ -1664,7 +1960,7 @@ class StdioMCPWrapper {
|
|
|
1664
1960
|
});
|
|
1665
1961
|
|
|
1666
1962
|
process.stdin.on('end', () => {
|
|
1667
|
-
console.error('
|
|
1963
|
+
console.error('Polydev MCP Server shutting down...');
|
|
1668
1964
|
this.stopSmartRefreshScheduler();
|
|
1669
1965
|
process.exit(0);
|
|
1670
1966
|
});
|
|
@@ -1682,30 +1978,243 @@ class StdioMCPWrapper {
|
|
|
1682
1978
|
process.exit(0);
|
|
1683
1979
|
});
|
|
1684
1980
|
|
|
1685
|
-
console.error('
|
|
1981
|
+
console.error('Polydev MCP Server ready.\n');
|
|
1982
|
+
|
|
1983
|
+
// Run the onboarding/status check
|
|
1984
|
+
await this.runStartupFlow();
|
|
1686
1985
|
|
|
1687
|
-
// Run
|
|
1688
|
-
|
|
1689
|
-
console.error('[Stdio Wrapper] Running initial CLI detection...');
|
|
1986
|
+
// Run CLI detection in background
|
|
1987
|
+
console.error('[Polydev] Detecting local CLI tools...');
|
|
1690
1988
|
|
|
1691
1989
|
this.localForceCliDetection({})
|
|
1692
|
-
.then(() => {
|
|
1693
|
-
console.error('[Stdio Wrapper] Initial CLI detection completed');
|
|
1990
|
+
.then(async () => {
|
|
1694
1991
|
this._cliDetectionComplete = true;
|
|
1992
|
+
// Display CLI status after detection
|
|
1993
|
+
await this.displayCliStatus();
|
|
1695
1994
|
})
|
|
1696
1995
|
.catch((error) => {
|
|
1697
|
-
console.error('[
|
|
1698
|
-
this._cliDetectionComplete = true;
|
|
1996
|
+
console.error('[Polydev] CLI detection failed:', error.message);
|
|
1997
|
+
this._cliDetectionComplete = true;
|
|
1699
1998
|
})
|
|
1700
1999
|
.finally(() => {
|
|
1701
|
-
// Resolve the readiness promise so waiting requests can proceed
|
|
1702
2000
|
if (this._cliDetectionResolver) {
|
|
1703
2001
|
this._cliDetectionResolver();
|
|
1704
2002
|
}
|
|
1705
|
-
// Start smart refresh scheduler after initial detection attempt
|
|
1706
2003
|
this.startSmartRefreshScheduler();
|
|
1707
2004
|
});
|
|
1708
2005
|
}
|
|
2006
|
+
|
|
2007
|
+
/**
|
|
2008
|
+
* Run startup flow - auto-login if no token, verify if has token
|
|
2009
|
+
*/
|
|
2010
|
+
async runStartupFlow() {
|
|
2011
|
+
if (!this.userToken) {
|
|
2012
|
+
// No token - auto-open browser for login
|
|
2013
|
+
console.error('─'.repeat(50));
|
|
2014
|
+
console.error('Polydev - First Time Setup');
|
|
2015
|
+
console.error('─'.repeat(50));
|
|
2016
|
+
console.error('Opening browser for authentication...\n');
|
|
2017
|
+
|
|
2018
|
+
await this.autoLoginOnStartup();
|
|
2019
|
+
} else {
|
|
2020
|
+
// Has token - verify it
|
|
2021
|
+
await this.verifyAndDisplayAuth();
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
/**
|
|
2026
|
+
* Auto-login on startup (opens browser automatically)
|
|
2027
|
+
*/
|
|
2028
|
+
async autoLoginOnStartup() {
|
|
2029
|
+
const http = require('http');
|
|
2030
|
+
const { execFile } = require('child_process');
|
|
2031
|
+
|
|
2032
|
+
return new Promise((resolve) => {
|
|
2033
|
+
const server = http.createServer((req, res) => {
|
|
2034
|
+
const url = new URL(req.url, `http://localhost`);
|
|
2035
|
+
|
|
2036
|
+
if (req.method === 'OPTIONS') {
|
|
2037
|
+
res.writeHead(204, {
|
|
2038
|
+
'Access-Control-Allow-Origin': '*',
|
|
2039
|
+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
2040
|
+
'Access-Control-Allow-Headers': 'Content-Type'
|
|
2041
|
+
});
|
|
2042
|
+
res.end();
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
if (url.pathname === '/callback') {
|
|
2047
|
+
const token = url.searchParams.get('token');
|
|
2048
|
+
|
|
2049
|
+
if (token && token.startsWith('pd_')) {
|
|
2050
|
+
this.saveTokenToFiles(token);
|
|
2051
|
+
this.userToken = token;
|
|
2052
|
+
this.isAuthenticated = true;
|
|
2053
|
+
|
|
2054
|
+
res.writeHead(200, {
|
|
2055
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
2056
|
+
'Access-Control-Allow-Origin': '*'
|
|
2057
|
+
});
|
|
2058
|
+
res.end(this.getLoginSuccessHTML());
|
|
2059
|
+
|
|
2060
|
+
console.error('[OK] Authentication successful!');
|
|
2061
|
+
console.error(' Token saved to ~/.zshrc and ~/.polydev.env');
|
|
2062
|
+
console.error('');
|
|
2063
|
+
console.error(' IMPORTANT: Restart your IDE to use the new token.');
|
|
2064
|
+
console.error('─'.repeat(50) + '\n');
|
|
2065
|
+
|
|
2066
|
+
setTimeout(() => {
|
|
2067
|
+
server.close();
|
|
2068
|
+
resolve(true);
|
|
2069
|
+
}, 500);
|
|
2070
|
+
} else {
|
|
2071
|
+
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
2072
|
+
res.end('Invalid or missing token');
|
|
2073
|
+
}
|
|
2074
|
+
} else {
|
|
2075
|
+
res.writeHead(404);
|
|
2076
|
+
res.end('Not found');
|
|
2077
|
+
}
|
|
2078
|
+
});
|
|
2079
|
+
|
|
2080
|
+
server.listen(0, 'localhost', () => {
|
|
2081
|
+
const port = server.address().port;
|
|
2082
|
+
const callbackUrl = `http://localhost:${port}/callback`;
|
|
2083
|
+
const authUrl = `https://polydev.ai/auth/cli?callback=${encodeURIComponent(callbackUrl)}&redirect=ide-plugin&auto=true`;
|
|
2084
|
+
|
|
2085
|
+
console.error('If browser does not open, visit:');
|
|
2086
|
+
console.error(authUrl);
|
|
2087
|
+
console.error('');
|
|
2088
|
+
|
|
2089
|
+
// Open browser
|
|
2090
|
+
const platform = process.platform;
|
|
2091
|
+
let cmd, args;
|
|
2092
|
+
if (platform === 'darwin') {
|
|
2093
|
+
cmd = 'open';
|
|
2094
|
+
args = [authUrl];
|
|
2095
|
+
} else if (platform === 'win32') {
|
|
2096
|
+
cmd = 'cmd';
|
|
2097
|
+
args = ['/c', 'start', '', authUrl];
|
|
2098
|
+
} else {
|
|
2099
|
+
cmd = 'xdg-open';
|
|
2100
|
+
args = [authUrl];
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
execFile(cmd, args, (err) => {
|
|
2104
|
+
if (err) {
|
|
2105
|
+
console.error('[!] Could not open browser automatically.');
|
|
2106
|
+
}
|
|
2107
|
+
});
|
|
2108
|
+
|
|
2109
|
+
// Don't block forever - resolve after timeout
|
|
2110
|
+
setTimeout(() => {
|
|
2111
|
+
if (!this.isAuthenticated) {
|
|
2112
|
+
console.error('[!] Login timeout - use "login" tool to try again');
|
|
2113
|
+
console.error('─'.repeat(50) + '\n');
|
|
2114
|
+
}
|
|
2115
|
+
server.close();
|
|
2116
|
+
resolve(false);
|
|
2117
|
+
}, 2 * 60 * 1000); // 2 minute timeout for startup
|
|
2118
|
+
});
|
|
2119
|
+
|
|
2120
|
+
server.on('error', (err) => {
|
|
2121
|
+
console.error('[!] Could not start login server:', err.message);
|
|
2122
|
+
console.error(' Use "login" tool or run: npx polydev-ai');
|
|
2123
|
+
console.error('─'.repeat(50) + '\n');
|
|
2124
|
+
resolve(false);
|
|
2125
|
+
});
|
|
2126
|
+
});
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
/**
|
|
2130
|
+
* Verify token and display auth status
|
|
2131
|
+
*/
|
|
2132
|
+
async verifyAndDisplayAuth() {
|
|
2133
|
+
try {
|
|
2134
|
+
const response = await fetch('https://www.polydev.ai/api/mcp', {
|
|
2135
|
+
method: 'POST',
|
|
2136
|
+
headers: {
|
|
2137
|
+
'Content-Type': 'application/json',
|
|
2138
|
+
'Authorization': `Bearer ${this.userToken}`,
|
|
2139
|
+
'User-Agent': 'polydev-mcp/1.0.0'
|
|
2140
|
+
},
|
|
2141
|
+
body: JSON.stringify({ action: 'check_status' })
|
|
2142
|
+
});
|
|
2143
|
+
|
|
2144
|
+
if (response.ok) {
|
|
2145
|
+
const data = await response.json();
|
|
2146
|
+
this.isAuthenticated = true;
|
|
2147
|
+
const credits = data.credits_remaining?.toLocaleString() || 0;
|
|
2148
|
+
const tier = data.subscription_tier || 'Free';
|
|
2149
|
+
|
|
2150
|
+
console.error('─'.repeat(50));
|
|
2151
|
+
console.error('Polydev - Authenticated');
|
|
2152
|
+
console.error('─'.repeat(50));
|
|
2153
|
+
console.error(`Account: ${data.email || 'Connected'}`);
|
|
2154
|
+
console.error(`Credits: ${credits} | Tier: ${tier}`);
|
|
2155
|
+
console.error('─'.repeat(50) + '\n');
|
|
2156
|
+
} else {
|
|
2157
|
+
this.isAuthenticated = false;
|
|
2158
|
+
console.error('─'.repeat(50));
|
|
2159
|
+
console.error('Polydev - Token Invalid');
|
|
2160
|
+
console.error('─'.repeat(50));
|
|
2161
|
+
console.error('Your token may have expired.');
|
|
2162
|
+
console.error('Use the "login" tool or run: npx polydev-ai login');
|
|
2163
|
+
console.error('─'.repeat(50) + '\n');
|
|
2164
|
+
}
|
|
2165
|
+
} catch (error) {
|
|
2166
|
+
console.error('[Polydev] Could not verify auth (offline?)');
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
/**
|
|
2171
|
+
* Display CLI tools status after detection
|
|
2172
|
+
*/
|
|
2173
|
+
async displayCliStatus() {
|
|
2174
|
+
try {
|
|
2175
|
+
const status = await this.loadLocalCliStatus();
|
|
2176
|
+
if (!status || Object.keys(status).length === 0) {
|
|
2177
|
+
console.error('[Polydev] No local CLI tools detected.\n');
|
|
2178
|
+
return;
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
const tools = Object.entries(status);
|
|
2182
|
+
const available = tools.filter(([_, s]) => s.available);
|
|
2183
|
+
const authenticated = available.filter(([_, s]) => s.authenticated);
|
|
2184
|
+
|
|
2185
|
+
console.error('─'.repeat(50));
|
|
2186
|
+
console.error('Local AI Tools (FREE - no API credits)');
|
|
2187
|
+
console.error('─'.repeat(50));
|
|
2188
|
+
|
|
2189
|
+
for (const [id, s] of tools) {
|
|
2190
|
+
const name = id.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase());
|
|
2191
|
+
if (s.available) {
|
|
2192
|
+
const authStatus = s.authenticated ? '[ready]' : '[needs auth]';
|
|
2193
|
+
console.error(`[OK] ${name.padEnd(15)} ${authStatus}`);
|
|
2194
|
+
} else {
|
|
2195
|
+
console.error(`[--] ${name.padEnd(15)} not installed`);
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
if (available.length > 0) {
|
|
2200
|
+
console.error('');
|
|
2201
|
+
console.error(`${available.length} tool(s) installed, ${authenticated.length} ready to use`);
|
|
2202
|
+
console.error('Local tools save API credits!');
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
console.error('─'.repeat(50) + '\n');
|
|
2206
|
+
} catch (error) {
|
|
2207
|
+
// Ignore errors in status display
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
/**
|
|
2212
|
+
* Check and display authentication status on startup
|
|
2213
|
+
* @deprecated Use verifyAndDisplayAuth instead
|
|
2214
|
+
*/
|
|
2215
|
+
async checkAndDisplayAuthStatus() {
|
|
2216
|
+
await this.verifyAndDisplayAuth();
|
|
2217
|
+
}
|
|
1709
2218
|
}
|
|
1710
2219
|
|
|
1711
2220
|
if (require.main === module) {
|