hedgequantx 2.9.20 → 2.9.22

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.
Files changed (36) hide show
  1. package/package.json +1 -1
  2. package/src/app.js +64 -42
  3. package/src/menus/connect.js +17 -14
  4. package/src/menus/dashboard.js +76 -58
  5. package/src/pages/accounts.js +49 -38
  6. package/src/pages/ai-agents-ui.js +388 -0
  7. package/src/pages/ai-agents.js +494 -0
  8. package/src/pages/ai-models.js +389 -0
  9. package/src/pages/algo/algo-executor.js +307 -0
  10. package/src/pages/algo/copy-executor.js +331 -0
  11. package/src/pages/algo/copy-trading.js +178 -546
  12. package/src/pages/algo/custom-strategy.js +313 -0
  13. package/src/pages/algo/index.js +75 -18
  14. package/src/pages/algo/one-account.js +57 -322
  15. package/src/pages/algo/ui.js +15 -15
  16. package/src/pages/orders.js +22 -19
  17. package/src/pages/positions.js +22 -19
  18. package/src/pages/stats/index.js +16 -15
  19. package/src/pages/user.js +11 -7
  20. package/src/services/ai-supervision/consensus.js +284 -0
  21. package/src/services/ai-supervision/context.js +275 -0
  22. package/src/services/ai-supervision/directive.js +167 -0
  23. package/src/services/ai-supervision/health.js +47 -35
  24. package/src/services/ai-supervision/index.js +359 -0
  25. package/src/services/ai-supervision/parser.js +278 -0
  26. package/src/services/ai-supervision/symbols.js +259 -0
  27. package/src/services/cliproxy/index.js +256 -0
  28. package/src/services/cliproxy/installer.js +111 -0
  29. package/src/services/cliproxy/manager.js +387 -0
  30. package/src/services/index.js +9 -1
  31. package/src/services/llmproxy/index.js +166 -0
  32. package/src/services/llmproxy/manager.js +411 -0
  33. package/src/services/rithmic/accounts.js +6 -8
  34. package/src/ui/box.js +5 -9
  35. package/src/ui/index.js +18 -5
  36. package/src/ui/menu.js +4 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "2.9.20",
3
+ "version": "2.9.22",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
package/src/app.js CHANGED
@@ -7,7 +7,7 @@ const chalk = require('chalk');
7
7
  const ora = require('ora');
8
8
 
9
9
  const { connections } = require('./services');
10
- const { getLogoWidth, centerText, prepareStdin } = require('./ui');
10
+ const { getLogoWidth, centerText, prepareStdin, clearScreen } = require('./ui');
11
11
  const { logger, prompts } = require('./utils');
12
12
  const { setCachedStats, clearCachedStats } = require('./services/stats-cache');
13
13
 
@@ -17,6 +17,7 @@ const log = logger.scope('App');
17
17
  const { showStats } = require('./pages/stats');
18
18
  const { showAccounts } = require('./pages/accounts');
19
19
  const { algoTradingMenu } = require('./pages/algo');
20
+ const { aiAgentsMenu, getActiveAgentCount } = require('./pages/ai-agents');
20
21
 
21
22
  // Menus
22
23
  const { rithmicMenu, dashboardMenu, handleUpdate } = require('./menus');
@@ -29,7 +30,7 @@ let currentService = null;
29
30
 
30
31
  const restoreTerminal = () => {
31
32
  try {
32
- process.stdout.write('\x1B[?1049l');
33
+ // Show cursor
33
34
  process.stdout.write('\x1B[?25h');
34
35
  if (process.stdin.isTTY && process.stdin.isRaw) {
35
36
  process.stdin.setRawMode(false);
@@ -41,7 +42,15 @@ const restoreTerminal = () => {
41
42
  };
42
43
 
43
44
  process.on('exit', restoreTerminal);
44
- process.on('SIGINT', () => { restoreTerminal(); process.exit(0); });
45
+ process.on('SIGINT', () => {
46
+ restoreTerminal();
47
+ // Draw bottom border before exit
48
+ const termWidth = process.stdout.columns || 100;
49
+ const boxWidth = termWidth < 60 ? Math.max(termWidth - 2, 40) : Math.max(getLogoWidth(), 98);
50
+ console.log(chalk.cyan('\n╚' + '═'.repeat(boxWidth - 2) + '╝'));
51
+ console.log(chalk.gray('GOODBYE!'));
52
+ process.exit(0);
53
+ });
45
54
  process.on('SIGTERM', () => { restoreTerminal(); process.exit(0); });
46
55
 
47
56
  process.on('uncaughtException', (err) => {
@@ -92,6 +101,7 @@ const refreshStats = async () => {
92
101
  balance: hasBalanceData ? totalBalance : null,
93
102
  pnl: hasPnlData ? totalPnl : null,
94
103
  pnlPercent: null,
104
+ agents: getActiveAgentCount(),
95
105
  });
96
106
  } catch (err) {
97
107
  log.warn('Failed to refresh stats', { error: err.message });
@@ -101,7 +111,7 @@ const refreshStats = async () => {
101
111
  // ==================== BANNER ====================
102
112
 
103
113
  const banner = async () => {
104
- console.clear();
114
+ clearScreen();
105
115
 
106
116
  const termWidth = process.stdout.columns || 100;
107
117
  const isMobile = termWidth < 60;
@@ -123,8 +133,11 @@ const banner = async () => {
123
133
 
124
134
  console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
125
135
 
126
- const tagline = isMobile ? `HQX v${version}` : `Prop Futures Algo Trading v${version}`;
127
- console.log(chalk.cyan('║') + chalk.white(centerText(tagline, innerWidth)) + chalk.cyan('║'));
136
+ const tagline = isMobile ? `HQX V${version}` : `PROP FUTURES ALGO TRADING V${version}`;
137
+ console.log(chalk.cyan('║') + chalk.yellow(centerText(tagline, innerWidth)) + chalk.cyan('║'));
138
+
139
+ // ALWAYS close the banner
140
+ console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
128
141
  };
129
142
 
130
143
  const getFullLogo = () => [
@@ -145,39 +158,37 @@ const getMobileLogo = () => [
145
158
  ['╚═╝ ╚═╝ ╚══▀▀═╝ ', '╚═╝ ╚═╝'],
146
159
  ];
147
160
 
148
- const bannerClosed = async () => {
149
- await banner();
150
- const termWidth = process.stdout.columns || 100;
151
- const boxWidth = termWidth < 60 ? Math.max(termWidth - 2, 40) : Math.max(getLogoWidth(), 98);
152
- console.log(chalk.cyan('╚' + '═'.repeat(boxWidth - 2) + '╝'));
153
- };
154
-
155
161
  // ==================== MAIN LOOP ====================
156
162
 
157
163
  const run = async () => {
158
164
  try {
159
165
  log.info('Starting HQX CLI');
160
- await bannerClosed();
161
-
162
- // Restore session
163
- const spinner = ora({ text: 'Restoring session...', color: 'yellow' }).start();
166
+
167
+ // First launch - show banner then try restore session
168
+ await banner();
169
+
170
+ const spinner = ora({ text: 'LOADING DASHBOARD...', color: 'yellow' }).start();
171
+
164
172
  const restored = await connections.restoreFromStorage();
165
173
 
166
174
  if (restored) {
167
- spinner.succeed('Session restored');
168
175
  currentService = connections.getAll()[0].service;
169
176
  await refreshStats();
177
+ // Store spinner globally - dashboard will stop it when ready to display
178
+ global.__hqxSpinner = spinner;
170
179
  } else {
171
- spinner.info('No active session');
180
+ spinner.stop(); // Stop spinner - no session to restore
181
+ global.__hqxSpinner = null;
172
182
  }
173
183
 
174
184
  // Main loop
175
185
  while (true) {
176
186
  try {
177
187
  prepareStdin();
178
- await banner();
179
188
 
180
189
  if (!connections.isConnected()) {
190
+ // Not connected - show banner + propfirm selection
191
+ await banner();
181
192
  // Not connected - show propfirm selection directly
182
193
  const boxWidth = getLogoWidth();
183
194
  const innerWidth = boxWidth - 2;
@@ -188,13 +199,14 @@ const run = async () => {
188
199
 
189
200
  // Find max name length for alignment
190
201
  const maxNameLen = Math.max(...numbered.map(n => n.name.length));
191
- const colWidth = 4 + 1 + maxNameLen + 2; // [##] + space + name + gap
192
- const totalContentWidth = numCols * colWidth;
193
- const leftMargin = Math.max(2, Math.floor((innerWidth - totalContentWidth) / 2));
202
+ const itemWidth = 4 + 1 + maxNameLen; // [##] + space + name
203
+ const gap = 3; // gap between columns
204
+ const totalContentWidth = (itemWidth * numCols) + (gap * (numCols - 1));
194
205
 
195
- console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
206
+ // New rectangle (banner is always closed)
207
+ console.log(chalk.cyan('╔' + '═'.repeat(innerWidth) + '╗'));
196
208
  console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PROPFIRM', innerWidth)) + chalk.cyan('║'));
197
- console.log(chalk.cyan('') + ' '.repeat(innerWidth) + chalk.cyan(''));
209
+ console.log(chalk.cyan('' + ''.repeat(innerWidth) + ''));
198
210
 
199
211
  const rows = Math.ceil(numbered.length / numCols);
200
212
  for (let row = 0; row < rows; row++) {
@@ -211,29 +223,32 @@ const run = async () => {
211
223
  }
212
224
  }
213
225
 
214
- let line = ' '.repeat(leftMargin);
226
+ // Build line content
227
+ let content = '';
215
228
  for (let i = 0; i < lineParts.length; i++) {
216
229
  if (lineParts[i]) {
217
- line += chalk.cyan(lineParts[i].num) + ' ' + chalk.white(lineParts[i].name);
230
+ content += chalk.cyan(lineParts[i].num) + ' ' + chalk.white(lineParts[i].name);
218
231
  } else {
219
- line += ' '.repeat(4 + 1 + maxNameLen);
232
+ content += ' '.repeat(itemWidth);
220
233
  }
221
- if (i < lineParts.length - 1) line += ' ';
234
+ if (i < lineParts.length - 1) content += ' '.repeat(gap);
222
235
  }
223
236
 
224
- const lineLen = line.replace(/\x1b\[[0-9;]*m/g, '').length;
225
- const rightPad = Math.max(0, innerWidth - lineLen);
226
- console.log(chalk.cyan('║') + line + ' '.repeat(rightPad) + chalk.cyan('║'));
237
+ // Center the content
238
+ const contentLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
239
+ const leftPad = Math.floor((innerWidth - contentLen) / 2);
240
+ const rightPad = innerWidth - contentLen - leftPad;
241
+ console.log(chalk.cyan('║') + ' '.repeat(leftPad) + content + ' '.repeat(rightPad) + chalk.cyan('║'));
227
242
  }
228
243
 
229
244
  console.log(chalk.cyan('╠' + '─'.repeat(innerWidth) + '╣'));
230
- console.log(chalk.cyan('║') + chalk.red(centerText('[X] Exit', innerWidth)) + chalk.cyan('║'));
245
+ console.log(chalk.cyan('║') + chalk.red(centerText('[X] EXIT', innerWidth)) + chalk.cyan('║'));
231
246
  console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
232
247
 
233
- const input = await prompts.textInput(chalk.cyan('Select number (or X):'));
248
+ const input = await prompts.textInput(chalk.cyan('SELECT (1-' + numbered.length + '/X): '));
234
249
 
235
250
  if (!input || input.toLowerCase() === 'x') {
236
- console.log(chalk.gray('Goodbye!'));
251
+ console.log(chalk.gray('GOODBYE!'));
237
252
  process.exit(0);
238
253
  }
239
254
 
@@ -244,26 +259,26 @@ const run = async () => {
244
259
  const credentials = await loginPrompt(selectedPropfirm.name);
245
260
 
246
261
  if (credentials) {
247
- const spinner = ora({ text: 'Connecting to Rithmic...', color: 'yellow' }).start();
262
+ const spinner = ora({ text: 'CONNECTING TO RITHMIC...', color: 'yellow' }).start();
248
263
  try {
249
264
  const { RithmicService } = require('./services/rithmic');
250
265
  const service = new RithmicService(selectedPropfirm.key);
251
266
  const result = await service.login(credentials.username, credentials.password);
252
267
 
253
268
  if (result.success) {
254
- spinner.text = 'Fetching accounts...';
269
+ spinner.text = 'FETCHING ACCOUNTS...';
255
270
  const accResult = await service.getTradingAccounts();
256
271
  connections.add('rithmic', service, service.propfirm.name);
257
- spinner.succeed(`Connected to ${service.propfirm.name} (${accResult.accounts?.length || 0} accounts)`);
272
+ spinner.succeed(`CONNECTED TO ${service.propfirm.name.toUpperCase()} (${accResult.accounts?.length || 0} ACCOUNTS)`);
258
273
  currentService = service;
259
274
  await refreshStats();
260
275
  await new Promise(r => setTimeout(r, 1500));
261
276
  } else {
262
- spinner.fail(result.error || 'Authentication failed');
277
+ spinner.fail((result.error || 'AUTHENTICATION FAILED').toUpperCase());
263
278
  await new Promise(r => setTimeout(r, 2000));
264
279
  }
265
280
  } catch (error) {
266
- spinner.fail(`Connection error: ${error.message}`);
281
+ spinner.fail(`CONNECTION ERROR: ${error.message.toUpperCase()}`);
267
282
  await new Promise(r => setTimeout(r, 2000));
268
283
  }
269
284
  }
@@ -296,13 +311,20 @@ const run = async () => {
296
311
  try {
297
312
  await algoTradingMenu(currentService);
298
313
  } catch (err) {
299
- console.log(chalk.red(` Algo error: ${err.message}`));
314
+ console.log(chalk.red(` ALGO ERROR: ${err.message.toUpperCase()}`));
300
315
  prepareStdin();
301
316
  }
302
317
  break;
303
318
 
319
+ case 'aiagents':
320
+ await aiAgentsMenu();
321
+ break;
322
+
304
323
  case 'update':
305
- await handleUpdate();
324
+ const updateResult = await handleUpdate();
325
+ if (updateResult === 'exit') {
326
+ running = false;
327
+ }
306
328
  break;
307
329
 
308
330
  case 'disconnect':
@@ -8,7 +8,7 @@ const ora = require('ora');
8
8
  const { connections } = require('../services');
9
9
  const { RithmicService } = require('../services/rithmic');
10
10
  const { PROPFIRM_CHOICES } = require('../config');
11
- const { getLogoWidth, centerText, prepareStdin } = require('../ui');
11
+ const { getLogoWidth, centerText, prepareStdin, displayBanner , clearScreen } = require('../ui');
12
12
  const { validateUsername, validatePassword } = require('../security');
13
13
  const { prompts } = require('../utils');
14
14
 
@@ -18,16 +18,16 @@ const { prompts } = require('../utils');
18
18
  const loginPrompt = async (propfirmName) => {
19
19
  prepareStdin();
20
20
  console.log();
21
- console.log(chalk.cyan(`Connecting to ${propfirmName}...`));
21
+ console.log(chalk.cyan(`CONNECTING TO ${propfirmName.toUpperCase()}...`));
22
22
  console.log();
23
23
 
24
- const username = await prompts.textInput('Username:', '', (input) => {
25
- try { validateUsername(input); return undefined; } catch (e) { return e.message; }
24
+ const username = await prompts.textInput('USERNAME:', '', (input) => {
25
+ try { validateUsername(input); return undefined; } catch (e) { return e.message.toUpperCase(); }
26
26
  });
27
27
  if (!username) return null;
28
28
 
29
- const pwd = await prompts.passwordInput('Password:', (input) => {
30
- try { validatePassword(input); return undefined; } catch (e) { return e.message; }
29
+ const pwd = await prompts.passwordInput('PASSWORD:', (input) => {
30
+ try { validatePassword(input); return undefined; } catch (e) { return e.message.toUpperCase(); }
31
31
  });
32
32
  if (!pwd) return null;
33
33
 
@@ -38,6 +38,10 @@ const loginPrompt = async (propfirmName) => {
38
38
  * Rithmic menu - Main connection menu
39
39
  */
40
40
  const rithmicMenu = async () => {
41
+ // Clear screen and show banner
42
+ clearScreen();
43
+ displayBanner();
44
+
41
45
  const propfirms = PROPFIRM_CHOICES;
42
46
  const boxWidth = getLogoWidth();
43
47
  const innerWidth = boxWidth - 2;
@@ -46,7 +50,6 @@ const rithmicMenu = async () => {
46
50
 
47
51
  const numbered = propfirms.map((pf, i) => ({ num: i + 1, key: pf.value, name: pf.name }));
48
52
 
49
- console.log();
50
53
  console.log(chalk.cyan('╔' + '═'.repeat(innerWidth) + '╗'));
51
54
  console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PROPFIRM', innerWidth)) + chalk.cyan('║'));
52
55
  console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
@@ -71,10 +74,10 @@ const rithmicMenu = async () => {
71
74
  }
72
75
 
73
76
  console.log(chalk.cyan('╠' + '─'.repeat(innerWidth) + '╣'));
74
- console.log(chalk.cyan('║') + chalk.red(centerText('[X] Exit', innerWidth)) + chalk.cyan('║'));
77
+ console.log(chalk.cyan('║') + chalk.red(centerText('[X] EXIT', innerWidth)) + chalk.cyan('║'));
75
78
  console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
76
79
 
77
- const input = await prompts.textInput(chalk.cyan('Select number (or X):'));
80
+ const input = await prompts.textInput(chalk.cyan('SELECT NUMBER (OR X):'));
78
81
  if (!input || input.toLowerCase() === 'x') return null;
79
82
 
80
83
  const action = parseInt(input);
@@ -84,26 +87,26 @@ const rithmicMenu = async () => {
84
87
  const credentials = await loginPrompt(selectedPropfirm.name);
85
88
  if (!credentials) return null;
86
89
 
87
- const spinner = ora({ text: 'Connecting to Rithmic...', color: 'yellow' }).start();
90
+ const spinner = ora({ text: 'CONNECTING TO RITHMIC...', color: 'yellow' }).start();
88
91
 
89
92
  try {
90
93
  const service = new RithmicService(selectedPropfirm.key);
91
94
  const result = await service.login(credentials.username, credentials.password);
92
95
 
93
96
  if (result.success) {
94
- spinner.text = 'Fetching accounts...';
97
+ spinner.text = 'FETCHING ACCOUNTS...';
95
98
  const accResult = await service.getTradingAccounts();
96
99
  connections.add('rithmic', service, service.propfirm.name);
97
- spinner.succeed(`Connected to ${service.propfirm.name} (${accResult.accounts?.length || 0} accounts)`);
100
+ spinner.succeed(`CONNECTED TO ${service.propfirm.name.toUpperCase()} (${accResult.accounts?.length || 0} ACCOUNTS)`);
98
101
  await new Promise(r => setTimeout(r, 1500));
99
102
  return service;
100
103
  } else {
101
- spinner.fail(result.error || 'Authentication failed');
104
+ spinner.fail((result.error || 'AUTHENTICATION FAILED').toUpperCase());
102
105
  await new Promise(r => setTimeout(r, 2000));
103
106
  return null;
104
107
  }
105
108
  } catch (error) {
106
- spinner.fail(`Connection error: ${error.message}`);
109
+ spinner.fail(`CONNECTION ERROR: ${error.message.toUpperCase()}`);
107
110
  await new Promise(r => setTimeout(r, 2000));
108
111
  return null;
109
112
  }
@@ -7,9 +7,10 @@ const ora = require('ora');
7
7
  const { execSync, spawn } = require('child_process');
8
8
 
9
9
  const { connections } = require('../services');
10
- const { getLogoWidth, centerText, prepareStdin } = require('../ui');
10
+ const { getLogoWidth, centerText, prepareStdin, displayBanner, clearScreen } = require('../ui');
11
11
  const { getCachedStats } = require('../services/stats-cache');
12
12
  const { prompts } = require('../utils');
13
+ const { getActiveAgentCount } = require('../pages/ai-agents');
13
14
 
14
15
  /**
15
16
  * Dashboard menu after login
@@ -17,6 +18,16 @@ const { prompts } = require('../utils');
17
18
  const dashboardMenu = async (service) => {
18
19
  prepareStdin();
19
20
 
21
+ // Stop any global spinner before clearing
22
+ if (global.__hqxSpinner) {
23
+ global.__hqxSpinner.stop();
24
+ global.__hqxSpinner = null;
25
+ }
26
+
27
+ // Clear screen and show banner (always closed)
28
+ clearScreen();
29
+ displayBanner();
30
+
20
31
  const boxWidth = getLogoWidth();
21
32
  const W = boxWidth - 2;
22
33
 
@@ -30,9 +41,9 @@ const dashboardMenu = async (service) => {
30
41
  return chalk.cyan('║') + content + ' '.repeat(Math.max(0, padding)) + chalk.cyan('║');
31
42
  };
32
43
 
33
- // Continue from banner (use not ╔)
34
- console.log(chalk.cyan('' + '═'.repeat(W) + ''));
35
- console.log(makeLine(chalk.yellow.bold('Welcome, HQX Trader!'), 'center'));
44
+ // New rectangle (banner is always closed)
45
+ console.log(chalk.cyan('' + '═'.repeat(W) + ''));
46
+ console.log(makeLine(chalk.yellow.bold('WELCOME, HQX TRADER!'), 'center'));
36
47
  console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
37
48
 
38
49
  // Show connected propfirms
@@ -51,8 +62,8 @@ const dashboardMenu = async (service) => {
51
62
  const balStr = statsInfo.balance !== null ? `$${statsInfo.balance.toLocaleString()}` : '--';
52
63
  const balColor = statsInfo.balance !== null ? chalk.green : chalk.gray;
53
64
 
54
- // AI Agents status
55
- const agentCount = statsInfo.agents || 0;
65
+ // AI Agents status - get fresh count, not from cache
66
+ const agentCount = getActiveAgentCount();
56
67
  const agentDisplay = agentCount > 0 ? 'ON' : 'OFF';
57
68
  const agentColor = agentCount > 0 ? chalk.green : chalk.red;
58
69
 
@@ -87,9 +98,9 @@ const dashboardMenu = async (service) => {
87
98
 
88
99
  // Find max width for alignment
89
100
  const menuItems = [
90
- { left: '[1] View Accounts', right: '[2] View Stats' },
91
- { left: '[+] Add Prop-Account', right: '[A] Algo-Trading' },
92
- { left: '[U] Update HQX', right: '[X] Disconnect' },
101
+ { left: '[1] VIEW ACCOUNTS', right: '[2] VIEW STATS' },
102
+ { left: '[+] ADD PROP-ACCOUNT', right: '[A] ALGO-TRADING' },
103
+ { left: '[I] AI AGENTS', right: '[U] UPDATE HQX' },
93
104
  ];
94
105
 
95
106
  const maxLeftLen = Math.max(...menuItems.map(m => m.left.length));
@@ -119,20 +130,25 @@ const dashboardMenu = async (service) => {
119
130
  );
120
131
  };
121
132
 
122
- menuRow('[1] View Accounts', '[2] View Stats', chalk.cyan, chalk.cyan);
123
- menuRow('[+] Add Prop-Account', '[A] Algo-Trading', chalk.cyan, chalk.magenta);
124
- menuRow('[U] Update HQX', '[X] Disconnect', chalk.yellow, chalk.red);
133
+ menuRow('[1] VIEW ACCOUNTS', '[2] VIEW STATS', chalk.cyan, chalk.cyan);
134
+ menuRow('[+] ADD PROP-ACCOUNT', '[A] ALGO-TRADING', chalk.cyan, chalk.magenta);
135
+ menuRow('[I] AI AGENTS', '[U] UPDATE HQX', chalk.green, chalk.yellow);
136
+
137
+ // Separator and centered Disconnect button
138
+ console.log(chalk.cyan('╠' + '─'.repeat(W) + '╣'));
139
+ console.log(makeLine(chalk.red('[X] DISCONNECT'), 'center'));
125
140
 
126
141
  console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
127
142
 
128
143
  // Simple input - no duplicate menu
129
- const input = await prompts.textInput(chalk.cyan('Select (1/2/+/A/U/X)'));
144
+ const input = await prompts.textInput(chalk.cyan('SELECT (1/2/+/A/I/U/X):'));
130
145
 
131
146
  const actionMap = {
132
147
  '1': 'accounts',
133
148
  '2': 'stats',
134
149
  '+': 'add_prop_account',
135
150
  'a': 'algotrading',
151
+ 'i': 'aiagents',
136
152
  'u': 'update',
137
153
  'x': 'disconnect'
138
154
  };
@@ -144,93 +160,95 @@ const dashboardMenu = async (service) => {
144
160
  * Handle update process
145
161
  */
146
162
  const handleUpdate = async () => {
163
+ clearScreen();
164
+ displayBanner();
147
165
  prepareStdin();
148
166
 
167
+ const boxWidth = getLogoWidth();
168
+ const W = boxWidth - 2;
169
+
170
+ console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
171
+ console.log(chalk.cyan('║') + chalk.yellow.bold(centerText('UPDATE HQX', W)) + chalk.cyan('║'));
172
+ console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
173
+
149
174
  let spinner = null;
150
175
  let currentVersion = 'unknown';
151
176
 
152
177
  try {
178
+ // Get current version
153
179
  try {
154
180
  currentVersion = require('../../package.json').version || 'unknown';
155
181
  } catch (e) {}
156
182
 
157
- console.log(chalk.cyan(`\n Current version: v${currentVersion}`));
158
- spinner = ora({ text: 'Checking for updates...', color: 'yellow' }).start();
183
+ console.log(chalk.cyan(`\n CURRENT VERSION: V${currentVersion.toUpperCase()}`));
184
+ spinner = ora({ text: 'CHECKING FOR UPDATES...', color: 'yellow' }).start();
159
185
 
186
+ // Check latest version from npm
160
187
  let latestVersion;
161
188
  try {
162
- latestVersion = execSync('npm view hedgequantx version', {
189
+ latestVersion = execSync('npm view hedgequantx version 2>/dev/null', {
163
190
  stdio: ['pipe', 'pipe', 'pipe'],
164
191
  timeout: 30000,
165
192
  encoding: 'utf8'
166
193
  }).trim();
167
194
 
168
195
  if (!latestVersion || !/^\d+\.\d+\.\d+/.test(latestVersion)) {
169
- throw new Error('Invalid version format');
196
+ throw new Error('INVALID VERSION FORMAT');
170
197
  }
171
198
  } catch (e) {
172
- spinner.fail('Cannot reach npm registry');
173
- console.log(chalk.gray(` Error: ${e.message}`));
174
- console.log(chalk.yellow(' Try manually: npm install -g hedgequantx@latest'));
199
+ spinner.fail('CANNOT REACH NPM REGISTRY');
200
+ console.log(chalk.yellow('\n TRY MANUALLY: npm update -g hedgequantx'));
175
201
  await prompts.waitForEnter();
176
202
  return;
177
203
  }
178
204
 
179
- spinner.succeed(`Latest version: v${latestVersion}`);
205
+ spinner.succeed(`LATEST VERSION: V${latestVersion.toUpperCase()}`);
180
206
 
207
+ // Already up to date
181
208
  if (currentVersion === latestVersion) {
182
- console.log(chalk.green(' Already up to date!'));
209
+ console.log(chalk.green('\n ALREADY UP TO DATE!'));
183
210
  await prompts.waitForEnter();
184
211
  return;
185
212
  }
186
213
 
187
- console.log(chalk.yellow(` Update available: v${currentVersion} → v${latestVersion}`));
188
- spinner = ora({ text: 'Installing update...', color: 'yellow' }).start();
214
+ // Update available
215
+ console.log(chalk.yellow(`\n UPDATE AVAILABLE: V${currentVersion} V${latestVersion}`));
216
+ spinner = ora({ text: 'INSTALLING UPDATE...', color: 'yellow' }).start();
189
217
 
218
+ // Try to install update
190
219
  try {
191
- // Try with sudo first on Unix systems
192
- const isWindows = process.platform === 'win32';
193
- const cmd = isWindows
194
- ? 'npm install -g hedgequantx@latest'
195
- : 'npm install -g hedgequantx@latest';
196
-
197
- execSync(cmd, {
220
+ execSync('npm update -g hedgequantx 2>/dev/null', {
198
221
  stdio: ['pipe', 'pipe', 'pipe'],
199
222
  timeout: 180000,
200
223
  encoding: 'utf8'
201
224
  });
202
225
  } catch (e) {
203
- spinner.fail('Update failed - permission denied?');
204
- console.log(chalk.gray(` Error: ${e.message}`));
205
- console.log(chalk.yellow(' Try manually with sudo:'));
206
- console.log(chalk.white(' sudo npm install -g hedgequantx@latest'));
207
- await prompts.waitForEnter();
208
- return;
226
+ // Try without redirecting stderr
227
+ try {
228
+ execSync('npm update -g hedgequantx', {
229
+ stdio: ['pipe', 'pipe', 'pipe'],
230
+ timeout: 180000,
231
+ encoding: 'utf8'
232
+ });
233
+ } catch (e2) {
234
+ spinner.fail('UPDATE FAILED');
235
+ console.log(chalk.yellow('\n TRY MANUALLY:'));
236
+ console.log(chalk.white(' npm update -g hedgequantx'));
237
+ console.log(chalk.gray(' OR WITH SUDO:'));
238
+ console.log(chalk.white(' sudo npm update -g hedgequantx'));
239
+ await prompts.waitForEnter();
240
+ return;
241
+ }
209
242
  }
210
243
 
211
- spinner.succeed(`Updated to v${latestVersion}!`);
212
- console.log(chalk.cyan(' Restarting HQX...'));
213
-
214
- await new Promise(r => setTimeout(r, 1500));
215
-
216
- try {
217
- const child = spawn('hqx', [], {
218
- stdio: 'inherit',
219
- detached: true,
220
- shell: true
221
- });
222
- child.unref();
223
- process.exit(0);
224
- } catch (e) {
225
- console.log(chalk.yellow('\n Please restart HQX manually:'));
226
- console.log(chalk.white(' hqx'));
227
- await prompts.waitForEnter();
228
- }
244
+ spinner.succeed(`UPDATED TO V${latestVersion}!`);
245
+ console.log(chalk.green('\n UPDATE SUCCESSFUL!'));
246
+ console.log(chalk.yellow('\n Run ') + chalk.cyan('hqx') + chalk.yellow(' to start the new version.\n'));
247
+ process.exit(0);
229
248
 
230
249
  } catch (error) {
231
- if (spinner) spinner.fail('Update error');
232
- console.log(chalk.gray(` Error: ${error.message}`));
233
- console.log(chalk.yellow(' Try manually: npm install -g hedgequantx@latest'));
250
+ if (spinner) spinner.fail('UPDATE ERROR');
251
+ console.log(chalk.yellow('\n TRY MANUALLY: npm update -g hedgequantx'));
234
252
  await prompts.waitForEnter();
235
253
  }
236
254
  };