polydev-ai 1.9.12 → 1.9.14

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.
Files changed (2) hide show
  1. package/mcp/stdio-wrapper.js +272 -42
  2. package/package.json +1 -1
@@ -476,27 +476,37 @@ Token will be saved automatically after login.`
476
476
  async handleLoginTool(params, id) {
477
477
  const crypto = require('crypto');
478
478
 
479
- // Check if already authenticated
479
+ // Check if already authenticated - verify with server first
480
480
  if (this.isAuthenticated && this.userToken) {
481
- return {
482
- jsonrpc: '2.0',
483
- id,
484
- result: {
485
- content: [{
486
- type: 'text',
487
- text: `ALREADY AUTHENTICATED
481
+ console.error('[Polydev] Token exists locally, verifying with server...');
482
+ const isValid = await this.verifyTokenWithServer();
483
+
484
+ if (isValid) {
485
+ return {
486
+ jsonrpc: '2.0',
487
+ id,
488
+ result: {
489
+ content: [{
490
+ type: 'text',
491
+ text: `ALREADY AUTHENTICATED
488
492
  =====================
489
493
 
490
- Your token is configured and ready.
494
+ Your token is configured and verified.
491
495
 
492
496
  Available commands:
493
497
  /polydev:ask Query multiple AI models
494
498
  /polydev:auth Check status & credits
495
499
 
496
500
  To re-login: npx polydev-ai`
497
- }]
498
- }
499
- };
501
+ }]
502
+ }
503
+ };
504
+ }
505
+
506
+ // Token is invalid/expired - clear state and proceed with login
507
+ console.error('[Polydev] Token invalid/expired, proceeding with re-authentication...');
508
+ this.isAuthenticated = false;
509
+ this.userToken = null;
500
510
  }
501
511
 
502
512
  // Generate unique session ID for polling-based auth
@@ -575,7 +585,16 @@ To re-login: npx polydev-ai`
575
585
  // if this process dies, the new one can resume polling with same session)
576
586
  this.startLoginPolling(sessionId);
577
587
 
578
- // Return immediately with accurate status
588
+ // Wait for authentication to complete (blocks up to 120s)
589
+ console.error('[Polydev] Waiting for browser authentication to complete...');
590
+ const authenticated = await this.waitForAuthCompletion();
591
+
592
+ if (authenticated) {
593
+ console.error('[Polydev] Login successful, returning status...');
594
+ return await this.fetchAuthStatusResponse(id);
595
+ }
596
+
597
+ // Timeout - return fallback message
579
598
  return {
580
599
  jsonrpc: '2.0',
581
600
  id,
@@ -585,34 +604,239 @@ To re-login: npx polydev-ai`
585
604
  text: `AUTHENTICATION STARTED
586
605
  ======================
587
606
 
588
- A browser window is opening for you to sign in.
607
+ A browser window was opened for you to sign in.
589
608
 
590
- If it doesn't open, visit:
609
+ If it didn't open, visit:
591
610
  ${authUrl}
592
611
 
593
- Once you sign in, your token will be saved automatically to:
594
- ~/.polydev.env
595
- ~/.zshrc
612
+ Still waiting for login. Use /polydev:auth to check status after signing in.`
613
+ }]
614
+ }
615
+ };
616
+
617
+ } catch (error) {
618
+ const cause = error.cause ? ` (${error.cause.code || error.cause.message || error.cause})` : '';
619
+ console.error('[Polydev] Login error:', error.message, cause);
620
+ return {
621
+ jsonrpc: '2.0',
622
+ id,
623
+ result: {
624
+ content: [{
625
+ type: 'text',
626
+ text: `Login failed: ${error.message}${cause}\n\nPlease try: npx polydev-ai`
627
+ }],
628
+ isError: true
629
+ }
630
+ };
631
+ }
632
+ }
633
+
634
+ /**
635
+ * Verify the current token is valid by checking with the server
636
+ * Returns true if valid, false if invalid/expired
637
+ * On network error, returns true (don't block for transient issues)
638
+ */
639
+ async verifyTokenWithServer() {
640
+ if (!this.userToken) return false;
641
+ try {
642
+ const response = await fetch('https://www.polydev.ai/api/auth/status', {
643
+ method: 'POST',
644
+ headers: {
645
+ 'Content-Type': 'application/json',
646
+ 'Authorization': `Bearer ${this.userToken}`,
647
+ 'User-Agent': 'polydev-stdio-wrapper/1.0.0'
648
+ }
649
+ });
650
+ if (!response.ok) {
651
+ console.error(`[Polydev] Token verification failed: ${response.status}`);
652
+ return false;
653
+ }
654
+ return true;
655
+ } catch (error) {
656
+ // Network error - assume valid to avoid blocking on transient issues
657
+ console.error('[Polydev] Token verification network error (assuming valid):', error.message);
658
+ return true;
659
+ }
660
+ }
661
+
662
+ /**
663
+ * Wait for authentication to complete by polling this.isAuthenticated
664
+ * Used to block tool calls until login is detected by startLoginPolling
665
+ * @param {number} timeoutMs - Max time to wait (default 120s)
666
+ * @returns {boolean} true if authenticated, false if timed out
667
+ */
668
+ async waitForAuthCompletion(timeoutMs = 120000) {
669
+ const pollInterval = 3000; // Check every 3 seconds
670
+ const maxPolls = Math.floor(timeoutMs / pollInterval);
671
+
672
+ for (let i = 0; i < maxPolls; i++) {
673
+ await new Promise(r => setTimeout(r, pollInterval));
674
+ if (this.isAuthenticated && this.userToken) {
675
+ return true;
676
+ }
677
+ }
678
+ return false;
679
+ }
680
+
681
+ /**
682
+ * Fetch account status and return formatted response
683
+ * Standalone helper that doesn't trigger re-auth (avoids circular calls)
684
+ * @param {*} id - JSON-RPC request id
685
+ * @returns {object} JSON-RPC response with account status
686
+ */
687
+ async fetchAuthStatusResponse(id) {
688
+ try {
689
+ const accountResponse = await fetch('https://www.polydev.ai/api/auth/status', {
690
+ method: 'POST',
691
+ headers: {
692
+ 'Content-Type': 'application/json',
693
+ 'Authorization': `Bearer ${this.userToken}`,
694
+ 'User-Agent': 'polydev-stdio-wrapper/1.0.0'
695
+ }
696
+ });
697
+
698
+ if (accountResponse.ok) {
699
+ const data = await accountResponse.json();
700
+ const credits = data.credits_remaining?.toLocaleString() || 0;
701
+ const messages = data.messages_remaining?.toLocaleString() || 0;
702
+ const tier = data.subscription_tier || 'Free';
703
+ const email = data.email || 'Connected';
704
+
705
+ return {
706
+ jsonrpc: '2.0',
707
+ id,
708
+ result: {
709
+ content: [{
710
+ type: 'text',
711
+ text: `LOGIN SUCCESSFUL
712
+ ================
713
+
714
+ Authentication: Connected
715
+ Account: ${email}
716
+ Credits: ${credits}
717
+ Messages: ${messages}
718
+ Tier: ${tier}
596
719
 
597
- After login completes, you can:
720
+ You can now use:
598
721
  /polydev:ask Query multiple AI models
599
- /polydev:auth Check status & credits
722
+ /polydev:auth Check full status & credits
723
+
724
+ Dashboard: https://polydev.ai/dashboard`
725
+ }]
726
+ }
727
+ };
728
+ }
729
+ } catch (e) {
730
+ console.error('[Polydev] Post-login status fetch failed:', e.message);
731
+ }
732
+
733
+ // Fallback if status fetch fails
734
+ return {
735
+ jsonrpc: '2.0',
736
+ id,
737
+ result: {
738
+ content: [{
739
+ type: 'text',
740
+ text: `LOGIN SUCCESSFUL
741
+ ================
742
+
743
+ Your token has been saved and is active.
744
+
745
+ You can now use:
746
+ /polydev:ask Query multiple AI models
747
+ /polydev:auth Check full status & credits
600
748
 
601
749
  Dashboard: https://polydev.ai/dashboard`
750
+ }]
751
+ }
752
+ };
753
+ }
754
+
755
+ /**
756
+ * Trigger re-authentication by opening browser and starting polling
757
+ * Waits for auth to complete inline (up to 120s) then returns full status
758
+ * Used when token is detected as invalid/expired
759
+ * @param {*} id - JSON-RPC request id
760
+ * @param {string} reason - Human-readable reason for re-auth
761
+ * @returns {object} JSON-RPC response
762
+ */
763
+ async triggerReAuth(id, reason = 'Token invalid or expired') {
764
+ const crypto = require('crypto');
765
+ const sessionId = crypto.randomBytes(32).toString('hex');
766
+
767
+ // Clear local auth state
768
+ this.isAuthenticated = false;
769
+ this.userToken = null;
770
+
771
+ try {
772
+ const createResponse = await fetch(`https://www.polydev.ai/api/auth/cli-session/${sessionId}`, {
773
+ method: 'POST',
774
+ headers: { 'Content-Type': 'application/json' }
775
+ });
776
+
777
+ if (!createResponse.ok) {
778
+ return {
779
+ jsonrpc: '2.0',
780
+ id,
781
+ result: {
782
+ content: [{
783
+ type: 'text',
784
+ text: `${reason}\n\nCould not initiate re-authentication. Please run:\n npx polydev-ai`
785
+ }],
786
+ isError: true
787
+ }
788
+ };
789
+ }
790
+
791
+ const authUrl = `https://polydev.ai/auth?session_id=${sessionId}&redirect=ide-plugin&auto=true`;
792
+
793
+ console.error(`[Polydev] Opening browser for re-authentication...`);
794
+ this.openBrowser(authUrl).catch(() => {
795
+ console.error('[Polydev] Could not open browser for re-authentication');
796
+ });
797
+
798
+ this.savePendingSession(sessionId);
799
+ this.startLoginPolling(sessionId);
800
+
801
+ // Wait for authentication to complete (blocks up to 120s)
802
+ console.error('[Polydev] Waiting for browser authentication to complete...');
803
+ const authenticated = await this.waitForAuthCompletion();
804
+
805
+ if (authenticated) {
806
+ console.error('[Polydev] Re-authentication successful, returning status...');
807
+ return await this.fetchAuthStatusResponse(id);
808
+ }
809
+
810
+ // Timeout - return fallback message
811
+ return {
812
+ jsonrpc: '2.0',
813
+ id,
814
+ result: {
815
+ content: [{
816
+ type: 'text',
817
+ text: `RE-AUTHENTICATION STARTED
818
+ =========================
819
+
820
+ ${reason}
821
+
822
+ A browser window was opened for re-authentication.
823
+
824
+ If it didn't open, visit:
825
+ ${authUrl}
826
+
827
+ Still waiting for login. Use /polydev:auth to check status after signing in.`
602
828
  }]
603
829
  }
604
830
  };
605
-
606
831
  } catch (error) {
607
832
  const cause = error.cause ? ` (${error.cause.code || error.cause.message || error.cause})` : '';
608
- console.error('[Polydev] Login error:', error.message, cause);
609
833
  return {
610
834
  jsonrpc: '2.0',
611
835
  id,
612
836
  result: {
613
837
  content: [{
614
838
  type: 'text',
615
- text: `Login failed: ${error.message}${cause}\n\nPlease try: npx polydev-ai`
839
+ text: `${reason}\n\nCould not reach server: ${error.message}${cause}\nPlease run: npx polydev-ai`
616
840
  }],
617
841
  isError: true
618
842
  }
@@ -808,24 +1032,9 @@ Configure: https://polydev.ai/dashboard/models`
808
1032
  }
809
1033
  };
810
1034
  } else {
811
- return {
812
- jsonrpc: '2.0',
813
- id,
814
- result: {
815
- content: [{
816
- type: 'text',
817
- text: `POLYDEV STATUS
818
- ==============
819
-
820
- Token invalid or expired.
821
-
822
- Please re-login:
823
- 1. Use the "login" tool
824
- 2. Or run: npx polydev-ai login`
825
- }],
826
- isError: true
827
- }
828
- };
1035
+ // Token invalid/expired - auto-trigger re-authentication
1036
+ console.error('[Polydev] Auth status: token invalid/expired, auto-triggering re-auth...');
1037
+ return await this.triggerReAuth(id, 'Token invalid or expired.');
829
1038
  }
830
1039
  } catch (error) {
831
1040
  return {
@@ -1228,6 +1437,12 @@ Error: ${error.message}`
1228
1437
  const errorText = await response.text();
1229
1438
  console.error(`[Stdio Wrapper] Remote server error: ${response.status} - ${errorText}`);
1230
1439
 
1440
+ // Handle 401 specifically - token expired/invalid, trigger re-auth
1441
+ if (response.status === 401) {
1442
+ console.error('[Polydev] Remote API returned 401, auto-triggering re-authentication...');
1443
+ return await this.triggerReAuth(request.id, 'Authentication expired. Re-authenticating...');
1444
+ }
1445
+
1231
1446
  return {
1232
1447
  jsonrpc: '2.0',
1233
1448
  id: request.id,
@@ -1282,6 +1497,13 @@ Error: ${error.message}`
1282
1497
  // 3. Combines results
1283
1498
  const result = await this.localSendCliPrompt(params.arguments);
1284
1499
 
1500
+ // Check if result indicates auth failure (from forwardToRemoteServer 401 handling)
1501
+ const resultText = result.content || this.formatCliResponse(result);
1502
+ if (result.result?.content?.[0]?.text?.includes('RE-AUTHENTICATION REQUIRED')) {
1503
+ // Auth failure already handled with re-auth response - pass through
1504
+ return result;
1505
+ }
1506
+
1285
1507
  return {
1286
1508
  jsonrpc: '2.0',
1287
1509
  id,
@@ -1289,7 +1511,7 @@ Error: ${error.message}`
1289
1511
  content: [
1290
1512
  {
1291
1513
  type: 'text',
1292
- text: result.content || this.formatCliResponse(result)
1514
+ text: resultText
1293
1515
  }
1294
1516
  ]
1295
1517
  }
@@ -1297,6 +1519,14 @@ Error: ${error.message}`
1297
1519
 
1298
1520
  } catch (error) {
1299
1521
  console.error(`[Stdio Wrapper] get_perspectives error:`, error);
1522
+
1523
+ // Check if error is auth-related
1524
+ const errMsg = (error.message || '').toLowerCase();
1525
+ if (errMsg.includes('401') || errMsg.includes('unauthorized') || errMsg.includes('auth') || errMsg.includes('token')) {
1526
+ console.error('[Polydev] Perspectives auth error, auto-triggering re-authentication...');
1527
+ return await this.triggerReAuth(id, 'Authentication expired. Re-authenticating...');
1528
+ }
1529
+
1300
1530
  return {
1301
1531
  jsonrpc: '2.0',
1302
1532
  id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polydev-ai",
3
- "version": "1.9.12",
3
+ "version": "1.9.14",
4
4
  "engines": {
5
5
  "node": ">=20.x <=22.x"
6
6
  },