hedgequantx 2.5.11 → 2.5.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "2.5.11",
3
+ "version": "2.5.13",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -89,6 +89,11 @@ const aiAgentMenu = async () => {
89
89
  }
90
90
  };
91
91
 
92
+ // Cache for scanned tokens (avoid multiple Keychain prompts)
93
+ let cachedTokens = null;
94
+ let cacheTimestamp = 0;
95
+ const CACHE_TTL = 60000; // 1 minute cache
96
+
92
97
  /**
93
98
  * Show existing tokens found on the system
94
99
  */
@@ -102,16 +107,26 @@ const showExistingTokens = async () => {
102
107
  return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
103
108
  };
104
109
 
105
- console.clear();
106
- displayBanner();
107
- drawBoxHeaderContinue('SCANNING FOR EXISTING SESSIONS...', boxWidth);
108
- console.log(makeLine(''));
109
- console.log(makeLine(chalk.gray('CHECKING VS CODE, CURSOR, CLAUDE CLI, OPENCODE...')));
110
- console.log(makeLine(''));
111
- drawBoxFooter(boxWidth);
110
+ // Check cache first
111
+ const now = Date.now();
112
+ let tokens;
112
113
 
113
- // Scan for tokens
114
- const tokens = tokenScanner.scanAllSources();
114
+ if (cachedTokens && (now - cacheTimestamp) < CACHE_TTL) {
115
+ tokens = cachedTokens;
116
+ } else {
117
+ console.clear();
118
+ displayBanner();
119
+ drawBoxHeaderContinue('SCANNING FOR EXISTING SESSIONS...', boxWidth);
120
+ console.log(makeLine(''));
121
+ console.log(makeLine(chalk.gray('CHECKING VS CODE, CURSOR, CLAUDE CLI, OPENCODE...')));
122
+ console.log(makeLine(''));
123
+ drawBoxFooter(boxWidth);
124
+
125
+ // Scan for tokens and cache
126
+ tokens = tokenScanner.scanAllSources();
127
+ cachedTokens = tokens;
128
+ cacheTimestamp = now;
129
+ }
115
130
 
116
131
  if (tokens.length === 0) {
117
132
  // No tokens found, go directly to category selection
@@ -156,40 +156,30 @@ const validateAnthropic = async (credentials) => {
156
156
  const isOAuthToken = token && token.startsWith('sk-ant-oat');
157
157
 
158
158
  if (isOAuthToken) {
159
- // OAuth tokens use Bearer auth and claude.ai endpoint
160
- const response = await fetch('https://api.claude.ai/api/organizations', {
161
- method: 'GET',
162
- headers: {
163
- 'Authorization': `Bearer ${token}`,
164
- 'Content-Type': 'application/json',
165
- 'User-Agent': 'HQX-CLI/1.0'
166
- }
167
- });
168
-
169
- if (response.ok) {
170
- return { valid: true, tokenType: 'oauth', subscriptionType: credentials.subscriptionType || 'max' };
171
- }
172
-
173
- // Try alternate endpoint
174
- const altResponse = await fetch('https://claude.ai/api/auth/session', {
175
- method: 'GET',
176
- headers: {
177
- 'Authorization': `Bearer ${token}`,
178
- 'Cookie': `sessionKey=${token}`
179
- }
180
- });
181
-
182
- if (altResponse.ok) {
183
- return { valid: true, tokenType: 'oauth', subscriptionType: credentials.subscriptionType || 'max' };
159
+ // For OAuth tokens from Keychain/SecureStorage, trust them directly
160
+ // No need to validate - they come from a trusted source (OS secure storage)
161
+ if (credentials.fromKeychain) {
162
+ return {
163
+ valid: true,
164
+ tokenType: 'oauth',
165
+ subscriptionType: credentials.subscriptionType || 'max',
166
+ trusted: true
167
+ };
184
168
  }
185
169
 
186
- // For OAuth tokens from Keychain, trust them without validation
187
- // since the Keychain already verified the user
188
- if (credentials.fromKeychain) {
189
- return { valid: true, tokenType: 'oauth', subscriptionType: credentials.subscriptionType || 'max', trusted: true };
170
+ // For manually entered OAuth tokens, we can't validate them easily
171
+ // Claude doesn't have a public API for OAuth token validation
172
+ // Just check the format and accept
173
+ if (token.length > 50) {
174
+ return {
175
+ valid: true,
176
+ tokenType: 'oauth',
177
+ subscriptionType: 'max',
178
+ warning: 'Token format looks valid, but could not verify online'
179
+ };
190
180
  }
191
181
 
192
- return { valid: false, error: 'OAuth token expired or invalid' };
182
+ return { valid: false, error: 'Invalid OAuth token format' };
193
183
  }
194
184
 
195
185
  // Standard API key validation
@@ -214,6 +204,16 @@ const validateAnthropic = async (credentials) => {
214
204
  const error = await response.json();
215
205
  return { valid: false, error: error.error?.message || 'Invalid API key' };
216
206
  } catch (e) {
207
+ // If validation fails but token is from Keychain, still trust it
208
+ if (credentials.fromKeychain) {
209
+ return {
210
+ valid: true,
211
+ tokenType: 'oauth',
212
+ subscriptionType: credentials.subscriptionType || 'max',
213
+ trusted: true,
214
+ warning: 'Could not validate online, but trusted from Keychain'
215
+ };
216
+ }
217
217
  return { valid: false, error: e.message };
218
218
  }
219
219
  };
@@ -657,14 +657,32 @@ const parseCredentialJson = (output, entry) => {
657
657
 
658
658
  /**
659
659
  * Read tokens from macOS Keychain
660
+ * Optimized: stops after finding first valid token per provider to minimize password prompts
660
661
  */
661
662
  const readMacOSKeychain = () => {
662
663
  if (platform !== 'darwin') return [];
663
664
 
664
665
  const results = [];
665
666
  const { execSync } = require('child_process');
667
+ const foundProviders = new Set(); // Track which providers we already found
668
+
669
+ // Sort entries to prioritize most common ones first
670
+ const priorityOrder = ['Claude Code-credentials', 'Cursor-credentials', 'Claude Safe Storage'];
671
+ const sortedEntries = [...CREDENTIAL_ENTRIES].sort((a, b) => {
672
+ const aIdx = priorityOrder.indexOf(a.service);
673
+ const bIdx = priorityOrder.indexOf(b.service);
674
+ if (aIdx === -1 && bIdx === -1) return 0;
675
+ if (aIdx === -1) return 1;
676
+ if (bIdx === -1) return -1;
677
+ return aIdx - bIdx;
678
+ });
666
679
 
667
- for (const entry of CREDENTIAL_ENTRIES) {
680
+ for (const entry of sortedEntries) {
681
+ // Skip if we already found a token for this provider
682
+ if (foundProviders.has(entry.provider)) {
683
+ continue;
684
+ }
685
+
668
686
  try {
669
687
  const output = execSync(
670
688
  `security find-generic-password -s "${entry.service}" -w 2>/dev/null`,
@@ -672,10 +690,14 @@ const readMacOSKeychain = () => {
672
690
  ).trim();
673
691
 
674
692
  if (output) {
675
- results.push(...parseCredentialJson(output, entry));
693
+ const tokens = parseCredentialJson(output, entry);
694
+ if (tokens.length > 0) {
695
+ results.push(...tokens);
696
+ foundProviders.add(entry.provider); // Mark this provider as found
697
+ }
676
698
  }
677
699
  } catch {
678
- // Entry not found or access denied
700
+ // Entry not found or access denied - no password prompt for missing entries
679
701
  }
680
702
  }
681
703