polydev-ai 1.9.26 → 1.9.28
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/stdio-wrapper.js +170 -263
- package/package.json +1 -1
package/mcp/stdio-wrapper.js
CHANGED
|
@@ -155,9 +155,6 @@ if (writableTmp) {
|
|
|
155
155
|
* Strips: provider info, approval, sandbox, reasoning, session id, MCP errors, etc.
|
|
156
156
|
*
|
|
157
157
|
* Codex CLI output structure:
|
|
158
|
-
* [metadata] → user → [echoed prompt] → thinking → [status] → codex → [RESPONSE] → tokens used → [count]
|
|
159
|
-
*
|
|
160
|
-
* Claude Code output structure:
|
|
161
158
|
* [may include JSON or plain text response]
|
|
162
159
|
*/
|
|
163
160
|
function cleanCliResponse(content) {
|
|
@@ -1046,9 +1043,28 @@ To authenticate, pass your token directly:
|
|
|
1046
1043
|
};
|
|
1047
1044
|
}
|
|
1048
1045
|
|
|
1049
|
-
// Not sandboxed — auto-
|
|
1050
|
-
console.error('[Polydev] No token found,
|
|
1051
|
-
return
|
|
1046
|
+
// Not sandboxed — tell user to use login tool (don't auto-open browser)
|
|
1047
|
+
console.error('[Polydev] No token found, prompting user to login');
|
|
1048
|
+
return {
|
|
1049
|
+
jsonrpc: '2.0',
|
|
1050
|
+
id,
|
|
1051
|
+
result: {
|
|
1052
|
+
content: [{
|
|
1053
|
+
type: 'text',
|
|
1054
|
+
text: `POLYDEV STATUS
|
|
1055
|
+
==============
|
|
1056
|
+
|
|
1057
|
+
Authentication: Not connected
|
|
1058
|
+
|
|
1059
|
+
To login:
|
|
1060
|
+
1. Use the "login" tool (opens browser)
|
|
1061
|
+
2. Or run: npx polydev-ai
|
|
1062
|
+
|
|
1063
|
+
Token will be saved automatically after login.`
|
|
1064
|
+
}],
|
|
1065
|
+
isError: true
|
|
1066
|
+
}
|
|
1067
|
+
};
|
|
1052
1068
|
}
|
|
1053
1069
|
|
|
1054
1070
|
try {
|
|
@@ -1138,9 +1154,26 @@ Configure: https://polydev.ai/dashboard/models`
|
|
|
1138
1154
|
}
|
|
1139
1155
|
};
|
|
1140
1156
|
} else {
|
|
1141
|
-
// Token invalid/expired - auto-
|
|
1142
|
-
console.error('[Polydev] Auth status: token invalid/expired
|
|
1143
|
-
return
|
|
1157
|
+
// Token invalid/expired - tell user to re-login (don't auto-open browser)
|
|
1158
|
+
console.error('[Polydev] Auth status: token invalid/expired');
|
|
1159
|
+
return {
|
|
1160
|
+
jsonrpc: '2.0',
|
|
1161
|
+
id,
|
|
1162
|
+
result: {
|
|
1163
|
+
content: [{
|
|
1164
|
+
type: 'text',
|
|
1165
|
+
text: `POLYDEV STATUS
|
|
1166
|
+
==============
|
|
1167
|
+
|
|
1168
|
+
Authentication: Token invalid or expired
|
|
1169
|
+
|
|
1170
|
+
To re-authenticate:
|
|
1171
|
+
1. Use the "login" tool (opens browser)
|
|
1172
|
+
2. Or run: npx polydev-ai`
|
|
1173
|
+
}],
|
|
1174
|
+
isError: true
|
|
1175
|
+
}
|
|
1176
|
+
};
|
|
1144
1177
|
}
|
|
1145
1178
|
} catch (error) {
|
|
1146
1179
|
// Categorize the error for clearer user messaging
|
|
@@ -1566,76 +1599,64 @@ To re-login: /polydev:login`
|
|
|
1566
1599
|
</html>`;
|
|
1567
1600
|
}
|
|
1568
1601
|
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
if (!response.ok) {
|
|
1591
|
-
const errorText = await response.text();
|
|
1592
|
-
console.error(`[Stdio Wrapper] Remote server error: ${response.status} - ${errorText}`);
|
|
1593
|
-
|
|
1594
|
-
// Handle 401 specifically - token expired/invalid, trigger re-auth
|
|
1595
|
-
if (response.status === 401) {
|
|
1596
|
-
console.error('[Polydev] Remote API returned 401, auto-triggering re-authentication...');
|
|
1597
|
-
return await this.triggerReAuth(request.id, 'Authentication expired. Re-authenticating...');
|
|
1598
|
-
}
|
|
1599
|
-
|
|
1600
|
-
return {
|
|
1601
|
-
jsonrpc: '2.0',
|
|
1602
|
-
id: request.id,
|
|
1603
|
-
error: {
|
|
1604
|
-
code: -32603,
|
|
1605
|
-
message: `Remote server error: ${response.status} - ${errorText}`
|
|
1606
|
-
}
|
|
1607
|
-
};
|
|
1602
|
+
/**
|
|
1603
|
+
* Send an MCP progress notification to the client
|
|
1604
|
+
* This is a JSON-RPC notification (no id) that compatible clients use to:
|
|
1605
|
+
* 1. Display progress UI (status bar, spinners)
|
|
1606
|
+
* 2. Reset their tool call timeout (if resetTimeoutOnProgress is enabled)
|
|
1607
|
+
* @param {string|number} progressToken - Token from request's _meta.progressToken
|
|
1608
|
+
* @param {number} progress - Current progress value (must increase monotonically)
|
|
1609
|
+
* @param {number} total - Total expected progress value
|
|
1610
|
+
* @param {string} message - Human-readable status message
|
|
1611
|
+
*/
|
|
1612
|
+
sendProgressNotification(progressToken, progress, total, message) {
|
|
1613
|
+
if (!progressToken) return; // Client didn't request progress
|
|
1614
|
+
|
|
1615
|
+
const notification = {
|
|
1616
|
+
jsonrpc: '2.0',
|
|
1617
|
+
method: 'notifications/progress',
|
|
1618
|
+
params: {
|
|
1619
|
+
progressToken,
|
|
1620
|
+
progress,
|
|
1621
|
+
total,
|
|
1622
|
+
message
|
|
1608
1623
|
}
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
jsonrpc: '2.0',
|
|
1617
|
-
id: request.id,
|
|
1618
|
-
error: {
|
|
1619
|
-
code: -32603,
|
|
1620
|
-
message: `Network error: ${error.message}`
|
|
1621
|
-
}
|
|
1622
|
-
};
|
|
1624
|
+
};
|
|
1625
|
+
|
|
1626
|
+
try {
|
|
1627
|
+
process.stdout.write(JSON.stringify(notification) + '\n');
|
|
1628
|
+
console.error(`[Polydev] Progress: ${progress}/${total} - ${message}`);
|
|
1629
|
+
} catch (err) {
|
|
1630
|
+
console.error(`[Polydev] Failed to send progress notification:`, err.message);
|
|
1623
1631
|
}
|
|
1624
1632
|
}
|
|
1625
1633
|
|
|
1626
1634
|
/**
|
|
1627
|
-
*
|
|
1635
|
+
* Start a periodic progress heartbeat to prevent client-side timeouts.
|
|
1636
|
+
* Sends a progress notification every intervalMs to signal the server is still working.
|
|
1637
|
+
* Compatible MCP clients will reset their timeout clock on each notification.
|
|
1638
|
+
* @param {string|number} progressToken - Token from request's _meta.progressToken
|
|
1639
|
+
* @param {number} intervalMs - Heartbeat interval in milliseconds (default: 10s)
|
|
1640
|
+
* @returns {{ stop: Function, tick: Function }} Controller to stop heartbeat and manually tick progress
|
|
1628
1641
|
*/
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1642
|
+
startProgressHeartbeat(progressToken, intervalMs = 10000) {
|
|
1643
|
+
if (!progressToken) return { stop: () => {}, tick: () => {} };
|
|
1644
|
+
|
|
1645
|
+
let currentProgress = 0;
|
|
1646
|
+
const total = 100; // Use percentage-style progress
|
|
1647
|
+
|
|
1648
|
+
const interval = setInterval(() => {
|
|
1649
|
+
currentProgress = Math.min(currentProgress + 5, 95); // Never reach 100 until done
|
|
1650
|
+
this.sendProgressNotification(progressToken, currentProgress, total, 'Processing perspectives...');
|
|
1651
|
+
}, intervalMs);
|
|
1652
|
+
|
|
1653
|
+
return {
|
|
1654
|
+
stop: () => clearInterval(interval),
|
|
1655
|
+
tick: (progress, message) => {
|
|
1656
|
+
currentProgress = progress;
|
|
1657
|
+
this.sendProgressNotification(progressToken, progress, total, message);
|
|
1658
|
+
}
|
|
1659
|
+
};
|
|
1639
1660
|
}
|
|
1640
1661
|
|
|
1641
1662
|
/**
|
|
@@ -1644,12 +1665,30 @@ To re-login: /polydev:login`
|
|
|
1644
1665
|
async handleGetPerspectivesWithCLIs(params, id) {
|
|
1645
1666
|
console.error(`[Stdio Wrapper] Handling get_perspectives with local CLIs + remote`);
|
|
1646
1667
|
|
|
1668
|
+
// Extract progress token from request metadata (MCP spec)
|
|
1669
|
+
// Clients that want progress updates include this in _meta.progressToken
|
|
1670
|
+
const progressToken = params?._meta?.progressToken;
|
|
1671
|
+
if (progressToken) {
|
|
1672
|
+
console.error(`[Stdio Wrapper] Client requested progress updates (token: ${progressToken})`);
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
// Start periodic heartbeat to prevent client-side timeouts
|
|
1676
|
+
// Sends progress notification every 10s to keep the connection alive
|
|
1677
|
+
const heartbeat = this.startProgressHeartbeat(progressToken);
|
|
1678
|
+
|
|
1647
1679
|
try {
|
|
1680
|
+
// Send initial progress
|
|
1681
|
+
heartbeat.tick(5, 'Initializing perspectives engine...');
|
|
1682
|
+
|
|
1648
1683
|
// Use existing localSendCliPrompt logic which already:
|
|
1649
1684
|
// 1. Checks all local CLIs
|
|
1650
1685
|
// 2. Calls remote perspectives
|
|
1651
1686
|
// 3. Combines results
|
|
1652
|
-
const result = await this.localSendCliPrompt(params.arguments);
|
|
1687
|
+
const result = await this.localSendCliPrompt(params.arguments, progressToken);
|
|
1688
|
+
|
|
1689
|
+
// Stop heartbeat before sending final response
|
|
1690
|
+
heartbeat.stop();
|
|
1691
|
+
heartbeat.tick(100, 'Complete');
|
|
1653
1692
|
|
|
1654
1693
|
// Check if result indicates auth failure (from forwardToRemoteServer 401 handling)
|
|
1655
1694
|
if (result?.result?.content?.[0]?.text?.includes('RE-AUTHENTICATION REQUIRED')) {
|
|
@@ -1678,13 +1717,27 @@ To re-login: /polydev:login`
|
|
|
1678
1717
|
};
|
|
1679
1718
|
|
|
1680
1719
|
} catch (error) {
|
|
1720
|
+
heartbeat.stop(); // Always clean up heartbeat
|
|
1681
1721
|
console.error(`[Stdio Wrapper] get_perspectives error:`, error);
|
|
1682
1722
|
|
|
1683
|
-
// Check if error is
|
|
1723
|
+
// Check if error is specifically an HTTP 401 unauthorized
|
|
1724
|
+
// IMPORTANT: Don't match generic 'token' or 'auth' strings — they appear in
|
|
1725
|
+
// unrelated errors like "Unexpected token" (JSON parse) or "authentication" (CLI output)
|
|
1684
1726
|
const errMsg = (error.message || '').toLowerCase();
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1727
|
+
const isHttp401 = errMsg.includes('401') || errMsg.includes('unauthorized');
|
|
1728
|
+
if (isHttp401) {
|
|
1729
|
+
console.error('[Polydev] Perspectives auth error (401), suggesting re-login');
|
|
1730
|
+
return {
|
|
1731
|
+
jsonrpc: '2.0',
|
|
1732
|
+
id,
|
|
1733
|
+
result: {
|
|
1734
|
+
content: [{
|
|
1735
|
+
type: 'text',
|
|
1736
|
+
text: `Authentication error: ${error.message}\n\nPlease re-authenticate using the "login" tool or run: npx polydev-ai`
|
|
1737
|
+
}],
|
|
1738
|
+
isError: true
|
|
1739
|
+
}
|
|
1740
|
+
};
|
|
1688
1741
|
}
|
|
1689
1742
|
|
|
1690
1743
|
return {
|
|
@@ -1704,9 +1757,13 @@ To re-login: /polydev:login`
|
|
|
1704
1757
|
async handleLocalCliTool(request) {
|
|
1705
1758
|
const { method, params, id } = request;
|
|
1706
1759
|
const { name: toolName, arguments: args } = params;
|
|
1760
|
+
const progressToken = params?._meta?.progressToken;
|
|
1707
1761
|
|
|
1708
1762
|
console.error(`[Stdio Wrapper] Handling local CLI tool: ${toolName}`);
|
|
1709
1763
|
|
|
1764
|
+
// Start heartbeat for long-running CLI operations
|
|
1765
|
+
const heartbeat = this.startProgressHeartbeat(progressToken);
|
|
1766
|
+
|
|
1710
1767
|
try {
|
|
1711
1768
|
let result;
|
|
1712
1769
|
|
|
@@ -1723,13 +1780,14 @@ To re-login: /polydev:login`
|
|
|
1723
1780
|
|
|
1724
1781
|
case 'send_cli_prompt':
|
|
1725
1782
|
case 'polydev.send_cli_prompt':
|
|
1726
|
-
result = await this.localSendCliPrompt(args);
|
|
1783
|
+
result = await this.localSendCliPrompt(args, progressToken);
|
|
1727
1784
|
break;
|
|
1728
1785
|
|
|
1729
1786
|
default:
|
|
1730
1787
|
throw new Error(`Unknown CLI tool: ${toolName}`);
|
|
1731
1788
|
}
|
|
1732
1789
|
|
|
1790
|
+
heartbeat.stop();
|
|
1733
1791
|
return {
|
|
1734
1792
|
jsonrpc: '2.0',
|
|
1735
1793
|
id,
|
|
@@ -1745,6 +1803,7 @@ To re-login: /polydev:login`
|
|
|
1745
1803
|
|
|
1746
1804
|
} catch (error) {
|
|
1747
1805
|
console.error(`[Stdio Wrapper] CLI tool error:`, error);
|
|
1806
|
+
heartbeat.stop();
|
|
1748
1807
|
return {
|
|
1749
1808
|
jsonrpc: '2.0',
|
|
1750
1809
|
id,
|
|
@@ -1885,7 +1944,7 @@ To re-login: /polydev:login`
|
|
|
1885
1944
|
* Respects user's perspectives_per_message setting for total perspectives
|
|
1886
1945
|
* Uses allProviders from dashboard - tries CLI first, falls back to API
|
|
1887
1946
|
*/
|
|
1888
|
-
async localSendCliPrompt(args) {
|
|
1947
|
+
async localSendCliPrompt(args, progressToken) {
|
|
1889
1948
|
console.error(`[Stdio Wrapper] Local CLI prompt sending with perspectives`);
|
|
1890
1949
|
|
|
1891
1950
|
try {
|
|
@@ -1912,6 +1971,8 @@ To re-login: /polydev:login`
|
|
|
1912
1971
|
} catch (prefError) {
|
|
1913
1972
|
console.error('[Stdio Wrapper] Model preferences fetch failed (will use CLI defaults):', prefError.message);
|
|
1914
1973
|
}
|
|
1974
|
+
|
|
1975
|
+
this.sendProgressNotification(progressToken, 15, 100, 'Loaded model preferences');
|
|
1915
1976
|
|
|
1916
1977
|
// Get the user's perspectives_per_message setting (default 2)
|
|
1917
1978
|
const maxPerspectives = this.perspectivesPerMessage || 2;
|
|
@@ -1920,14 +1981,11 @@ To re-login: /polydev:login`
|
|
|
1920
1981
|
let localResults = [];
|
|
1921
1982
|
|
|
1922
1983
|
if (provider_id) {
|
|
1923
|
-
//
|
|
1984
|
+
// Single provider requested - use only that one
|
|
1924
1985
|
console.error(`[Stdio Wrapper] Using specific provider: ${provider_id}`);
|
|
1925
1986
|
const model = modelPreferences[provider_id] || null;
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
} else {
|
|
1929
|
-
console.error(`[Stdio Wrapper] No model preference for ${provider_id}, using CLI default`);
|
|
1930
|
-
}
|
|
1987
|
+
console.error(`[Stdio Wrapper] Using user's preferred model for ${provider_id}: ${model}`);
|
|
1988
|
+
this.sendProgressNotification(progressToken, 20, 100, `Querying ${provider_id}...`);
|
|
1931
1989
|
const result = await this.cliManager.sendCliPrompt(provider_id, prompt, mode, gracefulTimeout, model);
|
|
1932
1990
|
localResults = [{ provider_id, ...result }];
|
|
1933
1991
|
} else {
|
|
@@ -1939,6 +1997,7 @@ To re-login: /polydev:login`
|
|
|
1939
1997
|
// Get available CLIs for checking
|
|
1940
1998
|
const { available: availableClis } = await this.getAllAvailableProviders();
|
|
1941
1999
|
console.error(`[Stdio Wrapper] Available CLIs: ${availableClis.join(', ') || 'none'}`);
|
|
2000
|
+
this.sendProgressNotification(progressToken, 25, 100, `Found ${availableClis.length} CLIs: ${availableClis.join(', ') || 'none'}`);
|
|
1942
2001
|
|
|
1943
2002
|
// Get all providers from dashboard (user's configured providers + CLI-only)
|
|
1944
2003
|
const allProviders = this.allProviders || [];
|
|
@@ -1983,7 +2042,7 @@ To re-login: /polydev:login`
|
|
|
1983
2042
|
for (const cliId of cliPriorityOrder) {
|
|
1984
2043
|
if (!availableClis.includes(cliId)) continue;
|
|
1985
2044
|
if (cliId === excludedCli) {
|
|
1986
|
-
console.error(`[Stdio Wrapper] [CLI-FIRST] Skipping ${
|
|
2045
|
+
console.error(`[Stdio Wrapper] [CLI-FIRST] Skipping ${excludedCli} (same as current IDE — would cause recursive call)`);
|
|
1987
2046
|
continue;
|
|
1988
2047
|
}
|
|
1989
2048
|
|
|
@@ -2041,6 +2100,7 @@ To re-login: /polydev:login`
|
|
|
2041
2100
|
// Run ALL CLI prompts concurrently with fast-collect pattern
|
|
2042
2101
|
// Resolves once we have maxPerspectives successes OR all complete
|
|
2043
2102
|
if (cliProviderEntries.length > 0) {
|
|
2103
|
+
this.sendProgressNotification(progressToken, 30, 100, `Querying ${cliProviderEntries.length} CLIs in parallel...`);
|
|
2044
2104
|
const cliPromises = cliProviderEntries.map(async (providerEntry) => {
|
|
2045
2105
|
try {
|
|
2046
2106
|
// ONLY use the model from providerEntry (which is filtered to user's own API keys, not credits)
|
|
@@ -2094,6 +2154,7 @@ To re-login: /polydev:login`
|
|
|
2094
2154
|
// Fast-collect: resolve early once we have maxPerspectives successes OR all complete
|
|
2095
2155
|
localResults = await this.collectFirstNSuccesses(cliPromises, maxPerspectives);
|
|
2096
2156
|
console.error(`[Stdio Wrapper] Fast-collect: got ${localResults.filter(r => r.success).length} successful, ${localResults.filter(r => !r.success).length} failed out of ${cliPromises.length} CLIs`);
|
|
2157
|
+
this.sendProgressNotification(progressToken, 55, 100, `Got ${localResults.filter(r => r.success).length} CLI responses`);
|
|
2097
2158
|
}
|
|
2098
2159
|
|
|
2099
2160
|
// Store API-only providers info for remote API call
|
|
@@ -2116,6 +2177,7 @@ To re-login: /polydev:login`
|
|
|
2116
2177
|
// Get remote perspectives for API-only providers and failed CLIs
|
|
2117
2178
|
let perspectivesResult;
|
|
2118
2179
|
if (remainingPerspectives > 0) {
|
|
2180
|
+
this.sendProgressNotification(progressToken, 65, 100, `Fetching ${remainingPerspectives} more perspectives from API...`);
|
|
2119
2181
|
console.error(`[Stdio Wrapper] Calling remote API for ${remainingPerspectives} more perspectives (have ${successfulLocalCount}/${maxPerspectives})`);
|
|
2120
2182
|
|
|
2121
2183
|
// Pass API-only provider info to help remote API choose correct models
|
|
@@ -2155,6 +2217,7 @@ To re-login: /polydev:login`
|
|
|
2155
2217
|
}
|
|
2156
2218
|
|
|
2157
2219
|
// Combine all results
|
|
2220
|
+
this.sendProgressNotification(progressToken, 90, 100, 'Combining all perspectives...');
|
|
2158
2221
|
return this.combineAllCliAndPerspectives(localResults, perspectivesResult, args);
|
|
2159
2222
|
|
|
2160
2223
|
} catch (error) {
|
|
@@ -2462,7 +2525,7 @@ To re-login: /polydev:login`
|
|
|
2462
2525
|
user_token: this.userToken,
|
|
2463
2526
|
// Exclude providers that succeeded locally
|
|
2464
2527
|
exclude_providers: excludeProviders,
|
|
2465
|
-
// NEW: Specific providers to request (from API-only
|
|
2528
|
+
// NEW: Specific providers to request (from API-only providers)
|
|
2466
2529
|
request_providers: requestProviders.length > 0 ? requestProviders : undefined,
|
|
2467
2530
|
// Pass CLI responses for dashboard logging
|
|
2468
2531
|
cli_responses: cliResponses,
|
|
@@ -2659,8 +2722,8 @@ To re-login: /polydev:login`
|
|
|
2659
2722
|
const statusFile = path.join(polydevevDir, 'cli-status.json');
|
|
2660
2723
|
|
|
2661
2724
|
// Ensure directory exists
|
|
2662
|
-
if (!fs.existsSync(
|
|
2663
|
-
fs.mkdirSync(
|
|
2725
|
+
if (!fs.existsSync(polydeveevDir)) {
|
|
2726
|
+
fs.mkdirSync(polydeveevDir, { recursive: true });
|
|
2664
2727
|
}
|
|
2665
2728
|
|
|
2666
2729
|
// Save status with timestamp
|
|
@@ -2849,11 +2912,11 @@ To re-login: /polydev:login`
|
|
|
2849
2912
|
try {
|
|
2850
2913
|
const homeDir = require('os').homedir();
|
|
2851
2914
|
const polydevevDir = path.join(homeDir, '.polydev');
|
|
2852
|
-
const usageFile = path.join(
|
|
2915
|
+
const usageFile = path.join(polydeveevDir, 'cli-usage.json');
|
|
2853
2916
|
|
|
2854
2917
|
// Ensure directory exists
|
|
2855
|
-
if (!fs.existsSync(
|
|
2856
|
-
fs.mkdirSync(
|
|
2918
|
+
if (!fs.existsSync(polydeveevDir)) {
|
|
2919
|
+
fs.mkdirSync(polydeveevDir, { recursive: true });
|
|
2857
2920
|
}
|
|
2858
2921
|
|
|
2859
2922
|
// Add new usage record
|
|
@@ -2963,7 +3026,7 @@ To re-login: /polydev:login`
|
|
|
2963
3026
|
this.userModelPreferences = result.modelPreferences;
|
|
2964
3027
|
this.modelPreferencesCacheTime = Date.now();
|
|
2965
3028
|
|
|
2966
|
-
// Also cache
|
|
3029
|
+
// Also cache perspectivesPerMessage setting (default 2)
|
|
2967
3030
|
this.perspectivesPerMessage = result.perspectivesPerMessage || 2;
|
|
2968
3031
|
|
|
2969
3032
|
// Cache provider order from user's dashboard (respects display_order)
|
|
@@ -3124,27 +3187,24 @@ To re-login: /polydev:login`
|
|
|
3124
3187
|
}
|
|
3125
3188
|
|
|
3126
3189
|
/**
|
|
3127
|
-
* Run startup flow -
|
|
3190
|
+
* Run startup flow - verify token if present, show instructions if not
|
|
3191
|
+
* NEVER auto-opens browser — only the explicit "login" tool should do that
|
|
3128
3192
|
*/
|
|
3129
3193
|
async runStartupFlow() {
|
|
3194
|
+
// Last-chance token reload before deciding
|
|
3130
3195
|
if (!this.userToken) {
|
|
3131
|
-
|
|
3196
|
+
this.reloadTokenFromFiles();
|
|
3197
|
+
}
|
|
3198
|
+
|
|
3199
|
+
if (!this.userToken) {
|
|
3200
|
+
// No token found — just log instructions, don't open browser
|
|
3132
3201
|
console.error('─'.repeat(50));
|
|
3133
|
-
console.error('Polydev -
|
|
3202
|
+
console.error('Polydev - Authentication Required');
|
|
3134
3203
|
console.error('─'.repeat(50));
|
|
3135
|
-
console.error('
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
console.error('[Polydev] Login completed, running CLI detection...');
|
|
3140
|
-
// After login, run CLI detection
|
|
3141
|
-
return this.localForceCliDetection({});
|
|
3142
|
-
}).then(() => {
|
|
3143
|
-
this._cliDetectionComplete = true;
|
|
3144
|
-
return this.displayCliStatus();
|
|
3145
|
-
}).catch((error) => {
|
|
3146
|
-
console.error('[Polydev] Auto-login flow error:', error.message);
|
|
3147
|
-
});
|
|
3204
|
+
console.error('No token found. To authenticate:');
|
|
3205
|
+
console.error(' 1. Use the "login" tool');
|
|
3206
|
+
console.error(' 2. Or run: npx polydev-ai');
|
|
3207
|
+
console.error('─'.repeat(50) + '\n');
|
|
3148
3208
|
} else {
|
|
3149
3209
|
// Has token - verify it (also non-blocking)
|
|
3150
3210
|
this.verifyAndDisplayAuth().catch((error) => {
|
|
@@ -3153,159 +3213,6 @@ To re-login: /polydev:login`
|
|
|
3153
3213
|
}
|
|
3154
3214
|
}
|
|
3155
3215
|
|
|
3156
|
-
/**
|
|
3157
|
-
* Auto-login on startup (opens browser automatically)
|
|
3158
|
-
*/
|
|
3159
|
-
async autoLoginOnStartup() {
|
|
3160
|
-
const http = require('http');
|
|
3161
|
-
const { spawn } = require('child_process');
|
|
3162
|
-
|
|
3163
|
-
return new Promise((resolve) => {
|
|
3164
|
-
const server = http.createServer((req, res) => {
|
|
3165
|
-
const url = new URL(req.url, `http://localhost`);
|
|
3166
|
-
|
|
3167
|
-
if (req.method === 'OPTIONS') {
|
|
3168
|
-
res.writeHead(204, {
|
|
3169
|
-
'Access-Control-Allow-Origin': '*',
|
|
3170
|
-
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
3171
|
-
'Access-Control-Allow-Headers': 'Content-Type'
|
|
3172
|
-
});
|
|
3173
|
-
res.end();
|
|
3174
|
-
return;
|
|
3175
|
-
}
|
|
3176
|
-
|
|
3177
|
-
if (url.pathname === '/callback') {
|
|
3178
|
-
const token = url.searchParams.get('token');
|
|
3179
|
-
|
|
3180
|
-
if (token && token.startsWith('pd_')) {
|
|
3181
|
-
this.saveTokenToFiles(token);
|
|
3182
|
-
this.userToken = token;
|
|
3183
|
-
this.isAuthenticated = true;
|
|
3184
|
-
this._freshLogin = true; // Flag for opening models page after CLI detection
|
|
3185
|
-
|
|
3186
|
-
res.writeHead(200, {
|
|
3187
|
-
'Content-Type': 'text/html; charset=utf-8',
|
|
3188
|
-
'Access-Control-Allow-Origin': '*'
|
|
3189
|
-
});
|
|
3190
|
-
res.end(this.getLoginSuccessHTML());
|
|
3191
|
-
|
|
3192
|
-
console.error('[Polydev] Login successful, token saved');
|
|
3193
|
-
|
|
3194
|
-
// Wait 7 seconds before closing server (gives time for 5s auto-close countdown + buffer)
|
|
3195
|
-
setTimeout(() => {
|
|
3196
|
-
server.close();
|
|
3197
|
-
resolve({
|
|
3198
|
-
jsonrpc: '2.0',
|
|
3199
|
-
id,
|
|
3200
|
-
result: {
|
|
3201
|
-
content: [{
|
|
3202
|
-
type: 'text',
|
|
3203
|
-
text: `LOGIN SUCCESSFUL
|
|
3204
|
-
================
|
|
3205
|
-
|
|
3206
|
-
Token saved to:
|
|
3207
|
-
~/.polydev.env
|
|
3208
|
-
~/.zshrc
|
|
3209
|
-
|
|
3210
|
-
IMPORTANT: Restart your IDE to activate.
|
|
3211
|
-
|
|
3212
|
-
After restart, you can:
|
|
3213
|
-
/polydev:ask Query multiple AI models
|
|
3214
|
-
/polydev:auth Check status & credits
|
|
3215
|
-
|
|
3216
|
-
Dashboard: https://polydev.ai/dashboard`
|
|
3217
|
-
}]
|
|
3218
|
-
}
|
|
3219
|
-
});
|
|
3220
|
-
}, 7000);
|
|
3221
|
-
} else {
|
|
3222
|
-
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
3223
|
-
res.end('Invalid or missing token');
|
|
3224
|
-
}
|
|
3225
|
-
} else {
|
|
3226
|
-
res.writeHead(404);
|
|
3227
|
-
res.end('Not found');
|
|
3228
|
-
}
|
|
3229
|
-
});
|
|
3230
|
-
|
|
3231
|
-
server.listen(0, 'localhost', () => {
|
|
3232
|
-
const port = server.address().port;
|
|
3233
|
-
const callbackUrl = `http://localhost:${port}/callback`;
|
|
3234
|
-
const authUrl = `https://polydev.ai/auth?callback=${encodeURIComponent(callbackUrl)}&redirect=ide-plugin&auto=true`;
|
|
3235
|
-
|
|
3236
|
-
console.error('If browser does not open, visit:');
|
|
3237
|
-
console.error(authUrl);
|
|
3238
|
-
console.error('');
|
|
3239
|
-
|
|
3240
|
-
// Best-in-class browser opening using 'open' package (cross-platform)
|
|
3241
|
-
// Falls back to platform-specific commands if package fails
|
|
3242
|
-
this.openBrowser(authUrl).catch(() => {
|
|
3243
|
-
console.error('[Polydev] All browser open methods failed');
|
|
3244
|
-
console.error('[Polydev] Please open this URL manually:', authUrl);
|
|
3245
|
-
});
|
|
3246
|
-
|
|
3247
|
-
// Don't block forever - resolve after timeout
|
|
3248
|
-
setTimeout(() => {
|
|
3249
|
-
if (!this.isAuthenticated) {
|
|
3250
|
-
console.error('[!] Login timeout - use "login" tool to try again');
|
|
3251
|
-
console.error('─'.repeat(50) + '\n');
|
|
3252
|
-
}
|
|
3253
|
-
server.close();
|
|
3254
|
-
resolve(false);
|
|
3255
|
-
}, 2 * 60 * 1000); // 2 minute timeout for startup
|
|
3256
|
-
});
|
|
3257
|
-
|
|
3258
|
-
server.on('error', (err) => {
|
|
3259
|
-
console.error('[!] Could not start login server:', err.message);
|
|
3260
|
-
console.error(' Use "login" tool or run: npx polydev-ai login');
|
|
3261
|
-
console.error('─'.repeat(50) + '\n');
|
|
3262
|
-
resolve(false);
|
|
3263
|
-
});
|
|
3264
|
-
});
|
|
3265
|
-
}
|
|
3266
|
-
|
|
3267
|
-
/**
|
|
3268
|
-
* Verify token and display auth status
|
|
3269
|
-
*/
|
|
3270
|
-
async verifyAndDisplayAuth() {
|
|
3271
|
-
try {
|
|
3272
|
-
const response = await fetch('https://www.polydev.ai/api/mcp', {
|
|
3273
|
-
method: 'POST',
|
|
3274
|
-
headers: {
|
|
3275
|
-
'Content-Type': 'application/json',
|
|
3276
|
-
'Authorization': `Bearer ${this.userToken}`,
|
|
3277
|
-
'User-Agent': 'polydev-mcp/1.0.0'
|
|
3278
|
-
},
|
|
3279
|
-
body: JSON.stringify({ action: 'check_status' })
|
|
3280
|
-
});
|
|
3281
|
-
|
|
3282
|
-
if (response.ok) {
|
|
3283
|
-
const data = await response.json();
|
|
3284
|
-
const credits = data.credits_remaining?.toLocaleString() || 0;
|
|
3285
|
-
const tier = data.subscription_tier || 'Free';
|
|
3286
|
-
|
|
3287
|
-
console.error('─'.repeat(50));
|
|
3288
|
-
console.error('Polydev - Authenticated');
|
|
3289
|
-
console.error('─'.repeat(50));
|
|
3290
|
-
console.error(`Account: ${data.email || 'Connected'}`);
|
|
3291
|
-
console.error(`Credits: ${credits} | Tier: ${tier}`);
|
|
3292
|
-
console.error('─'.repeat(50) + '\n');
|
|
3293
|
-
} else {
|
|
3294
|
-
// Don't clear isAuthenticated here — handleGetAuthStatus has its own
|
|
3295
|
-
// verification and triggerReAuth logic. Clearing here causes a race condition
|
|
3296
|
-
// where startup verification can invalidate auth before the user checks status.
|
|
3297
|
-
console.error('─'.repeat(50));
|
|
3298
|
-
console.error('Polydev - Token may be invalid');
|
|
3299
|
-
console.error('─'.repeat(50));
|
|
3300
|
-
console.error('Server returned non-OK. Will re-verify on next auth check.');
|
|
3301
|
-
console.error('Use the "login" tool or run: npx polydev-ai login');
|
|
3302
|
-
console.error('─'.repeat(50) + '\n');
|
|
3303
|
-
}
|
|
3304
|
-
} catch (error) {
|
|
3305
|
-
console.error('[Polydev] Could not verify auth (offline?):', error.message || 'unknown');
|
|
3306
|
-
}
|
|
3307
|
-
}
|
|
3308
|
-
|
|
3309
3216
|
/**
|
|
3310
3217
|
* Display CLI tools status after detection
|
|
3311
3218
|
*/
|