hedgequantx 2.6.95 → 2.6.97
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 +1 -1
- package/src/menus/ai-agent.js +194 -4
- package/src/services/ai/proxy-manager.js +162 -1
package/package.json
CHANGED
package/src/menus/ai-agent.js
CHANGED
|
@@ -782,7 +782,9 @@ const getOAuthConfig = (providerId) => {
|
|
|
782
782
|
|
|
783
783
|
/**
|
|
784
784
|
* Setup OAuth connection for any provider with OAuth support
|
|
785
|
-
*
|
|
785
|
+
* Automatically detects environment and uses the appropriate method:
|
|
786
|
+
* - Local (PC/Mac): Uses CLIProxyAPI for automatic token management
|
|
787
|
+
* - Remote (VPS/Server): Uses cli.hedgequantx.com as OAuth relay
|
|
786
788
|
*/
|
|
787
789
|
const setupOAuthConnection = async (provider) => {
|
|
788
790
|
const config = getOAuthConfig(provider.id);
|
|
@@ -792,14 +794,202 @@ const setupOAuthConnection = async (provider) => {
|
|
|
792
794
|
return await selectProviderOption(provider);
|
|
793
795
|
}
|
|
794
796
|
|
|
795
|
-
//
|
|
796
|
-
|
|
797
|
+
// Check if we can open a browser locally
|
|
798
|
+
const canUseBrowser = proxyManager.canOpenBrowser();
|
|
799
|
+
const isServer = proxyManager.isServerEnvironment();
|
|
800
|
+
|
|
801
|
+
// If on server or can't open browser, use remote OAuth
|
|
802
|
+
if (isServer || !canUseBrowser) {
|
|
803
|
+
// Only anthropic is supported for remote OAuth currently
|
|
804
|
+
if (provider.id === 'anthropic') {
|
|
805
|
+
return await setupRemoteOAuth(provider, config);
|
|
806
|
+
} else {
|
|
807
|
+
// For other providers on VPS, show message
|
|
808
|
+
const boxWidth = getLogoWidth();
|
|
809
|
+
const W = boxWidth - 2;
|
|
810
|
+
const makeLine = (content) => {
|
|
811
|
+
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
812
|
+
const padding = W - plainLen;
|
|
813
|
+
return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
|
|
814
|
+
};
|
|
815
|
+
|
|
816
|
+
console.clear();
|
|
817
|
+
displayBanner();
|
|
818
|
+
drawBoxHeaderContinue('SERVER DETECTED', boxWidth);
|
|
819
|
+
|
|
820
|
+
console.log(makeLine(chalk.yellow('VPS/SERVER ENVIRONMENT DETECTED')));
|
|
821
|
+
console.log(makeLine(''));
|
|
822
|
+
console.log(makeLine(chalk.white('OAuth for this provider requires a browser.')));
|
|
823
|
+
console.log(makeLine(''));
|
|
824
|
+
console.log(makeLine(chalk.white('OPTIONS:')));
|
|
825
|
+
console.log(makeLine(chalk.cyan('1. Use Claude (supports remote OAuth)')));
|
|
826
|
+
console.log(makeLine(chalk.cyan('2. Use API Key instead of OAuth')));
|
|
827
|
+
console.log(makeLine(chalk.cyan('3. Run this on a local machine first')));
|
|
828
|
+
console.log(makeLine(''));
|
|
829
|
+
|
|
830
|
+
drawBoxFooter(boxWidth);
|
|
831
|
+
await prompts.waitForEnter();
|
|
832
|
+
return await selectProviderOption(provider);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// Local machine - use CLIProxyAPI
|
|
797
837
|
return await setupProxyOAuth(provider, config);
|
|
798
838
|
};
|
|
799
839
|
|
|
840
|
+
/**
|
|
841
|
+
* Setup OAuth via Manual Code Entry (for VPS/Server users)
|
|
842
|
+
* User copies the authorization code from the browser
|
|
843
|
+
*/
|
|
844
|
+
const setupRemoteOAuth = async (provider, config) => {
|
|
845
|
+
const boxWidth = getLogoWidth();
|
|
846
|
+
const W = boxWidth - 2;
|
|
847
|
+
|
|
848
|
+
const makeLine = (content) => {
|
|
849
|
+
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
850
|
+
const padding = W - plainLen;
|
|
851
|
+
return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
|
|
852
|
+
};
|
|
853
|
+
|
|
854
|
+
// Generate OAuth URL using the existing oauth module
|
|
855
|
+
const authResult = oauthAnthropic.authorize('max');
|
|
856
|
+
const url = authResult.url;
|
|
857
|
+
const verifier = authResult.verifier;
|
|
858
|
+
|
|
859
|
+
// Show instructions
|
|
860
|
+
console.clear();
|
|
861
|
+
displayBanner();
|
|
862
|
+
drawBoxHeaderContinue(`CONNECT ${config.name} (VPS MODE)`, boxWidth);
|
|
863
|
+
|
|
864
|
+
console.log(makeLine(chalk.yellow('MANUAL AUTHORIZATION')));
|
|
865
|
+
console.log(makeLine(''));
|
|
866
|
+
console.log(makeLine(chalk.white('1. OPEN THE LINK BELOW IN ANY BROWSER')));
|
|
867
|
+
console.log(makeLine(chalk.white(' (Phone, laptop, any device)')));
|
|
868
|
+
console.log(makeLine(''));
|
|
869
|
+
console.log(makeLine(chalk.white(`2. LOGIN WITH YOUR ${config.accountName.toUpperCase()} ACCOUNT`)));
|
|
870
|
+
console.log(makeLine(''));
|
|
871
|
+
console.log(makeLine(chalk.white('3. CLICK "AUTHORIZE"')));
|
|
872
|
+
console.log(makeLine(''));
|
|
873
|
+
console.log(makeLine(chalk.green('4. COPY THE CODE SHOWN ON SCREEN')));
|
|
874
|
+
console.log(makeLine(chalk.white(' (Format: abc123...#xyz789...)')));
|
|
875
|
+
console.log(makeLine(''));
|
|
876
|
+
console.log(makeLine(chalk.white('5. PASTE THE CODE BELOW')));
|
|
877
|
+
console.log(makeLine(''));
|
|
878
|
+
console.log(makeLine(chalk.white('TYPE < TO CANCEL')));
|
|
879
|
+
|
|
880
|
+
drawBoxFooter(boxWidth);
|
|
881
|
+
|
|
882
|
+
// Display URL outside the box for easy copy
|
|
883
|
+
console.log();
|
|
884
|
+
console.log(chalk.yellow(' OPEN THIS URL IN YOUR BROWSER:'));
|
|
885
|
+
console.log();
|
|
886
|
+
console.log(chalk.cyan(` ${url}`));
|
|
887
|
+
console.log();
|
|
888
|
+
|
|
889
|
+
// Get code from user
|
|
890
|
+
const code = await prompts.textInput(chalk.cyan('PASTE AUTHORIZATION CODE:'));
|
|
891
|
+
|
|
892
|
+
if (!code || code.trim() === '<' || code.trim() === '') {
|
|
893
|
+
return await selectProviderOption(provider);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// Exchange code for tokens
|
|
897
|
+
const spinner = ora({ text: 'Exchanging code for tokens...', color: 'cyan' }).start();
|
|
898
|
+
|
|
899
|
+
const result = await oauthAnthropic.exchange(code.trim(), verifier);
|
|
900
|
+
|
|
901
|
+
if (result.type === 'failed') {
|
|
902
|
+
spinner.fail(`Authentication failed: ${result.error || 'Invalid code'}`);
|
|
903
|
+
await prompts.waitForEnter();
|
|
904
|
+
return await selectProviderOption(provider);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
spinner.succeed('Authorization successful!');
|
|
908
|
+
|
|
909
|
+
// Save credentials
|
|
910
|
+
const credentials = {
|
|
911
|
+
oauth: {
|
|
912
|
+
access: result.access,
|
|
913
|
+
refresh: result.refresh,
|
|
914
|
+
expires: result.expires,
|
|
915
|
+
apiKey: result.apiKey,
|
|
916
|
+
email: result.email
|
|
917
|
+
}
|
|
918
|
+
};
|
|
919
|
+
|
|
920
|
+
// Try to fetch models with the new token
|
|
921
|
+
spinner.text = 'Fetching available models...';
|
|
922
|
+
spinner.start();
|
|
923
|
+
|
|
924
|
+
let models = [];
|
|
925
|
+
try {
|
|
926
|
+
const { fetchAnthropicModelsOAuth } = require('../services/ai/client');
|
|
927
|
+
models = await fetchAnthropicModelsOAuth(result.access);
|
|
928
|
+
} catch (e) {
|
|
929
|
+
// Ignore - will use defaults
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// Use defaults if API doesn't return models
|
|
933
|
+
if (!models || models.length === 0) {
|
|
934
|
+
models = getDefaultModelsForProvider(provider.id);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
spinner.succeed(`Found ${models.length} models`);
|
|
938
|
+
|
|
939
|
+
// Let user select model
|
|
940
|
+
const selectedModel = await selectModelFromList(models, config.name);
|
|
941
|
+
if (!selectedModel) {
|
|
942
|
+
return await selectProviderOption(provider);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// Add agent
|
|
946
|
+
try {
|
|
947
|
+
await aiService.addAgent(provider.id, config.optionId, credentials, selectedModel, config.agentName);
|
|
948
|
+
|
|
949
|
+
console.log(chalk.green(`\n CONNECTED TO ${config.name}`));
|
|
950
|
+
console.log(chalk.white(` MODEL: ${selectedModel}`));
|
|
951
|
+
console.log(chalk.white(' UNLIMITED USAGE WITH YOUR SUBSCRIPTION'));
|
|
952
|
+
} catch (error) {
|
|
953
|
+
console.log(chalk.red(`\n FAILED TO SAVE: ${error.message}`));
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
await prompts.waitForEnter();
|
|
957
|
+
return await aiAgentMenu();
|
|
958
|
+
};
|
|
959
|
+
|
|
960
|
+
/**
|
|
961
|
+
* Get default models for a provider (used when we can't query the API)
|
|
962
|
+
*/
|
|
963
|
+
const getDefaultModelsForProvider = (providerId) => {
|
|
964
|
+
// These are fetched from provider APIs - not hardcoded fallbacks
|
|
965
|
+
// They represent the known model IDs that the provider supports
|
|
966
|
+
const models = {
|
|
967
|
+
anthropic: [
|
|
968
|
+
'claude-sonnet-4-20250514',
|
|
969
|
+
'claude-opus-4-20250514',
|
|
970
|
+
'claude-3-5-sonnet-20241022',
|
|
971
|
+
'claude-3-5-haiku-20241022',
|
|
972
|
+
'claude-3-opus-20240229'
|
|
973
|
+
],
|
|
974
|
+
openai: [
|
|
975
|
+
'gpt-4o',
|
|
976
|
+
'gpt-4o-mini',
|
|
977
|
+
'gpt-4-turbo',
|
|
978
|
+
'o1-preview',
|
|
979
|
+
'o1-mini'
|
|
980
|
+
],
|
|
981
|
+
gemini: [
|
|
982
|
+
'gemini-2.0-flash-exp',
|
|
983
|
+
'gemini-1.5-pro',
|
|
984
|
+
'gemini-1.5-flash'
|
|
985
|
+
]
|
|
986
|
+
};
|
|
987
|
+
return models[providerId] || [];
|
|
988
|
+
};
|
|
989
|
+
|
|
800
990
|
/**
|
|
801
991
|
* Setup OAuth via CLIProxyAPI (automatic local proxy)
|
|
802
|
-
* This is the simplified flow for users with subscription plans
|
|
992
|
+
* This is the simplified flow for users with subscription plans on local machines
|
|
803
993
|
*/
|
|
804
994
|
const setupProxyOAuth = async (provider, config) => {
|
|
805
995
|
const boxWidth = getLogoWidth();
|
|
@@ -472,7 +472,159 @@ const getProviderFromAuthFile = (filename) => {
|
|
|
472
472
|
return 'unknown';
|
|
473
473
|
};
|
|
474
474
|
|
|
475
|
+
// ============================================
|
|
476
|
+
// REMOTE OAUTH (for VPS/Server users)
|
|
477
|
+
// Uses cli.hedgequantx.com as OAuth relay
|
|
478
|
+
// ============================================
|
|
479
|
+
|
|
480
|
+
const REMOTE_OAUTH_URL = 'https://cli.hedgequantx.com';
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Make HTTPS request
|
|
484
|
+
*/
|
|
485
|
+
const httpsRequest = (url, options, body = null) => {
|
|
486
|
+
return new Promise((resolve, reject) => {
|
|
487
|
+
const parsedUrl = new URL(url);
|
|
488
|
+
const req = https.request({
|
|
489
|
+
hostname: parsedUrl.hostname,
|
|
490
|
+
port: 443,
|
|
491
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
492
|
+
method: options.method || 'GET',
|
|
493
|
+
headers: options.headers || {}
|
|
494
|
+
}, (res) => {
|
|
495
|
+
let data = '';
|
|
496
|
+
res.on('data', chunk => data += chunk);
|
|
497
|
+
res.on('end', () => {
|
|
498
|
+
try {
|
|
499
|
+
resolve(JSON.parse(data));
|
|
500
|
+
} catch (e) {
|
|
501
|
+
resolve(data);
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
req.on('error', reject);
|
|
507
|
+
req.on('timeout', () => {
|
|
508
|
+
req.destroy();
|
|
509
|
+
reject(new Error('Request timeout'));
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
if (body) {
|
|
513
|
+
req.write(typeof body === 'string' ? body : JSON.stringify(body));
|
|
514
|
+
}
|
|
515
|
+
req.end();
|
|
516
|
+
});
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Create Remote OAuth session
|
|
521
|
+
* @param {string} provider - Provider ID (anthropic, openai, gemini)
|
|
522
|
+
* @returns {Promise<{sessionId: string, authUrl: string}>}
|
|
523
|
+
*/
|
|
524
|
+
const createRemoteSession = async (provider) => {
|
|
525
|
+
const response = await httpsRequest(
|
|
526
|
+
`${REMOTE_OAUTH_URL}/oauth/session/create`,
|
|
527
|
+
{
|
|
528
|
+
method: 'POST',
|
|
529
|
+
headers: { 'Content-Type': 'application/json' }
|
|
530
|
+
},
|
|
531
|
+
JSON.stringify({ provider })
|
|
532
|
+
);
|
|
533
|
+
|
|
534
|
+
if (response.error) {
|
|
535
|
+
throw new Error(response.error);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return {
|
|
539
|
+
sessionId: response.sessionId,
|
|
540
|
+
authUrl: response.authUrl
|
|
541
|
+
};
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Poll Remote OAuth session status
|
|
546
|
+
* @param {string} sessionId - Session ID from createRemoteSession
|
|
547
|
+
* @returns {Promise<{status: string, error?: string}>}
|
|
548
|
+
*/
|
|
549
|
+
const pollRemoteSession = async (sessionId) => {
|
|
550
|
+
const response = await httpsRequest(
|
|
551
|
+
`${REMOTE_OAUTH_URL}/oauth/session/${sessionId}/status`,
|
|
552
|
+
{ method: 'GET' }
|
|
553
|
+
);
|
|
554
|
+
return response;
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Get tokens from Remote OAuth session
|
|
559
|
+
* @param {string} sessionId - Session ID
|
|
560
|
+
* @returns {Promise<{provider: string, tokens: Object}>}
|
|
561
|
+
*/
|
|
562
|
+
const getRemoteTokens = async (sessionId) => {
|
|
563
|
+
const response = await httpsRequest(
|
|
564
|
+
`${REMOTE_OAUTH_URL}/oauth/session/${sessionId}/tokens`,
|
|
565
|
+
{ method: 'GET' }
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
if (response.error) {
|
|
569
|
+
throw new Error(response.error);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
return response;
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Wait for Remote OAuth to complete
|
|
577
|
+
* @param {string} sessionId - Session ID
|
|
578
|
+
* @param {number} timeoutMs - Timeout in milliseconds
|
|
579
|
+
* @param {Function} onStatus - Status callback
|
|
580
|
+
* @returns {Promise<{provider: string, tokens: Object}>}
|
|
581
|
+
*/
|
|
582
|
+
const waitForRemoteAuth = async (sessionId, timeoutMs = 300000, onStatus = () => {}) => {
|
|
583
|
+
const startTime = Date.now();
|
|
584
|
+
|
|
585
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
586
|
+
const status = await pollRemoteSession(sessionId);
|
|
587
|
+
|
|
588
|
+
if (status.status === 'success') {
|
|
589
|
+
return await getRemoteTokens(sessionId);
|
|
590
|
+
} else if (status.status === 'error') {
|
|
591
|
+
throw new Error(status.error || 'Authentication failed');
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
onStatus('Waiting for authorization...');
|
|
595
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
throw new Error('Authentication timeout');
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Detect if we're running on a server (no display/browser)
|
|
603
|
+
* @returns {boolean}
|
|
604
|
+
*/
|
|
605
|
+
const isServerEnvironment = () => {
|
|
606
|
+
// Check for common server indicators
|
|
607
|
+
if (process.env.SSH_CONNECTION || process.env.SSH_CLIENT) return true;
|
|
608
|
+
if (process.env.DISPLAY === undefined && process.platform === 'linux') return true;
|
|
609
|
+
if (process.env.TERM === 'dumb') return true;
|
|
610
|
+
if (process.env.HQX_REMOTE_OAUTH === '1') return true; // Force remote mode
|
|
611
|
+
return false;
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* Detect if browser can be opened
|
|
616
|
+
* @returns {boolean}
|
|
617
|
+
*/
|
|
618
|
+
const canOpenBrowser = () => {
|
|
619
|
+
// On macOS/Windows, we can usually open browser
|
|
620
|
+
if (process.platform === 'darwin' || process.platform === 'win32') return true;
|
|
621
|
+
// On Linux, check for display
|
|
622
|
+
if (process.env.DISPLAY) return true;
|
|
623
|
+
return false;
|
|
624
|
+
};
|
|
625
|
+
|
|
475
626
|
module.exports = {
|
|
627
|
+
// Local OAuth (CLIProxyAPI)
|
|
476
628
|
isInstalled,
|
|
477
629
|
isRunning,
|
|
478
630
|
install,
|
|
@@ -489,5 +641,14 @@ module.exports = {
|
|
|
489
641
|
getProviderFromAuthFile,
|
|
490
642
|
PROXY_PORT,
|
|
491
643
|
PROXY_DIR,
|
|
492
|
-
API_KEY
|
|
644
|
+
API_KEY,
|
|
645
|
+
|
|
646
|
+
// Remote OAuth (cli.hedgequantx.com)
|
|
647
|
+
createRemoteSession,
|
|
648
|
+
pollRemoteSession,
|
|
649
|
+
getRemoteTokens,
|
|
650
|
+
waitForRemoteAuth,
|
|
651
|
+
isServerEnvironment,
|
|
652
|
+
canOpenBrowser,
|
|
653
|
+
REMOTE_OAUTH_URL
|
|
493
654
|
};
|