hedgequantx 2.6.101 → 2.6.103

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.6.101",
3
+ "version": "2.6.103",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -782,7 +782,7 @@ const getOAuthConfig = (providerId) => {
782
782
 
783
783
  /**
784
784
  * Setup OAuth connection for any provider with OAuth support
785
- * Unified flow: copy code from URL (works on local and VPS)
785
+ * Uses CLIProxyAPI for proper OAuth handling and API access
786
786
  */
787
787
  const setupOAuthConnection = async (provider) => {
788
788
  const config = getOAuthConfig(provider.id);
@@ -792,8 +792,8 @@ const setupOAuthConnection = async (provider) => {
792
792
  return await selectProviderOption(provider);
793
793
  }
794
794
 
795
- // Use unified manual flow for all providers and environments
796
- return await setupRemoteOAuth(provider, config);
795
+ // Use CLIProxyAPI for OAuth flow - it handles token exchange and API calls
796
+ return await setupProxyOAuth(provider, config);
797
797
  };
798
798
 
799
799
  /**
@@ -948,12 +948,169 @@ const setupRemoteOAuth = async (provider, config) => {
948
948
  // NOTE: promptForModelName was removed - models MUST come from API (RULES.md)
949
949
 
950
950
  /**
951
- * Setup OAuth via Manual Code Entry (unified flow for local and VPS)
952
- * User copies the authorization code from the URL or page
951
+ * Setup OAuth using CLIProxyAPI
952
+ * CLIProxyAPI handles OAuth flow, token storage, and API calls
953
+ * Models are fetched from CLIProxyAPI /v1/models endpoint
953
954
  */
954
955
  const setupProxyOAuth = async (provider, config) => {
955
- // Use the same flow as VPS - it works everywhere
956
- return await setupRemoteOAuth(provider, config);
956
+ const boxWidth = getLogoWidth();
957
+ const W = boxWidth - 2;
958
+
959
+ const makeLine = (content) => {
960
+ const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
961
+ const padding = W - plainLen;
962
+ return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
963
+ };
964
+
965
+ // Step 1: Ensure CLIProxyAPI is installed and running
966
+ const spinner = ora({ text: 'Setting up CLIProxyAPI...', color: 'cyan' }).start();
967
+
968
+ try {
969
+ await proxyManager.ensureRunning();
970
+ spinner.succeed('CLIProxyAPI ready');
971
+ } catch (error) {
972
+ spinner.fail(`Failed to start CLIProxyAPI: ${error.message}`);
973
+ console.log();
974
+ console.log(chalk.yellow(' CLIProxyAPI is required for OAuth authentication.'));
975
+ console.log(chalk.gray(' It will be downloaded automatically on first use.'));
976
+ console.log();
977
+ await prompts.waitForEnter();
978
+ return await selectProviderOption(provider);
979
+ }
980
+
981
+ // Step 2: Get OAuth URL from CLIProxyAPI
982
+ spinner.text = 'Getting authorization URL...';
983
+ spinner.start();
984
+
985
+ let authUrl, authState;
986
+ try {
987
+ const authInfo = await proxyManager.getAuthUrl(provider.id);
988
+ authUrl = authInfo.url;
989
+ authState = authInfo.state;
990
+ spinner.succeed('Authorization URL ready');
991
+ } catch (error) {
992
+ spinner.fail(`Failed to get auth URL: ${error.message}`);
993
+ await prompts.waitForEnter();
994
+ return await selectProviderOption(provider);
995
+ }
996
+
997
+ // Step 3: Show instructions to user
998
+ console.clear();
999
+ displayBanner();
1000
+ drawBoxHeaderContinue(`CONNECT ${config.name}`, boxWidth);
1001
+
1002
+ console.log(makeLine(chalk.yellow('CONNECT YOUR ACCOUNT')));
1003
+ console.log(makeLine(''));
1004
+ console.log(makeLine(chalk.white('1. OPEN THE LINK BELOW IN YOUR BROWSER')));
1005
+ console.log(makeLine(''));
1006
+ console.log(makeLine(chalk.white(`2. LOGIN WITH YOUR ${config.accountName.toUpperCase()} ACCOUNT`)));
1007
+ console.log(makeLine(''));
1008
+ console.log(makeLine(chalk.white('3. CLICK "AUTHORIZE"')));
1009
+ console.log(makeLine(''));
1010
+ console.log(makeLine(chalk.green('4. WAIT FOR CONFIRMATION HERE')));
1011
+ console.log(makeLine(chalk.white(' (The page will close automatically)')));
1012
+ console.log(makeLine(''));
1013
+
1014
+ drawBoxFooter(boxWidth);
1015
+
1016
+ // Display URL
1017
+ console.log();
1018
+ console.log(chalk.yellow(' OPEN THIS URL IN YOUR BROWSER:'));
1019
+ console.log();
1020
+ console.log(chalk.cyan(` ${authUrl}`));
1021
+ console.log();
1022
+
1023
+ // Try to open browser automatically
1024
+ const browserOpened = await openBrowser(authUrl);
1025
+ if (browserOpened) {
1026
+ console.log(chalk.gray(' (Browser opened automatically)'));
1027
+ } else {
1028
+ console.log(chalk.gray(' (Copy and paste the URL in your browser)'));
1029
+ }
1030
+ console.log();
1031
+
1032
+ // Step 4: Wait for OAuth callback
1033
+ const waitSpinner = ora({ text: 'Waiting for authorization...', color: 'cyan' }).start();
1034
+
1035
+ try {
1036
+ await proxyManager.waitForAuth(authState, 300000, (status) => {
1037
+ waitSpinner.text = status;
1038
+ });
1039
+ waitSpinner.succeed('Authorization successful!');
1040
+ } catch (error) {
1041
+ waitSpinner.fail(`Authorization failed: ${error.message}`);
1042
+ await prompts.waitForEnter();
1043
+ return await selectProviderOption(provider);
1044
+ }
1045
+
1046
+ // Step 5: Fetch models from CLIProxyAPI
1047
+ waitSpinner.text = 'Fetching available models from API...';
1048
+ waitSpinner.start();
1049
+
1050
+ let models = [];
1051
+ try {
1052
+ models = await proxyManager.getModels();
1053
+ } catch (error) {
1054
+ waitSpinner.fail(`Failed to fetch models: ${error.message}`);
1055
+ await prompts.waitForEnter();
1056
+ return await selectProviderOption(provider);
1057
+ }
1058
+
1059
+ // Filter models for this provider
1060
+ const providerPrefixes = {
1061
+ anthropic: ['claude'],
1062
+ openai: ['gpt', 'o1', 'o3', 'o4'],
1063
+ gemini: ['gemini'],
1064
+ qwen: ['qwen'],
1065
+ iflow: ['deepseek', 'kimi', 'glm']
1066
+ };
1067
+
1068
+ const prefixes = providerPrefixes[provider.id] || [];
1069
+ const filteredModels = models.filter(m => {
1070
+ const modelLower = m.toLowerCase();
1071
+ return prefixes.some(p => modelLower.includes(p));
1072
+ });
1073
+
1074
+ // Use filtered models if available, otherwise use all models
1075
+ const availableModels = filteredModels.length > 0 ? filteredModels : models;
1076
+
1077
+ if (!availableModels || availableModels.length === 0) {
1078
+ waitSpinner.fail('No models available');
1079
+ console.log();
1080
+ console.log(chalk.red(' ERROR: No models found for this provider'));
1081
+ console.log(chalk.gray(' Make sure your subscription is active.'));
1082
+ console.log();
1083
+ await prompts.waitForEnter();
1084
+ return await selectProviderOption(provider);
1085
+ }
1086
+
1087
+ waitSpinner.succeed(`Found ${availableModels.length} models`);
1088
+
1089
+ // Step 6: Let user select model
1090
+ const selectedModel = await selectModelFromList(availableModels, config.name);
1091
+ if (!selectedModel) {
1092
+ return await selectProviderOption(provider);
1093
+ }
1094
+
1095
+ // Step 7: Save agent config (CLIProxyAPI stores the actual tokens)
1096
+ // We just save a reference to use the proxy
1097
+ const credentials = {
1098
+ useProxy: true,
1099
+ provider: provider.id
1100
+ };
1101
+
1102
+ try {
1103
+ await aiService.addAgent(provider.id, config.optionId, credentials, selectedModel, config.agentName);
1104
+
1105
+ console.log(chalk.green(`\n CONNECTED TO ${config.name}`));
1106
+ console.log(chalk.white(` MODEL: ${selectedModel}`));
1107
+ console.log(chalk.white(' UNLIMITED USAGE WITH YOUR SUBSCRIPTION'));
1108
+ } catch (error) {
1109
+ console.log(chalk.red(`\n FAILED TO SAVE: ${error.message}`));
1110
+ }
1111
+
1112
+ await prompts.waitForEnter();
1113
+ return await aiAgentMenu();
957
1114
  };
958
1115
 
959
1116
  /**
@@ -527,13 +527,13 @@ const fetchAnthropicModels = async (apiKey) => {
527
527
  * Fetch available models from Anthropic API (OAuth auth)
528
528
  *
529
529
  * @param {string} accessToken - OAuth access token
530
- * @returns {Promise<Array|null>} Array of model IDs from API, null if unavailable
530
+ * @returns {Promise<Object>} { models: Array, error: string|null }
531
531
  *
532
532
  * Data source: https://api.anthropic.com/v1/models (GET with Bearer token)
533
533
  * NO HARDCODED FALLBACK - models must come from API only
534
534
  */
535
535
  const fetchAnthropicModelsOAuth = async (accessToken) => {
536
- if (!accessToken) return null;
536
+ if (!accessToken) return { models: null, error: 'No access token provided' };
537
537
 
538
538
  const modelsUrl = 'https://api.anthropic.com/v1/models';
539
539
 
@@ -547,14 +547,11 @@ const fetchAnthropicModelsOAuth = async (accessToken) => {
547
547
  const response = await makeRequest(modelsUrl, { method: 'GET', headers, timeout: 15000 });
548
548
  if (response.data && Array.isArray(response.data)) {
549
549
  const models = response.data.map(m => m.id).filter(Boolean);
550
- if (models.length > 0) return models;
550
+ if (models.length > 0) return { models, error: null };
551
551
  }
552
- return null;
552
+ return { models: null, error: 'API returned empty or invalid response' };
553
553
  } catch (error) {
554
- if (process.env.HQX_DEBUG) {
555
- console.error('[DEBUG] fetchAnthropicModelsOAuth error:', error.message);
556
- }
557
- return null;
554
+ return { models: null, error: error.message };
558
555
  }
559
556
  };
560
557
 
@@ -27,6 +27,7 @@ const PROXY_BIN = path.join(PROXY_DIR, process.platform === 'win32' ? 'cli-proxy
27
27
  const PROXY_CONFIG = path.join(PROXY_DIR, 'config.yaml');
28
28
  const PROXY_AUTH_DIR = path.join(PROXY_DIR, 'auths');
29
29
  const API_KEY = 'hqx-local-key';
30
+ const MANAGEMENT_KEY = 'hqx-mgmt-key';
30
31
 
31
32
  // GitHub release URLs
32
33
  const getDownloadUrl = () => {
@@ -202,6 +203,9 @@ const install = async (onProgress = () => {}) => {
202
203
  auth-dir: "${PROXY_AUTH_DIR}"
203
204
  api-keys:
204
205
  - "${API_KEY}"
206
+ remote-management:
207
+ secret-key: "${MANAGEMENT_KEY}"
208
+ allow-remote-management: false
205
209
  request-retry: 3
206
210
  quota-exceeded:
207
211
  switch-project: true
@@ -288,7 +292,7 @@ const ensureRunning = async (onProgress = () => {}) => {
288
292
  };
289
293
 
290
294
  /**
291
- * Make request to local proxy
295
+ * Make request to local proxy (API endpoints)
292
296
  */
293
297
  const proxyRequest = (method, endpoint, body = null) => {
294
298
  return new Promise((resolve, reject) => {
@@ -329,6 +333,58 @@ const proxyRequest = (method, endpoint, body = null) => {
329
333
  });
330
334
  };
331
335
 
336
+ /**
337
+ * Make request to local proxy (Management API endpoints)
338
+ * Uses management key for authentication
339
+ */
340
+ const managementRequest = (method, endpoint, body = null) => {
341
+ return new Promise((resolve, reject) => {
342
+ const options = {
343
+ hostname: '127.0.0.1',
344
+ port: PROXY_PORT,
345
+ path: endpoint,
346
+ method,
347
+ headers: {
348
+ 'Authorization': `Bearer ${MANAGEMENT_KEY}`,
349
+ 'Content-Type': 'application/json'
350
+ },
351
+ timeout: 30000
352
+ };
353
+
354
+ const req = http.request(options, (res) => {
355
+ let data = '';
356
+ res.on('data', chunk => data += chunk);
357
+ res.on('end', () => {
358
+ try {
359
+ const json = JSON.parse(data);
360
+ if (res.statusCode >= 400) {
361
+ reject(new Error(json.error || `HTTP ${res.statusCode}`));
362
+ } else {
363
+ resolve(json);
364
+ }
365
+ } catch (e) {
366
+ if (res.statusCode >= 400) {
367
+ reject(new Error(`HTTP ${res.statusCode}: ${data}`));
368
+ } else {
369
+ resolve(data);
370
+ }
371
+ }
372
+ });
373
+ });
374
+
375
+ req.on('error', reject);
376
+ req.on('timeout', () => {
377
+ req.destroy();
378
+ reject(new Error('Request timeout'));
379
+ });
380
+
381
+ if (body) {
382
+ req.write(JSON.stringify(body));
383
+ }
384
+ req.end();
385
+ });
386
+ };
387
+
332
388
  /**
333
389
  * Get OAuth authorization URL for a provider
334
390
  * @param {string} provider - Provider ID (anthropic, openai, gemini, qwen, iflow)
@@ -350,7 +406,7 @@ const getAuthUrl = async (provider) => {
350
406
  throw new Error(`Unknown provider: ${provider}`);
351
407
  }
352
408
 
353
- const response = await proxyRequest('GET', endpoint);
409
+ const response = await managementRequest('GET', endpoint);
354
410
 
355
411
  if (response.status !== 'ok') {
356
412
  throw new Error(response.error || 'Failed to get auth URL');
@@ -368,7 +424,7 @@ const getAuthUrl = async (provider) => {
368
424
  * @returns {Promise<{status: string, error?: string}>}
369
425
  */
370
426
  const pollAuthStatus = async (state) => {
371
- const response = await proxyRequest('GET', `/v0/management/get-auth-status?state=${state}`);
427
+ const response = await managementRequest('GET', `/v0/management/get-auth-status?state=${state}`);
372
428
  return response;
373
429
  };
374
430
 
@@ -422,7 +478,7 @@ const getAuthFiles = async () => {
422
478
  await ensureRunning();
423
479
 
424
480
  try {
425
- const response = await proxyRequest('GET', '/v0/management/auth-files');
481
+ const response = await managementRequest('GET', '/v0/management/auth-files');
426
482
  return response.files || [];
427
483
  } catch (e) {
428
484
  return [];