hedgequantx 2.5.23 → 2.5.25
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/README.md +87 -14
- package/package.json +1 -1
- package/src/menus/ai-agent.js +142 -176
- package/src/services/ai/client.js +77 -8
- package/src/services/ai/index.js +10 -40
- package/src/services/ai/oauth-anthropic.js +265 -0
- package/src/services/ai/providers/index.js +12 -11
- package/src/services/ai/token-scanner.js +0 -1414
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
### Prop Futures Algo Trading CLI
|
|
8
8
|
|
|
9
|
-
*Connect to
|
|
9
|
+
*Connect to 38+ prop firms and automate your futures trading with AI supervision*
|
|
10
10
|
|
|
11
11
|
[](https://hedgequantx.com)
|
|
12
12
|
[](https://www.npmjs.com/package/hedgequantx)
|
|
@@ -21,10 +21,11 @@
|
|
|
21
21
|
|
|
22
22
|
[](https://hedgequantx.com)
|
|
23
23
|
[](https://hedgequantx.com)
|
|
24
|
-
[](https://hedgequantx.com)
|
|
25
|
+
[](https://hedgequantx.com)
|
|
25
26
|
[](https://hedgequantx.com)
|
|
26
27
|
|
|
27
|
-
[Website](https://hedgequantx.com) | [Installation](#installation) | [Features](#features) | [Algo Trading](#algo-trading) | [Discord](https://discord.gg/UBKCERctZu)
|
|
28
|
+
[Website](https://hedgequantx.com) | [Installation](#installation) | [Features](#features) | [AI Integration](#ai-integration) | [Algo Trading](#algo-trading) | [Discord](https://discord.gg/UBKCERctZu)
|
|
28
29
|
|
|
29
30
|
</div>
|
|
30
31
|
|
|
@@ -34,12 +35,13 @@
|
|
|
34
35
|
|
|
35
36
|
| Feature | Description |
|
|
36
37
|
|---------|-------------|
|
|
37
|
-
| **Multi-Platform** | ProjectX &
|
|
38
|
-
| **
|
|
38
|
+
| **Multi-Platform** | ProjectX, Rithmic & Tradovate APIs |
|
|
39
|
+
| **38+ Prop Firms** | TopStep, Apex, Bulenox, and more |
|
|
39
40
|
| **Multi-Account** | Connect multiple accounts simultaneously |
|
|
40
41
|
| **Real-Time Stats** | Balance, P&L, positions, orders |
|
|
41
42
|
| **Algo Trading** | One Account & Copy Trading modes |
|
|
42
|
-
| **
|
|
43
|
+
| **AI Supervision** | Claude, GPT, Gemini, and 15+ providers |
|
|
44
|
+
| **Claude Pro/Max OAuth** | Login with your Claude subscription |
|
|
43
45
|
| **Market Hours** | Auto-blocks when market closed |
|
|
44
46
|
| **Local Execution** | Direct API trading, no server needed |
|
|
45
47
|
| **Secure Storage** | AES-256-GCM encrypted sessions |
|
|
@@ -95,6 +97,55 @@ hqx --version
|
|
|
95
97
|
|
|
96
98
|
---
|
|
97
99
|
|
|
100
|
+
## AI Integration
|
|
101
|
+
|
|
102
|
+
HedgeQuantX integrates with leading AI providers for intelligent trading supervision.
|
|
103
|
+
|
|
104
|
+
### Supported AI Providers
|
|
105
|
+
|
|
106
|
+
| Provider | Authentication | Models |
|
|
107
|
+
|----------|---------------|--------|
|
|
108
|
+
| **Anthropic Claude** | OAuth (Pro/Max) or API Key | Claude 4, Sonnet, Haiku, Opus |
|
|
109
|
+
| **OpenAI** | API Key | GPT-4o, GPT-4, GPT-3.5 |
|
|
110
|
+
| **Google Gemini** | API Key | Gemini Pro, Gemini Flash |
|
|
111
|
+
| **OpenRouter** | API Key | 100+ models (unified API) |
|
|
112
|
+
| **DeepSeek** | API Key | DeepSeek Chat, Coder |
|
|
113
|
+
| **Groq** | API Key | Llama, Mixtral (fast inference) |
|
|
114
|
+
| **xAI** | API Key | Grok |
|
|
115
|
+
| **Mistral** | API Key | Mistral Large, Medium, Small |
|
|
116
|
+
| **Perplexity** | API Key | Sonar models |
|
|
117
|
+
| **Together AI** | API Key | Open source models |
|
|
118
|
+
| **Ollama** | Local | Any local model |
|
|
119
|
+
| **LM Studio** | Local | Any local model |
|
|
120
|
+
|
|
121
|
+
### Claude Pro/Max OAuth Login
|
|
122
|
+
|
|
123
|
+
If you have a Claude Pro or Max subscription, you can login directly without an API key:
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
1. AI Agent Menu -> [+] Add Agent
|
|
127
|
+
2. Select DIRECT PROVIDERS -> CLAUDE (ANTHROPIC)
|
|
128
|
+
3. Choose "CLAUDE PRO/MAX (OAUTH)"
|
|
129
|
+
4. Browser opens -> Login to claude.ai
|
|
130
|
+
5. Copy the authorization code
|
|
131
|
+
6. Paste in terminal -> Connected!
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Benefits:
|
|
135
|
+
- No API key needed
|
|
136
|
+
- Use your existing subscription
|
|
137
|
+
- Unlimited usage with your plan
|
|
138
|
+
- Tokens auto-refresh
|
|
139
|
+
|
|
140
|
+
### AI Features
|
|
141
|
+
|
|
142
|
+
- **Trading Analysis**: Real-time position and risk analysis
|
|
143
|
+
- **Multi-Agent Support**: Connect multiple AI providers simultaneously
|
|
144
|
+
- **Auto Token Scanner**: Detects existing AI sessions from VS Code, Cursor, Claude CLI
|
|
145
|
+
- **Token Auto-Refresh**: OAuth tokens refresh automatically when expired
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
98
149
|
## Algo Trading
|
|
99
150
|
|
|
100
151
|
### One Account Mode
|
|
@@ -107,12 +158,18 @@ Trade on a single account with HQX algo strategy.
|
|
|
107
158
|
|
|
108
159
|
### Copy Trading Mode
|
|
109
160
|
|
|
110
|
-
Mirror trades from Lead to Follower accounts.
|
|
161
|
+
Mirror trades from Lead to Follower accounts with real execution.
|
|
111
162
|
|
|
112
163
|
<div align="center">
|
|
113
164
|
<img src="assets/copy-trading.png" alt="HQX Copy Trading" width="800">
|
|
114
165
|
</div>
|
|
115
166
|
|
|
167
|
+
Features:
|
|
168
|
+
- Real order execution via API
|
|
169
|
+
- Position synchronization
|
|
170
|
+
- Multi-account support
|
|
171
|
+
- Configurable lot multiplier
|
|
172
|
+
|
|
116
173
|
---
|
|
117
174
|
|
|
118
175
|
## Supported Prop Firms
|
|
@@ -151,13 +208,27 @@ Mirror trades from Lead to Follower accounts.
|
|
|
151
208
|
| Rate Limiting | API protection |
|
|
152
209
|
| File Permissions | 0600 (owner only) |
|
|
153
210
|
| Credentials | Never stored in plain text |
|
|
211
|
+
| OAuth Tokens | Secure PKCE flow |
|
|
154
212
|
|
|
155
213
|
---
|
|
156
214
|
|
|
157
215
|
## Changelog
|
|
158
216
|
|
|
159
217
|
<details>
|
|
160
|
-
<summary><b>
|
|
218
|
+
<summary><b>v2.5.x (Current)</b></summary>
|
|
219
|
+
|
|
220
|
+
- **AI Integration**: Multi-provider AI supervision
|
|
221
|
+
- **Claude OAuth**: Login with Pro/Max subscription (no API key needed)
|
|
222
|
+
- **Token Scanner**: Auto-detect AI sessions from VS Code, Cursor, Claude CLI
|
|
223
|
+
- **Real API Models**: Fetch models from provider APIs (no hardcoded lists)
|
|
224
|
+
- **Copy Trading**: Real order execution via API
|
|
225
|
+
- **Multi-Agent**: Connect multiple AI providers simultaneously
|
|
226
|
+
- **Auto Token Refresh**: OAuth tokens refresh automatically
|
|
227
|
+
|
|
228
|
+
</details>
|
|
229
|
+
|
|
230
|
+
<details>
|
|
231
|
+
<summary><b>v1.8.x</b></summary>
|
|
161
232
|
|
|
162
233
|
- Separate UI for One Account and Copy Trading
|
|
163
234
|
- Market hours validation
|
|
@@ -194,12 +265,14 @@ Mirror trades from Lead to Follower accounts.
|
|
|
194
265
|
|
|
195
266
|
| Done | Done | Coming Soon |
|
|
196
267
|
|------|------|-------------|
|
|
197
|
-
| :white_check_mark: ProjectX integration | :white_check_mark: One Account mode | :hourglass:
|
|
198
|
-
| :white_check_mark: Rithmic integration | :white_check_mark: Copy Trading mode | :hourglass:
|
|
199
|
-
| :white_check_mark: 38+ prop firms | :white_check_mark: HQX Server | :hourglass:
|
|
200
|
-
| :white_check_mark: Multi-account | :white_check_mark: Market hours check | :hourglass:
|
|
201
|
-
| :white_check_mark: Trailing SL & BE | :white_check_mark: Session summary | :hourglass:
|
|
202
|
-
| :white_check_mark: Encrypted sessions | :white_check_mark: Auto-update | :hourglass:
|
|
268
|
+
| :white_check_mark: ProjectX integration | :white_check_mark: One Account mode | :hourglass: Telegram alerts |
|
|
269
|
+
| :white_check_mark: Rithmic integration | :white_check_mark: Copy Trading mode | :hourglass: Multi-symbol trading |
|
|
270
|
+
| :white_check_mark: 38+ prop firms | :white_check_mark: HQX Server | :hourglass: Performance analytics |
|
|
271
|
+
| :white_check_mark: Multi-account | :white_check_mark: Market hours check | :hourglass: Trade journal export |
|
|
272
|
+
| :white_check_mark: Trailing SL & BE | :white_check_mark: Session summary | :hourglass: Web dashboard |
|
|
273
|
+
| :white_check_mark: Encrypted sessions | :white_check_mark: Auto-update | :hourglass: Advanced AI strategies |
|
|
274
|
+
| :white_check_mark: AI Integration | :white_check_mark: Claude OAuth | :hourglass: Voice commands |
|
|
275
|
+
| :white_check_mark: Multi-AI Agents | :white_check_mark: Token Scanner | |
|
|
203
276
|
|
|
204
277
|
---
|
|
205
278
|
|
package/package.json
CHANGED
package/src/menus/ai-agent.js
CHANGED
|
@@ -10,7 +10,7 @@ const { getLogoWidth, drawBoxHeader, drawBoxHeaderContinue, drawBoxFooter, displ
|
|
|
10
10
|
const { prompts } = require('../utils');
|
|
11
11
|
const aiService = require('../services/ai');
|
|
12
12
|
const { getCategories, getProvidersByCategory } = require('../services/ai/providers');
|
|
13
|
-
const
|
|
13
|
+
const oauthAnthropic = require('../services/ai/oauth-anthropic');
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Main AI Agent menu
|
|
@@ -117,7 +117,7 @@ const aiAgentMenu = async () => {
|
|
|
117
117
|
|
|
118
118
|
switch (input) {
|
|
119
119
|
case '+':
|
|
120
|
-
return await
|
|
120
|
+
return await selectCategory();
|
|
121
121
|
case 's':
|
|
122
122
|
if (agentCount > 1) {
|
|
123
123
|
return await selectActiveAgent();
|
|
@@ -366,178 +366,6 @@ const selectAgentToRemove = async () => {
|
|
|
366
366
|
return await aiAgentMenu();
|
|
367
367
|
};
|
|
368
368
|
|
|
369
|
-
// Cache for scanned tokens (avoid multiple Keychain prompts)
|
|
370
|
-
let cachedTokens = null;
|
|
371
|
-
let cacheTimestamp = 0;
|
|
372
|
-
const CACHE_TTL = 60000; // 1 minute cache
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* Show existing tokens found on the system
|
|
376
|
-
*/
|
|
377
|
-
const showExistingTokens = async () => {
|
|
378
|
-
const boxWidth = getLogoWidth();
|
|
379
|
-
const W = boxWidth - 2;
|
|
380
|
-
|
|
381
|
-
const makeLine = (content) => {
|
|
382
|
-
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
383
|
-
const padding = W - plainLen;
|
|
384
|
-
return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
|
|
385
|
-
};
|
|
386
|
-
|
|
387
|
-
// Check cache first
|
|
388
|
-
const now = Date.now();
|
|
389
|
-
let tokens;
|
|
390
|
-
|
|
391
|
-
if (cachedTokens && (now - cacheTimestamp) < CACHE_TTL) {
|
|
392
|
-
tokens = cachedTokens;
|
|
393
|
-
} else {
|
|
394
|
-
console.clear();
|
|
395
|
-
displayBanner();
|
|
396
|
-
drawBoxHeaderContinue('SCANNING FOR EXISTING SESSIONS...', boxWidth);
|
|
397
|
-
console.log(makeLine(''));
|
|
398
|
-
console.log(makeLine(chalk.gray('CHECKING VS CODE, CURSOR, CLAUDE CLI, OPENCODE...')));
|
|
399
|
-
console.log(makeLine(''));
|
|
400
|
-
drawBoxFooter(boxWidth);
|
|
401
|
-
|
|
402
|
-
// Scan for tokens and cache
|
|
403
|
-
tokens = tokenScanner.scanAllSources();
|
|
404
|
-
cachedTokens = tokens;
|
|
405
|
-
cacheTimestamp = now;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
if (tokens.length === 0) {
|
|
409
|
-
// No tokens found, go directly to category selection
|
|
410
|
-
return await selectCategory();
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// Show found tokens
|
|
414
|
-
console.clear();
|
|
415
|
-
displayBanner();
|
|
416
|
-
drawBoxHeaderContinue('EXISTING SESSIONS FOUND', boxWidth);
|
|
417
|
-
|
|
418
|
-
console.log(makeLine(chalk.green(`FOUND ${tokens.length} EXISTING SESSION(S)`)));
|
|
419
|
-
console.log(makeLine(''));
|
|
420
|
-
|
|
421
|
-
const formatted = tokenScanner.formatResults(tokens);
|
|
422
|
-
|
|
423
|
-
for (const t of formatted) {
|
|
424
|
-
const providerColor = t.provider.includes('CLAUDE') ? chalk.magenta :
|
|
425
|
-
t.provider.includes('OPENAI') ? chalk.green :
|
|
426
|
-
t.provider.includes('OPENROUTER') ? chalk.yellow : chalk.cyan;
|
|
427
|
-
|
|
428
|
-
console.log(makeLine(
|
|
429
|
-
chalk.white(`[${t.index}] `) +
|
|
430
|
-
providerColor(t.provider) +
|
|
431
|
-
chalk.gray(` (${t.type})`)
|
|
432
|
-
));
|
|
433
|
-
console.log(makeLine(
|
|
434
|
-
chalk.gray(` ${t.icon} ${t.source} - ${t.lastUsed}`)
|
|
435
|
-
));
|
|
436
|
-
console.log(makeLine(
|
|
437
|
-
chalk.gray(` TOKEN: ${t.tokenPreview}`)
|
|
438
|
-
));
|
|
439
|
-
console.log(makeLine(''));
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
443
|
-
console.log(makeLine(chalk.cyan('[N] CONNECT NEW PROVIDER')));
|
|
444
|
-
console.log(makeLine(chalk.gray('[<] BACK')));
|
|
445
|
-
|
|
446
|
-
drawBoxFooter(boxWidth);
|
|
447
|
-
|
|
448
|
-
const choice = await prompts.textInput(chalk.cyan('SELECT (1-' + tokens.length + '/N/<):'));
|
|
449
|
-
|
|
450
|
-
if (choice === '<' || choice?.toLowerCase() === 'b') {
|
|
451
|
-
return await aiAgentMenu();
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
if (choice?.toLowerCase() === 'n') {
|
|
455
|
-
return await selectCategory();
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
const index = parseInt(choice) - 1;
|
|
459
|
-
if (isNaN(index) || index < 0 || index >= tokens.length) {
|
|
460
|
-
return await showExistingTokens();
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// Use selected token
|
|
464
|
-
const selectedToken = tokens[index];
|
|
465
|
-
|
|
466
|
-
const spinner = ora({ text: 'VALIDATING TOKEN...', color: 'cyan' }).start();
|
|
467
|
-
|
|
468
|
-
try {
|
|
469
|
-
// Validate the token - include metadata from scanner
|
|
470
|
-
const credentials = {
|
|
471
|
-
apiKey: selectedToken.token,
|
|
472
|
-
sessionKey: selectedToken.token,
|
|
473
|
-
accessToken: selectedToken.token,
|
|
474
|
-
fromKeychain: selectedToken.sourceId === 'secureStorage' || selectedToken.sourceId === 'keychain',
|
|
475
|
-
subscriptionType: selectedToken.subscriptionType,
|
|
476
|
-
refreshToken: selectedToken.refreshToken,
|
|
477
|
-
expiresAt: selectedToken.expiresAt
|
|
478
|
-
};
|
|
479
|
-
const validation = await aiService.validateConnection(selectedToken.provider, selectedToken.type, credentials);
|
|
480
|
-
|
|
481
|
-
if (!validation.valid) {
|
|
482
|
-
spinner.fail(`TOKEN INVALID OR EXPIRED: ${validation.error}`);
|
|
483
|
-
await prompts.waitForEnter();
|
|
484
|
-
return await showExistingTokens();
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
// Get provider info
|
|
488
|
-
const { getProvider } = require('../services/ai/providers');
|
|
489
|
-
const provider = getProvider(selectedToken.provider);
|
|
490
|
-
|
|
491
|
-
if (!provider) {
|
|
492
|
-
spinner.fail('PROVIDER NOT SUPPORTED');
|
|
493
|
-
await prompts.waitForEnter();
|
|
494
|
-
return await showExistingTokens();
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
spinner.text = 'FETCHING AVAILABLE MODELS...';
|
|
498
|
-
|
|
499
|
-
// Fetch models from API with the token
|
|
500
|
-
const { fetchAnthropicModels, fetchOpenAIModels } = require('../services/ai/client');
|
|
501
|
-
|
|
502
|
-
let models = null;
|
|
503
|
-
if (selectedToken.provider === 'anthropic') {
|
|
504
|
-
models = await fetchAnthropicModels(credentials.apiKey);
|
|
505
|
-
} else {
|
|
506
|
-
models = await fetchOpenAIModels(provider.endpoint, credentials.apiKey);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
if (!models || models.length === 0) {
|
|
510
|
-
spinner.fail('COULD NOT FETCH MODELS FROM API');
|
|
511
|
-
await prompts.waitForEnter();
|
|
512
|
-
return await showExistingTokens();
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
spinner.succeed(`FOUND ${models.length} MODELS`);
|
|
516
|
-
|
|
517
|
-
// Let user select model
|
|
518
|
-
const selectedModel = await selectModelFromList(models, provider.name);
|
|
519
|
-
if (!selectedModel) {
|
|
520
|
-
return await showExistingTokens();
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
// Add agent with selected model
|
|
524
|
-
const agentName = `${provider.name} (${selectedToken.source})`;
|
|
525
|
-
await aiService.addAgent(selectedToken.provider, 'api_key', credentials, selectedModel, agentName);
|
|
526
|
-
|
|
527
|
-
console.log(chalk.green(`\n AGENT ADDED: ${provider.name}`));
|
|
528
|
-
console.log(chalk.gray(` SOURCE: ${selectedToken.source}`));
|
|
529
|
-
console.log(chalk.gray(` MODEL: ${selectedModel}`));
|
|
530
|
-
|
|
531
|
-
await prompts.waitForEnter();
|
|
532
|
-
return await aiAgentMenu();
|
|
533
|
-
|
|
534
|
-
} catch (error) {
|
|
535
|
-
spinner.fail(`CONNECTION FAILED: ${error.message}`);
|
|
536
|
-
await prompts.waitForEnter();
|
|
537
|
-
return await showExistingTokens();
|
|
538
|
-
}
|
|
539
|
-
};
|
|
540
|
-
|
|
541
369
|
/**
|
|
542
370
|
* Select provider category
|
|
543
371
|
*/
|
|
@@ -845,10 +673,137 @@ const getCredentialInstructions = (provider, option, field) => {
|
|
|
845
673
|
return instructions[field] || { title: field.toUpperCase(), steps: [] };
|
|
846
674
|
};
|
|
847
675
|
|
|
676
|
+
/**
|
|
677
|
+
* Setup OAuth connection for Anthropic Claude Pro/Max
|
|
678
|
+
*/
|
|
679
|
+
const setupOAuthConnection = async (provider) => {
|
|
680
|
+
const boxWidth = getLogoWidth();
|
|
681
|
+
const W = boxWidth - 2;
|
|
682
|
+
|
|
683
|
+
const makeLine = (content) => {
|
|
684
|
+
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
685
|
+
const padding = W - plainLen;
|
|
686
|
+
return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
console.clear();
|
|
690
|
+
displayBanner();
|
|
691
|
+
drawBoxHeaderContinue('CLAUDE PRO/MAX LOGIN', boxWidth);
|
|
692
|
+
|
|
693
|
+
console.log(makeLine(chalk.yellow('OAUTH AUTHENTICATION')));
|
|
694
|
+
console.log(makeLine(''));
|
|
695
|
+
console.log(makeLine(chalk.white('1. A BROWSER WINDOW WILL OPEN')));
|
|
696
|
+
console.log(makeLine(chalk.white('2. LOGIN WITH YOUR CLAUDE ACCOUNT')));
|
|
697
|
+
console.log(makeLine(chalk.white('3. COPY THE AUTHORIZATION CODE')));
|
|
698
|
+
console.log(makeLine(chalk.white('4. PASTE IT HERE')));
|
|
699
|
+
console.log(makeLine(''));
|
|
700
|
+
console.log(makeLine(chalk.gray('OPENING BROWSER IN 3 SECONDS...')));
|
|
701
|
+
|
|
702
|
+
drawBoxFooter(boxWidth);
|
|
703
|
+
|
|
704
|
+
// Generate OAuth URL
|
|
705
|
+
const { url, verifier } = oauthAnthropic.authorize('max');
|
|
706
|
+
|
|
707
|
+
// Wait a moment then open browser
|
|
708
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
709
|
+
openBrowser(url);
|
|
710
|
+
|
|
711
|
+
// Redraw with code input
|
|
712
|
+
console.clear();
|
|
713
|
+
displayBanner();
|
|
714
|
+
drawBoxHeaderContinue('CLAUDE PRO/MAX LOGIN', boxWidth);
|
|
715
|
+
|
|
716
|
+
console.log(makeLine(chalk.green('BROWSER OPENED')));
|
|
717
|
+
console.log(makeLine(''));
|
|
718
|
+
console.log(makeLine(chalk.white('AFTER LOGGING IN, YOU WILL SEE A CODE')));
|
|
719
|
+
console.log(makeLine(chalk.white('COPY THE ENTIRE CODE AND PASTE IT BELOW')));
|
|
720
|
+
console.log(makeLine(''));
|
|
721
|
+
console.log(makeLine(chalk.gray('THE CODE LOOKS LIKE: abc123...#xyz789...')));
|
|
722
|
+
console.log(makeLine(''));
|
|
723
|
+
console.log(makeLine(chalk.gray('TYPE < TO CANCEL')));
|
|
724
|
+
|
|
725
|
+
drawBoxFooter(boxWidth);
|
|
726
|
+
console.log();
|
|
727
|
+
|
|
728
|
+
const code = await prompts.textInput(chalk.cyan('PASTE AUTHORIZATION CODE:'));
|
|
729
|
+
|
|
730
|
+
if (!code || code === '<') {
|
|
731
|
+
return await selectProviderOption(provider);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// Exchange code for tokens
|
|
735
|
+
const spinner = ora({ text: 'EXCHANGING CODE FOR TOKENS...', color: 'cyan' }).start();
|
|
736
|
+
|
|
737
|
+
const result = await oauthAnthropic.exchange(code.trim(), verifier);
|
|
738
|
+
|
|
739
|
+
if (result.type === 'failed') {
|
|
740
|
+
spinner.fail(`AUTHENTICATION FAILED: ${result.error || 'Invalid code'}`);
|
|
741
|
+
await prompts.waitForEnter();
|
|
742
|
+
return await selectProviderOption(provider);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
spinner.text = 'FETCHING AVAILABLE MODELS...';
|
|
746
|
+
|
|
747
|
+
// Store OAuth credentials
|
|
748
|
+
const credentials = {
|
|
749
|
+
oauth: {
|
|
750
|
+
access: result.access,
|
|
751
|
+
refresh: result.refresh,
|
|
752
|
+
expires: result.expires
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
// Fetch models using OAuth token
|
|
757
|
+
const { fetchAnthropicModelsOAuth } = require('../services/ai/client');
|
|
758
|
+
const models = await fetchAnthropicModelsOAuth(result.access);
|
|
759
|
+
|
|
760
|
+
if (!models || models.length === 0) {
|
|
761
|
+
// Use default models if API doesn't return list
|
|
762
|
+
spinner.warn('COULD NOT FETCH MODEL LIST, USING DEFAULTS');
|
|
763
|
+
} else {
|
|
764
|
+
spinner.succeed(`FOUND ${models.length} MODELS`);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
if (!models || models.length === 0) {
|
|
768
|
+
spinner.fail('COULD NOT FETCH MODELS FROM API');
|
|
769
|
+
console.log(chalk.gray(' OAuth authentication may not support model listing.'));
|
|
770
|
+
console.log(chalk.gray(' Please use API KEY authentication instead.'));
|
|
771
|
+
await prompts.waitForEnter();
|
|
772
|
+
return await selectProviderOption(provider);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
spinner.succeed(`FOUND ${models.length} MODELS`);
|
|
776
|
+
|
|
777
|
+
// Let user select model
|
|
778
|
+
const selectedModel = await selectModelFromList(models, 'CLAUDE PRO/MAX');
|
|
779
|
+
if (!selectedModel) {
|
|
780
|
+
return await selectProviderOption(provider);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// Add agent with OAuth credentials
|
|
784
|
+
try {
|
|
785
|
+
await aiService.addAgent('anthropic', 'oauth_max', credentials, selectedModel, 'Claude Pro/Max');
|
|
786
|
+
|
|
787
|
+
console.log(chalk.green('\n CONNECTED TO CLAUDE PRO/MAX'));
|
|
788
|
+
console.log(chalk.gray(` MODEL: ${selectedModel}`));
|
|
789
|
+
console.log(chalk.gray(' UNLIMITED USAGE WITH YOUR SUBSCRIPTION'));
|
|
790
|
+
} catch (error) {
|
|
791
|
+
console.log(chalk.red(`\n FAILED TO SAVE: ${error.message}`));
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
await prompts.waitForEnter();
|
|
795
|
+
return await aiAgentMenu();
|
|
796
|
+
};
|
|
797
|
+
|
|
848
798
|
/**
|
|
849
799
|
* Setup connection with credentials
|
|
850
800
|
*/
|
|
851
801
|
const setupConnection = async (provider, option) => {
|
|
802
|
+
// Handle OAuth flow separately
|
|
803
|
+
if (option.authType === 'oauth') {
|
|
804
|
+
return await setupOAuthConnection(provider);
|
|
805
|
+
}
|
|
806
|
+
|
|
852
807
|
const boxWidth = getLogoWidth();
|
|
853
808
|
const W = boxWidth - 2;
|
|
854
809
|
|
|
@@ -1047,13 +1002,24 @@ const selectModel = async (agent) => {
|
|
|
1047
1002
|
drawBoxFooter(boxWidth);
|
|
1048
1003
|
|
|
1049
1004
|
// Fetch models from real API
|
|
1050
|
-
const { fetchAnthropicModels, fetchOpenAIModels } = require('../services/ai/client');
|
|
1005
|
+
const { fetchAnthropicModels, fetchAnthropicModelsOAuth, fetchOpenAIModels } = require('../services/ai/client');
|
|
1051
1006
|
|
|
1052
1007
|
let models = null;
|
|
1053
1008
|
const agentCredentials = aiService.getAgentCredentials(agent.id);
|
|
1054
1009
|
|
|
1055
1010
|
if (agent.providerId === 'anthropic') {
|
|
1056
|
-
|
|
1011
|
+
// Check if OAuth credentials or OAuth-like token (sk-ant-oat...)
|
|
1012
|
+
const token = agentCredentials?.apiKey || agentCredentials?.accessToken || agentCredentials?.sessionKey;
|
|
1013
|
+
const isOAuthToken = agentCredentials?.oauth?.access || (token && token.startsWith('sk-ant-oat'));
|
|
1014
|
+
|
|
1015
|
+
if (isOAuthToken) {
|
|
1016
|
+
// Use OAuth endpoint with Bearer token
|
|
1017
|
+
const accessToken = agentCredentials?.oauth?.access || token;
|
|
1018
|
+
models = await fetchAnthropicModelsOAuth(accessToken);
|
|
1019
|
+
} else {
|
|
1020
|
+
// Standard API key
|
|
1021
|
+
models = await fetchAnthropicModels(token);
|
|
1022
|
+
}
|
|
1057
1023
|
} else {
|
|
1058
1024
|
// OpenAI-compatible providers
|
|
1059
1025
|
const endpoint = agentCredentials?.endpoint || agent.provider?.endpoint;
|
|
@@ -105,8 +105,32 @@ const callOpenAICompatible = async (agent, prompt, systemPrompt) => {
|
|
|
105
105
|
}
|
|
106
106
|
};
|
|
107
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Get valid OAuth token (refresh if needed)
|
|
110
|
+
* @param {Object} credentials - Agent credentials with oauth data
|
|
111
|
+
* @returns {Promise<string|null>} Valid access token or null
|
|
112
|
+
*/
|
|
113
|
+
const getValidOAuthToken = async (credentials) => {
|
|
114
|
+
if (!credentials?.oauth) return null;
|
|
115
|
+
|
|
116
|
+
const oauthAnthropic = require('./oauth-anthropic');
|
|
117
|
+
const validToken = await oauthAnthropic.getValidToken(credentials.oauth);
|
|
118
|
+
|
|
119
|
+
if (!validToken) return null;
|
|
120
|
+
|
|
121
|
+
// If token was refreshed, we should update storage (handled by caller)
|
|
122
|
+
if (validToken.refreshed) {
|
|
123
|
+
credentials.oauth.access = validToken.access;
|
|
124
|
+
credentials.oauth.refresh = validToken.refresh;
|
|
125
|
+
credentials.oauth.expires = validToken.expires;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return validToken.access;
|
|
129
|
+
};
|
|
130
|
+
|
|
108
131
|
/**
|
|
109
132
|
* Call Anthropic Claude API
|
|
133
|
+
* Supports both API key and OAuth authentication
|
|
110
134
|
* @param {Object} agent - Agent configuration
|
|
111
135
|
* @param {string} prompt - User prompt
|
|
112
136
|
* @param {string} systemPrompt - System prompt
|
|
@@ -116,19 +140,31 @@ const callAnthropic = async (agent, prompt, systemPrompt) => {
|
|
|
116
140
|
const provider = getProvider('anthropic');
|
|
117
141
|
if (!provider) return null;
|
|
118
142
|
|
|
119
|
-
const apiKey = agent.credentials?.apiKey;
|
|
120
143
|
const model = agent.model || provider.defaultModel;
|
|
121
|
-
|
|
122
|
-
if (!apiKey) return null;
|
|
123
|
-
|
|
124
144
|
const url = `${provider.endpoint}/messages`;
|
|
125
145
|
|
|
126
|
-
|
|
146
|
+
// Determine authentication method
|
|
147
|
+
const isOAuth = agent.credentials?.oauth?.refresh;
|
|
148
|
+
let headers = {
|
|
127
149
|
'Content-Type': 'application/json',
|
|
128
|
-
'x-api-key': apiKey,
|
|
129
150
|
'anthropic-version': '2023-06-01'
|
|
130
151
|
};
|
|
131
152
|
|
|
153
|
+
if (isOAuth) {
|
|
154
|
+
// OAuth Bearer token authentication
|
|
155
|
+
const accessToken = await getValidOAuthToken(agent.credentials);
|
|
156
|
+
if (!accessToken) return null;
|
|
157
|
+
|
|
158
|
+
headers['Authorization'] = `Bearer ${accessToken}`;
|
|
159
|
+
headers['anthropic-beta'] = 'oauth-2025-04-20,interleaved-thinking-2025-05-14';
|
|
160
|
+
} else {
|
|
161
|
+
// Standard API key authentication
|
|
162
|
+
const apiKey = agent.credentials?.apiKey;
|
|
163
|
+
if (!apiKey) return null;
|
|
164
|
+
|
|
165
|
+
headers['x-api-key'] = apiKey;
|
|
166
|
+
}
|
|
167
|
+
|
|
132
168
|
const body = {
|
|
133
169
|
model,
|
|
134
170
|
max_tokens: 500,
|
|
@@ -273,7 +309,7 @@ Analyze and provide recommendation.`;
|
|
|
273
309
|
};
|
|
274
310
|
|
|
275
311
|
/**
|
|
276
|
-
* Fetch available models from Anthropic API
|
|
312
|
+
* Fetch available models from Anthropic API (API Key auth)
|
|
277
313
|
* @param {string} apiKey - API key
|
|
278
314
|
* @returns {Promise<Array|null>} Array of model IDs or null on error
|
|
279
315
|
*
|
|
@@ -300,6 +336,37 @@ const fetchAnthropicModels = async (apiKey) => {
|
|
|
300
336
|
}
|
|
301
337
|
};
|
|
302
338
|
|
|
339
|
+
/**
|
|
340
|
+
* Fetch available models from Anthropic API (OAuth auth)
|
|
341
|
+
* @param {string} accessToken - OAuth access token
|
|
342
|
+
* @returns {Promise<Array|null>} Array of model IDs or null on error
|
|
343
|
+
*
|
|
344
|
+
* Data source: https://api.anthropic.com/v1/models (GET with Bearer token)
|
|
345
|
+
*/
|
|
346
|
+
const fetchAnthropicModelsOAuth = async (accessToken) => {
|
|
347
|
+
if (!accessToken) return null;
|
|
348
|
+
|
|
349
|
+
const url = 'https://api.anthropic.com/v1/models';
|
|
350
|
+
|
|
351
|
+
const headers = {
|
|
352
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
353
|
+
'anthropic-version': '2023-06-01',
|
|
354
|
+
'anthropic-beta': 'oauth-2025-04-20'
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
try {
|
|
358
|
+
const response = await makeRequest(url, { method: 'GET', headers, timeout: 10000 });
|
|
359
|
+
if (response.data && Array.isArray(response.data)) {
|
|
360
|
+
const models = response.data.map(m => m.id).filter(Boolean);
|
|
361
|
+
if (models.length > 0) return models;
|
|
362
|
+
}
|
|
363
|
+
return null;
|
|
364
|
+
} catch (error) {
|
|
365
|
+
// OAuth token may not support /models endpoint
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
303
370
|
/**
|
|
304
371
|
* Fetch available models from OpenAI-compatible API
|
|
305
372
|
* @param {string} endpoint - API endpoint
|
|
@@ -339,5 +406,7 @@ module.exports = {
|
|
|
339
406
|
callAnthropic,
|
|
340
407
|
callGemini,
|
|
341
408
|
fetchAnthropicModels,
|
|
342
|
-
|
|
409
|
+
fetchAnthropicModelsOAuth,
|
|
410
|
+
fetchOpenAIModels,
|
|
411
|
+
getValidOAuthToken
|
|
343
412
|
};
|