hedgequantx 2.7.22 → 2.7.23

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.7.22",
3
+ "version": "2.7.23",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -60,14 +60,40 @@ const draw2ColTable = (title, titleColor, items, backText, W) => {
60
60
  * @param {Array} providers - List of AI providers
61
61
  * @param {Object} config - Current config
62
62
  * @param {number} boxWidth - Box width
63
+ * @param {string} cliproxyUrl - Current CLIProxy URL (optional)
63
64
  */
64
- const drawProvidersTable = (providers, config, boxWidth) => {
65
+ const drawProvidersTable = (providers, config, boxWidth, cliproxyUrl = null) => {
65
66
  const W = boxWidth - 2;
67
+
68
+ console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
69
+ console.log(chalk.cyan('║') + chalk.yellow.bold(centerText('AI AGENTS CONFIGURATION', W)) + chalk.cyan('║'));
70
+
71
+ // Show CLIProxy URL if provided
72
+ if (cliproxyUrl) {
73
+ console.log(chalk.cyan('╠' + '─'.repeat(W) + '╣'));
74
+ const proxyText = chalk.gray('CLIProxy: ') + chalk.cyan(cliproxyUrl);
75
+ console.log(chalk.cyan('║') + centerText(proxyText, W) + chalk.cyan('║'));
76
+ }
77
+
78
+ console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
79
+
66
80
  const items = providers.map((p, i) => {
67
81
  const status = config.providers[p.id]?.active ? chalk.green(' ●') : '';
68
82
  return chalk.cyan(`[${i + 1}]`) + ' ' + chalk[p.color](p.name) + status;
69
83
  });
70
- draw2ColTable('AI AGENTS CONFIGURATION', chalk.yellow.bold, items, '[B] Back to Menu', W);
84
+
85
+ const rows = Math.ceil(items.length / 2);
86
+ for (let row = 0; row < rows; row++) {
87
+ const left = items[row];
88
+ const right = items[row + rows];
89
+ draw2ColRow(left || '', right || '', W);
90
+ }
91
+
92
+ console.log(chalk.cyan('╠' + '─'.repeat(W) + '╣'));
93
+ console.log(chalk.cyan('║') + chalk.gray(centerText('[C] Configure CLIProxy URL', W)) + chalk.cyan('║'));
94
+ console.log(chalk.cyan('╠' + '─'.repeat(W) + '╣'));
95
+ console.log(chalk.cyan('║') + chalk.red(centerText('[B] Back to Menu', W)) + chalk.cyan('║'));
96
+ console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
71
97
  };
72
98
 
73
99
  /**
@@ -15,7 +15,7 @@ const { getLogoWidth } = require('../ui');
15
15
  const { prompts } = require('../utils');
16
16
  const { fetchModelsFromApi } = require('./ai-models');
17
17
  const { drawProvidersTable, drawModelsTable, drawProviderWindow } = require('./ai-agents-ui');
18
- const { isCliProxyRunning, fetchModelsFromCliProxy, getOAuthUrl, checkOAuthStatus } = require('../services/cliproxy');
18
+ const { isCliProxyRunning, fetchModelsFromCliProxy, getOAuthUrl, checkOAuthStatus, getCliProxyUrl, setCliProxyUrl, DEFAULT_CLIPROXY_URL } = require('../services/cliproxy');
19
19
 
20
20
  // Config file path
21
21
  const CONFIG_DIR = path.join(os.homedir(), '.hqx');
@@ -101,18 +101,63 @@ const activateProvider = (config, providerId, data) => {
101
101
  /** Handle CLIProxy connection */
102
102
  const handleCliProxyConnection = async (provider, config, boxWidth) => {
103
103
  console.log();
104
- const spinner = ora({ text: 'Checking CLIProxy status...', color: 'yellow' }).start();
105
- const proxyStatus = await isCliProxyRunning();
104
+ const currentUrl = getCliProxyUrl();
105
+ const spinner = ora({ text: `Checking CLIProxy at ${currentUrl}...`, color: 'yellow' }).start();
106
+ let proxyStatus = await isCliProxyRunning();
106
107
 
107
108
  if (!proxyStatus.running) {
108
- spinner.fail('CLIProxy is not running');
109
- console.log(chalk.yellow('\n CLIProxy must be running on localhost:8317'));
110
- console.log(chalk.gray(' Install: https://help.router-for.me\n'));
111
- await prompts.waitForEnter();
112
- return false;
109
+ spinner.fail(`CLIProxy not reachable at ${currentUrl}`);
110
+ console.log();
111
+ console.log(chalk.yellow(' CLIProxy Options:'));
112
+ console.log(chalk.gray(' [1] Local - localhost:8317 (default)'));
113
+ console.log(chalk.gray(' [2] Remote - Enter custom URL (e.g., http://your-pc-ip:8317)'));
114
+ console.log(chalk.gray(' [B] Back'));
115
+ console.log();
116
+
117
+ const urlChoice = await prompts.textInput(chalk.cyan(' Select option: '));
118
+
119
+ if (!urlChoice || urlChoice.toLowerCase() === 'b') {
120
+ return false;
121
+ }
122
+
123
+ let newUrl = null;
124
+ if (urlChoice === '1') {
125
+ newUrl = DEFAULT_CLIPROXY_URL;
126
+ } else if (urlChoice === '2') {
127
+ console.log(chalk.gray('\n Enter CLIProxy URL (e.g., http://192.168.1.100:8317):'));
128
+ const customUrl = await prompts.textInput(chalk.cyan(' URL: '));
129
+ if (!customUrl || customUrl.trim() === '') {
130
+ console.log(chalk.gray(' Cancelled.'));
131
+ await prompts.waitForEnter();
132
+ return false;
133
+ }
134
+ newUrl = customUrl.trim();
135
+ // Add http:// if missing
136
+ if (!newUrl.startsWith('http://') && !newUrl.startsWith('https://')) {
137
+ newUrl = 'http://' + newUrl;
138
+ }
139
+ }
140
+
141
+ if (newUrl) {
142
+ const testSpinner = ora({ text: `Testing connection to ${newUrl}...`, color: 'yellow' }).start();
143
+ proxyStatus = await isCliProxyRunning(newUrl);
144
+
145
+ if (!proxyStatus.running) {
146
+ testSpinner.fail(`Cannot connect to ${newUrl}`);
147
+ console.log(chalk.gray(` Error: ${proxyStatus.error || 'Connection failed'}`));
148
+ console.log(chalk.yellow('\n Make sure CLIProxy is running and accessible.'));
149
+ await prompts.waitForEnter();
150
+ return false;
151
+ }
152
+
153
+ testSpinner.succeed(`Connected to CLIProxy at ${newUrl}`);
154
+ setCliProxyUrl(newUrl);
155
+ } else {
156
+ return false;
157
+ }
158
+ } else {
159
+ spinner.succeed(`CLIProxy connected at ${currentUrl}`);
113
160
  }
114
-
115
- spinner.succeed('CLIProxy is running');
116
161
  const oauthResult = await getOAuthUrl(provider.id);
117
162
 
118
163
  if (!oauthResult.success) {
@@ -303,6 +348,66 @@ const getActiveProvider = () => {
303
348
  /** Count active AI agents */
304
349
  const getActiveAgentCount = () => getActiveProvider() ? 1 : 0;
305
350
 
351
+ /** Configure CLIProxy URL */
352
+ const configureCliProxyUrl = async () => {
353
+ const currentUrl = getCliProxyUrl();
354
+ console.clear();
355
+ console.log(chalk.yellow('\n Configure CLIProxy URL\n'));
356
+ console.log(chalk.gray(` Current: ${currentUrl}`));
357
+ console.log();
358
+ console.log(chalk.white(' [1] Local - localhost:8317 (default)'));
359
+ console.log(chalk.white(' [2] Remote - Enter custom URL'));
360
+ console.log(chalk.white(' [B] Back'));
361
+ console.log();
362
+
363
+ const choice = await prompts.textInput(chalk.cyan(' Select option: '));
364
+
365
+ if (!choice || choice.toLowerCase() === 'b') return;
366
+
367
+ if (choice === '1') {
368
+ setCliProxyUrl(DEFAULT_CLIPROXY_URL);
369
+ console.log(chalk.green(`\n ✓ CLIProxy URL set to ${DEFAULT_CLIPROXY_URL}`));
370
+ await prompts.waitForEnter();
371
+ return;
372
+ }
373
+
374
+ if (choice === '2') {
375
+ console.log(chalk.gray('\n Enter CLIProxy URL (e.g., http://192.168.1.100:8317):'));
376
+ const customUrl = await prompts.textInput(chalk.cyan(' URL: '));
377
+
378
+ if (!customUrl || customUrl.trim() === '') {
379
+ console.log(chalk.gray(' Cancelled.'));
380
+ await prompts.waitForEnter();
381
+ return;
382
+ }
383
+
384
+ let newUrl = customUrl.trim();
385
+ if (!newUrl.startsWith('http://') && !newUrl.startsWith('https://')) {
386
+ newUrl = 'http://' + newUrl;
387
+ }
388
+
389
+ // Test connection
390
+ const spinner = ora({ text: `Testing connection to ${newUrl}...`, color: 'yellow' }).start();
391
+ const status = await isCliProxyRunning(newUrl);
392
+
393
+ if (status.running) {
394
+ spinner.succeed(`Connected to ${newUrl}`);
395
+ setCliProxyUrl(newUrl);
396
+ console.log(chalk.green(`\n ✓ CLIProxy URL saved.`));
397
+ } else {
398
+ spinner.warn(`Cannot connect to ${newUrl}`);
399
+ console.log(chalk.gray(` Error: ${status.error || 'Connection failed'}`));
400
+ console.log(chalk.yellow('\n Save anyway? (URL will be used when CLIProxy is available)'));
401
+ const save = await prompts.textInput(chalk.cyan(' Save? (y/N): '));
402
+ if (save && save.toLowerCase() === 'y') {
403
+ setCliProxyUrl(newUrl);
404
+ console.log(chalk.green(' ✓ URL saved.'));
405
+ }
406
+ }
407
+ await prompts.waitForEnter();
408
+ }
409
+ };
410
+
306
411
  /** Main AI Agents menu */
307
412
  const aiAgentsMenu = async () => {
308
413
  let config = loadConfig();
@@ -310,13 +415,19 @@ const aiAgentsMenu = async () => {
310
415
 
311
416
  while (true) {
312
417
  console.clear();
313
- drawProvidersTable(AI_PROVIDERS, config, boxWidth);
418
+ const cliproxyUrl = getCliProxyUrl();
419
+ drawProvidersTable(AI_PROVIDERS, config, boxWidth, cliproxyUrl);
314
420
 
315
- const input = await prompts.textInput(chalk.cyan('Select provider: '));
421
+ const input = await prompts.textInput(chalk.cyan('Select (1-8/C/B): '));
316
422
  const choice = (input || '').toLowerCase().trim();
317
423
 
318
424
  if (choice === 'b' || choice === '') break;
319
425
 
426
+ if (choice === 'c') {
427
+ await configureCliProxyUrl();
428
+ continue;
429
+ }
430
+
320
431
  const num = parseInt(choice);
321
432
  if (!isNaN(num) && num >= 1 && num <= AI_PROVIDERS.length) {
322
433
  config = await handleProviderConfig(AI_PROVIDERS[num - 1], config);
@@ -1,16 +1,61 @@
1
1
  /**
2
2
  * CLIProxy Service
3
3
  *
4
- * Connects to CLIProxyAPI (localhost:8317) for AI provider access
4
+ * Connects to CLIProxyAPI for AI provider access
5
5
  * via paid plans (Claude Pro, ChatGPT Plus, etc.)
6
6
  *
7
+ * Supports both local (localhost:8317) and remote connections.
7
8
  * Docs: https://help.router-for.me
8
9
  */
9
10
 
10
11
  const http = require('http');
12
+ const https = require('https');
13
+ const os = require('os');
14
+ const path = require('path');
15
+ const fs = require('fs');
11
16
 
12
- // CLIProxy default endpoint
13
- const CLIPROXY_BASE = 'http://localhost:8317';
17
+ // Config file path (same as ai-agents)
18
+ const CONFIG_DIR = path.join(os.homedir(), '.hqx');
19
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'ai-config.json');
20
+
21
+ // Default CLIProxy endpoint
22
+ const DEFAULT_CLIPROXY_URL = 'http://localhost:8317';
23
+
24
+ /**
25
+ * Get CLIProxy URL from config or default
26
+ * @returns {string} CLIProxy base URL
27
+ */
28
+ const getCliProxyUrl = () => {
29
+ try {
30
+ if (fs.existsSync(CONFIG_FILE)) {
31
+ const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
32
+ if (config.cliproxyUrl && config.cliproxyUrl.trim()) {
33
+ return config.cliproxyUrl.trim();
34
+ }
35
+ }
36
+ } catch (error) { /* ignore */ }
37
+ return DEFAULT_CLIPROXY_URL;
38
+ };
39
+
40
+ /**
41
+ * Set CLIProxy URL in config
42
+ * @param {string} url - CLIProxy URL
43
+ * @returns {boolean} Success status
44
+ */
45
+ const setCliProxyUrl = (url) => {
46
+ try {
47
+ let config = { providers: {} };
48
+ if (fs.existsSync(CONFIG_FILE)) {
49
+ config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
50
+ }
51
+ if (!fs.existsSync(CONFIG_DIR)) fs.mkdirSync(CONFIG_DIR, { recursive: true });
52
+ config.cliproxyUrl = url;
53
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
54
+ return true;
55
+ } catch (error) {
56
+ return false;
57
+ }
58
+ };
14
59
 
15
60
  /**
16
61
  * Make HTTP request to CLIProxy
@@ -18,24 +63,30 @@ const CLIPROXY_BASE = 'http://localhost:8317';
18
63
  * @param {string} method - HTTP method
19
64
  * @param {Object} headers - Request headers
20
65
  * @param {number} timeout - Timeout in ms (default 60000 per RULES.md #15)
66
+ * @param {string} baseUrl - Optional base URL override
21
67
  * @returns {Promise<Object>} { success, data, error }
22
68
  */
23
- const fetchCliProxy = (path, method = 'GET', headers = {}, timeout = 60000) => {
69
+ const fetchCliProxy = (path, method = 'GET', headers = {}, timeout = 60000, baseUrl = null) => {
24
70
  return new Promise((resolve) => {
25
- const url = new URL(path, CLIPROXY_BASE);
71
+ const base = baseUrl || getCliProxyUrl();
72
+ const url = new URL(path, base);
73
+ const isHttps = url.protocol === 'https:';
74
+ const httpModule = isHttps ? https : http;
75
+
26
76
  const options = {
27
77
  hostname: url.hostname,
28
- port: url.port || 8317,
78
+ port: url.port || (isHttps ? 443 : 8317),
29
79
  path: url.pathname + url.search,
30
80
  method,
31
81
  headers: {
32
82
  'Content-Type': 'application/json',
33
83
  ...headers
34
84
  },
35
- timeout
85
+ timeout,
86
+ rejectUnauthorized: false // Allow self-signed certs for remote
36
87
  };
37
88
 
38
- const req = http.request(options, (res) => {
89
+ const req = httpModule.request(options, (res) => {
39
90
  let data = '';
40
91
  res.on('data', chunk => data += chunk);
41
92
  res.on('end', () => {
@@ -53,7 +104,7 @@ const fetchCliProxy = (path, method = 'GET', headers = {}, timeout = 60000) => {
53
104
 
54
105
  req.on('error', (error) => {
55
106
  if (error.code === 'ECONNREFUSED') {
56
- resolve({ success: false, error: 'CLIProxy not running', data: null });
107
+ resolve({ success: false, error: 'CLIProxy not reachable', data: null });
57
108
  } else {
58
109
  resolve({ success: false, error: error.message, data: null });
59
110
  }
@@ -69,14 +120,17 @@ const fetchCliProxy = (path, method = 'GET', headers = {}, timeout = 60000) => {
69
120
  };
70
121
 
71
122
  /**
72
- * Check if CLIProxy is running
73
- * @returns {Promise<Object>} { running, error }
123
+ * Check if CLIProxy is running/reachable
124
+ * @param {string} url - Optional URL to test (uses config if not provided)
125
+ * @returns {Promise<Object>} { running, error, url }
74
126
  */
75
- const isCliProxyRunning = async () => {
76
- const result = await fetchCliProxy('/v1/models', 'GET', {}, 5000);
127
+ const isCliProxyRunning = async (url = null) => {
128
+ const testUrl = url || getCliProxyUrl();
129
+ const result = await fetchCliProxy('/v1/models', 'GET', {}, 5000, testUrl);
77
130
  return {
78
131
  running: result.success,
79
- error: result.success ? null : result.error
132
+ error: result.success ? null : result.error,
133
+ url: testUrl
80
134
  };
81
135
  };
82
136
 
@@ -189,7 +243,9 @@ const getAuthFiles = async () => {
189
243
  };
190
244
 
191
245
  module.exports = {
192
- CLIPROXY_BASE,
246
+ DEFAULT_CLIPROXY_URL,
247
+ getCliProxyUrl,
248
+ setCliProxyUrl,
193
249
  isCliProxyRunning,
194
250
  fetchModelsFromCliProxy,
195
251
  getOAuthUrl,